Flask-AlchemyView

A Flask ModelView that makes it a bit easier to manage views for SQLAlchemy Declarative models. The AlchemyView class extends the very nice Flask-Classy FlaskView and supports all Flask-Classy FlaskView functionality.

Flask-AlchemyView uses colander for validation and dictalchemy for updating/creating/reading models.

Python 3

Note

New in 0.1.4

Flask-AlchemyView passes all tests with Python 3.3.2. However, at the time of writing (2013-09-06) there is a bug in Flask-SQLAlchemy that hasn’t been fixed in a release so in order to use Flask-AlchemyView with Flask-SQLAlchemy under Python 3.3.2 one must install an unreleased version of Flask-AlchemyView.

Installing unreleased Flask-SQLAlchemy version:

pip install -e git+https://github.com/mitsuhiko/flask-sqlalchemy#egg=Flask-SQLAlchemy

To summarize, Python3 is not well tested and only against 3.3.2 but it should work if Flask-SQLAlchemy works.

The session

A AlchemyView uses either AlchemyView.session or, if that is not set, the session from Flask-SQLAlchemy(new since v0.1.3). The prefered way to access the session is to use the Flask-SQLAlchemy session.

Using AlchemyView without Flask-SQLAlchemy

This is not recommended but if Flask-SQLAlchemy is not used the session can be set on AlchemyView directly. Be aware of that this will probably create problems if AlchemyView is used by several applications.

Using AlchemyView with Flask-SQLAlchemy

Nothing needs to be done in order to use Flask-SQLAlchemy. However, db.Model should be made dictable.

Setup Flask-SQLAlchemy:

...
from flask import Flask
app = Flask(__name__)

from flask.ext.sqlalchemy import SQLAlchemy

db = SQLAlchemy(app)

from dictalchemy import make_class_dictable
make_class_dictable(db.Model)

Responses

Note

New in 0.1.4

AlchemyView will return json or HTML depending on the HTTP Accept Header. When returning HTML the template used will be determined by AlchemyView._get_template_name(). The response data will be passed to the template in the parameter data.

Ambigous accept header

