Django APIs, Versioning, and You

Rebecca Martin

@laches1sm

PyConIE '18

Introduction

  • Junior developer at Flexera in Belfast
  • Mostly work with Python and Go
  • A year's experience with Python so far
  • @laches1sm on the Twitter, if you really care that much

Introduction

  • Three parts to this talk:
  • What is versioning, and why should I do it?
  • How do I effectivity version a Django API using the REST framework? (and other methods, too)
  • Live demo showing the things we've learnt in part 2. 

 

What is versioning?

  • One day, you decide to create an API.
  • For a while, everyone's happy with it...
  • ...until...

What is versioning?

  • oh no, the customer or whoever is requesting changes to how the data is being sent or received

What is versioning?

  • That's ok, let's just make the changes.
  • What could go wrong?

What is versioning?

  • Turns out they were breaking changes and users of the previous version can't use it anymore...

What is versioning?

  • So how could this situation be avoided?
  • Welcome to the wonderful world of versioning!

What is versioning?

  • Basically every time you make a change to how data is processed by  your API, you create a new version.
  • Best of both worlds!
  • So no, you can't just make changes.

Versioning with Django REST Framework

  • So now we know why we should version an API. 
  • But what about the how?
  • This talk will be mostly focused on the Django REST Framework and DjangoRestFramework-Transforms libraries
  • But the principles should remain framework agnostic.

Versioning with Django REST Framework

  • There are various means of versioning with the Django REST Framework...
  • The fun part of this is that you have the choice to configure whatever versioning method you want to use
  • Can also set a default version as well

Versioning with Django REST Framework

# in settings.py

REST_FRAMEWORK = {
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning'
    'DEFAULT_VERSION': 'v1' # Default is None
    'ALLOWED_VERSIONS': ... # Default is None
    'VERSION_PARAM': 'pyconie' # Default is 'version'
}

Versioning with Django REST Framework

  • The first (and most common and recommended as best practice) is to set versioning in the headers of the request.
  • In the Django REST framework, this is set though the Accept headers.

Versioning with Django REST Framework

  • Or, you can create an entire new serializer to handle the new version... 
# in my_cool_function...

if req.version == 'v6':
    return FancyNewSerializerV6
else if req.version == 'v2':
    return OldSerializer

Versioning with Django REST Framework

  • URLPathVersioning
urlpatterns = [
    url(
        r'^(?P<version>(v1|v2))/bookings/$',
        bookings_list,
        name='bookings-list'
    ),
    url(
        r'^(?P<version>(v1|v2))/bookings/(?P<pk>[0-9]+)/$',
        bookings_detail,
        name='bookings-detail'
    )
]

Versioning with Django REST Framework

  • NamespaceVersioning
# bookings/urls.py
urlpatterns = [
    url(r'^$', bookings_list, name='bookings-list'),
    url(r'^(?P<pk>[0-9]+)/$', bookings_detail, name='bookings-detail')
]

# urls.py
urlpatterns = [
    url(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
    url(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]

Versioning with Django REST Framework

  • HostnameVersioning
^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$

Versioning with Django REST Framework

  • QueryVersioning
  • Takes a query parameter in the URL

Versioning with Django REST Framework

  • You can use the built in reverse() function to apply any transformations that you define....
  • i.e. You used namespace versioning, so it will configure the URL to fit that.

But wait! There's more...

  • At the risk of being nerdsniped I also looked at other methods as well....
  • DjangoRESTFramewrok-Transforms!

But wait! There's more...

  • "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."

  • (from https://django-rest-framework-version-transforms.readthedocs.io/en/latest/)

But wait! There's more...

  • As in the name...

  • Uses Transforms in order to convert between versions

But wait! There's more...

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 targeted 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.")

oh no it's the live demo

Versioning with Django REST Framework

deck

By laches1sm

deck

  • 763