#37431 closed Bug (fixed)
MigrationWriter silently serializes Python 3.15 frozendict values as tuples of keys
| Reported by: | amanjain | Owned by: | amanjain |
|---|---|---|---|
| Component: | Migrations | Version: | 6.1 |
| Severity: | Release blocker | Keywords: | |
| Cc: | Triage Stage: | Ready for checkin | |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
On Python 3.15.0, Django's migration serializer treats built-in frozendict instances as generic iterables, preserving only their keys and silently discarding their values.
Reproduced against Django main at d430084791.
Minimal reproducer:
from django.db.migrations.writer import MigrationWriter MigrationWriter.serialize(frozendict(answer=42))
Actual result:
("('answer',)", set())
Expected behavior: preserve the mapping's keys and values instead of serializing it as a tuple of keys.
This affects field defaults:
models.JSONField(default=frozendict(answer=42))
The generated migration contains:
models.JSONField(default=('answer',))
The database consequence was reproduced on SQLite: executing a generated AddField migration against an existing row stores ["answer"] instead of {"answer": 42}.
The serializer registry handles dict explicitly, but frozendict is not a dict subclass and falls through to IterableSerializer.
A proposed patch adds a dedicated serializer, regression tests in the migration writer and operation suites, and documentation noting that generated migrations containing frozendict require Python 3.15+.
Validation:
- Python 3.15: 75 writer/regression tests passed.
- Python 3.15: 181 migration operation tests ran successfully, with 2 skipped.
- Python 3.14: 75 writer/regression tests ran successfully, with 3 skipped.
- Formatting and whitespace checks passed.
- The full Django test suite has not been run.
AI disclosure: OpenAI Codex (GPT-6) assisted with investigation, implementation, regression tests, documentation, and commit preparation.
Change History (8)
comment:1 by , 3 weeks ago
| Component: | Uncategorized → Migrations |
|---|---|
| Description: | modified (diff) |
| Version: | 6.1 → dev |
comment:2 by , 3 weeks ago
comment:3 by , 3 weeks ago
| Has patch: | set |
|---|
comment:4 by , 3 weeks ago
| Owner: | set to |
|---|---|
| Patch needs improvement: | set |
| Severity: | Normal → Release blocker |
| Status: | new → assigned |
| Triage Stage: | Unreviewed → Accepted |
| Type: | Uncategorized → Bug |
Thanks, I see we need similar special handling just like frozenset. We'll backport this to 6.1 as part of 3.15 support. You don't need a release note, as we caught it before the first release declaring support for 3.15.
comment:5 by , 3 weeks ago
| Version: | dev → 6.1 |
|---|
comment:6 by , 2 weeks ago
| Patch needs improvement: | unset |
|---|---|
| Triage Stage: | Accepted → Ready for checkin |
Proposed patch: https://github.com/django/django/pull/22157
Regression tests reproduce both the incorrect serialization and the incorrect JSON stored for existing rows.