.. _api: Towel API ********* .. currentmodule:: towel.api :py:mod:`towel.api` is a set of classes which facilitate building a RESTful API. In contrast to other, well known projects such as `django-piston `_ and `tastypie `_ it does not try to cover all HTTP verbs out of the box, and does not come with as many configuration knobs and classes for everything, and tries staying small and simple instead. The API consists of the following classes and methods, which are explained in more depth further down this page: - :py:class:`API`: A collection of resources. - :py:class:`Resource`: A single resource which exposes a Django model instance. - :py:class:`Serializer`: The API response serializer, responsible for content type negotiation and creation of :py:class:`~django.http.HttpResponse` instances. - :py:class:`RequestParser`: Understands requests in various formats (JSON, urlencoded, etc.) and handles the differences. - :py:class:`APIException`: An exception which can be raised deep down in the API / resource machinery and will be converted into a nicely formatted response in the requested content format. - :py:class:`Objects` and :py:class:`Page`: Containers for objects related to a particular resource and / or URI. They are returned by the method :py:meth:`Resource.objects`. - :py:func:`api_reverse`: Helper for reversing URLs inside a particular API instance. - :py:func:`serialize_model_instance`: The default Django model serializer. - :py:func:`querystring`: Helper for constructing querystrings. .. _api-api: The ``API`` class ================= .. class:: API(name, decorators=[csrf_exempt]) This class acts as a collection of resources. The arguments are: - ``name``: The name of this API. If you don't know what to use here, simply use ``'v1'``. - ``decorators``: A list of decorators which should be applied to the root API view and to all resources (if you don't override it upon resource registration). The list of decorators is applied in reverse, which means that you should follow the same order as if you were using the ``@decorator`` notation. It's recommended to always use :py:func:`~django.views.decorators.csrf.csrf_exempt` here, otherwise API requests other than GET, HEAD, OPTIONS and TRACE (the HTTP verbs defined as safe by RFC2616) will have to include a valid CSRF middleware token. Example:: api_v1 = API('v1') .. attribute:: name The name of this API. .. attribute:: decorators The decorators passed upon initialization. .. attribute:: resources A list of dictionaries holding resource configuration. .. attribute:: serializers A dictionary mapping models to serialization functions. If a model does not exist inside this dictionary, the default serialization function :py:func:`serialize_model_instance` is used. .. attribute:: urls This property returns a URL pattern instance suitable for including inside your main URLconf:: from .views import api_v1 urlpatterns = patterns('', url(r'^api/v1/', include(api_v1.urls)), ) .. method:: register(self, model, view_class=None, canonical=True, decorators=None, prefix=None, view_init=None, serializer=None) Resources are normally not created by hand. This method should be used instead. The arguments are: - ``model``: The Django model used in this resource. - ``view_class``: The resource view class used, defaults to :py:class:`Resource`. - ``canonical``: Whether this resource is the canonical location of the model in this API. Allows registering the same model several times in the API (only one location should be the canonical location!) - ``decorators``: A list of decorators which should be applied to the view. Function decorators only, method decorators aren't supported. The list is applied in reverse, the order is therefore the same as with the ``@decorator`` notation. If unset, the set of decorators is determined from the API initialization. Pass an empty list if you want no decorators at all. - ``prefix``: The prefix for this model, defaults to the model name in lowercase. You should include a caret and a trailing slash if you specify this yourself (``prefix=r'^library/'``). - ``view_init``: Python dictionary which contains keyword arguments used during the instantiation of the ``view_class``. - ``serializer``: Function which takes a model instance, the API instance and additional keyword arguments (accept ``**kwargs`` for forward compatibility) and returns the serialized representation as a Python dictionary. .. method:: serialize_instance(self, instance, \**kwargs) Returns a serialized version of the passed model instance. This method should always be used for serialization, because it knows about custom serializers specified when registering resources with this API. .. method:: root(self, request) Main API view, returns a list of all available resources .. _api-resources: Resources ========= .. class:: Resource(self, \**kwargs) This is a :py:class:`~django.views.generic.base.View` subclass with additional goodies for exposing a Django model in a RESTful way. You should not instantiate this class yourself, but use :py:meth:`API.register` instead. .. attribute:: api The :py:class:`API` instance to which this resource is bound to. .. attribute:: model The model exposed by this resource. .. attribute:: queryset Prefiltered queryset for this resource or ``None`` if all objects accessible through the first defined manager on the model should be exposed (or if you do the limiting yourself in :py:meth:`Resource.get_query_set`) .. attribute:: limit_per_page Standard count of items in a single request. Defaults to 20. This can be overridden by sending a different value with the ``limit`` querystring parameter. .. attribute:: max_limit_per_page Maximal count of items in a single request. ``limit`` query values higher than this are not allowed. Defaults to 1000. .. attribute:: http_method_names Allowed HTTP method names. The :py:class:`Resource` only comes with implementations for GET, HEAD and OPTIONS. You have to implement all other handlers yourself. A typical request-response cycle -------------------------------- .. method:: Resource.dispatch(self, request, \*args, \**kwargs) This method is the primary entry point for requests. It is similar to the base class implementation but has a few important differences: - It uses ``self.request``, ``self.args`` and ``self.kwargs`` in all places. - It calls :py:meth:`~Resource.unserialize_request` after assigning the aforementioned variables on ``self`` which may modify all aspects and all variables (f.e. deserialize a JSON request and serialize it again to look like a standard POST request) and only then determines whether the request should be handled by this view at all. - The return value of the :py:meth:`~Resource.get`, :py:meth:`~Resource.post` etc. methods is passed to :py:meth:`~Resource.serialize_response` and only then returned to the client. The processing methods should return a dictionary which is then serialized into the requested format. If the format is unknown or unsupported, a 406 Not acceptable HTTP error is returned instead. - :py:exc:`APIException` and :py:class:`~django.http.Http404` exceptions are caught and transformed into appropriate responses according to the content type requested. .. method:: Resource.unserialize_request(self) This method's intent is to standardize various aspects of the incoming request so that the following code does not have to care about the format of the incoming data. It might decode incoming JSON data and reformat it as a standard HTTP POST. Currently, this method does nothing, and because of that, content is only accepted in two forms: - urlencoded in the request body - multipart in the request body .. method:: Resource.get(self, request, \*args, \**kwargs) .. method:: Resource.head(self, request, \*args, \**kwargs) These methods return serialized lists, sets or details depending upon the request URI. All of the following are valid URIs for a fictional resource for books: - ``/api/v1/book/``: Returns 20 books. - ``/api/v1/book/?offset=20&limit=20``: Returns books 21-40. - ``/api/v1/book/42/``: Returns the book with the primary key of 42. - ``/api/v1/book/1;3;15/``: Returns a set of three books. The :py:meth:`~Resource.get` method offloads processing into three distinct methods depending upon the URI: .. method:: Resource.get_single(self, request, objects, \*args, \**kwargs) .. method:: Resource.get_set(self, request, objects, \*args, \**kwargs) .. method:: Resource.get_page(self, request, objects, \*args, \**kwargs) These methods receive an :py:class:`Objects` instance containing all instances they have to process. The default implementation of all these methods use :py:meth:`API.serialize_instance` to do the serialization work (using the :py:class:`API` instance at :py:attr:`Resource.api`). If any of the referenced objects do not exist for the single and the set case, a HTTP 404 is returned instead of returning a partial response. The list URI does not only return a list of objects, but another mapping containing metadata about the response such as URIs for the previous and next page (if they exist) and the total object count. .. method:: Resource.options(self, request, \*args, \**kwargs) Returns a list of allowed HTTP verbs in the ``Allow`` response header. The response is otherwise empty. .. note:: URIs inside the resource might still return 405 Method not allowed erorrs if a particular HTTP verb is only implemented for a subset of URIs, for example only for single instances. .. method:: Resource.post(self, request, \*args, \**kwargs) .. method:: Resource.put(self, request, \*args, \**kwargs) .. method:: Resource.delete(self, request, \*args, \**kwargs) .. method:: Resource.patch(self, request, \*args, \**kwargs) .. method:: Resource.trace(self, request, \*args, \**kwargs) Default implementations do not exist, that means that if you do not provide your own, the only answer will ever be a HTTP 405 Method not allowed error. .. method:: Resource.serialize_response(self, response, status=httplib.OK, headers={}) This method is a thin wrapper around :py:meth:`Serializer.serialize`. If ``response`` is already a :py:class:`~django.http.HttpResponse` instance, it is returned directly. The content types supported by :py:class:`Serializer` are JSON, but more on that later. The serializer ============== .. class:: Serializer() The API supports output as JSON. The format is determined by looking at the HTTP ``Accept`` header first. If no acceptable encoding is found, a HTTP 406 Not acceptable error is returned to the client. The detection of supported content types can be circumvented by adding a querystring parameter naemd ``format``. The supported values are as follows: - ``?format=json`` or ``?format=application/json`` for JSON output The request parser ================== .. class:: RequestParser() Parses the request body into a format independent of its content type. Does nothing for the following HTTP methods because they are not supposed to have a request body: - ``GET`` - ``HEAD`` - ``OPTIONS`` - ``TRACE`` - ``DELETE`` Otherwise, the code tries determining a parser for the request. The following content types are supported: - ``application/x-www-form-urlencoded`` (the default) - ``multipart/form-data`` - ``application/json`` The two former content types are supported directly by Django, all capabilities and restrictions are inherited directly. When using JSON, file uploads are not supported. The parsed data is available as ``request.POST`` and ``request.FILES``. ``request.POST`` is used instead of something else even for ``PUT`` and ``PATCH`` requests (among others), because most code written for Django expects data to be provided under that name. .. method:: RequestParser.parse(self, request) Decides whether the request body should be parsed, and if yes, decides which parser to use. Returns a HTTP 415 Unsupported media type if the request isn't understood. .. method:: RequestParser.parse_form(self, request) .. method:: RequestParser.parse_json(self, request) The actual work horses. Additional classes and exceptions ================================= .. exception:: APIException(error_message=None, status=None, data={}) Custom exception which signals a problem detected somewhere inside the API machinery. Usage:: # Use official W3C error names from ``httplib.responses`` raise ClientError(status=httplib.NOT_ACCEPTABLE) or:: raise ServerError('Not implemented, go away', status=httplib.NOT_IMPLEMENTED) Additional information can be passed through by setting the ``data`` argument to a dict instance. The :py:exc:`APIException` handler will merge the dict into the default error data and return everything to the client:: raise APIException('Validation failed', data={ 'form': form.errors, }) .. class:: Objects(queryset, page, set, single) A :py:class:`~collections.namedtuple` holding the return value of :py:meth:`Resource.objects`. .. class:: Page(queryset, offset, limit, total) A :py:class:`~collections.namedtuple` for the ``page`` object from :py:class:`Objects` above. Utility functions ================= .. function:: api_reverse(model, ident, api_name='api', fail_silently=False, \**kwargs) Determines the canonical URL of API endpoints for arbitrary models. - ``model`` is the Django model you want to use, - ``ident`` should be one of ``list``, ``set`` or ``detail`` at the moment - Additional keyword arguments are forwarded to the :py:func:`~django.core.urlresolvers.reverse` call. Usage:: api_reverse(Product, 'detail', pk=42) Passing an instance works too:: api_reverse(instance, 'detail', pk=instance.pk) .. function:: serialize_model_instance(instance, api, inline_depth=0, exclude=(), only_registered=True, build_absolute_uri=lambda uri: uri, \**kwargs) Serializes a single model instance. If ``inline_depth`` is a positive number, ``inline_depth`` levels of related objects are inlined. The performance implications of this feature might be severe! Note: Additional arguments specified when calling ``serialize_model_instance`` such as ``exclude``, ``only_registered`` and further keyword arguments are currently **not** forwarded to inlined objects. Those parameters should be set upon resource registration time as documented in the ``API`` docstring above. The ``exclude`` parameter is especially helpful when used together with ``functools.partial``. Set ``only_registered=False`` if you want to serialize models which do not have a canonical URI inside this API. ``build_absolute_uri`` should be a callable which transforms any passed URI fragment into an absolute URI including the protocol and the hostname, for example ``request.build_absolute_uri``. This implementation has a few characteristics you should be aware of: - Only objects which have a canonical URI inside this particular API are serialized; if no such URI exists, this method returns ``None``. This behavior can be overridden by passing ``only_registered=False``. - Many to many relations are only processed if ``inline_depth`` has a positive value. The reason for this design decision is that the database has to be queried for showing the URIs of related objects anyway and because of that we either show the full objects or nothing at all. - Some fields (currently only fields with choices) have a machine readable and a prettified value. The prettified values are delivered inside the ``__pretty__`` dictionary for your convenience. - The primary key of the model instance is always available as ``__pk__``. .. function:: querystring(data, exclude=(), \**kwargs) Returns a properly encoded querystring The supported arguments are as follows: - ``data`` should be a ``MultiValueDict`` instance (i.e. ``request.GET``) - ``exclude`` is a list of keys from ``data`` which should be skipped - Additional key-value pairs are accepted as keyword arguments Usage:: next_page_url = querystring( request.GET, exclude=('page',), page=current + 1, ) API behavior ============ Resource list ------------- The available resources can be determined by sending a request to the root URI of this API, ``/api/v1/``. Resources can either be canonical or not. All resources are returned in a list, the canonical URIs for objects are additionally returned as a hash. The individual resources are described by a hash containing two values (as do most objects returned by the API): - ``__uri__``: The URI of the particular object - ``__str__``: A string containing the 'name' of the object, whatever that would be (it's the return value of the ``__str__`` method for Django models, and the lowercased class name of the model registered with the resource). In the list of resources, a particular ``__str__`` value will exist several times if a model is exposed through more than one resource; ``__uri__`` values will always be unique. Listing endpoints ----------------- All API endpoints currently support GET, HEAD and OPTIONS requests. All listing endpoints support the following parameters: - ``?limit=``: Determines how many objects will be shown on a single page. The default value is 20. The lower limit is zero, the upper limit is determined by the variable ``max_limit_per_page`` which defaults to 1000. - ``?offset=``: Can be used for retrieving a different page of objects. Passing ``?offset=20`` with a limit of 20 will return the next page. The offset is zero-indexed. .. note:: You won't have to construct query strings containing these parameters yourself in most cases. All list views return a mapping with additional information about the current request and ``next`` and ``previous`` links for your convenience as well. List views return two data structures, ``objects`` and ``meta``. The former is a list of all objects for the current request, the latter a mapping of additional information about the current set of objects: - ``offset``: The offset value as described above. - ``limit``: The limit value as described above. - ``total``: The total count of objects. - ``previous``: A link to the previous page or ``null``. - ``next``: A link to the next page or ``null``. Object representation --------------------- The following fields should always be available on objects returned: - ``__uri__``: The URI. - ``__pk__``: The primary key of this object. - ``__str__``: The return value of the ``__str__`` or ``__unicode__`` method. A few fields' values have to be treated specially, because their values do not have an obvious representation in an JSON document. The fields and their representations are as follows: - :py:class:`~datetime.date` and :py:class:`~datetime.datetime` objects are converted into strings using :py:func:`str`. - :py:class:`~decimal.Decimal` is converted into a string without (lossy) conversion to :py:class:`float ` first. - :py:class:`~django.db.models.FileField` and :py:class:`~django.db.models.ImageField` are shown as the URL of the file. - :py:class:`~django.db.models.ForeignKey` fields are shown as their canonical URI (if there exists such a URI inside this API) or even inlined if ``?full=1`` is passed when requesting the details of an object.