Welcome to djangorest-alchemy’s documentation!

Contents:

djangorest-alchemy

A library to integrate the awesome frameworks Django REST Framework and SQLAlchemy

  • Free software: MIT license
  • Supports SQLAlchemy 0.7.8 and above

Features

  • Provides GET verb implementation for SQLAlchemy models
  • List, filter and paginate multiple rows
  • Fetch single object with nested objects as complete URIs
  • Supports multiple primary keys
  • Provides ability to use ‘Manager’ like classes to work with SQLAlchemy models
  • Supports both Declarative and Classical styles

Install dependencies

` pip install -r requirements.txt `

Run tests

` make test `

Usage

Getting Started

Assuming you have a SQLAlchemy model defined as below:

class DeclarativeModel(Base):
    __tablename__ = 'test_model'

    declarativemodel_id = Column(INTEGER, primary_key=True)
    field = Column(String)
    datetime = Column(DateTime, default=datetime.datetime.utcnow)
    floatfield = Column(Float)
    bigintfield = Column(BigInteger)
    child_model = relationship(ChildModel, uselist=False, primaryjoin=
                              (declarativemodel_id == ChildModel.parent_id))

Define the ‘manager’ class to work on above model:

class DeclarativeModelManager(SessionMixin, AlchemyModelManager):
    model_class = DeclarativeModel

SessionMixin just provides a convenient way to initialize the SQLAlchemy session. You can achieve the same by definining __init__ and setting ```self.session``` instance

Define the Django REST viewset and specify the manager class:

class DeclModelViewSet(AlchemyModelViewSet):
    manager_class = DeclarativeModelManager

Finally, register the routers as you would normally do using Django REST:

viewset_router = routers.SimpleRouter()
viewset_router.register(r'api/declmodels', DeclModelViewSet,
                        base_name='test-decl')

Pagination

Pagination works exactly like Django REST Framework (and Django). Provided your viewset has the `paginate_by` field set, pass page number in querystring:

class ModelViewSet(AlchemyModelViewSet):
    paginate_by = 25
  • 5th page `curl -v  http://server/api/declmodels/?page=5`
  • Last page `curl -v  http://server/api/declmodels/?page=last`
  • First page `curl -v  http://server/api/declmodels/`

Filters

Filters work exactly like Django REST Framework. Pass the field value pair in querystring.

`curl -v  http://server/api/declmodels/?field=value`

Advanced Usage

Multiple primary keys

To use some sort of identifier in the URI, the library tries to use the following logic.

  1. If a single primary key is found, use it! That was simple..
  2. For multiple keys, try to find a field with convention ‘model_id’
  3. If not found, see if the model has ‘pk_field’ class variable
  4. If not found, raise KeyNotFoundException

In addition, to support multiple primary keys which cannot be accomodated in the URI, the viewset needs to override the `get_other_pks` method and return back dictionary of primary keys. Example:

class ModelViewSet(AlchemyModelViewSet):
    manager_class = ModelManager
    def get_other_pks(self, request):
        pks = {
            'pk1': request.META.get('PK1'),
            'pk2': request.META.get('PK2'),
        }
        return pks

Manager factory

The base AlchemyModelViewSet viewset provides a way to override the instantiation of the manager. Example:

class ModelViewSet(AlchemyModelViewSet):
    def manager_factory(self, *args, **kwargs):
        return ModelManager()

Nested Models

This library recommends using the drf-nested-routers for implementing nested child models. Example:

child_router = routers.NestedSimpleRouter(viewset_router, r'api/declmodels',
                                      lookup='declmodels')

For more details, refer to the drf-nested-routers documentation.

Custom methods

DRF allows to add custom methods other than the default list, retrieve, create, update and destroy using the @action decorator. However, if you have managers, then you can simply provide action methods on the manager and specify the action methods using action_methods field The methods have to return back appropriate status per below map.

STATUS_CODES = {
‘created’: status.HTTP_201_CREATED, ‘updated’: status.HTTP_200_OK, ‘accepted’: status.HTTP_202_ACCEPTED

}

class MyManager(AlchemyModelManager):

action_methods = {‘do_something’: [‘POST’]}

def do_something(self, data, pk=None, **kwargs):
# data is actual payload return {‘status’: ‘created’}
class ModelViewSet(AlchemyModelViewSet):
manager_class = MyManager

`curl -X POST http://server/api/declmodels/1/do_something/`

Read-only API

If you need only the GET method, and do not wish to expose/support POST/PUT/DELETE then you can use the djangorest_alchemy.routers.ReadOnlyRouter instead of the DefaultRouter

Examples

The examples folder demonstrates a real-world example using Cars and Parts as the object models.

Run the following command just as you would normally run a Django project:

` cd examples python manage.py runserver --settings=settings `

Then type the following in your favorite browser:

` http://localhost/api/cars/ `

Installation

At the command line:

$ easy_install djangorest-alchemy

Or, if you have virtualenvwrapper installed:

$ mkvirtualenv djangorest-alchemy
$ pip install djangorest-alchemy

API Documentation

djangorest_alchemy package

Submodules

djangorest_alchemy.fields module

