#33174: Having a model inherit from Generic[T] breaks makemigrations
------------------------------------------+------------------------
Reporter: Antoine Humeau | Owner: nobody
Type: Uncategorized | Status: new
Component: Uncategorized | Version: 3.2
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
------------------------------------------+------------------------
Here is a simple example that can help me explain the issue (and maybe
show you why this might be a valid usecase):
{{{
#!python
import typing
from django.db import models
import stripe
from stripe.stripe_object import StripeObject
StripeClassT = typing.TypeVar('StripeClassT', bound=StripeObject)
class StripeObjectModel(typing.Generic[StripeClassT], models.Model):
stripe_class: type[StripeClassT]
id = models.TextField(primary_key=True)
class Meta:
abstract = True
def api_retrieve(self) -> StripeClassT:
return self.stripe_class.retrieve(self.id)
class Customer(StripeObjectModel):
stripe_class = stripe.Customer
class Source(StripeObjectModel):
stripe_class = stripe.Source
...
}}}
Running `makemigrations` will result in the following traceback:
{{{
Traceback (most recent call last):
File "...django/core/management/__init__.py", line 419, in
execute_from_command_line
utility.execute()
File "...django/core/management/__init__.py", line 413, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "...django/core/management/base.py", line 354, in run_from_argv
self.execute(*args, **cmd_options)
File "...django/core/management/base.py", line 398, in execute
output = self.handle(*args, **options)
File "...django/core/management/base.py", line 89, in wrapped
res = handle_func(*args, **kwargs)
File "...django/core/management/commands/makemigrations.py", line 172,
in handle
changes = autodetector.changes(
File "...django/db/migrations/autodetector.py", line 41, in changes
changes = self._detect_changes(convert_apps, graph)
File "...django/db/migrations/autodetector.py", line 127, in
_detect_changes
self.new_apps = self.to_state.apps
File "...django/utils/functional.py", line 48, in __get__
res = instance.__dict__[self.name] = self.func(instance)
File "...django/db/migrations/state.py", line 208, in apps
return StateApps(self.real_apps, self.models)
File "...django/db/migrations/state.py", line 270, in __init__
self.render_multiple([*models.values(), *self.real_models])
File "...django/db/migrations/state.py", line 305, in render_multiple
model.render(self)
File "...django/db/migrations/state.py", line 572, in render
return type(self.name, bases, body)
File "...django/db/models/base.py", line 99, in __new__
new_class = super_new(cls, name, bases, new_attrs, **kwargs)
File "/usr/lib/python3.9/typing.py", line 1010, in __init_subclass__
raise TypeError("Cannot inherit from plain Generic")
TypeError: Cannot inherit from plain Generic
}}}
I have tracked down the issue to the following chain of events:
-
[https://github.com/django/django/blob/4540e976d4e941166fbd2d3f1df1853f7e348740/django/db/migrations/state.py#L396
ModelState.from_model] is called with `Customer` as argument
- It recursively builds a list of base classes for `Customer` from
`Customer.__bases__` and uses it to return a `ModelState` instance
- This base class list will contain `typing.Generic` – note: not
`typing.Generic[StripeClassT]` – because it is present in
`StripeObjectModel.__bases__`
- The `ModelState` instance's
[https://github.com/django/django/blob/4540e976d4e941166fbd2d3f1df1853f7e348740/django/db/migrations/state.py#L551
render] method gets called
- An exception is raised in when calling `type(self.name, bases, body)`
because subclassing unparameterized `typing.Generic` is not allowed
A simple solution might be to filter out `typing.Generic` from `bases` in
`ModelState.from_model`.
--
Ticket URL: <https://code.djangoproject.com/ticket/33174>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/050.a8786d1486b42a0b6d8edbe760801dee%40djangoproject.com.