Take a REST with django-nap: APIs for Django

https://travis-ci.org/funkybob/django-nap.png https://pypip.in/d/django-nap/badge.png https://pypip.in/v/django-nap/badge.png

Whilst there are many existing RESTful API tools about, many of them are very complex, and [as I found] some are quite slow!

I wanted to take the solid serialising pattern from TastyPie, with the separation of roles of django-rest-framework, and include a Publisher API I developed some time ago.

In the spirit of the Unix philosophy, Nap provides a few tools which each do one thing, and do it well. They are:

  1. Serialiser

    Declarative style Serialiser definitions for reducing complex Python objects to simple types expressible in JSON.

  2. Publisher

    A Class-based view system which merges many related views into a single class, including url routing.

  3. API

    Manage many Publishers and API versions with simple auto-discover resource registration.

Nap does not provide the wide range of features you see in tools like Django REST Framework and TastyPie, such as rate limiting, token authentication, automatic UI, etc. Instead, it provides a flexible framework that makes it easy to combine with other specialised apps.

Contents:

Serialisers

Quick Overview

Serialisers define how to turn a Python object into a collection of JSON compatible types.

They are defined using the familiar declarative syntax, like Models and Forms.

Serialiser objects

A Serialiser class is defined much like a Form or Model:

class MySerialiser(Serialiser):

    foo = fields.Field()
    bar = fields.Field('foo.bar')
    baz = fields.IntegerField()

Without an attribute specified, the field will use its name as the attribute name.

The Deflate Cycle

For each declared field:

  • Call the fields deflate method. The field is expected to to store its result in the data dict.
  • If there is a deflate_FOO method on the Serialiser, set the value in the data dict to its return value.

The Inflate Cycle

For reach declared field:

  • If the Serialiser has an inflate_FOO method, its result is stored in the obj_data dict.
  • Otherwise, call the fields inflate method. The field is expected to store its result in the obj_data dict.
  • If there were no ValidationError exceptions raised, pass the obj_data, instance, and kwargs to restore_object.

If any ValidationError exceptions are raised during inflation, a ValidationErrors exception is raised, containing all the validation errors.

Custom Deflaters

deflate_FOO(obj=obj, data=data, **kwargs)

After the Field’s deflate method is called, if a matching deflate_FOO method exists on the class it will be passed the source object obj, the updated data dict data, and any additional keyword arguments as passed to object_deflate.

Note

The custom deflate methods are called after all Field deflate methods have been called.

Custom Inflaters

inflate_FOO(data, obj, instance, **kwargs)

If an inflate_FOO method exists on the class, it will be called instead of the Field’s inflate method. It is passed the source data, the inflated object data, the instance (which defaults to None), and any keyword arguments as passed to object_inflate.

Note

The custom inflate methods are called after all Field inflate methods have been called.

The custom inflater may raise a nap.exceptions.ValidationException to indicate the data are invalid.

Serialiser API

class Serialiser
object_deflate(obj, **kwargs)

Returns obj reduced to its serialisable format.

The kwargs are passed on to field and custom deflate methods.

list_deflate(obj_list, **kwargs)

Return a list made by calling object_deflate on each item in the supplied iterable.

Passes kwargs to each call to object_deflate.

object_inflate(data, instance=None, **kwargs)

Restore data to an object. If the instance is passed, it is expected to be updated by restore_object.

restore_object(obj_data, **kwargs)

Construct an object from the inflated data.

By default, if Serialiser.obj_class has been provided, it will construct a new instance, passing objdata as keyword arguments. Otherwise, it will raise a NotImplementedError.

Fields

Fields are declared on Serialisers to pluck values from the object for deflation, as well as to cast them back when inflating.

The basic Field class can be used for any value that has a matching JSON counterpart; i.e. bools, strings, floats, dicts, lists.

There are also some for common types:

  • BooleanField
  • IntegerField
  • DecimalField
  • DateTimeField
  • DateField
  • TimeField
  • StringField

Finally, there are the two Serialiser Fields, which will generate their value using a serialiser class. They are the SerialiserField, and ManySerialiserField.

Field

