1
0
mirror of https://github.com/django/django.git synced 2025-06-24 04:49:11 +00:00

Refs #36383 -- Extended DeconstructibleSerializer to support non-identifier keyword arguments.

In Python, keyword arguments must normally be valid identifiers (i.e.,
variable names that follow Python's naming rules). However, Python dicts
can have keys that aren't valid identifiers, like "foo-bar" or "123foo".

This commit ensures that keyword arguments that are nt valid
identifiers, are properly handled when deconstructing an object.
This commit is contained in:
Adam Johnson 2025-05-11 23:07:47 +02:00 committed by nessita
parent 0f94ecd49d
commit 4647e2b866
4 changed files with 42 additions and 3 deletions

View File

@ -102,10 +102,20 @@ class DeconstructibleSerializer(BaseSerializer):
arg_string, arg_imports = serializer_factory(arg).serialize()
strings.append(arg_string)
imports.update(arg_imports)
non_ident_kwargs = {}
for kw, arg in sorted(kwargs.items()):
if kw.isidentifier():
arg_string, arg_imports = serializer_factory(arg).serialize()
imports.update(arg_imports)
strings.append("%s=%s" % (kw, arg_string))
else:
non_ident_kwargs[kw] = arg
if non_ident_kwargs:
# Serialize non-identifier keyword arguments as a dict.
kw_string, kw_imports = serializer_factory(non_ident_kwargs).serialize()
strings.append(f"**{kw_string}")
imports.update(kw_imports)
return "%s(%s)" % (name, ", ".join(strings)), imports
@staticmethod

View File

@ -181,6 +181,9 @@ Migrations
* Migrations now support serialization of :class:`zoneinfo.ZoneInfo` instances.
* Serialization of deconstructible objects now supports keyword arguments with
names that are not valid Python identifiers.
Models
~~~~~~

View File

@ -113,6 +113,7 @@ databrowse
datafile
datetimes
declaratively
deconstructible
deduplicates
deduplication
deepcopy

View File

@ -41,6 +41,13 @@ class DeconstructibleInstances:
return ("DeconstructibleInstances", [], {})
@deconstructible
class DeconstructibleArbitrary:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
class Money(decimal.Decimal):
def deconstruct(self):
return (
@ -1143,6 +1150,24 @@ class WriterTests(SimpleTestCase):
"models.CharField(default=migrations.test_writer.DeconstructibleInstances)",
)
def test_serialize_non_identifier_keyword_args(self):
instance = DeconstructibleArbitrary(
**{"kebab-case": 1, "my_list": [1, 2, 3], "123foo": {"456bar": set()}},
regular="kebab-case",
**{"simple": 1, "complex": 3.1416},
)
string, imports = MigrationWriter.serialize(instance)
self.assertEqual(
string,
"migrations.test_writer.DeconstructibleArbitrary(complex=3.1416, "
"my_list=[1, 2, 3], regular='kebab-case', simple=1, "
"**{'123foo': {'456bar': set()}, 'kebab-case': 1})",
)
self.assertEqual(imports, {"import migrations.test_writer"})
result = self.serialize_round_trip(instance)
self.assertEqual(result.args, instance.args)
self.assertEqual(result.kwargs, instance.kwargs)
def test_register_serializer(self):
class ComplexSerializer(BaseSerializer):
def serialize(self):