Django Piston's API framework on steroids
Python
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
docs
piston_perfect
.gitignore
README
setup.py

README

piston-perfect
**************

Django Piston on steroids. Design inspired by slide 25 of
http://www.slideshare.net/landlessness/teach-a-dog-to-rest and the
reality of developing a RESTful web API for Smart.pr. Lives in a
module named ``piston_perfect`` that functions as a layer on top of
``piston``.

The contents of this module are modeled after the contents of
``piston`` (save for some subtle changes in naming).

You can use the following settings attribute to specify the data
formats that you want your API to support:

   PISTON_FORMATS = 'json',     # Defaults to *'json',*.

TODO:
   Examples


``handlers``
============

class class piston_perfect.handlers.BaseHandler

   All handlers should (directly or indirectly) inherit from this one.
   Its public attributes and methods were designed with extensibility
   in mind, so don't hesitate to override.

   Note: Piston's ``piston.handler.BaseHandler.allowed_methods`` attribute
     should not be used, as it is auto-generated based on the values
     of ``create()``, ``read()``, ``update()`` and ``delete()``.

   fields

      Specifies the fields that are allowed to be included in a
      response. It also serves as the set of options for request-level
      fields selection (see ``request_fields``) and the base set of
      fields that are allowed as incoming data (see
      ``may_input_field()``). If it is an empty iterable (the default)
      the decision whether or not a field is allowed to be included is
      taken by ``is_field_allowed()``.

      Note that the value of this attribute is not automatically used
      as field definition on the emitter, as is the behavior of
      Piston's default base handler. See the monkey-patched
      ``piston.emitters.Emitter.construct()`` in ``patches`` for more
      information.

   request_fields

      Determines if request-level fields selection is enabled. Should
      be the name of the query string parameter in which the selection
      can be found. If this attribute is defined as ``True`` (which is
      the default) the default parameter name ``field`` will be used.
      Note that setting to (as opposed to "defining as") ``True`` will
      not work. Disable request-level fields selection by defining
      this as ``False``.

      Multiple fields can be specified by including the parameter
      multiple times: ``?field=id&field=name`` is interpreted as the
      selection ``('id', 'name')``.

   exclude

      Is used by ``is_field_allowed()`` to decide if a field should be
      included in the result. Note that this only applies to scenarios
      in which ``fields`` is empty. Should be an iterable of field
      names and/or regular expression patterns. Its default setting is
      to exclude all field names that begin with ``_``.

   exclude_in

      A list of field names that will be filtered out of incoming
      data. Fields that are not listed in ``fields`` will never be
      considered either, so this attribute should contain field names
      that are also in ``fields``. Is used by ``may_input_field()``.

   get_requested_fields(request)

      Returns the fields selection for this specific request. Takes
      into account the settings for ``fields`` and ``request_fields``,
      and the query string in *request*. Returns ``()`` in case no
      selection has been specified in any way.

   may_output_field(field)

      Determines if the field named *field* should be included in the
      response. Returns ``False`` for any field that matches the
      specification in ``exclude``, ``True`` otherwise. Note that this
      method will not be consulted if ``fields`` is non-empty.

   may_input_field(field)

      Decides if a field should be filtered out of incoming data (in
      the request body). The default behavior is to accept any fields
      that are in ``fields`` (if not empty) and not in ``exclude_in``.

   model_fields

      The set of fields that is used to represent a model instance in
      case no explicit fields set has been specified (either via
      ``fields`` or via a fields definition in the request).

   classmethod model_key(instance)

      Returns a key identifying the provided model instance.

   classmethod model_type(instance)

      Returns a text string representing the type of the provided
      model instance.

   classmethod model_description(instance)

      Returns a description of the provided model instance.

   authentication

      The Piston authenticator that should be in effect on this
      handler. If defined as ``True`` (which is not the same as
      assigning ``True``, as this will not work) an instance of
      ``authentication.DjangoAuthentication`` is used. A value of
      ``None`` implies no authentication, which is the default.

   validate(request, *args, **kwargs)

      Validates and cleanses incoming data (in the request body). Can
      be overridden to extend this behavior with other types of
      request validation.

   working_set(request, *args, **kwargs)

      Returns the operation's base data set. No data beyond this set
      will be accessed or modified. The reason why we need this one in
      addition to ``data_set()`` is that ``data_item()`` needs to have
      a data set to pick from -- we need to define which items it is
      allowed to obtain (and which not). This data set should not have
      user filters applied because those do not apply to item views.

   data_set(request, *args, **kwargs)

      Returns the operation's result data set, which is always an
      iterable. The difference with ``working_set()`` is that it
      returns the data *after* all filters and ordering (not slicing)
      are applied.

   data_item(request, *args, **kwargs)

      Returns the data item that is being worked on. This is how the
      handler decides if the requested data is singular or not. By
      returning ``None`` we signal that this request should be handled
      as a request for a set of data, as opposed to a request for a
      single record.

   data(request, *args, **kwargs)

      Returns the data that is the result of the current operation,
      without having to specify if the request is singular or plural.

   get_response_data(request, response)

      Reads the data from a response structure. Raises a *KeyError* if
      response contains no data.

   set_response_data(request, data, response=None)

      Sets data onto a response structure. Creates a new response
      structure if none is provided.

   filters

      User filter data query string parameter, or ``True`` if the
      default (``filter``) should be used. Disabled (``False``) by
      default.

   filter_data(data, definition, values)

      Applies user filters (as specified in ``filters``) to the
      provided data. Does nothing unless overridden with a method that
      implements filter logic.

   order

      Order data query string parameter, or ``True`` if the default
      (``order``) should be used. Disabled (``False``) by default.

   order_data(data, *order)

      Orders the provided data. Does nothing unless overridden with a
      method that implements ordering logic.

   slice

      Slice data query string parameter, or ``True`` if the default
      (``slice``) should be used. Disabled (``False``) by default.

   response_slice_data(response, request, *args, **kwargs)

      Slices the data set in *response*. This method's job is to
      interpret the order parameters in the request (if any),
      translate them to a call to ``slice_data()`` and alter the
      response respectively. Returns a boolean value that indicates
      whether the data has been sliced or not.

   slice_data(data, start=None, stop=None, step=None)

      Slices the provided data according to *start*, *stop* and
      *step*.

   request(request, *args, **kwargs)

      All requests are entering the handler here.

   create(request, *args, **kwargs)

      Default implementation of a create operation, put in place when
      the handler defines ``create = True``.

   read(request, *args, **kwargs)

      Default implementation of a read operation, put in place when
      the handler defines ``read = True``.

   update(request, *args, **kwargs)

      Default implementation of an update operation, put in place when
      the the handler defines ``update = True``.

   delete(request, *args, **kwargs)

      Default implementation of a delete operation, put in place when
      the the handler defines ``delete = True``.

   response_add_debug(response, request)

      Adds debug information to the response -- currently the database
      queries that were performed in this operation. May be overridden
      to extend with custom debug information.

   response_constructed(response, unconstructed, request)

      Is called right after the response has been constructed
      (converted to a data structure with just dictionaries and
      lists), and right before the response is being sent back to the
      client. Allows for some last-minute operations that need the
      guarantee of being the last, or that would impact the response
      data if it hadn't been constructed yet.

   data_safe_for_delete(data)

      If we want the delete operation to remove data without impacting
      the data in the response we can do it safely here.

