Skip to content

thedrow/django-rest-framework-version-transforms

 
 

Repository files navigation

djangorestframework-version-transforms

build-status-image pypi-version read-the-docs

Overview

A library to enable the use of delta transformations for versioning of Django Rest Framwork API representations.

Change is Inevitable

Wouldn't it be nice if we only needed to design our API once?

Unfortunately for API developers, changes in API design are inevitable for many projects. If developers cannot avoid API change, then the next best option is to manage it without sacrificing software quality. Managing API change requires maintenance of multiple versions for our resource representations, but django rest framework sacrifices maintainability in its default versioning support.

Using the default versioning support in DRF demands that endpoint code maintain the semantic differences between version of a representation. Forcing the responsibility of version compatibility onto the endpoint code results in endpoints that are hard to read and difficult to maintain. The length of version compatibility boilerplate code will increase as versions are created, and so will increasingly degrade the maintainability of the endpoint code.

There is a better way.

Representational Version Transforms

djangorestframework-version-transforms enables REST framework developers to remove version compatibility boilerplate from their endpoints. Developers need only write version compatibility once per version change, in the form of version transforms. Version transforms encapsulate the necessary changes to promote or demote a resource representation between versions. Endpoint logic can then only concern itself with the highest (or current) version of the resource, leading to great benefits in maintainability.

Using version transforms enables API maintainers to make the necessary changes to endpoint logic that they want to make without worrying about the impact on version compatibility.

This Library

This library aims to make maintenance of resource versions simple and intuitive for users of django rest framework by using version transforms to record incremental API changes separately from endpoint logic.

Requirements

  • Python (2.7, 3.3, 3.4)
  • Django (1.6, 1.7, 1.8)
  • Django REST Framework (2.4, 3.0, 3.1)

Installation

Install using pip...

$ pip install djangorestframework-version-transforms

Usage

Transforms

Transforms are the classes that will be used to convert between versions of your representation during endpoint request handling. Transforms are expected to implement two methods which enable forward (request) and backward (response) compatibility between two versions of the representation:

class BaseTransform(object):
    """
    All transforms should extend 'BaseTransform', overriding the two
    methods '.forwards()' and '.backwards()' to provide forwards and backwards
    conversions between representation versions.
    """
    def forwards(self, data, request):
        """
        Converts from this transform's base version to the next version of the representation.

        :returns: Dictionary with the correct structure for the next version of the representation.
        """
        raise NotImplementedError(".forwards() must be overridden.")

    def backwards(self, data, request, instance):
        """
        Converts from the next version back to this transform's base version of the representation.

        :returns: Dictionary with the correct structure for the base version of the representation.
        """
        raise NotImplementedError(".backwards() must be overridden.")

To create a transform that can be used for conversion, define a subclass of BaseTransform:

class TestModelTransform0001(BaseTransform):
    def forwards(self, data, request):
        if 'test_field_one' in data:
            data['new_test_field'] = data.get('test_field_one')
            data.pop('test_field_one')
        return data

    def backwards(self, data, request, instance):
        data['test_field_one'] = data.get('new_test_field')
        data.pop('new_test_field')
        return data

In this example transform, the .forwards() method would be used to change a v1 representation into a v2 representation by substituting a field key. This transform indicates that it will be used to convert from v1 to v2 by appending a numerical indicator of the version it is based upon, 0001.

To define a second transform that would enable conversion between a v2 and v3, we would simply use the same prefix with an incremented numerical indicator.

class TestModelTransform0002(BaseTransform):
    def forwards(self, data, request):
        data['new_related_object_id_list'] = [1, 2, 3, 4, 5]
        return data

    def backwards(self, data, request, instance):
        data.pop('new_related_object_id_list')
        return data

Therefore, the second transform is indicating that it is based upon the version 0002, and can convert forwards from a v2 to a v3 and backwards again from a v3 to a v2. In this second example transform, the .forwards() method adds a required field with default values onto the representation to maintain compatibility with version 3 of the endpoint code which requires that field.

In both example transforms, the .backwards() method is used to convert a representation from the version above the base version down into the base version. In the first case, this means substituting the original key back into the representation, and removing the key from the v2 representation. In the second example, the .backwards() method would remove the field that is required by the v3 representation, since it was not expected in the v2 representation.

Parsers

Parsers are useful in django rest framework for defining content-types for your RESTful API resources.

Using djangorestframework-version-transforms, custom parsers can also be used to ensure that the representation parsed out of a request match the latest version of that resource. This means that endpoint logic no longer needs to be aware of previous versions of a resource. Whenever a request is made at a previous resource version, the inbound representation will be converted into the latest version during the parsing operation.

To make use of version transforms in parsers, define a subclass of BaseVersioningParser:

class TestParser(BaseVersioningParser):
    media_type = 'application/vnd.test.testtype+json'
    transform_base = 'tests.test_transforms.TestModelTransform'

The media_type property must be defined, but can be defined simply as application/json if no custom content type is necessary.

With a transform_base defined, this serializer will automatically retrieve transform classes from the specified module that are prefixed with the base transform name.

In this example, the full module name is 'tests.test_transforms', which indicates module from which the transform classes will be loaded. The base name in the example is 'TestModelTransform', which indicates the prefix for transform classes to be loaded from the module for use during conversion.

The VersioningParser will automatically run the .forwards() methods of each transform in ascending order, starting with the requested version. After this operation is complete, the parser will return the representation in the latest version for handling by the endpoint logic.

Serializers

Serializers are useful in django rest framework for returning consistent response representations to the requesting client.

Using djangorestframework-version-transforms, custom serializers can also be used to ensure that the response representation matches the representation version which the client knows how to handle. As a response representation is being prepared for transmission back to the client, the outbound representaiton will be converted back down to the requested version during the serialization operation.

To make use of transforms in serializers, define a subclass of BaseVersioningSerializer:

class TestSerializerV3(BaseVersioningSerializer):
    transform_base = 'tests.test_transforms.TestModelTransform'

    class Meta:
        model = TestModelV3
        fields = (
            'test_field_two',
            'test_field_three',
            'test_field_four',
            'test_field_five',
            'new_test_field',
            'new_related_object_id_list',
        )

With a transform_base defined, this serializer will automatically retrieve transform classes from the specified module that are prefixed with the base name.

In this example, the module name is 'tests.test_transforms', which indicates the module from which the transform classes will be loaded. The base name in the example is 'TestModelTransform', which indicates the prefix for transform classes to be loaded from the module for use during conversion.

The VersioningSerializer will automatically run the .backwards() methods of each transform in descending order, ending with the transform that has the requested version as its base. After the conversion operation is complete, the serializer will return the representation in the version requested by the client.

Development

Testing

Install testing requirements.

$ pip install -r requirements.txt

Run with runtests.

$ ./runtests.py

You can also use the excellent tox testing tool to run the tests against all supported versions of Python and Django. Install tox globally, and then simply run:

$ tox

Documentation

To build the documentation, you’ll need to install mkdocs.

$ pip install mkdocs

To preview the documentation:

$ mkdocs serve
Running at: http://127.0.0.1:8000/

To build the documentation:

$ mkdocs build

About

A library to enable the use of delta transformations for versioning of Django Rest Framwork API representations.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Python 100.0%