The extension is instantiated in backend/extensions/api.py
and should
be used in views via from backend.extensions.api import api.
backend.api.
Api
(name, app=None, prefix='', default_mediatype='application/json', decorators=None, catch_all_404s=False, serve_challenge_on_401=False, url_part_order='bae', errors=None)[source]¶Extends flask_restful.Api
to support integration with
Flask-Marshmallow serializers, along with a few other minor enhancements:
model_resource
(*args, **kwargs)[source]¶Decorator to wrap a backend.api.ModelResource
class, adding
it to the api. There are two supported method signatures:
Api.model_resource(model, *urls, **kwargs)
and
Api.model_resource(blueprint, model, *urls, *kwargs)
Example without blueprint:
from backend.extensions.api import api
from models import User
@api.model_resource(User, '/users', '/users/<int:id>')
class UserResource(Resource):
def get(self, user):
return user
def list(self, users):
return users
Example with blueprint:
from backend.extensions.api import api
from models import User
from views import bp
@api.model_resource(bp, User, '/users', '/users/<int:id>')
class UserResource(Resource):
def get(self, user):
return user
def list(self, users):
return users
serializer
(*args, many=False)[source]¶Decorator to wrap a ModelSerializer
class,
registering the wrapped serializer as the specific one to use for the
serializer’s model.
For example:
from backend.extensions.api import api from backend.api import ModelSerializer from models import Foo @api.serializer # @api.serializer() works too class FooSerializer(ModelSerializer): class Meta: model = Foo @api.serializer(many=True) class FooListSerializer(ModelSerializer): class Meta: model = Foo
route
(*args, **kwargs)[source]¶Decorator for registering individual view functions.
Usage without blueprint:
api = Api('api', prefix='/api/v1')
@api.route('/foo') # resulting url: /api/v1/foo
def get_foo():
# do stuff
Usage with blueprint:
api = Api('api', prefix='/api/v1')
team = Blueprint('team', url_prefix='/team')
@api.route(team, '/users') # resulting url: /api/v1/team/users
def users():
# do stuff
backend.api.
ModelResource
[source]¶Base class for database model resource views. Includes a bit of configurable magic to automatically support the basic CRUD endpoints.
For example:
from backend.api import ModelResource
from backend.extensions.api import api # url_prefix='/api/v1'
from backend.security.models import User
from backend.security.views import security # url_prefix='/auth'
@api.model_resource(security, User, '/users', '/users/<int:id>')
class UserResource(ModelResource):
pass
This results in the following URL endpoints:
GET /api/v1/auth/users # list all Users
POST /api/v1/auth/users # create new User
GET /api/v1/auth/users/<id> # get User.id == <id>
PUT /api/v1/auth/users/<id> # update User.id == <id>
PATCH /api/v1/auth/users/<id> # partial update User.id == <id>
DELETE /api/v1/auth/users/<id> # delete User.id == <id>
To limit which endpoints are included, override the include_methods or exclude_methods class attributes:
from backend.api import CREATE, DELETE, GET, LIST, PATCH, PUT
# only support list & create
@api.model_resource(security, User, '/users')
class UserResource(ModelResource):
include_methods = (CREATE, LIST)
# only support get & update
@api.model_resource(security, User, '/users/<int:id>')
class UserResource(ModelResource):
include_methods = (GET, PATCH, PUT)
# all except delete
@api.model_resource(security, User, '/users', '/users/<int:id>')
class UserResource(ModelResource):
exclude_methods = (DELETE,)
To customize the implementation of any of the methods, implement the lower-cased method name (shown below are the default implementations):
@api.model_resource(security, User, '/users', '/users/<int:id>')
class UserResource(ModelResource):
def list(self, users):
return users
def create(self, user, errors):
if errors:
return self.errors(errors)
return self.created(user)
def get(self, user):
return user
def put(self, user, errors):
if errors:
return self.errors(errors)
return self.updated(user)
def patch(self, user, errors):
if errors:
return self.errors(errors)
return self.updated(user)
def delete(self, user):
return self.deleted(user)
As you can see from above, there’s still a bit more magic happening behind the scenes to convert the request parameters/data into models. This happens via method decorators (and if applicable, the respective serializer for the model assigned to this resource). The default decorators effectively look like this:
from backend.api import (
ALL_METHODS,
param_converter,
list_loader,
patch_loader,
post_loader,
put_loader,
)
@api.model_resource(security, User, '/users', '/users/<int:id>')
class UserResource(ModelResource):
exclude_decorators = ALL_METHODS # disable the default decorators
include_decorators = () # alternative way to disable them
@list_loader(User)
def list(self, users):
return users
@post_loader(UserResource.serializer_create) # <- invalid syntax
def create(self, user, errors):
if errors:
return self.errors(errors)
return self.created(user)
@param_converter(id=User)
def get(self, user):
return user
@put_loader(UserResource.serializer) # <- invalid syntax
def put(self, user, errors):
if errors:
return self.errors(errors)
return self.updated(user)
@patch_loader(UserResource.serializer) # <- invalid syntax
def patch(self, user, errors):
if errors:
return self.errors(errors)
return self.updated(user)
@param_converter(id=User)
def delete(self, user):
return self.deleted(user)
Furthermore, you can add extra decorators to methods using method_decorators:
from backend.security import auth_required_same_user
@api.model_resource(security, User, '/users/<int:id>')
class UserResource(ModelResource):
include_methods = (PATCH, PUT)
method_decorators = (auth_required_same_user,)
Or on a per-method basis:
from backend.security import (
anonymous_user_required,
auth_required_same_user,
)
@api.model_resource(security, User, '/users/<int:id>')
class UserResource(ModelResource):
include_methods = (CREATE, PATCH, PUT)
method_decorators = {CREATE: [anonymous_user_required],
PATCH: [auth_required_same_user],
PUT: [auth_required_same_user]}
model
= None¶The database model class for a ModelResource
(automatically set by the backend.api.Api
extension instance)
serializer
= None¶The serializer to be used for serializing single instances of self.model
(automatically set by the backend.api.Api
extension instance)
serializer_create
= None¶The serializer to be used when creating an instance of self.model
(automatically set by the backend.api.Api
extension instance)
include_methods
= ('create', 'delete', 'get', 'list', 'patch', 'put')¶Override to limit methods supported by ModelResource
exclude_methods
= ()¶Override to exclude methods supported by ModelResource
include_decorators
= ('create', 'delete', 'get', 'list', 'patch', 'put')¶Override to limit automatic decorators applied by ModelResource
exclude_decorators
= ()¶Override to exclude automatic decorators applied by ModelResource
created
(obj, save=True)[source]¶Convenience method for saving a model (automatically commits it to the database and returns the object with an HTTP 201 status code)
deleted
(obj)[source]¶Convenience method for deleting a model (automatically commits the delete to the database and returns with an HTTP 204 status code)
backend.api.
ModelSerializer
(*args, **kwargs)[source]¶Base class for database model serializers. This is pretty much a stock
flask_marshmallow.sqla.ModelSchema
: it will automatically create
fields from the attached database Model, the only difference being that it
will automatically dump to (and load from) the camel-cased variants of the
field names.
For example:
from backend.api import ModelSerializer
from backend.security.models import Role
class RoleSerializer(ModelSerializer):
class Meta:
model = Role
Is roughly equivalent to:
from marshmallow import Schema, fields
class RoleSerializer(Schema):
id = fields.Integer()
name = fields.String()
description = fields.String()
created_at = fields.DateTime(dump_to='createdAt',
load_from='createdAt')
updated_at = fields.DateTime(dump_to='updatedAt',
load_from='updatedAt')
Obviously you probably shouldn’t be loading created_at or updated_at from JSON; it’s just an example to show the automatic snake-to-camelcase field naming conversion.
backend.api.
WrappedSerializer
(*args, **kwargs)[source]¶Extends backend.api.ModelSchema
to automatically wrap serialized
results with the model name, and automatically unwrap it when loading.
NOTE: this might not behave as you’d expect if your serializer uses nested fields (if a nested object’s serializer is also a WrappedSerializer, then the nested objects will also end up wrapped, which probably isn’t what you want…)
Example usage:
class Foo(PrimaryKeyMixin, BaseModel):
name = Column(String)
class FooSerializer(WrappedSerializer):
class Meta:
model = Foo
foo_serializer = FooSerializer()
foo = Foo(id=1, name='FooBar')
foo_json = foo_serializer.dump(foo).data
# results in:
foo_json == {
"foo": { # <- added by self.wrap_with_envelope on @post_dump
"id": 1,
"name": "FooBar"
}
}
# and on deserialization, self.unwrap_envelope loads it correctly:
foo = foo_serializer.load(foo_json).data
isinstance(foo, Foo) == True