class Field(attribute=None, default=None, readonly=False, null=True, *args, **kwargs)
Parameters:
  • attribute – Define the attribute this field sources it value from on the object. If omitted, the name of this field in its Serialiser class will be used. This may be in Django template dotted-lookup syntax.
  • default – The value to use if the source value is absent.
  • readonly – Is this field only for deflating?
  • null – Can this value be None when inflating?
  • virtual

    The value for this field will be generated by a custom deflate method, so don’t raise an AttributeError.

    Can also be used to omit a field if a value is not found.

type_class

For simple fields, type_class is used to restore values.

reduce(value, **kwargs)

Reduce the sourced value to a serialisable type.

restore(value, **kwargs)

Restore a serialisable form of the value to its Python type. By default this will use type_class unless the value is None.

deflate(name, obj, data, **kwargs)

Deflate our value from the obj, and store it into data. The name will be used only if self.attribute is None.

This uses digattr to extract the value from the obj, then calls reduce if the value is not None.

inflate(name, data, obj, **kwargs)

Inflate a value from data into obj. The name will be used only if self.attribute is None.

Deflate Cycle

  • Determine if we use name or attribute.
  • Use nap.utils.digattr to get the value
  • If the value is not None, call self.reduce
  • Add our value to the data dict under the key in name

The reduce method is the last stage of casting. By default, it does nothing.

Inflate Cycle

  • If this field is read-only, return immediately.
  • Determine if we use name or attribute
  • Try to get our value from the data dict. If it’s not there, return.
  • Pass the value through self.restore
  • Save the value in the obj dict

By default, restore tries to construct a new self.type_class from the value, unless type_class is None.

StringField

StringField is for cases where you want to ensure the value is forced to a string. Its reduce method uses django.utils.encoding.force_text.

Serialiser Fields

SerialiserField follows the same pattern as above, but replaces the normal reduce/restore methods with calls to its serialisers object_deflate/object_inflate.

ManySerialiserField does the same, but uses list_deflate/list_inflate.

Publishers

The publisher is a general purpose class for dispatching requests. It’s similar to the generic Class-Based Views in Django, but handles many views in a single class.

This pattern works well for APIs, where typically a group of views require the same functions.

r'^object/(?P<object_id>[-\w]+)/(?P<action>\w+)/(?P<argument>.+?)/?$'
r'^object/(?P<object_id>[-\w]+)/(?P<action>\w+)/?$'
r'^object/(?P<object_id>[-\w]+)/?$'
r'^(?P<action>\w+)/(?P<argument>.+?)/$'
r'^(?P<action>\w+)/?$'
r'^$'

Clearly this list does not permit ‘object’ to be an action.

The publisher recognises a small, fixed set of URL patterns, and dispatches them to methods on the class according to a simple pattern: target, method, action. The target is either “list” or “object”, depending on if an object_id was supplied. The method is the HTTP method, lower cased (i.e. get, put, post, delete, etc.). And finally, the action, which defaults to ‘default’.

So, for example, a GET request to /foo would call the list_get_foo handler. Whereas a POST to /object/123/nudge/ would call object_post_nudge, passing “123” as the object_id.

All handlers should follow the same definition:

def handler(self, request, action, object_id, **kwargs):

Both action and object_id are passed as kwargs, so where they’re not needed they can be omitted.

Like a view, every handler is expected to return a proper HttpResponse object.

Publishing

In order to add a Publisher to your URL patterns, you need to include all of its patterns. Fortunately, it provides a handy method to make this simple:

url(r’^myresource/’, include(MyPublisher.patterns())),

Base Publisher

class BasePublisher(request [,*args] [,**kwargs])
CSRF = True

Determines if CSRF protection is applied to the view function used in patterns

ACTION_PATTERN
OBJECT_PATTERN
ARGUMENT_PATTERN

Used by the patterns method to control the URL patterns generated.

classmethod patterns(api_name=None)

Builds a list of URL patterns for this Publisher.

The api_name argument will be used for naming the url patterns.

classmethod index()

Returns details about handlers available on this publisher.

The result will be a dict with two keys: list, and detail.

Each item will contain a list of handlers and the HTTP verbs they accept.

dispatch(request, action='default', object_id=None, **kwargs):

Entry point used by the view function.

execute(handler):

Call hook for intercepting handlers. dispatch passes the handler method here to invoke. It will call the handler, and catch any BaseHttpResponse exceptions, returning them.