If the client sends both application/json and text/html (or */*) and application/json and text/html has the same weight text/html will be assumed. This is because some browsers sends */*. This means that the client must send application/json with a greater weight than */* or text/html to recieve JSON. AngularJS for example does not do this, so in order to use angularjs and recieve JSON one must explicitly declare the accept header.

AngularJS example:

$http({method: 'GET', url: '/mymodel/', headers: {Accept: 'application/json'});

Providing extra variables for a template

If the response is a 200 it’s possible to provide extra parameters to the template By adding a method called ‘before_<view method>_render’. If that method returns anything it must be a dict containing parameters that will be passed to the template.

Adding is_owner parameter to a GET request template:

def before_get_render(self, data):
    return {'is_owner': data[owner_id'] === current_user.id}

Missing templates

If a template is missing a 406 will be returned, not a 500.

Errors

Note

New in 0.1.4

If an error occurs and json is returned a dict containing ‘message’ and ‘errors’ will be returned. If html is returned a BadRequest is raised.

Using AlchemyView

Lets start with a simple model, a user:

class User(Base):
    id = Column(Integer, primary_key)
    name = Column(Unicode)
    email = Column(Unicode)

The colander schema looks like this:

import colander as c

class UserSchema(c.MappingSchema):
    name = c.SchemaNode(c.String())
    email = c.SchemaNode(c.String())

The View:

class UserView(AlchemyView):
    model = UserModel
    schema = UserSchema

UserView.register(app)

After this the following routes has been defined:

  • GET /user/[ID]
  • POST /user/
  • PUT /user/[ID]
  • DELETE /user/[ID]
  • GET /user/

So far so good, but that can easily be done without AlchemyView. So why use AlchemyView? Well, it’s pretty configurable. There is support for different schemas depending on weather a PUT or POST is made, it can follow relationships on GET, and to some extent on PUT and POST also. It can take limit, offset, sortby and direction arguments when listing.

GET an item

In case of a GET item request the view will check if the actual item exists. If it does the AlchemyView will return that object in JSON form.

What is returned by GET(individual item or a list) is defined by the parameters AlchemyView.dict_params and AlchemyView.asdict_params. If non of them is set dictalchemy.utils.asdict() will be called on the model without parameters.

The method AlchemyView._base_query() can be overridden in order to add joins, exclude/include columns etc. The returned query is the one that will be used when performing a GET.

Examples

Returning only a specific attribute:

asdict_params = {'only': ['name']}

Following a relationship:

asdict_params = {'follow': {'group':{}}}

Adding a join to the query:

def _base_query(self):
    return self.session.query(User).join(Group)

PUT an item

Updating an item is pretty basic. If the item exists it will be updated with the data returned by the update schema. The update schema is either AlchemyView.update_schema or AlchemyView.schema if update_schema isn’t set. If any SchemaNode in the schema returns colander.null it will be removed from the update data, None will be preserved. This behaviour cannot be modified at the moment.

Updating the item will be done by calling fromdict() on AlchemyView.model. The parameters will be AlchemyView.fromdict_params, or AlchemyView.dict_params if fromdict_params isn’t set.

On validation error a 400 will be returned, on other errors a 500 will be returned.

Out of the box a AlchemyView is a bit limited in it’s update/create functionality. This is by design, if creating/updating a model is more complex it’s best to not try to do it automagically.

POST a new item

When post:ing data the data will be validated by AlchemyView.create_schema or AlchemyView.schema if create_schema isn’t set. Colander null values will not be removed. The validated data will be sent to the model constructor. On validation error an error message will be returned, on other errors a 500 will be returned.

DELETE an item

A delete will simply delete the instance if it exists. The delete method is defined as AlchemyView.delete() and AlchemyView._delete().

Listing items

The listing URL takes the additional parameters limit, offset, sortby and direction. The View has a max_page_limit attribute that ensures that limit can’t be set to high.

Sorting a list

If sortby isn’t set the sortby attribute will be used. It that is set to None no sorting will be done. The sortby argument is checked against sortby_map which is a map of string: expression. The expression must be something that can be inserted into the _base_query, so either a column or a valid string. If the sortby parameter is not found in sortby_map a 400 will be returned.

sortby_map Example:

sortby_map = {'name': User.name, 'group_id': 'Group.id'}

API

class flask.ext.alchemyview.AlchemyView[source]

Bases: flask_classy.FlaskView

View for SQLAlchemy dictable models

The pre-defined methods will always return JSON, with the mimetype set to application/json.

JSONEncoder

The JSON Encoder that should be used to load/dump json

alias of _JSONEncoder

_base_query()[source]

Get the base query that should be used

For example add joins here. Default implementation returns self._get_session().query(self.model).

Returns:Sqlalchemy query
_delete(id)[source]

Delete an item

_get_create_schema(data)[source]

Get colander schema for create

Returns:Colander schema for create data
_get_item(id)[source]

Get item based on id

Can handle models with int and string primary keys.

Raises :Exception if the primary key is a composite or not int or string
Returns:An item, calls flask.abort(400) if the item isn’t found
_get_response_mimetype()[source]

Get response type from response

Returns:‘application/json’ or ‘text/html’
_get_schema(data)[source]

Get basic colander schema

This schema is used if create_schema or update_schema isn’t set.

Returns:Colander schema for create or update schema
_get_session()[source]

Get SQLAlchemy session

Raises :An exception if self.session isn’t set and Flask-SQLAlchemy isn’t used
Returns:SQLAlchemy session
_get_template_name(name, mimetype)[source]

Get template name for a specific mimetype

The template name is by default get_route_base()/name.suffix, the suffixes are stored in AlchemyView.template_suffixes.

Returns:string
_get_update_schema(data)[source]

Get colander update schema

Returns:Colander schema for create data
_item_url(item)[source]

Get the url to read an item

Raises :Exception if the items primary key is a composite key
_json_dumps(obj, ensure_ascii=False, **kwargs)[source]

Load object from json string

Uses AlchemyView.JSONEncoder to dump the data.

Parameters:obj – Object that should be dumped
Returns:JSON string
_json_loads(string, **kwargs)[source]

Load json

_json_response(obj, status=200)[source]

Get a json response

Parameters:obj – Exception OR something that can used by AlchemyView._json_dumps() If this is an exception the status will be set to 400 if status is less than 400.
_response(data, template, status=200)[source]

Get a response

If the response will be rendered with a template and the method ‘_TEMPLATE_template_vars’ is set that method will be called and the returnvalue will be added to the template parameters.

Raises :If status is beteen 400 and 500 OR data is an exception a BadRequest will be raised.
Returns:A json or html response, based on the request accept headers
asdict_params = None

Parameters that will be used when getting an item

The parameters will be used in dictalchemy.utils.asdict().

create_schema = None

Create schema

delete(id)

Delete an item

This is just an alias for AlchemyView._delete().

dict_params = None

Will be used instead of AlchemyView.asdict_params and AlchemyView.from_params if they’re not set

fromdict_params = None

Parameters that will be used when updating an item

The parameters will be used in dictalchemy.utils.fromdict().

get(id)[source]

Handles GET requests

index()[source]

Returns a list

The response look like this:

items: [...]
count: Integer
limit: Integer
offset: Integer
max_page_limit = 50

Max page limit

model = None

SQLAlchemy declarative model

page_limit = 10

Default page limit

post()[source]

Handles POST

This method will create a model with request data if the data was valid. It validates the data with AlchemyView._get_create_schema().

If everything was successful it will return a 303 redirect to the newly created item.

If any error except validation errors are encountered a 500 will be returned.

Returns:A response
Return type:flask.Response
put(id)[source]

Handles PUT

If any error except validation errors are encountered a 500 will be returned.

schema = None

Colander schema that will be used as both update_schema and create_schema if they aren’t set

session = None

The SQLAlchemy session

If not set the session will be taken from the Flask-SQLAlchemy extension. If that is missing the view will not work.

sort_direction = 'asc'

Default sort direction

sortby = None

Default sortby column

If not set no sortby will be applied by default in AlchemyView.index().

In order for sortby to have any effect it also needs to be set in AlchemyView.sortby_map

sortby_map = None

Map of string=>column for sortby

The values can be anything that will work together with the query returned by AlchemyView._base_query(). So if there is a join in the base query that column, or name of that colum can be mapped to a key in the sortby_map.

template_suffixes = {'text/html': 'jinja2'}

Suffixes for response types, currently ‘text/html’ is the only one supported

update_schema = None

Update schema

class flask.ext.alchemyview.BadRequest(code, data)[source]

Bases: werkzeug.exceptions.HTTPException

HTTPException class that also contains error data

This will be raised when a request is invalid, use Flask.errorhandler() to handle these for HTML responses.

Variables:data – Dict explaining the error, contains the keys ‘message’ and ‘errors’

License

Flask-AlchemyView is released under the MIT license.