Makes RESTful CRUD easier
Project description
Makes RESTful CRUD easier.
Test status
IMPORTANT CHANGE IN 1.0.0
Previously, the CollectionResource and SingleResource classes took db_session as a parameter to the constructor. As of 1.0.0, they now take db_engine instead. The reason for this is to keep the sessions short-lived and under autocrud’s control to explicitly close the sessions.
This WILL impact you as your routing should now pass the db_engine instead of the db_session, and if you override these classes, then, if you have overridden the constructor, you may also have to update that.
Quick start for contributing
virtualenv -p `which python3` virtualenv source virtualenv/bin/activate pip install -r requirements.txt pip install -r dev_requirements.txt nosetests
This runs the tests with SQLite. To run the tests with Postgres (using pg8000), you must have a Postgres server running, and a postgres user with permission to create databases:
export AUTOCRUD_DSN=postgresql+pg8000://myuser:mypassword@localhost:5432 nosetests
Usage
Declare your SQLAlchemy models:
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import create_engine, Column, Integer, String Base = declarative_base() class Employee(Base): __tablename__ = 'employees' id = Column(Integer, primary_key=True) name = Column(String(50)) age = Column(Integer)
Declare your resources:
from falcon_autocrud.resource import CollectionResource, SingleResource class EmployeeCollectionResource(CollectionResource): model = Employee class EmployeeResource(SingleResource): model = Employee
Apply them to your app, ensuring you pass an SQLAlchemy engine to the resource classes:
from sqlalchemy import create_engine import falcon import falconjsonio.middleware db_engine = create_engine('sqlite:///stuff.db') app = falcon.API( middleware=[ falconjsonio.middleware.RequireJSON(), falconjsonio.middleware.JSONTranslator(), ], ) app.add_route('/employees', EmployeeCollectionResource(db_engine)) app.add_route('/employees/{id}', EmployeeResource(db_engine))
This automatically creates RESTful endpoints for your resources:
http GET http://localhost/employees http GET http://localhost/employees?name=Bob http GET http://localhost/employees?age__gt=24 http GET http://localhost/employees?age__gte=25 http GET http://localhost/employees?age__lt=25 http GET http://localhost/employees?age__lte=24 http GET http://localhost/employees?name__contains=John http GET http://localhost/employees?name__startswith=John http GET http://localhost/employees?company_id__null=1 http GET http://localhost/employees?company_id__null=0 echo '{"name": "Jim"}' | http POST http://localhost/employees http GET http://localhost/employees/100 echo '{"name": "Jim"}' | http PUT http://localhost/employees/100 echo '{"name": "Jim"}' | http PATCH http://localhost/employees/100 http DELETE http://localhost/employees/100 # PATCHing a collection to add entities in bulk echo '{"patches": [{"op": "add", "path": "/", "value": {"name": "Jim"}}]}' | http PATCH http://localhost/employees
Limiting methods
By default collections will autogenerate methods GET, POST and PATCH, while single resources will autogenerate methods GET, PUT, PATCH, DELETE.
To limit which methods are autogenerated for your resource, simply list method names as follows:
# Able to create and search collection: class AccountCollectionResource(CollectionResource): model = Account methods = ['GET', 'POST'] # Only able to read individual accounts: class AccountResource(CollectionResource): model = Account methods = ['GET']
Post-method functionality
To do something after success of a method, after special methods as follows:
class AccountCollectionResource(CollectionResource): model = Account def after_get(self, req, resp, collection, *args, **kwargs): # 'collection' is the SQLAlchemy collection resulting from the search pass def after_post(self, req, resp, new, *args, **kwargs): # 'new' is the created SQLAlchemy instance pass def after_patch(self, req, resp, *args, **kwargs): pass class AccountResource(CollectionResource): model = Account def after_get(self, req, resp, item, *args, **kwargs): # 'item' is the retrieved SQLAlchemy instance pass def after_put(self, req, resp, item, *args, **kwargs): # 'item' is the changed SQLAlchemy instance pass def after_patch(self, req, resp, item, *args, **kwargs): # 'item' is the patched SQLAlchemy instance pass def after_delete(self, req, resp, item, *args, **kwargs): pass
Be careful not to throw an exception in the above methods, as this will end up propagating a 500 Internal Server Error.
Filters/Preconditions
You may filter on GET, and set preconditions on single resource PATCH or DELETE:
class AccountCollectionResource(CollectionResource): model = Account def get_filter(self, req, resp, query, *args, **kwargs): # Only allow getting accounts below id 5 return query.filter(Account.id < 5) class AccountResource(SingleResource): model = Account def get_filter(self, req, resp, query, *args, **kwargs): # Only allow getting accounts below id 5 return query.filter(Account.id < 5) def patch_precondition(self, req, resp, query, *args, **kwargs): # Only allow setting owner of non-owned account if 'owner' in req.context['doc'] and req.context['doc']['owner'] is not None: return query.filter(Account.owner == None) else: return query def delete_precondition(self, req, resp, query, *args, **kwargs): # Only allow deletes of non-owned accounts return query.filter(Account.owner == None)
Not really deleting
If you want to just mark a resource as deleted in the database, but not really delete the row, define a ‘mark_deleted’ in your SingleResource subclass:
class AccountResource(SingleResource): model = Account def mark_deleted(self, req, resp, instance, *args, **kwargs): instance.deleted = datetime.utcnow()
This will cause the changed instance to be updated in the database instead of doing a DELETE.
Of course, the database row will still be accessible via GET, but you can automatically filter out “deleted” rows like this:
class AccountCollectionResource(CollectionResource): model = Account def get_filter(self, req, resp, resources, *args, **kwargs): return resources.filter(Account.deleted == None) class AccountResource(SingleResource): model = Account def get_filter(self, req, resp, resources, *args, **kwargs): return resources.filter(Account.deleted == None) def mark_deleted(self, req, resp, instance, *args, **kwargs): instance.deleted = datetime.utcnow()
You could also look at the request to only filter out “deleted” rows for some users.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.