This was originally added to make New Relic support simpler.

Custom Patterns

By overridding the patterns method, you can provide your own url patterns.

One sample is included: nap.publisher.SimplePatternsMixin

It omits the object/ portion of the object urls above, but limits object_ids to just digits.

Alternatively, if you just want to change the regex used for each part of the URL, you can overrid them using OBJECT_PATTERN, ACTION_PATTERN, and ARGUMENT_PATTERN, which default to ‘[-w]+’, ‘w+’ and ‘.*?’ respectively.

Publisher

The Publisher extends the BasePublisher class with some useful methods for typical REST-ful uses.

class Publisher
page_size

Enable pagination and specify the default page size. Default: unset

max_page_size

Limit the maximum page size. Default: page_size

If a request passes an override LIMIT value, it can not exceed this.

LIMIT_PARAM

Specifies the query parameter name used to specify the pagination size limit. Default: ‘limit’

OFFSET_PARAM

Specifies the query parameter name used to specify the pagination offset. Default: ‘offset’

PAGE_PARAM

Specifies the query parameter name used to specify the pagination page. Default: ‘page’

response_class

Default class to use in create_response

CONTENT_TYPES

A list of content types supported by the de/serialiser. Default: [‘application/json’, ‘text/json’]

The first value in the list will be used as the content type of responses.

dumps(data)

Used to serialise data. By default calls json.dumps.

loads(data)

Deserialise data. By default calls json.loads.

get_serialiser()

Called to get the Serialiser instance to use for this request. Default: returns self.serialiser

get_serialiser_kwargs()

Used to generate extra kwargs to pass to serialiser calls (i.e. object_deflate, list_deflate, etc)

get_object_list()

Return the raw object list for this request. This is Not Implemented. You must provide this method in your Serialiser class.

get_object(object_id)

Return the object for the given ID. You must provide this method in your Serialiser class.

filter_object_list(object_list)

Apply filtering to an object list, returning the filtered list. Default: Returns the passed object_list.

sort_object_list(object_list)

Apply sorting to an object list, returning the sorted list. Default: Returns the passed object_list.

get_page(object_list):

Paginate the object_list.

If the page_size is not defined on the Serialiser, no pagination is performed, and the following dict is returned:

{ 'meta': {}, 'objects': object_list }

Otherwise, the object_list is paginated. If self.PAGE_PARAM was passed, it will be used for the page number. It not, and self.OFFSET_PARAM is supplied, the page will be determined by dividing the offset by page_size.

The meta dict will contain:

'offset': page.start_index() - 1,
'page': page_num,
'total_pages': paginator.num_pages,
'limit': page_size,
'count': paginator.count,
'has_next': page.has_next(),
'has_prev': page.has_previous(),
get_request_data()

Returns the data sent in this request. If the request type is specified in CONTENT_TYPES it will be used to de-serialise the data. Otherwise, request.GET or request.POST will be returned as apporpriate for the HTTP method used.

render_single_object(obj, serialiser=None, **kwargs):

A helper function to serialise the object and create a response, using self.response_class. If serialiser is None, it will call get_serialiser. The kwargs will be passed on to create_response

create_response(content, **kwargs):

A helper function for building self.response_class. Passing response_class as an argument overrides the class used.

It sets ‘content_type’ in kwargs to self.CONTENT_TYPES[0] if it’s not set. Then, it passes content to self.dumps, and passes that, along with kwargs, to build a new response_class instance, returning it.

list_get_default(request, **kwargs):

Default list handler.

Calls get_object_list, filter_object_list and sort_object_list, then passes the list to get_page. It then uses the object from get_serialiser to deflate the object list.

Returns the resulting data using create_response.

Filtering and Sorting

The Publisher class has two methods for sorting and filtering:

filter_object_list(object_list)
sort_object_list(object_list)

By default, these simply return the list they are passed.

Filtering and sorting are not applied by get_object_list. This lets you apply required filtering [site, security, user, etc] in get_object_list, and optional filtering [query, etc] where it’s wanted. Also, ordering can be an unwanted expense when it’s not important to the use.

The default Publisher.list_get_default will pass the result of get_object_list to filter_object_list and sort_object_list in turn before serialising.

ModelPublisher

