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 ofHttpResponse
instances.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.Objects
andPage
: 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@decorator
notation. 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@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 theview_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.
- 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
View
subclass 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
None
if 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
limit
querystring parameter.
- max_limit_per_page¶
Maximal count of items in a single request.
limit
query 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.args
andself.kwargs
in all places.It calls
unserialize_request()
after assigning the aforementioned variables onself
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
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.APIException
andHttp404
exceptions 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
Objects
instance containing all instances they have to process. The default implementation of all these methods useAPI.serialize_instance()
to do the serialization work (using theAPI
instance 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
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.
- 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()
. Ifresponse
is already aHttpResponse
instance, it is returned directly.The content types supported by
Serializer
are 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=json
or?format=application/json
for 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:
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
andrequest.FILES
.request.POST
is used instead of something else even forPUT
andPATCH
requests (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
data
argument to a dict instance. TheAPIException
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 towel.api.Objects(queryset, page, set, single)¶
A
namedtuple
holding 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.
model
is the Django model you want to use,ident
should be one oflist
,set
ordetail
at 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_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 callingserialize_model_instance
such asexclude
,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 theAPI
docstring above.The
exclude
parameter is especially helpful when used together withfunctools.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 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_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__
.
- towel.api.querystring(data, exclude=(), \**kwargs)¶
Returns a properly encoded querystring
The supported arguments are as follows:
data
should be aMultiValueDict
instance (i.e.request.GET
)exclude
is a list of keys fromdata
which 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_page
which defaults to 1000.?offset=<integer>
: 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 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:
date
anddatetime
objects are converted into strings usingstr()
.Decimal
is converted into a string without (lossy) conversion tofloat
first.FileField
andImageField
are shown as the URL of the file.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.