Skip to main content

A dataclasses serializer for Django REST Framework

Project description

A dataclasses serializer for the Django REST Framework.

https://travis-ci.org/oxan/djangorestframework-dataclasses.svg?branch=master https://codecov.io/gh/oxan/djangorestframework-dataclasses/branch/master/graph/badge.svg https://badge.fury.io/py/djangorestframework-dataclasses.svg

Requirements

  • Python (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-dataclasses

Basic usage

The package provides the DataclassSerializer serializer, defined in the rest_framework_dataclasses.serializers namespace.

from rest_framework_dataclasses.serializers import DataclassSerializer

This serializer provides a shortcut that lets you automatically create a Serializer class with fields that correspond to the fields on a dataclass. In usage, the DataclassSerializer is the same as a regular Serializer class, except that:

  • It will automatically generate fields for you, based on the declaration in the dataclass.

  • To make this possible it requires that a dataclass property is specified in the Meta subclass, with as value a dataclass that has type annotations.

  • It includes simple default implementations of .create() and .update().

For example, define a dataclass as follows:

@dataclass
class Person:
    name: str
    email: str
    alive: bool
    gender: typing.Literal['male', 'female']
    birth_date: typing.Optional[datetime.date]
    phone: typing.List[str]
    movie_ratings: typing.Dict[str, int]

    def age(self) -> int:
        return datetime.date.today().year - self.birth_date.year

The serializer for this dataclass can now trivially be defined without having to duplicate all fields:

class PersonSerializer(DataclassSerializer):
    class Meta:
        dataclass = Person

# is equivalent to
class PersonSerializer(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())

Note that this usage pattern is very similar to that of the built-in ModelSerializer. This is intentional, with the whole API modelled after that of ModelSerializer.

You can add extra fields or override default fields by declaring them explicitly on the class, just as you would for a regular Serializer class. This allows to specify extra options or change the field type.

class PersonSerializer(Serializer):
    email = fields.EmailField()

    class Meta:
        dataclass = Person

Customize field generation

To customize the generated fields, the DataclassSerializer accepts the following options in the Meta class. All options have the same behaviour as the identical options in ModelSerializer.

  • dataclass specifies the type of dataclass used by the serializer. This is equivalent to the model option in ModelSerializer.

  • fields and exclude can be used to specify which fields should respectively be included and excluded in the serializer. These cannot both be specified.

    The fields option 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 either fields or exclude.

  • read_only_fields can be used to mark a subset of fields as read-only.

  • extra_kwargs can 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 fields child field (that is, the field used for the items in the list or dictionary) list), they should be specified as a nested dictionary under the child_kwargs name (see Nesting with extra kwargs section below for an example).

    class PersonSerializer(DataclassSerializer):
        class Meta:
            extra_kwargs = {
                'height': { 'decimal_places': 1 },
                'movie_ratings': { 'child_kwargs': { 'min_value': 0, 'max_value': 10 } }
            }
  • validators functionality is unchanged.

  • depth (as known from ModelSerializer) is not yet supported.

Nesting and models

If your dataclass has a field that contains a dataclass instance as well, the DataclassSerializer will automatically create another DataclassSerializer for that field, so that its value will be nested. This also works for dataclasses contained in lists or dictionaries, or even several layers deep.

@dataclass
class House:
    address: str
    owner: Person
    residents: typing.List[Person]

class HouseSerializer(DataclassSerializer):
    class Meta:
        dataclass = House

This 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 option to customize the field generation of the nested dataclasses. If that is needed, you should declare the serializer to be used explicitly.

Likewise, if your dataclass has a field that contains a Django model, the DataclassSerializer will automatically generate a relational field for you.

class Company(models.Model):
    name = models.CharField()

@dataclass
class Person:
    name: str
    employer: Company

This 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 the HyperlinkedDataclassSerializer class: it generates a HyperlinkedRelatedField instead of a PrimaryKeyRelatedField.

Nesting with extra kwargs

The extra_kwargs option can be nested, in order to provide kwargs to fields belonging to nested dataclasses. Consider the following:

@dataclass
class Transaction:
  amount: Decimal
  account_number: str

@dataclass
class Company:
  sales: List[Transaction]

In order to tell DRF to give 2 decimal places to the transaction account number, write the serializer as follows:

class CompanySerializer(DataclassSerializer):
    class Meta:
        dataclass = Company

        extra_kwargs = {
            'sales': {
                'child_kwargs': { # Required because sales is a List, otherwise you could have the extra_kwargs directly
                    'extra_kwargs': {
                        'amount': {
                            'max_digits': 6,
                            'decimal_places': 2
                        }
                    }
                }
            }
        }

Advanced usage

  • The 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 the fields option in the Meta class.

  • If you don’t need to customize the generated fields, DataclassSerializer can also be used directly without creating a subclass. In that case, the dataclass should be specified using the dataclass constructor parameter:

    serializer = DataclassSerializer(data=request.data, dataclass=Person)

Field mappings

So far, field generation is supported for the following types and their subclasses:

  • str, bool, int and float.

  • date, datetime, time and timedelta from the datetime package.

  • decimal.Decimal (requires specifying max_digits and decimal_places through extra_kwargs).

  • uuid.UUID

  • typing.Iterable (including typing.List).

  • typing.Mapping (including typing.Dict).

  • typing.Literal (mapped to a ChoiceField).

  • django.db.Model

For advanced users, the DataclassSerializer also exposes an API that you can override in order to alter how serializer fields are generated:

  • The serializer_field_mapping property 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.

  • The serializer_related_field is the serializer field class that is used for relations to models.

  • The build_unknown_typed_field() method is called to create serializer field classes for types that it does not understand. By default this throws an error, but you can extend this with custom logic to create serializer fields.

  • The build_standard_field(), build_relational_field(), build_nested_field() and build_property_field() methods are used to process respectively fields, embedded models, embedded dataclasses and properties. These can be overridden to change the field generation logic, but at that point it’s usually a better idea to just declare the field explicitly.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

djangorestframework-dataclasses-0.4.tar.gz (17.5 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page