class class piston_perfect.handlers.ModelHandler

   Provides off-the-shelf CRUD operations on data of a certain model
   type.

   Note that in order to prevent accidental exposure of data that was
   never intended to be public, model data fields will not be included
   in the response if they are not explicitly mentioned in ``fields``.
   If it is empty model data will be represented in a generic way as
   specified by ``model_fields``.

   model

      A model class of type ``django.db.models.Model``.

   exclude_nested

      A list of field names that should be excluded from the fields
      selection in case of a nested representation; i.e. when the
      model is contained by another model object.

   may_input_field(field)

   validate(request, *args, **kwargs)

      Turns the data on the request into model instances; a new
      instance with the ``POST``'ed data or a current instance updated
      with the ``PUT``'ed data.

   working_set(request, *args, **kwargs)

   data_item(request, *args, **kwargs)

   filter_data(data, definition, values)

      Recognizes and applies two types of filters:

      * If its *definition* (the value of the filter in ``filters``)
        is a text string, it will be interpreted as a filter on the
        *QuerySet*.

      * If its definition is a list (or tuple or set), it will be
        interpreted as a search operation on all fields that are
        mentioned in this list.

   order_data(data, *order)

   response_slice_data(response, request, *args, **kwargs)

   create(request, *args, **kwargs)

   update(request, *args, **kwargs)

   data_safe_for_delete(data)


``authentication``
==================

class class piston_perfect.authentication.DjangoAuthentication

   Piston authenticator that blocks all requests with non-
   authenticated sessions.


``resource``
============

class class piston_perfect.resource.Resource(handler, authentication=None)

   Simple subclass of Piston's implementation.

   callmap

      Route every request type to ``handlers.BaseHandler.request()``.

   error_handler(e, *args, **kwargs)

      If anything went wrong inside the handler, this method will try
      to construct a meaningful error response (or not if we want to
      hide the character of the problem from the user).


``patches``
===========

We need a few real hacks into Piston's internal logic, all of which
originate here. "Real hacks" as in; extensions that are kind of like
black magic because they require detailed knowledge of Piston's
workings at code level. They depend on some very specific pieces of
Piston code and are therefore likely to break with future versions of
Piston.

The idea is to have all the messy stuff collected in this one module
and isolate it as much as possible from the public-facing interfaces
in ``handlers``. Hopefully this will allow us to deal with Piston
updates without touching anything beyond this module.