{"id":13813965,"url":"https://github.com/mrhwick/django-rest-framework-version-transforms","last_synced_at":"2025-03-17T03:31:29.035Z","repository":{"id":57421649,"uuid":"44611685","full_name":"mrhwick/django-rest-framework-version-transforms","owner":"mrhwick","description":"A library to enable the use of delta transformations for versioning of Django Rest Framwork API representations.","archived":false,"fork":false,"pushed_at":"2019-11-15T07:21:03.000Z","size":58,"stargazers_count":75,"open_issues_count":2,"forks_count":8,"subscribers_count":3,"default_branch":"dev","last_synced_at":"2025-02-27T17:34:11.563Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrhwick.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS.md","security":null,"support":null}},"created_at":"2015-10-20T14:25:15.000Z","updated_at":"2024-11-28T16:31:46.000Z","dependencies_parsed_at":"2022-09-10T14:10:44.628Z","dependency_job_id":null,"html_url":"https://github.com/mrhwick/django-rest-framework-version-transforms","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrhwick%2Fdjango-rest-framework-version-transforms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrhwick%2Fdjango-rest-framework-version-transforms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrhwick%2Fdjango-rest-framework-version-transforms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrhwick%2Fdjango-rest-framework-version-transforms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrhwick","download_url":"https://codeload.github.com/mrhwick/django-rest-framework-version-transforms/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243841203,"owners_count":20356443,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T04:01:38.316Z","updated_at":"2025-03-17T03:31:28.737Z","avatar_url":"https://github.com/mrhwick.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"djangorestframework-version-transforms\n======================================\n\n[![build-status-image](https://secure.travis-ci.org/mrhwick/django-rest-framework-version-transforms.svg?branch=master)](http://travis-ci.org/mrhwick/django-rest-framework-version-transforms?branch=master)\n[![pypi-version](https://img.shields.io/pypi/v/djangorestframework-version-transforms.svg)](https://pypi.python.org/pypi/djangorestframework-version-transforms)\n[![read-the-docs](https://readthedocs.org/projects/django-rest-framework-version-transforms/badge/?version=latest)](http://django-rest-framework-version-transforms.readthedocs.org/en/latest/?badge=latest)\n\n# Overview\n\nA library to enable the use of functional transforms for versioning of [Django Rest Framework] API representations.\n\n## API Change Management - State of the Art\n\nUnfortunately for API developers, changes in API schema are inevitable for any significant web service.\n\nIf developers cannot avoid changing their API representations, then the next best option is to manage these changes without making sacrifices to software quality. Managing API changes often requires a developer to define and maintain multiple versions of resource representations for their API. Django Rest Framework makes some code quality sacrifices in its default support for version definition.\n\nUsing the default versioning support in DRF, API developers are required to manage version differences within their endpoint code. Forcing the responsibility of version compatibility into this layer of your API increases the complexity of endpoints. As the number of supported versions increases, the length, complexity, and duplication of version compatibility boilerplate will increase, leading to ever-increasing difficulty when making subsequent changes.\n\nWe can do better than duplicating code and maintaining ever-increasing boilerplate within our APIs.\n\n## Representation Version Transforms\n\n`djangorestframework-version-transforms` empowers DRF users to forgo the introduction of unecessary boilerplate into their endpoint code.\n\nVersion compability is instead implemented as version transform functions that translate from one version of a resource to another. The general concept of a version transform should already be familiar to Django users, since it is derived from the frequently-used migration tool and uses similar patterns. Developers need only write version compatibility code once per version change, and need only maintain their endpoint code at the latest version.\n\nVersion transforms encapsulate the necessary changes to promote or demote a resource representation between versions, and a stack of version transforms can be used as a promotion or demotion pipeline when needed. With the correct stack of version transforms in place, endpoint logic should only be concerned with the latest (or current) version of the resource.\n\nWhen backwards incompatible changes are required, the endpoint can be upgraded to work against the new version. Then a single version transform is introduced that converts between the now outdated version and the newly created \"current\" version that the endpoint code expects.\n\n## Requirements\n\n-  Python (2.7, 3.4)\n-  Django (1.6, 1.7, 1.8)\n-  Django REST Framework (2.4, 3.0, 3.1)\n\n## Installation\n\nInstall using ```pip```...\n\n```bash\n$ pip install djangorestframework-version-transforms\n```\n\n## Usage\n\n### Creating Version Transforms\n\nTransforms are defined as python subclasses of the `BaseTransform` class. They are expected to implement two methods (`forwards` and `backwards`) which describe the necessary transformations for forward (request) and backward (response) conversion between two versions of the resource. The base version number for a transform is appended to the name.\n\nFor example:\n\n```python\n# Notice that this is a subclass of the `BaseTransform` class\nclass MyFirstTransform0001(BaseTransform):\n\n    # .forwards() is used to promote request data\n    def forwards(self, data, request):\n        if 'test_field_one' in data:\n            data['new_test_field'] = data.get('test_field_one')\n            data.pop('test_field_one')\n        return data\n\n    # .backwards() is used to demote response data\n    def backwards(self, data, request, instance):\n        data['test_field_one'] = data.get('new_test_field')\n        data.pop('new_test_field')\n        return data\n```\n\nIn this example transform, the `.forwards()` method would be used to change a v1 representation into a v2 representation by substituting the field key `new_test_field` for the previous key `test_field_one`. This transform indicates that it will be used to convert between v1 and v2 by appending a numerical indicator of the version it is based upon, `0001`, to the transform name. The `.backwards()` method simply does the swap operation in reverse, replacing the original field key that is expected in v1.\n\nTo define a second transform that would enable conversion between a v2 and v3, we would simply use the same prefix and increment the base version number to `0002`.\n\n```python\n# Again, subclassing `BaseTransform`.\n# The postfix integer indicates the base version.\nclass MyFirstTransform0002(BaseTransform):\n\n    def forwards(self, data, request):\n        data['new_related_object_id_list'] = [1, 2, 3, 4, 5]\n        return data\n\n    def backwards(self, data, request, instance):\n        data.pop('new_related_object_id_list')\n        return data\n```\n\nIn this second example transform, the `.forwards()` method adds a newly required field with some default values onto the representation. The `.backwards()` method simply removes the new field, since v2 does not require it.\n\n### Whole-API vs. Per-Endpoint Versioning\n\nThere are two general strategies for introducing new API versions, and this library supports either version strategy.\n\n#### Whole-API Versioning\n\nIn the Whole-API versioning strategy, any backwards-incompatible change to any endpoint within the API introduces a new API version for all endpoints. Clients are expected to maintain knowledge of the various changes particular to any resources affected by a given version change.\n\nIn this strategy, changes to resources will be bundled together as a new version alongside any unchanged resources.\n\nWhole-API versioning offers convenience for client-side developers at runtime, since the client must only interact with one version of an API at a time. One drawback is that the client must be made to support all changes to endpoints included in each new version of the API.\n\n##### Usage\n\nFor example, assume you have two resources `User` and `Profile`.\n\nIn the course of development, you must make several backwards incompatible changes over time:\n\n- v1 - Some initial version of `Profile` and `User`.\n- v2 - The `Profile` resource changes in some incompatible way.\n- v3 - The `User` resource changes in some incompatible way.\n- v4 - Both `Profile` and `User` resources change in some incompatible way at the same time.\n\nIn order to support these version changes, you would define these transforms:\n\n```python\nclass ProfileTransform0002(BaseTransform):\n    \"\"\"\n    Targets v2 of the profile representation.\n    Will convert forwards and backwards for requests at v1.\n    \"\"\"\n\nclass UserTransform0003(BaseTransform):\n    \"\"\"\n    Targets v3 of the user representation.\n    Will convert forwards and backwards for requests at v1 or v2.\n    \"\"\"\n\nclass ProfileTransform0004(BaseTransform):\n    \"\"\"\n    Targets v4 of the profile representation.\n    Will convert forwards and backwards for requests at v1, v2, or v3.\n    \"\"\"\n\nclass UserTransform0004(BaseTransform):\n    \"\"\"\n    Targets v4 of the user representation.\n    Will convert forwards and backwards for requests at v1, v2, or v3.\n    \"\"\"\n```\n\nIn the Whole-API strategy, each transform targets the version to which it promotes a resource. Using this pattern, the transforms \"opt in\" to a particular version number.\n\nIn this example:\n\n- `ProfileTransform0002` targets `v2`.\n- `UserTransform0003` targets `v3`.\n- `ProfileTransform0004` and `UserTransform0004` both target `v4`.\n\n#### Per-Endpoint Versioning\n\nPer-Endpoint API versioning requires a client to maintain knowledge of the various versions of each endpoint. The client will access each endpoint at its associated version, and can expect to independently change the version number for each endpoint. This allows for finer-grained control for the client to manage which resource versions with which it expects to interact.\n\nIn this strategy, changes to resources are made independently of each other. Unchanged resources stay at the same version number no matter how many new versions of other resources are created.\n\nPer-Endpoint versioning offers convenience for client developers in that they can improve a single resource interaction at a time. One major drawback of this strategy is that the client must maintain a mapping of which resource versions are to be used at runtime.\n\n##### Usage\n\nFor example, assume you have two resources `User` and `Profile`.\n\nIn the course of development, you must make several backwards incompatible changes over time:\n\nSome changes to the `Profile` endpoint:\n\n- v1 `Profile` - Some initial version of `Profile`.\n- v2 `Profile` - The `Profile` resource changes in some incompatible way.\n\nSome changes to the `User` endpoint:\n\n- v1 `User` - Some initial version of `User`.\n- v2 `User` - The `User` resource changes in some incompatible way.\n- v3 `User` - The `User` resource changes in some incompatible way.\n- v4 `User` - The `User` resource changes in some incompatible way.\n\nIn order to support these versions, you would define these transforms:\n\n```python\nclass ProfileTransform0002(BaseTransform):\n    \"\"\"\n    Targets v2 of the profile representation.\n    Will convert forwards and backwards for requests at v1.\n    \"\"\"\n\nclass UserTransform0002(BaseTransform):\n    \"\"\"\n    Targets v2 of the user representation.\n    Will convert forwards and backwards for requests at v1.\n    \"\"\"\n\nclass UserTransform0003(BaseTransform):\n    \"\"\"\n    Targets v3 of the user representation.\n    Will convert forwards and backwards for requests at v1 or v2.\n    \"\"\"\n\nclass UserTransform0004(BaseTransform):\n    \"\"\"\n    Targets v4 of the user representation.\n    Will convert forwards and backwards for requests at v1, v2, or v3.\n    \"\"\"\n```\n\nIn this example, the `User` and `Profile` resources are versioned independently from one another.\n\nThe `User` resource supports `v1`, `v2`, `v3`, and `v4`. Three transforms are defined, with each stating their targeted version after promotion by the postfix integer in their names.\n\nThe `Profile` resource supports `v1` and `v2`. One transform is defined to enable this support, and that transform states that it targets `v2` after promotion of the representation.\n\nUsing this strategy, the client-side interactions can target a different version for each of the resources independently from one another.\n\n### Parsers\n\nParsers are useful in Django Rest Framework for defining content-types for your RESTful API resources.\n\nUsing this library, custom parsers can also be used to ensure that the representation parsed out of a request match the latest version of that resource. This relieves the endpoint from the burden of maintaining knowledge of previous resource versions.\n\nWhen using a custom parser, inbound representations at lower-than-latest versions will be converted into the latest version during parsing.\n\nTo make use of version transforms in custom parsers, define a subclass of `BaseVersioningParser`:\n\n```python\n# Notice that this is a subclass of the provided `BaseVersioningParser`\nclass MyFirstVersioningParser(BaseVersioningParser):\n    media_type = 'application/vnd.test.testtype+json'\n    transform_base = 'my_version_transforms.MyFirstTransform'\n```\n\nThe `media_type` property must be defined, but can be defined simply as `application/json` if no custom content type is desired.\n\nThe `transform_base` property can be defined for use with this library. This parser will now automatically retrieve transform classes from the specified module that are prefixed with the base transform name.\n\nIn this example, the full module name is `'my_version_transforms'`, which indicates the module from which the transform classes will be loaded. The base transform name in this example is `'MyFirstTransform'`, which indicates a prefix to be used for pattern matching to find the version transforms associated with this parser.\n\nThe VersioningParser will automatically discover the transforms from the provided module that match the given base transform name. Then, the parser will use the version being requested to identify which transform to run first. The parser then creates a pipeline from the `.forwards()` methods of each later transform in ascending order. After this promotion pipeline is complete, the parser provides the request representation at the latest version for handling by the endpoint logic.\n\n### Serializers\n\nSerializers are useful in Django Rest Framework for consistently returning well-formated responses to the client.\n\nUsing this library, custom serializers can also be used to ensure that responses match the version which the client originally requested. A response representation is automatically demoted back to the requested version during serialization. This again relieves endpoints from the burden of maintaining knowledge of previous versions.\n\nTo make use of transforms in serializers, define a subclass of `BaseVersioningSerializer`:\n\n```python\nfrom rest_framework import serializers\n\n# using a plain serializer\nclass MyFirstVersioningSerializer(BaseVersioningSerializer, serializers.Serializer):\n    transform_base = 'my_version_transforms.MyFirstTransform'\n\n    test_field_two = serializers.CharField()\n\n# using model serializer\nclass MyFirstVersioningSerializer(BaseVersioningSerializer, serializers.ModelSerializer):\n    transform_base = 'my_version_transforms.MyFirstTransform'\n\n    class Meta:\n        model = TestModelV3\n        fields = (\n            'test_field_two',\n            'test_field_three',\n            'test_field_four',\n            'test_field_five',\n            'new_test_field',\n            'new_related_object_id_list',\n        )\n```\n\nThe `transform_base` property is defined in the same manner as with parsers, using the first portions of the definition to identify from which module to load transforms, and the last part to identify the transforms to be used.\n\nThe versioning serializer will automatically discover the transforms from the provided module that match the base transform name. Then the serializer builds a pipeline of transforms to be used for demotion down to the requested version of the resource. The pipeline is run in sequence by executing the `.backwards()` methods on each transform in descending order until the requested version is reached.\n\n## Development\n\n### Testing\n\nInstall testing requirements.\n\n```bash\n$ pip install -r requirements.txt\n```\n\nRun with runtests.\n\n```bash\n$ ./runtests.py\n```\n\nYou can also use the excellent [tox] testing tool to run the tests\nagainst all supported versions of Python and Django. Install tox globally, and then simply run:\n\n```bash\n$ tox\n```\n\n### Documentation\n\nTo build the documentation, you’ll need to install ```mkdocs```.\n\n```bash\n$ pip install mkdocs\n```\n\nTo preview the documentation:\n\n```bash\n$ mkdocs serve\nRunning at: http://127.0.0.1:8000/\n```\n\nTo build the documentation:\n\n```bash\n$ mkdocs build\n```\n\n[Django Rest Framework]: https://github.com/tomchristie/django-rest-framework\n[tox]: http://tox.readthedocs.org/en/latest/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrhwick%2Fdjango-rest-framework-version-transforms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrhwick%2Fdjango-rest-framework-version-transforms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrhwick%2Fdjango-rest-framework-version-transforms/lists"}