Package: djangorestframework-dataclasses-piti
Adataclassesserializer for theDjango REST Framework.RequirementsPython (3.7+)Django (2.0+)Django REST Framework (3.9+)These are the supported Python and package versions. Older versions will probably work as well, but haven’t been tested
by the author.Installation$ pip install djangorestframework-dataclassesThis package followssemantic versioning. SeeCHANGELOGfor breaking changes and new features, andLICENSEfor
the complete license (BSD-3-clause).Basic usageThe package provides theDataclassSerializerserializer, defined in therest_framework_dataclasses.serializersnamespace.fromrest_framework_dataclasses.serializersimportDataclassSerializerThis serializer provides a shortcut that lets you automatically create aSerializerclass with fields that
correspond to the fields on a dataclass. In usage, theDataclassSerializeris the same as a regularSerializerclass, except that:It will automatically generate fields for you, based on the declaration in the dataclass.To make this possible it requires that adataclassproperty is specified in theMetasubclass, with as value
a dataclass that has type annotations.It includes default implementations of.create()and.update().For example, define a dataclass as follows:@dataclassclassPerson:name:stremail:stralive:boolgender:typing.Literal['male','female']birth_date:typing.Optional[datetime.date]phone:typing.List[str]movie_ratings:typing.Dict[str,int]The serializer for this dataclass can now trivially be defined without having to duplicate all fields:classPersonSerializer(DataclassSerializer):classMeta:dataclass=Person# is equivalent toclassPersonSerializer(Serializer):name=fields.CharField()email=fields.CharField()alive=fields.BooleanField()gender=fields.ChoiceField(choices=['male','female'])birth_date=fields.DateField(allow_null=True)phone=fields.ListField(child=fields.CharField())movie_ratings=fields.DictField(child=fields.IntegerField())You can add extra fields or override default fields by declaring them explicitly on the class, just as you would for a
regularSerializerclass. This allows to specify extra field options or change a field type.classPersonSerializer(Serializer):email=fields.EmailField()classMeta:dataclass=PersonDataclass serializers behave in the same way and can be used in the same places as the built-in serializers from Django
REST Framework: you can retrieve the serialized representation using the.dataproperty, and the deserialized
dataclass instance using the.validated_dataproperty. Furthermore, thesave()method is implemented to create
or update an existing dataclass instance. You can find more information on serializer usage in theDjango REST Frameworkdocumentation.Note that this usage pattern is very similar to that of the built-inModelSerializer. This is intentional, with the
whole API modelled after that ofModelSerializer. Most features and behaviour known fromModelSerializerapplies
to dataclass serializers as well.Customize field generationThe auto-generated serializer fields are configured based on type qualifiers in the dataclass (these can be mixed):Fields with a default value (factory) are marked as optional on the serializer (required=False). This means that
these fields don’t need to be supplied during deserialization.Fields marked as nullable throughtyping.Optionalortyping.Union[X, None]are marked as nullable on the
serializer (allow_null=True). This means thatNoneis accepted as a valid value during deserialization.Fields marked as final throughtyping.Final(as inPEP 591) are marked as read-only on the serializer
(read_only=True).@dataclassclassPerson:birth_date:typing.Optional[datetime.date]alive:bool=Truespecies:typing.Final[str]='Human'# the autogenerated serializer will be equal toclassPersonSerializer(Serializer):birth_date=fields.DateField(allow_null=True)alive=fields.BooleanField(required=False)species=fields.CharField(read_only=True)Besides overriding fields by declaring them explicitly on the serializer, you can also change or override the generated
serializer field using metadata on the dataclass field. Currently, two keys are recognized in this dictionary:serializer_fieldcan be used to replace the auto-generated field with a user-supplied one. Should contain an
instance of a field, not a field type.serializer_kwargscan be used to specify arbitrary additional keyword arguments for the generated field. Manually
specified arguments will have precedence over generated arguments (so e.g. by supplying{required: True}, a field
with a default value can be made required).@dataclasses.dataclassclassPerson:email:str=dataclasses.field(metadata={'serializer_field':fields.EmailField()})age:int=dataclasses.field(metadata={'serializer_kwargs':{'min_value':0}})# the autogenerated serializer will be equal toclassPersonSerializer(Serializer):email=fields.EmailField()age=fields.IntegerField(min_value=0)To further customize the serializer, theDataclassSerializeraccepts the following options in theMetasubclass. All options have the same behaviour as the identical options inModelSerializer.dataclassspecifies the type of dataclass used by the serializer. This is equivalent to themodeloption inModelSerializer.fieldsandexcludecan be used to specify which fields should respectively be included and excluded in the
serializer. These cannot both be specified.Thefieldsoption accepts the magic value__all__to specify that all fields on the dataclass should be used.
This is also the default value, so it is not mandatory to specify eitherfieldsorexclude.read_only_fieldscan be used to mark a subset of fields as read-only.extra_kwargscan be used to specify arbitrary additional keyword arguments on fields. This can be useful to
extend or change the autogenerated field without explicitly declaring the field on the serializer. This option should
be a dictionary, mapping field names to a dictionary of keyword arguments.If the autogenerated field is a composite field (a list or dictionary), the arguments are applied to the composite
field. To add keyword arguments to the composite field’s child field (that is, the field used for the items in the
list or dictionary), they should be specified as a nested dictionary under thechild_kwargsname (seeNested dataclassessection below for an example).classPersonSerializer(DataclassSerializer):classMeta:extra_kwargs={'height':{'decimal_places':1},'movie_ratings':{'child_kwargs':{'min_value':0,'max_value':10}}}validatorsfunctionality is unchanged.depth(as known fromModelSerializer) is not supported, it will always nest infinitely deep.NestingNested dataclassesIf your dataclass has a field that also contains a dataclass instance, theDataclassSerializerwill automatically
create anotherDataclassSerializerfor that field, so that its value will be nested. This also works for dataclasses
contained in lists or dictionaries, or even several layers deep.@dataclassclassHouse:address:strowner:Personresidents:typing.List[Person]classHouseSerializer(DataclassSerializer):classMeta:dataclass=HouseThis will serialize as:>>>serializer=HouseSerializer(instance=house)>>>serializer.data{'address':'Main Street 5','owner':{'name':'Alice'}'residents':[{'name':'Alice','email':'alice@example.org',...},{'name':'Bob','email':'bob@example.org',...},{'name':'Charles','email':'charles@example.org',...}]}This does not give the ability to customize the field generation of the nested dataclasses. If that is needed, you
should declare the serializer to be used for the nested field explicitly. Alternatively, you could use theextra_kwargsoption to provide arguments to fields belonging to the nested dataclasses. Consider the following:@dataclassclassTransaction:amount:Decimalaccount_number:str@dataclassclassCompany:sales:List[Transaction]In order to tell DRF to give 2 decimal places to the transaction account number, write the serializer as follows:classCompanySerializer(DataclassSerializer):classMeta:dataclass=Companyextra_kwargs={'sales':{# Arguments here are for the ListField generated for the sales field on Company'min_length':1,# requires at least 1 item to be present in the sales list'child_kwargs':{# Arguments here are passed to the DataclassSerializer for the Transaction dataclass'extra_kwargs':{# Arguments here are the extra arguments for the fields in the Transaction dataclass'amount':{'max_digits':6,'decimal_places':2}}}}}Nesting modelsLikewise, if your dataclass has a field that contains a Django model, theDataclassSerializerwill automatically
generate a relational field for you.classCompany(models.Model):name=models.CharField()@dataclassclassPerson:name:stremployer:CompanyThis will serialize as:>>>serializer=PersonSerializer(instance=user)>>>print(repr(serializer))PersonSerializer():name=fields.CharField()employer=fields.PrimaryKeyRelatedField(queryset=Company.objects.all())>>>serializer.data{"name":"Alice","employer":1}If you want to nest the model in the serialized representation, you should specify the model serializer to be used by
declaring the field explicitly.If you prefer to use hyperlinks to represent relationships rather than primary keys, in the same package you can find
theHyperlinkedDataclassSerializerclass: it generates aHyperlinkedRelatedFieldinstead of aPrimaryKeyRelatedField.New serializer field typesTo handle some types for which DRF does not ship a serializer field, some new serializer field types are shipped in therest_framework_dataclasses.fieldsnamespace. These fields can be used independently of theDataclassSerializeras well.DefaultDecimalFieldA subclass ofDecimalFieldthat defaultsmax_digitstoNoneanddecimal_placesto 2. Used to represent
decimal values which there is no explicit field configured.EnumFieldA subclass ofChoiceFieldto represent Pythonenumerations. The enumeration members can be represented by either
their name or value. The member name is used as display name.Signature:EnumField(enum_class, by_name=False)enum_class: The enumeration class.by_name: Whether members are represented by their value (False) or name (True).Advanced usageThe output of methods or properties on the dataclass can be included as a (read-only) field in the serialized state
by adding their name to thefieldsoption in theMetaclass.If you don’t need to customize the generated fields,DataclassSerializercan also be used directly without
creating a subclass. In that case, the dataclass should be specified using thedataclassconstructor parameter:serializer=DataclassSerializer(data=request.data,dataclass=Person)Partial updates are supported by setting thepartialargument toTrue. Nested dataclasses will also be
partially updated, but nested fields and dictionaries will be replaced in full with the supplied value:@dataclassclassCompany:name:strlocation:Optional[str]=None@dataclassclassPerson:name:strcurrent_employer:Companypast_employers:List[Company]alice=Person(name='Alice',current_employer=Company('Acme Corp.','New York City'),past_employers=[Company('PSF','Delaware'),Company('Ministry of Silly Walks','London')])data={'current_employer':{'location':'Los Angeles'},'past_employers':[{'name':'OsCorp','location':'NYC'}]}>>>serializer=PersonSerializer(partial=True,instance=alice,data=data)>>>print(serializer.save())Person(name='Alice',current_employer=Company('Acme Corp.','Los Angeles'),past_employers=[Company(name='OsCorp',location='NYC')])If you override thecreate()orupdate()methods, the dataclass instance passed in thevalidated_dataargument will have the specialrest_framework.fields.emptyvalue for any fields for which no data was provided.
This is required to distinguish between not-provided fields and fields with the default value, as needed for (both
regular and partial) updates. You can get rid of theseemptymarkers and replace them with the default value by
calling the parentupdate()orcreate()methods - this is the only thing they do.classCompanySerializer(DataclassSerializer):defcreate(self,validated_data):instance=super(CompanySerializer,self).create(validated_data)# if no value is provided for location, these will both holdassertvalidated_data.location==rest_framework.fields.emptyassertinstance.locationisNone# None is the default value of Company.location (see previous example)Thevalidated_dataproperty on the serializer has theseemptymarkers stripped as well, and replaced with the
default values for not-provided fields. Note that this means you cannot accessvalidated_dataon the serializer
for partial updates where no data has been provided for fields without a default value, an Exception will be thrown.Field mappingsSo far, field generation is supported for the following types and their subclasses:str,bool,intandfloat.date,datetime,timeandtimedeltafrom thedatetimepackage.decimal.Decimal(max_digitsanddecimal_placesdefault toNoneand2respectively).uuid.UUIDenum.Enum(mapped to aEnumField)typing.Iterable(includingtyping.ListandPEP 585-style generics such aslist[int]).typing.Mapping(includingtyping.DictandPEP 585-style generics such asdict[str, int]).typing.Literal(mapped to aChoiceField).django.db.ModelThe serializer also supports type variables that have an upper bound or are constrained. Type unions are not supported
yet.For advanced users, theDataclassSerializeralso exposes an API that you can override in order to alter how
serializer fields are generated:Theserializer_field_mappingproperty contains a dictionary that maps types to REST framework serializer classes.
You can override or extend this mapping to change the serializer field classes that are used for fields based on
their type. This dictionary also accepts dataclasses as keys to change the serializer used for nested dataclass.Theserializer_related_fieldproperty is the serializer field class that is used for relations to models.Theserializer_dataclass_fieldproperty is the serializer field class that is used for nested dataclasses. If you
subclassDataclassSerializerto customize behaviour, you probably want to change this property to use the subclass
as well. Note that since Python process the class body before it defines the class, this property is implemented using
theproperty decoratorto allow it to reference the containing class.Thebuild_unknown_field()method is called to create serializer fields for dataclass fields that are not
understood. By default this just throws an error, but you can extend this with custom logic to create serializer
fields.Thebuild_property_field()method is called to create serializer fields for methods. By default this creates a
read-only field with the method return value.Thebuild_standard_field(),build_relational_field(),build_dataclass_field(),build_enum_field(),build_literal_field()andbuild_composite_field()methods are used to process respectively fields, nested
models, nested dataclasses, enums, literals, and lists or dictionaries. These can be overridden to change the field
generation logic.Note that until version 1.0 these API’s are not declared stable yet and might change if necessary.