django-pydantic-field
django-pydantic-field copied to clipboard
Polymorphism (oneOf) with pydantic union discriminator
I have the following pydantic models:
class EUTaxonomyValue(BaseModel):
type: Literal["eu_taxonomy"] = "eu_taxonomy"
nace_code: str
economic_activity: str
classification: str | None
class EconomicActivity(BaseModel):
type: Literal["economic_activity"] = "economic_activity"
activity_code: str
activity_name: str
class DateRange(BaseModel):
type: Literal["daterange"] = "daterange"
start_date: date
end_date: date
ComplexValue = Annotated[
None | EUTaxonomyValue | EconomicActivity | DateRange,
Field(discriminator="type", title="Complex Value"),
]
Now I define a new field value_complex
in one of my serializers:
class FieldValueSerializer(serializers.ModelSerializer):
value_complex = SchemaField(schema=ComplexValue, required=False)
I'm not sure if this issue is related to django-pydantic-field or drf-spectacular.
The generated Open API 3 Schema is invalid.
value_complex:
title: value_complex
discriminator:
propertyName: type
mapping:
eu_taxonomy: '#/definitions/EUTaxonomyValue'
economic_activity: '#/definitions/EconomicActivity'
daterange: '#/definitions/DateRange'
oneOf:
- title: EUTaxonomyValue
type: object
properties:
type:
title: Type
default: eu_taxonomy
enum:
- eu_taxonomy
type: string
nace_code:
title: Nace Code
type: string
economic_activity:
title: Economic Activity
type: string
classification:
title: Classification
type: string
required:
- nace_code
- economic_activity
- title: EconomicActivity
type: object
properties:
type:
title: Type
default: economic_activity
enum:
- economic_activity
type: string
activity_code:
title: Activity Code
type: string
activity_name:
title: Activity Name
type: string
required:
- activity_code
- activity_name
- title: DateRange
type: object
properties:
type:
title: Type
default: daterange
enum:
- daterange
type: string
start_date:
title: Start Date
type: string
format: date
end_date:
title: End Date
type: string
format: date
required:
- start_date
- end_date
definitions:
EUTaxonomyValue:
title: EUTaxonomyValue
type: object
properties:
type:
title: Type
default: eu_taxonomy
enum:
- eu_taxonomy
type: string
nace_code:
title: Nace Code
type: string
economic_activity:
title: Economic Activity
type: string
classification:
title: Classification
type: string
required:
- nace_code
- economic_activity
EconomicActivity:
title: EconomicActivity
type: object
properties:
type:
title: Type
default: economic_activity
enum:
- economic_activity
type: string
activity_code:
title: Activity Code
type: string
activity_name:
title: Activity Name
type: string
required:
- activity_code
- activity_name
DateRange:
title: DateRange
type: object
properties:
type:
title: Type
default: daterange
enum:
- daterange
type: string
start_date:
title: Start Date
type: string
format: date
end_date:
title: End Date
type: string
format: date
required:
- start_date
- end_date
Regarding https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/ oneOf
should have a list of $ref
and those 3 pydantic models should be defined in #/components/schemas/
In addition to this issue with serializers this use case doesn't seem to work properly in models either. For me, on v0.3.3, I'm not able to create a migration if I use a Annotated[A| B, Field(discriminator="type")]
as the schema for a model field. It leads to:
Traceback (most recent call last):
File "/.../backend/manage.py", line 25, in <module>
main()
File "/.../backend/manage.py", line 21, in main
execute_from_command_line(sys.argv)
File "/.../lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
utility.execute()
File "/.../lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/.../lib/python3.11/site-packages/django/core/management/base.py", line 412, in run_from_argv
self.execute(*args, **cmd_options)
File "/.../lib/python3.11/site-packages/django/core/management/base.py", line 458, in execute
output = self.handle(*args, **options)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/core/management/base.py", line 106, in wrapper
res = handle_func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/core/management/commands/makemigrations.py", line 259, in handle
self.write_migration_files(changes)
File "/.../lib/python3.11/site-packages/django/core/management/commands/makemigrations.py", line 364, in write_migration_files
migration_string = writer.as_string()
^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/db/migrations/writer.py", line 141, in as_string
operation_string, operation_imports = OperationWriter(operation).serialize()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/db/migrations/writer.py", line 99, in serialize
_write(arg_name, arg_value)
File "/.../lib/python3.11/site-packages/django/db/migrations/writer.py", line 51, in _write
arg_string, arg_imports = MigrationWriter.serialize(item)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/db/migrations/writer.py", line 282, in serialize
return serializer_factory(value).serialize()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/db/migrations/serializer.py", line 42, in serialize
item_string, item_imports = serializer_factory(item).serialize()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/db/migrations/serializer.py", line 226, in serialize
return self.serialize_deconstructed(path, args, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/db/migrations/serializer.py", line 91, in serialize_deconstructed
arg_string, arg_imports = serializer_factory(arg).serialize()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django_pydantic_field/compat/django.py", line 81, in serialize
arg_repr, arg_imports = serializer_factory(arg).serialize()
^^^^^^^^^^^^^^^^^^^^^^^
File "/.../lib/python3.11/site-packages/django/db/migrations/serializer.py", line 391, in serializer_factory
raise ValueError(
ValueError: Cannot serialize: FieldInfo(annotation=NoneType, required=True, discriminator='type')
There are some values Django cannot serialize into migration files.
For more, see https://docs.djangoproject.com/en/4.2/topics/migrations/#migration-serializing
@henribru I have played around a bit with Annotated
schemas in #52 and figured out the way to serialize them as long as annotated metadata contains only serializable entities, e.g. dataclasses (for annotated_types package) and Pydantic's FieldInfo.
As a workaround for the time being I can suggest using a pydantic.RootModel
wrapper around your annotated expression, if that suits your particular case.