#36695: Model field validator with a generic parameter causes infinite recursion
when making migrations
-------------------------------+--------------------------------------
     Reporter:  Michal Dabski  |                    Owner:  (none)
         Type:  Bug            |                   Status:  new
    Component:  Migrations     |                  Version:  4.2
     Severity:  Normal         |               Resolution:
     Keywords:                 |             Triage Stage:  Unreviewed
    Has patch:  0              |      Needs documentation:  0
  Needs tests:  0              |  Patch needs improvement:  0
Easy pickings:  0              |                    UI/UX:  0
-------------------------------+--------------------------------------
Description changed by Michal Dabski:

Old description:

> Minimal code to reproduce (in `models.py`) in `Django==4.2.25`
>
> {{{
> from django.utils.deconstruct import deconstructible
> from django.db import models
>
> @deconstructible
> class SchemaValidator:
>     def __init__(self, expected_type: type):
>         pass
>
>     def __call__(self, *args, **kwargs):
>         pass
>
> class TestModel(models.Model):
>     config: dict[str, float] =
> models.JSONField(validators=[SchemaValidator(dict[str, float])])
>
> }}}
>
> This triggers an error when running makemigrations:
>
> {{{
> $ ./manage.py makemigrations
> C:\Users\Michal\src\mat-cms\.venv\Lib\site-
> packages\django_downloadview\__init__.py:4: UserWarning: pkg_resources is
> deprecated as an API. See
> https://setuptools.pypa.io/en/latest/pkg_resources.html. The
> pkg_resources package is slated for removal as early as 2025-11-30.
> Refrain from using this package or pin to Setuptools<81.
>   import pkg_resources
> Migrations for 'audit_builder':
>   meg_forms\audit_builder\migrations\0083_alter_testmodel_config.py
>     - Alter field config on testmodel
> Traceback (most recent call last):
>   File "C:\Users\Michal\src\mat-cms\.venv\Lib\site-
> packages\django\db\migrations\serializer.py", line 214, in serialize
>     item_string, item_imports = serializer_factory(item).serialize()
>                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "C:\Users\Michal\src\mat-cms\.venv\Lib\site-
> packages\django\db\migrations\serializer.py", line 214, in serialize
>     item_string, item_imports = serializer_factory(item).serialize()
>                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "C:\Users\Michal\src\mat-cms\.venv\Lib\site-
> packages\django\db\migrations\serializer.py", line 214, in serialize
>     item_string, item_imports = serializer_factory(item).serialize()
>                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   [Previous line repeated 996 more times]
>   File "C:\Users\Michal\src\mat-cms\.venv\Lib\site-
> packages\django\db\migrations\serializer.py", line 389, in
> serializer_factory
>     if isinstance(value, type_):
>        ^^^^^^^^^^^^^^^^^^^^^^^^
> RecursionError: maximum recursion depth exceeded
> }}}
>
> Debugging locally shows that:
> * removing generic parameters from the `dict` type passed to
> `SchemaValidator` does not trigger the error
> * The loop happens in
> `django.db.migrations.serializer.IterableSerializer.serialize`, it keeps
> recursively invoking `serializer_factory(item).serialize()` with
> `item='*dict[str, float]'` (`GenericAlias`)

New description:

 Minimal code to reproduce (in `models.py`) in `Django==4.2.25`

 {{{
 from django.utils.deconstruct import deconstructible
 from django.db import models

 @deconstructible
 class SchemaValidator:
     def __init__(self, expected_type: type):
         pass

     def __call__(self, *args, **kwargs):
         pass

 class TestModel(models.Model):
     config: dict[str, float] =
 models.JSONField(validators=[SchemaValidator(dict[str, float])])

 }}}

 This triggers an error when running makemigrations:

 {{{
 $ ./manage.py makemigrations
 Migrations for 'audit_builder':
   meg_forms\audit_builder\migrations\0083_alter_testmodel_config.py
     - Alter field config on testmodel
 Traceback (most recent call last):
   File "C:\Users\Michal\src\mat-cms\.venv\Lib\site-
 packages\django\db\migrations\serializer.py", line 214, in serialize
     item_string, item_imports = serializer_factory(item).serialize()
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "C:\Users\Michal\src\mat-cms\.venv\Lib\site-
 packages\django\db\migrations\serializer.py", line 214, in serialize
     item_string, item_imports = serializer_factory(item).serialize()
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "C:\Users\Michal\src\mat-cms\.venv\Lib\site-
 packages\django\db\migrations\serializer.py", line 214, in serialize
     item_string, item_imports = serializer_factory(item).serialize()
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   [Previous line repeated 996 more times]
   File "C:\Users\Michal\src\mat-cms\.venv\Lib\site-
 packages\django\db\migrations\serializer.py", line 389, in
 serializer_factory
     if isinstance(value, type_):
        ^^^^^^^^^^^^^^^^^^^^^^^^
 RecursionError: maximum recursion depth exceeded
 }}}

 Debugging locally shows that:
 * removing generic parameters from the `dict` type passed to
 `SchemaValidator` does not trigger the error
 * The loop happens in
 `django.db.migrations.serializer.IterableSerializer.serialize`, it keeps
 recursively invoking `serializer_factory(item).serialize()` with
 `item='*dict[str, float]'` (`GenericAlias`)

--
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36695#comment:1>
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 visit 
https://groups.google.com/d/msgid/django-updates/0107019a3005207a-5180aeaa-5e3c-40a9-981b-573bea559c20-000000%40eu-central-1.amazonses.com.

Reply via email to