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.
- If a single primary key is found, use it! That was simple..
- For multiple keys, try to find a field with convention ‘model_id’
- If not found, see if the model has ‘pk_field’ class variable
- 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
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.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
- 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_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¶
- paginator_class¶
alias of Paginator
- query_object = None¶
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 = {}¶
- 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'>}¶
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
- 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.
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.
Fork the djangorest-alchemy repo on GitHub.
Clone your fork locally:
$ git clone git@github.com:your_name_here/djangorest-alchemy.git
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
Create a branch for local development:
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
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.
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
Submit a pull request through the GitHub website.
Pull Request Guidelines¶
Before you submit a pull request, check that it meets these guidelines:
- The pull request should include tests.
- 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.
- 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¶
- Ashish Gore <ashish.gore@dealertrack.com>
Contributors¶
None yet. Why not be the first?