The ModelPublisher implements some default handlers that are more sensible for a Model.

It includes a default model property that will return the model from the meta class of self.serialiser. This way, by default, it will publish the model of its default Serialiser.

Authorisation

Authorisation is handled using the permit decorator.

Use it to decorate any handler method, passing it a function that will yield True/False.

The function will be passed all the arguments the handler will receive.

APIs

When you have multiple Publishers collected together, it can be handy to publish them under the same path. This is where the Api class comes in.

api = Api('name')

api.register(MyPublisher)
api.register(OtherPublisher, 'friendlyname')

If not overridden by being passed in the call to register, the Publisher will be registered with the name in its api_name property. This is used as the root of its URL within the Api.

Once registered, you can get a list of URL patterns to include:

(r'^api/', include(api.patterns())),

When you add the patterns, you can optionally pass a ‘flat’ argument, which will omit the Api’s name from the url patterns.

Also, the Api class provides an introspection view that will list all its Publishers, as well as their handlers and methods supported.

Auto-discover

Just like Django’s Admin, Api supports auto-discover.

In your publishers.py use:

from nap import api

...

api.register('apiname', Publisher,....)

If you’re only registering a single Publisher, you can use api.register as a decorator.

@api.register('apiname')
class JoyPublisher(Publisher):

An Api instance with that name will be created, if it hasn’t already, and put into api.APIS. Then the publisher(s) you pass will be registered with it.

Note

The publisher will be registered as the lower-cased version of its class name. You can control this by setting an api_name property.

Then, in your urls.py, just add:

from nap import api
api.autodiscover()

Which will trigger it to import $APP.serialiser from each of your INSTALLED_APPS. Then you can just include the urls:

(r'^apis/', include(api.patterns()))

Model Classes

Of course, there are classes to support dealing with Models.

ModelSerialiser

This class will, like a ModelForm, inspect a Model and generate appropriate fields for it.

class MyModelSerialiser(ModelSerialiser):

    class Meta:
        model = MyModel
        fields = [...fields to include...]
        exclude = [...fields to exclude...]
        read_only = [...fields to mark read-only...]

Like any other serialiser, you can define additional fields, as well as custom inflate/deflate methods.

By default, the restore_object method will pass the inflated dict to the model to create a new instance, or update all the properties on an existing instance. It will save the updated instance, however you can pass commit=False to prevent this.

Meta

The ModelSerialiser supports additional Meta properties:

class ModelMeta
model

Default: None The model this Serialiser is for

fields

Default: () The list of fields to use from the Model

exclude

Default: () The list of fields to ignore from the Model

read_only_fields

Default: () The list of fields from the Model to mark as read-only

field_types

Default {} A map of field names to Field class overrides.

related_fields

Default: ()

ignore_fields

Default: () When restoring an object, the fields which should not be passed to the instance.

key_fields

Default: (‘id’,) When trying to get_or_create a model instance, which fields from the inflated data should be used to match the model.

defaults

Default: {} When trying to get_or_create a model instance, additional default values to pass.

core_fields

Default: () When trying to get_or_create a model instance, additional fields to include in the defaults dict.

ModelPublisher sub-classes

There are two extra sub-classes to help building complex cases when restoring instances.

ModelReadSerialiser will only retrieve existing instances, passing all data to the managers get method.

The ModelCreateUpdateSerialier will try to construct a new instance, or update an existing one if it can be found.

The values found from Meta.key_fields will be passed to get_or_create. The defaults argument will be constructed from Meta.defaults, and the infalted values listed in Meta.core_fields.

Then, the instance will be updated for all fields not listed in Meta.related_fields or Meta.ignored_fields.

Finally, all Meta.related_fields will be set by calling their add method.

ModelPublisher

A ModelPublisher adds a model property to a publisher, which by default yields the model of the serialiser class.

It also adds get_object_list and get_object, where get_object assumes object_id is the pk of the model.

This gives basic read-only access to your model through the API.

modelserialiser_factory

This utility class allows you to programmatically generate a ModelSerialiser.

myser = modelserialiser_factory(name, model, [fields=], [exclude=], [read_only=], **kwargs)

The optional arguments will be treated the same as if passed in the Meta of a ModelSerialiser. Additional deflate/inflate methods may be passed in kwargs.

