Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include model base subclasses in plugin base class hook condition #1670

Closed
wants to merge 1 commit into from

Conversation

flaeppe
Copy link
Member

@flaeppe flaeppe commented Aug 31, 2023

I have made things!

We now also trigger the base class hook for subclasses of ModelBase.

Not at all sure how or if we should incorporate this into our test suite.

Related issues

Refs: #1668 (comment)

@flaeppe flaeppe requested a review from sobolevn August 31, 2023 20:37
Copy link
Member

@sobolevn sobolevn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a test case, maybe?

@flaeppe
Copy link
Member Author

flaeppe commented Sep 1, 2023

Yeah, I just don't know how I could write a reasonable test here.

Perhaps just reveal_type(CustomizedModel().pk) exists?

@sobolevn
Copy link
Member

sobolevn commented Sep 1, 2023

I would go with a realistic example (solving metaclass conflicts):

class CustomMeta(type): ...

class CustomBase(metaclass=CustomMeta): ...
class ModelCustomMeta(modes.ModelBase, CustomMeta): ...

class MyModel(models.Model, CustomBase, metaclass=ModelCustomMeta): ...

I've seen code like this in the wild. Do you agree?

@flaeppe
Copy link
Member Author

flaeppe commented Sep 1, 2023

I would go with a realistic example (solving metaclass conflicts):

class CustomMeta(type): ...

class CustomBase(metaclass=CustomMeta): ...
class ModelCustomMeta(modes.ModelBase, CustomMeta): ...

class MyModel(models.Model, CustomBase, metaclass=ModelCustomMeta): ...

I've seen code like this in the wild. Do you agree?

Great, I'll add that case! Probably would've taken me ages to come up with that. Not very familiar with metaclass subclassing..

@flaeppe
Copy link
Member Author

flaeppe commented Sep 1, 2023

Hm, it's not a breaking case though. Since the metaclass is still django.db.models.base.ModelBase

Then I'm not even sure if you could subclass metaclasses?

@sobolevn
Copy link
Member

sobolevn commented Sep 1, 2023

You sure can :)
https://stackoverflow.com/a/61350480/4842742

@flaeppe
Copy link
Member Author

flaeppe commented Sep 1, 2023

I can't come up with a reasonable case that doesn't use django.db.models.base.Model and the second we use it we don't need to check for subclassing of ModelBase. As mypy will call our hook for each base.

@sobolevn
Copy link
Member

sobolevn commented Sep 1, 2023

@flaeppe

I can't come up with a reasonable case that doesn't use django.db.models.base.Model

Sorry, I am confused. Why do you think that my case in #1670 (comment) is not realistic? I've used it several times when I mixed two metaclasses.

and the second we use it we don't need to check for subclassing of ModelBase

Why? :)

In my example, I am using models.Model as the base class, but then I use a different metaclass (which is just a conflict resolution helper).

So, the metaclass would be not models.ModelBase, but my custom ModelCustomBase. Which is just the test case we need.

I think that I might be missing something obvious :)

@flaeppe
Copy link
Member Author

flaeppe commented Sep 1, 2023

I'm not saying it isn't realistic. It's just not a case that covers for subclassing of the metaclass in our hook condition.

We're looking at the metaclass of the bases. Which means in your case the hook condition instantly look at models.Model since that's the first base.

@sobolevn
Copy link
Member

sobolevn commented Sep 1, 2023

Yeap, as I said, I was missing something obvious :)

Here's how mypy executes this hook:

        for base_expr in defn.base_type_exprs:
            base_name = self.get_fullname_for_hook(base_expr)
            if base_name:
                hook = self.plugin.get_base_class_hook(base_name)
                if hook:
                    hook(ClassDefContext(defn, base_expr, self))

Given this example:

-   case: test_custom_model_base_metaclass
    main: |
        from myapp.models import MyModel

        m1 = MyModel(field=1)
        reveal_type(m1.pk)
        reveal_type(m1.field)

        m2 = MyModel(field=None)
        reveal_type(m2.pk)
        reveal_type(m2.field)
    installed_apps:
        - myapp
    files:
        - path: myapp/__init__.py
        - path: myapp/models.py
          content: |
              from django.db import models
              from django.db.models.base import ModelBase

              class CustomMeta(type): ...
              class CustomBase(metaclass=CustomMeta): ...
              class ModelCustomMeta(ModelBase, CustomMeta): ...

              class MyModel(models.Model, CustomBase, metaclass=ModelCustomMeta):
                  field = models.IntegerField()

The fullname would be:

  • django.db.models.base.Model Gdef/TypeInfo (django.db.models.base.Model)
  • myapp.models.CustomBase Gdef/TypeInfo (myapp.models.CustomBase)

And not MyModel as I originally thought.

Thanks a lot, @flaeppe, for bearing with me!
But, since it works this way, I don't think we need to check the subclass of ModelBase.

@flaeppe
Copy link
Member Author

flaeppe commented Sep 1, 2023

👍

Yeah, lets close this off for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants