Towel API¶
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:
API: A collection of resources.Resource: A single resource which exposes a Django model instance.Serializer: The API response serializer, responsible for content type negotiation and creation ofHttpResponseinstances.RequestParser: Understands requests in various formats (JSON, urlencoded, etc.) and handles the differences.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.ObjectsandPage: Containers for objects related to a particular resource and / or URI. They are returned by the methodResource.objects().api_reverse(): Helper for reversing URLs inside a particular API instance.serialize_model_instance(): The default Django model serializer.querystring(): Helper for constructing querystrings.
The API class¶
- class towel.api.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@decoratornotation. It’s recommended to always usecsrf_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')
- name¶
The name of this API.
- decorators¶
The decorators passed upon initialization.
- resources¶
A list of dictionaries holding resource configuration.
- serializers¶
A dictionary mapping models to serialization functions. If a model does not exist inside this dictionary, the default serialization function
serialize_model_instance()is used.
- 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)), )
- 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 toResource.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@decoratornotation. 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 theview_class.serializer: Function which takes a model instance, the API instance and additional keyword arguments (accept**kwargsfor forward compatibility) and returns the serialized representation as a Python dictionary.
- 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.
- root(self, request)¶
Main API view, returns a list of all available resources
Resources¶
- class towel.api.Resource(self, \**kwargs)¶
This is a
Viewsubclass with additional goodies for exposing a Django model in a RESTful way. You should not instantiate this class yourself, but useAPI.register()instead.- model¶
The model exposed by this resource.
- queryset¶
Prefiltered queryset for this resource or
Noneif all objects accessible through the first defined manager on the model should be exposed (or if you do the limiting yourself inResource.get_query_set())
- 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
limitquerystring parameter.
- max_limit_per_page¶
Maximal count of items in a single request.
limitquery values higher than this are not allowed. Defaults to 1000.
A typical request-response cycle¶
- 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.argsandself.kwargsin all places.It calls
unserialize_request()after assigning the aforementioned variables onselfwhich 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
get(),post()etc. methods is passed toserialize_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.APIExceptionandHttp404exceptions are caught and transformed into appropriate responses according to the content type requested.
- 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
- Resource.get(self, request, \*args, \**kwargs)¶
- 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
get()method offloads processing into three distinct methods depending upon the URI:- get_single(self, request, objects, \*args, \**kwargs)¶
- Resource.get_set(self, request, objects, \*args, \**kwargs)¶
- Resource.get_page(self, request, objects, \*args, \**kwargs)¶
These methods receive an
Objectsinstance containing all instances they have to process. The default implementation of all these methods useAPI.serialize_instance()to do the serialization work (using theAPIinstance atResource.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.
- Resource.options(self, request, \*args, \**kwargs)¶
Returns a list of allowed HTTP verbs in the
Allowresponse 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.
- Resource.post(self, request, \*args, \**kwargs)¶
- Resource.put(self, request, \*args, \**kwargs)¶
- Resource.delete(self, request, \*args, \**kwargs)¶
- Resource.patch(self, request, \*args, \**kwargs)¶
- 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.
- Resource.serialize_response(self, response, status=httplib.OK, headers={})¶
This method is a thin wrapper around
Serializer.serialize(). Ifresponseis already aHttpResponseinstance, it is returned directly.The content types supported by
Serializerare JSON, but more on that later.
The serializer¶
- class towel.api.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=jsonor?format=application/jsonfor JSON output
The request parser¶
- class towel.api.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:
GETHEADOPTIONSTRACEDELETE
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-dataapplication/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.POSTandrequest.FILES.request.POSTis used instead of something else even forPUTandPATCHrequests (among others), because most code written for Django expects data to be provided under that name.- 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.
- parse_form(self, request)¶
- parse_json(self, request)¶
The actual work horses.
Additional classes and exceptions¶
- exception towel.api.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
dataargument to a dict instance. TheAPIExceptionhandler will merge the dict into the default error data and return everything to the client:raise APIException('Validation failed', data={ 'form': form.errors, })
- class towel.api.Objects(queryset, page, set, single)¶
A
namedtupleholding the return value ofResource.objects().
Utility functions¶
- towel.api.api_reverse(model, ident, api_name='api', fail_silently=False, \**kwargs)¶
Determines the canonical URL of API endpoints for arbitrary models.
modelis the Django model you want to use,identshould be one oflist,setordetailat the momentAdditional keyword arguments are forwarded to the
reverse()call.
Usage:
api_reverse(Product, 'detail', pk=42)
Passing an instance works too:
api_reverse(instance, 'detail', pk=instance.pk)
- towel.api.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_depthis a positive number,inline_depthlevels of related objects are inlined. The performance implications of this feature might be severe! Note: Additional arguments specified when callingserialize_model_instancesuch asexclude,only_registeredand further keyword arguments are currently not forwarded to inlined objects. Those parameters should be set upon resource registration time as documented in theAPIdocstring above.The
excludeparameter is especially helpful when used together withfunctools.partial.Set
only_registered=Falseif you want to serialize models which do not have a canonical URI inside this API.build_absolute_urishould be a callable which transforms any passed URI fragment into an absolute URI including the protocol and the hostname, for examplerequest.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 passingonly_registered=False.Many to many relations are only processed if
inline_depthhas 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__.
- towel.api.querystring(data, exclude=(), \**kwargs)¶
Returns a properly encoded querystring
The supported arguments are as follows:
datashould be aMultiValueDictinstance (i.e.request.GET)excludeis a list of keys fromdatawhich should be skippedAdditional 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=<integer>: 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 variablemax_limit_per_pagewhich defaults to 1000.?offset=<integer>: Can be used for retrieving a different page of objects. Passing?offset=20with 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 ornull.next: A link to the next page ornull.
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:
dateanddatetimeobjects are converted into strings usingstr().Decimalis converted into a string without (lossy) conversion tofloatfirst.FileFieldandImageFieldare shown as the URL of the file.ForeignKeyfields are shown as their canonical URI (if there exists such a URI inside this API) or even inlined if?full=1is passed when requesting the details of an object.