Quickstart¶
This page is intended to help get you off and running with Drowsy as quickly as possible. To do that, we’ll be walking through the creation of an example API using the Chinook Sqlite database that comes bundled with Drowsy’s tests, and Flask as our web framework. Note that Drowsy is web framework agnostic, and that Flask is by no means a requirement.
Some familiarity with SQLAlchemy and Marshmallow is assumed prior to working with Drowsy.
A working version of all of the below code can be found in the examples that come bundled in the Drowsy distribution, under the chinook_api folder.
Defining Models¶
The first thing you’ll need to do is define your SQLAlchemy models. Hopefully you’re familiar with SQLAlchemy already, but as a quick example, we’ll define a few models here:
from sqlalchemy import Column, ForeignKey, Integer, Unicode, orm
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import ForeignKeyConstraint
Base = declarative_base()
metadata = Base.metadata
class Album(Base):
"""SQLAlchemy model for the Album table in our database."""
__tablename__ = 'Album'
album_id = Column("AlbumId", Integer, primary_key=True)
title = Column("Title", Unicode(160), nullable=False)
artist_id = Column(
"ArtistId", ForeignKey('Artist.ArtistId'),
nullable=False, index=True)
artist = orm.relationship('Artist', backref="albums")
class Artist(Base):
"""SQLAlchemy model for the Artist table in our database."""
__tablename__ = 'Artist'
artist_id = Column("ArtistId", Integer, primary_key=True)
name = Column("Name", Unicode(120)
A complete set of example model definitions can be seen in models.py.
Defining Schemas¶
Once you’ve defined some models, the next step is to define some schemas. Schemas handle a number of important tasks, including serializing data from model instances, and validating and deserializing user input into model instances.
Drowsy provides the ModelResourceSchema class as the
main building block to creating schemas around your models.
from drowsy.convert import ModelResourceConverter
from drowsy.schema import ModelResourceSchema
from chinook_api.models import (
Album, Artist, CompositeOne, CompositeMany, CompositeNode,
Customer, Employee, Genre, Invoice, InvoiceLine, MediaType,
Node, Playlist, Track
)
class AlbumSchema(ModelResourceSchema):
class Meta:
model = Album
model_converter = ModelResourceConverter
class ArtistSchema(ModelResourceSchema):
class Meta:
model = Artist
model_converter = ModelResourceConverter
Here we’re using a ModelResourceConverter to
do a lot of work behind the scenes for us. The job of the converter is to take
fields from the specified model and turn them into fields for the schema.
By default, fields are named by using the attr names of the SQLAlchemy model.
If we were to use CamelModelResourceConverter
rather than the default ModelResourceConverter, it
would convert field names like album_id to albumId, to make the
serialized result conform to standard JSON naming convention.
There’s a good chance you’ll want to extend one of the included converters and create your own, as currently they include child resources by default and are very permissive. Also note that you can always explicitly define schema fields individually as you would in any other Marshmallow schema, if using one of the converters feels too much like magic for your taste.
A complete set of example resource definitions can be seen in schemas.py.
Defining Resources¶
After defining some schemas, the next step is to define our resources. Resources utilize schemas to perform CRUD style operations, and help manage any nested resource embedding in API query results.
from drowsy.resource import ModelResource
from chinook_api.schemas import AlbumSchema, ArtistSchema
class AlbumResource(ModelResource):
class Meta:
schema_cls = AlbumSchema
class ArtistResource(ModelResource):
class Meta:
schema_cls = ArtistSchema
A complete set of example resource definitions can be seen in resources.py.
Routing¶
The bulk of work is now done, and now we just need to tie it all together. We’ll be using Flask here, but again, the same principals will apply in any other Python web framework.
To put everything together, we’ll create a single endpoint that relies on a
ModelResourceRouter to use the URL and determine
which ModelResource the request should go to. The
router also makes use of ModelQueryParamParser to
parse out user supplied filters, embeds, and more from query parameters.
import os
import json
from flask import Flask, g, request, Response
from sqlalchemy import create_engine
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import sessionmaker, scoped_session
from drowsy.exc import (
UnprocessableEntityError, BadRequestError, MethodNotAllowedError,
ResourceNotFoundError
)
from drowsy.router import ModelResourceRouter
from .models import *
from .schemas import *
from .resources import *
app = Flask(__name__)
# Set up SQLAlchemy session factory
# You'll want to do this more robustly in a real app.
DB_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"chinook.sqlite")
DB_ENGINE = create_engine("sqlite+pysqlite:///" + DB_PATH)
@app.before_request
def prepare_db_session():
"""Prepare a database session and attach it to Flask.g"""
g.db_session = scoped_session(sessionmaker(bind=DB_ENGINE))
@app.teardown_request
def end_db_session(error):
"""Commit any changes or rollback on failure."""
if hasattr(g, "db_session"):
db_session = g.db_session
try:
if error:
raise error
db_session.commit()
except SQLAlchemyError:
db_session.rollback()
finally:
db_session.remove()
@app.route(
"/api/<path:path>",
methods=["GET", "POST", "PATCH", "PUT", "DELETE", "HEAD", "OPTIONS"])
def api_router(path):
"""Generic API router.
You'll probably want to be more specific with your routing.
We're using the ModelResourceRouter, which automatically routes
based on the class name of each Resource, and handles nested
routing, querying, and updating automatically.
"""
# get your SQLAlchemy db session however you normally would
db_session = g.db_session
# query params are used to parse fields to include, embeds,
# sorts, and filters.
router = ModelResourceRouter(session=db_session)
query_params = request.values.to_dict()
errors = None
message = None
code = None
status = 200
try:
if request.method.lower() == "POST":
status = 201
result = router.dispatcher(
request.method,
path,
query_params=query_params,
data=request.json)
if result is None:
status = 204
else:
result = json.dumps(result)
return Response(
result,
mimetype="application/json",
status=status)
except UnprocessableEntityError as exc:
status = 433
errors = exc.errors
message = exc.message
code = exc.code
except MethodNotAllowedError as exc:
status = 405
message = exc.message
code = exc.code
except BadRequestError as exc:
status = 400
message = exc.message
code = exc.code
except ResourceNotFoundError as exc:
status = 404
message = exc.message
code = exc.code
print(code)
if code is not None or message:
result = {"message": message, "code": code}
if errors:
result["errors"] = errors
return Response(
json.dumps(result),
mimetype="application/json",
status=status)
if __name__ == '__main__':
# Never run with debug in production!
app.run(debug=True)
You can now run this the same way you would any other Flask app and have an incredibly flexible API up and running in front of your database.
Keep in mind that this is a very, very simplistic implementation. Routing is
done here using resource class name, such that the resource used for the path
/albums is determined by transforming albums to upper camel case
(Albums), using the inflection library to remove pluralization
(Album), and adding Resource to the end (AlbumResource). If this
feels too much like magic for your tastes, and I don’t blame you if so, you can
pass the resource you want to use in explicitly to the router’s constructor and
have separate routing definitions for each top level resource. You’re also more
than welcome to handle routing on your own if the included
ModelResourceRouter doesn’t handle your use cases.
A slightly more robust implementation can be seen in the example API project in api.py.
Next Steps¶
Now that you’ve got an actual API up and running, you can head over to the Querying and Mutations sections to get an overview of how to interact with your new API.
You’ll also want to be sure to check out the Permissions Handling section to gain an understanding of how to properly secure a Drowsy based API.