ModelSerialiserField & ModelManySerialiserField

Model counterparts to SerialiserField and ManySerialiserField. If not passed a serialiser, they will generate one from the model provided.

HTTP Utilities

In nap.http is a set of tools to go one step further than Django’s existing HttpResponse.

Status

Firstly, there is STATUS_CODES, which is a list of two-tuples of HTTP Status codes and their descriptions.

Also, and more usefully, there is the STATUS object. Accessing it as a dict, you can look up HTTP status code descriptions by code:

>>> STATUS[401]
'Unauthorized'

However, you can also look up attributes to find out the status code:

>>> STATUS.UNAUTHORIZED
401

This lets it act as a two-way constant.

BaseHttpResponse

This class blends Django’s HttpResponse with Python’s Exception. Why? Because then, when you’re nested who-knows how deep in your code, it can raise a response, instead of having to return one and hope everyone bubbles it all the way up.

  • BaseHttpResponse

    • HttpResponseSuccess

      • OK
      • Created
      • Accepted
      • NoContent
      • ResetContent
      • PartialContent
    • HttpResponseRedirect

      • MultipleChoices
      • MovedPermanently*
      • Found*
      • SeeOther*
      • NotModified
      • UseProxy*
      • TemporaryRedirect
      • PermanentRedirect

      Items marked with a * require a location passed as their first argument. It will be set as the Location header in the response.

    • HttpResponseError

      • BadRequest
      • Unauthorized
      • PaymentRequired
      • Forbidden
      • NotFound
      • MethodNotAllowed
      • NotAcceptable
      • ProxyAuthenticationRequired
      • RequestTimeout
      • Conflict
      • Gone
      • LengthRequired
      • PreconditionFailed
      • RequestEntityTooLarge
      • RequestURITooLong
      • UnsupportedMediaType
      • RequestedRangeNotSatisfiable
      • ExpectationFailed
    • HttpResponseServerError

      • InternalServerError
      • NotImplemented
      • BadGateway
      • ServiceUnavailable
      • GatewayTimeout
      • HttpVersiontNotSupported

It will be clear that, unlike Django, these mostly do not start with HttpResponse. This is a personal preference, in that typically you’d use:

from nap import http

...
    return http.Accept(...)

Http404 versus http.NotFound

Generally in your API, you’ll want to prefer http.NotFound for returning a 404 response. This avoids being caught by the normal 404 handling, so it won’t invoke your handler404.

New Relic

If you are using NewRelic, you will want to use the hooks provided, otherwise all Nap API calls will be logged as “nap.publisher.view”.

Simply add the following lines to your newrelic.ini

[import-hook:nap.publisher]
enabled = true
execute = nap.newrelic:instrument_django_nap_publisher

Examples

Sometimes, an example is much easier to understand than abstract API docs, so here’s some sample use cases.

Case 1: Simple Blog API

models.py

from django.db import models
from taggit.managers import TaggableManager

class Post(models.Model):
    title = models.CharField(max_length=255)
    author = models.ForeignKey('auth.User')
    published = models.BooleanField(default=False)
    content = models.TextField(blank=True)
    tags = TaggableManager(blank=True)

serialiser.py

from nap.models import ModelSerialiser
from nap import fields

class PostSerialiser(ModelSerialiser):
    class Meta:
        model = models.Post

    tags = fields.Field()

    def deflate_tags(self, obj, \**kwargs):
        '''Turn the tags into a flat list of names'''
        return [tag.name for tag in obj.tags.all()]

publishers.py

from nap.models import ModelPublisher
from .serialiser import PostSerialiser

class PostPublisher(ModelPublisher):
    serialiser = PostSerialiser()

urls.py

from .publishers import PostPublisher

urlpatters = patterns('',
    (r'^api/', include(PostPublisher.patterns())),
)

Changelog

v0.13.9

Enhancements:

  • Added Django 1.7 AppConfig, which will auto-discover on ready
  • Added a default implementation of ModelPublsiher.list_post_default
  • Tidied code with flake8

Bug Fixes:

  • Fixed use of wrong argument in auth.permit_groups

v0.13.8

Enhancements:

  • Added prefetch_related and select_related support to ExportCsv action
  • Added Field.virtual to smooth changes to Field now raising AttributeError, and support optional fields