Relationship field

class djangorest_alchemy.fields.AlchemyRelatedField(*args, **kwargs)[source]

Bases: rest_framework.relations.RelatedField

to_native(obj)[source]
class djangorest_alchemy.fields.AlchemyUriField(*args, **kwargs)[source]

Bases: rest_framework.relations.RelatedField

to_native(obj)[source]

djangorest_alchemy.inspector module

Functions to reflect over SQLAlchemy models

exception djangorest_alchemy.inspector.KeyNotFoundException[source]

Bases: exceptions.Exception

Primary key not found exception

djangorest_alchemy.inspector.class_keys(cls)[source]

This is a utility function to get the attribute names for the primary keys of a class

# >>> class_keys(Deal) # >>> (‘dealer_code’, ‘deal_jacket_id’, ‘deal_id’)

djangorest_alchemy.inspector.primary_key(cls)[source]

Utility function to get the primary key of the class. In case of multiple primary keys, use the <classname>_id convention

djangorest_alchemy.managers module

Base for interfacing with SQLAlchemy Provides the necessary plumbing for CRUD using SA session

class djangorest_alchemy.managers.AlchemyModelManager(*args, **kwargs)[source]

Bases: object

list(other_pks=None, filters=None)[source]

List returns back list of URI In case of multiple pks, We guess the pk by using ‘<modelname>_id’ as the field convention

retrieve(pks, other_pks=None)[source]

Retrieve fetches the object based on the following pk logic: if ‘other’ pks are not found, just use the pk list (coming from URLS) assuming their order is already correct if ‘other’ pks are found, then use the class keys to get the correct order of pks, look them up

djangorest_alchemy.mixins module

class djangorest_alchemy.mixins.ManagerMeta[source]

Bases: type

Meta class to read action methods from manager and attach them to viewset This allows us to directly call manager methods without writing any action methods on viewsets

class djangorest_alchemy.mixins.ManagerMixin[source]

Bases: object

Manager mixin allows to use a manager class to provide the actual CRUD implementation in addition to providing action methods

Example:

class MyManager(AlchemyModelManager):
    action_methods = {'my_method': ['POST']}

    def my_method(self, data, pk=None, **kwargs):
        # data is actual payload
        return {'status': 'created'}

class MyViewSet(viewset.Viewsets, ManagerMixin):
    manager_class = MyManager
manager_factory(*args, **kwargs)[source]

Factory method for instantiating manager class Override to return back your instance

class djangorest_alchemy.mixins.MultipleObjectMixin[source]

Bases: object

SQLAlchemy analog to Django’s MultipleObjectMixin.

allow_empty = True
filter_query_object(query_object)[source]

Generic filtering.

This is a stub and has yet to be implemented.

get_allow_empty()[source]

Returns True to display empty lists, False to 404.

get_page(queryset)[source]

Add the object list to the template context.

get_paginate_by(query_object)[source]

Get the number of items to paginate by. None for no pagination.

get_paginator(query_object, per_page, orphans=0, allow_empty_first_page=True)[source]

Get a paginator instance.

The class used is overridable by setting the paginator_class attribute.

paginate_by = None
paginate_query_object(query_object, page_size)[source]

Paginate the query object.

paginator_class

alias of Paginator

query_object = None
djangorest_alchemy.mixins.make_action_method(name, methods, **kwargs)[source]

djangorest_alchemy.routers module

class djangorest_alchemy.routers.ReadOnlyRouter(trailing_slash=True)[source]

Bases: rest_framework.routers.DefaultRouter

A router for read-only APIs, which USES trailing slashes.

routes = [Route(url='^{prefix}{trailing_slash}$', mapping={'get': 'list'}, name='{basename}-list', initkwargs={'suffix': 'List'}), Route(url='^{prefix}/{lookup}{trailing_slash}$', mapping={'get': 'retrieve'}, name='{basename}-detail', initkwargs={'suffix': 'Detail'})]

djangorest_alchemy.serializers module

Base AlchemyModelSerializer which provides the mapping between SQLALchemy and DRF fields to serialize/deserialize objects

class djangorest_alchemy.serializers.AlchemyListSerializer(*args, **kwargs)[source]

Bases: djangorest_alchemy.serializers.AlchemyModelSerializer

base_fields = {}
get_default_fields()[source]
class djangorest_alchemy.serializers.AlchemyModelSerializer(*args, **kwargs)[source]

Bases: rest_framework.serializers.Serializer

Alchemy -> DRF field serializer