v0.13.7

Enhancements:

  • Added ReadTheDocs, and prettied up the docs
  • Use Pythons content-type parsing
  • Added RPC publisher [WIP]
  • Allow api.register to be used as a decorator
  • Make Meta classes more proscriptive
  • Allow ModelSerialiser to override Field type used for fields.
  • Added ModelReadSerialiser and ModelCreateUpdateSerialiser to support more complex inflate scenarios [WIP]

Bug Fixes:

  • Fixed ExportCsv and simplecsv extras
  • Raise AttributeError if a deflating a field with no default set would result in using its default. [Fixes #28]
  • Fixed auto-generated api_names.
  • Purged under-developed ModelFormMixin class

v0.13.6

Enhancements:

  • Overhauled testing
  • Added ‘total_pages’ to page meta.
  • Added Serialiser.obj_class

v0.13.5.1

Bug Fixes:

  • Fix fix for b’’ from last release, to work in py2

v0.13.5

Bug Fixes:

  • Fix use of b’’ for Py3.3 [thanks zzing]

Enhancements:

  • Add options to control patterns

v0.13.4

Bug Fixes:

  • Return http.NotFound instead of raising it

Enhancements:

  • Added views publisher
  • Updated docs
  • Re-added support for ujson, if installed
  • Tidied up with pyflakes/pylint
  • Added Publisher.response_class property

v0.13.3

Bugs Fixed:

  • Make API return NotFound, instead of Raising it
  • Remove bogus CSV Reader class

v0.13.2.1

Bugs Fixed:

  • Fixed typo
  • Fixed resolving cache in mixin

v0.13.2

Enhancements:

  • Separate Publisher.build_view from Publisher.patterns to ease providing custom patterns
  • Added SimplePatternsMixin for Publisher
  • Added Publisher.sort_object_list and Publisher.filter_object_list hooks

v0.13.1

Bugs Fixed:

  • Fixed silly bug in inflate

v0.13.0

WARNING: API breakage

Changed auto-discover to look for ‘publishers’ instead of ‘seraliser’.

Enhancements:

  • Added Field.null support
  • Now use the Field.default value
  • ValidationError handled in all field and custom inflator methods

v0.12.5.1

Bugs Fixed:

  • Fix mistake introduced in 0.12.3 which broke NewRelic support

v0.12.5

Bugs Fixed:

  • Restored Django 1.4 compatibility

Enhancements:

  • Allow disabling of API introspection index

v0.12.4

Bugs Fixed:

  • Fixed filename generation in csv export action
  • Fixed unicode/str issues with type() calls

Enhancements:

  • Split simplecsv and csv export into extras module
  • Merged engine class directly into Publisher
  • Added fields.StringField

v0.12.3

Bugs Fixed:

  • Fix argument handling in Model*SerialiserFields
  • Tidied up with pyflakes

Enhancements:

  • Added support for Py3.3 [thanks ioneyed]
  • Overhauled the MetaSerialiser class
  • Overhauled “sandbox” app
  • Added csv export action

v0.12.2

Enhancements:

  • Support read_only in modelserialiser_factory

v0.12.1

Bugs Fixed:

  • Flatten url patterns so object_default can match without trailing /
  • Fix class returned in permit decorator [Thanks emilkjer]

Enhancements:

  • Allow passing an alternative default instead of None for Publisher.get_request_data
  • Added “read_only_fields” to ModelSerialiser [thanks jayant]

v0.12

Enhancements:

  • Tune Serialisers to pre-build their deflater/inflater method lists, removing work from the inner loop
  • Remove *args where it’s no helpful

v0.11.6.1

Bugs Fixed:

  • Renamed HttpResponseRedirect to HttpResponseRedirection to avoid clashing with Django http class

v0.11.6

Bugs Fixed:

  • Raise a 404 on paginator raising EmptyPage, instead of failing

v0.11.5.1

Bugs Fixed:

  • Fix arguments passed to execute method

v0.11.5

Enhancements:

  • Add Publisher.execute to make wrapping handler calls easier [also, makes NewRelic simpler to hook in]
  • Allow empty first pages in pagination
  • Added support module for NewRelic

v0.11.4

Enhancements:

  • Make content-type detection more forgiving

v0.11.3

Enhancements:

  • Make get_page honor limit parameter, but bound it to max_page_size, which defaults to page_size
  • Allow changing the GET param names for page, offset and limit
  • Allow passing page+limit or offset+limit

v0.11.2

Enhancements:

  • Added BooleanField
  • Extended tests
  • Force CSRF protection

v0.11.1

Enhancements:

  • Changed SerialiserField/ManySerialiserField to replace reduce/restore instead of overriding inflate/deflate methods
  • Fixed broken url pattern for object action
  • Updated fields documentation

v0.11

API breakage

Serialiser.deflate_object and Serialiser.deflate_list have been renamed.

Enhancements:

  • Changed deflate_object and deflate_list to object_deflate and list_deflate to avoid potential field deflater name conflict
  • Moved all model related code to models.py
  • Added modelserialiser_factory
  • Updated ModelSerialiserField/ModelManySerialiserField to optionally auto-create a serialiser for the supplied model

v0.10.3

Enhancements:

  • Added python2.6 support back [thanks nkuttler]
  • Added more documentation
  • Added Publisher.get_serialiser_kwargs hook
  • Publisher.get_data was renamed to Publisher.get_request_data for clarity

v0.10.2

Bugs Fixed:

  • Removed leftover debug print

v0.10.1

Enhancements:

  • Added Publisher introspection
  • Added LocationHeaderMixin to HTTP classes

v0.10

Bugs Fixed:

  • Removed useless cruft form utils

Enhancements:

  • Replaced http subclasses with Exceptional ones
  • Wrap call to handlers to catch Exceptional http responses

v0.9.1

Enhancements:

  • Started documentation
  • Added permit_groups decorator
  • Minor speedup in MetaSerialiser

v0.9

Bugs Fixed:

  • Fixed var name bug in ModelSerialiser.restore_object
  • Removed old ‘may’ auth API

Enhancements:

  • Added permit decorators
  • use string formatting not join - it’s slightly faster

v0.8

Enhancements:

  • Added create/delete methods to ModelPublisher
  • Renamed HttpResponse subclasses
  • Split out BasePublisher class
  • Added http.STATUS dict/list utility class

Note

Because this uses OrderedDict nap is no longer python2.6 compatible

v0.7.1

Enhancements:

  • Use first engine.CONTENT_TYPES as default content type for responses

v0.7

Bugs Fixed:

  • Removed custom JSON class

Enhancements:

  • Added Engine mixin classes
  • Added MsgPack support
  • Added type-casting fields

v0.6

Bugs Fixed:

  • Fixed JSON serialising of date/datetime objects

Enhancements:

  • Added index view to API
  • Make render_single_object use create_response
  • Allow create_response to use a supplied response class

v0.5

Enhancements:

  • Added names to URL patterns
  • Added “argument” URL patterns

v0.4

Enhancements:

  • Added next/prev flags to list meta-data
  • Added tests

v0.3

Enhancements:

  • Changed to more generic extra arguments in Serialiser

v0.2

Bugs Fixed:

  • Fixed bug in serialiser meta-class that broke inheritance
  • Fixed variable names

Enhancements:

  • Pass the Publisher down into the Serialiser for more flexibility
  • Allow object IDs to be slugs
  • Handle case of empty request body with JSON content type
  • Added SerialiserField and ManySerialiserField
  • Added Api machinery
  • Changed Serialiser to use internal Meta class
  • Added ModelSerialiser class

v0.1

Enhancements:

  • Initial release, fraught with bugs :)

Quick Start

  1. Create a Serialiser for your Model in serialisers.py
from nap import models
from myapp.models import MyModel

class MyModelSerialiser(models.ModelSerialiser):
    class Meta:
        model = MyModel
        exclude = ['user',]
  1. Create a Publisher in publishers.py, and register it.
from nap import api, models
from myapp.serialisers import MyModelSerialiser

class MyModelPublisher(models.ModelPublisher):
    serialiser = MyModelSerialiser()

api.register('api', MyModelPublisher)
  1. Auto-discover publishers, and add your APIs to your URLs:
from nap import api

api.autodiscover()

urlpatterns('',
    (r'', include(api.patterns())
    ...
)

Indices and tables