base_fields = {}
field_mapping = {<class 'sqlalchemy.types.DateTime'>: <class 'rest_framework.fields.DateTimeField'>, <class 'sqlalchemy.types.Numeric'>: <class 'rest_framework.fields.IntegerField'>, <class 'sqlalchemy.types.BigInteger'>: <class 'rest_framework.fields.IntegerField'>, <class 'sqlalchemy.types.INTEGER'>: <class 'rest_framework.fields.IntegerField'>, <class 'sqlalchemy.types.SMALLINT'>: <class 'rest_framework.fields.IntegerField'>, <class 'sqlalchemy.types.BIGINT'>: <class 'rest_framework.fields.IntegerField'>, <class 'sqlalchemy.types.TIMESTAMP'>: <class 'rest_framework.fields.DateTimeField'>, <class 'sqlalchemy.types.DATE'>: <class 'rest_framework.fields.DateTimeField'>, <class 'sqlalchemy.types.CLOB'>: <class 'rest_framework.fields.CharField'>, <class 'sqlalchemy.types.VARCHAR'>: <class 'rest_framework.fields.CharField'>, <class 'sqlalchemy.types.CHAR'>: <class 'rest_framework.fields.CharField'>, <class 'sqlalchemy.types.Float'>: <class 'rest_framework.fields.FloatField'>, <class 'sqlalchemy.types.String'>: <class 'rest_framework.fields.CharField'>, <class 'sqlalchemy.types.Boolean'>: <class 'rest_framework.fields.BooleanField'>}
get_default_fields()[source]

djangorest_alchemy.settings module

djangorest_alchemy.viewsets module

Base AlchemyViewSet which provides the necessary plumbing to interface with AlchemyModelSerializer and AlchemyModelManager

class djangorest_alchemy.viewsets.AlchemyModelViewSet(**kwargs)[source]

Bases: djangorest_alchemy.mixins.MultipleObjectMixin, djangorest_alchemy.mixins.ManagerMixin, rest_framework.viewsets.ViewSet

Generic SQLAlchemy viewset which calls methods over the specified manager_class and uses specified serializer_class

create(request)[source]
destroy(request, pk=None)[source]
get_other_pks(request)[source]

Return default empty {} Override to return back your primary keys from other source (possibly from headers)

Parameters:request – REST request object
Returns:dict
get_pks(request, **kwargs)[source]

Return list of pks from the keyword args e.g. /models/pk1/childmodel/pk2 return back [pk1, pk2]

Parameters:request – REST request object
Kwargs kwargs:URI keyword args
Returns:List e.g. [pk1, pk2]
list(request, **kwargs)[source]

Returns back serialized list of objects URIs in the results key

Returns:json {
“results”: [
{
“href”: “http://server/api/models/pk/

}

]

}

Note:

* URI contains the same pk field
* Complete URI with server/port is returned back
retrieve(request, **kwargs)[source]

Retrieve returns back serialized object.

Returns:json {
“href”: “http://server/api/models/pk/”, “field”: “value” “childmodel”: “http://serv/api/parentmodels/pk/childmodels/pk

}

Note:

As of now, only SQLAlchemy mapper properties are returned
No other fields or properties are serialized. You will need
to override retrieve and provide your own implementation to
query those additional properties for now.
serializer_factory(multiple, queryset, model_class, context)[source]

Factory method to instantiate appropriate serializer class Override to return back your instance

update(request, pk=None)[source]

Module contents

Contributing

Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.

You can contribute in many ways:

Types of Contributions

Report Bugs

Report bugs at https://github.com/Dealertrack/djangorest-alchemy/issues.

If you are reporting a bug, please include:

  • Your operating system name and version.
  • Any details about your local setup that might be helpful in troubleshooting.
  • Detailed steps to reproduce the bug.

Fix Bugs

Look through the GitHub issues for bugs. Anything tagged with “bug” is open to whoever wants to implement it.

Implement Features

Look through the GitHub issues for features. Anything tagged with “feature” is open to whoever wants to implement it.

Write Documentation

djangorest-alchemy could always use more documentation, whether as part of the official djangorest-alchemy docs, in docstrings, or even on the web in blog posts, articles, and such.

Submit Feedback

The best way to send feedback is to file an issue at https://github.com/Dealertrack/djangorest-alchemy/issues.

If you are proposing a feature:

  • Explain in detail how it would work.
  • Keep the scope as narrow as possible, to make it easier to implement.
  • Remember that this is a volunteer-driven project, and that contributions are welcome :)

Get Started!

Ready to contribute? Here’s how to set up djangorest-alchemy for local development.

  1. Fork the djangorest-alchemy repo on GitHub.

  2. Clone your fork locally:

    $ git clone git@github.com:your_name_here/djangorest-alchemy.git
    
  3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:

    $ mkvirtualenv djangorest-alchemy
    $ cd djangorest-alchemy/
    $ python setup.py develop
    
  4. Create a branch for local development:

    $ git checkout -b name-of-your-bugfix-or-feature
    

    Now you can make your changes locally.

  5. When you’re done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:

    $ flake8 djangorest-alchemy tests
    $ python setup.py test
    $ tox
    

    To get flake8 and tox, just pip install them into your virtualenv.

  6. Commit your changes and push your branch to GitHub:

    $ git add .
    $ git commit -m "Your detailed description of your changes."
    $ git push origin name-of-your-bugfix-or-feature
    
  7. Submit a pull request through the GitHub website.

Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

  1. The pull request should include tests.
  2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst.
  3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check https://travis-ci.org/iagore/djangorest-alchemy/pull_requests and make sure that the tests pass for all supported Python versions.

Credits

Development Lead

Contributors

None yet. Why not be the first?

History

0.1.0 (2014-09-03)

  • First release

Indices and tables