Recipes

Class Based Views

Some people prefer basing Flask view functions on classes rather than functions, and other REST frameworks for Flask base themselves on classes.

First, an opinion: people often prefer classes simply because they are used to them. If you’re looking for classes because functions make you uncomfortable, I encourage you to take a moment to reconsider your feelings. Embracing functions, thread locals, and all of Flask’s little quirks can feel oh so good.

With that, there are perfectly valid use cases for class based views, like creating abstract views that can be inherited and customized. This is the main intent of Flask’s built-in pluggable views.

Here is a simple recipe for using Flask-Rebar with these pluggable views:

from flask import Flask
from flask import request
from flask.views import MethodView
from flask_rebar import Rebar


rebar = Rebar()
registry = rebar.create_handler_registry()


class AbstractResource(MethodView):
    def __init__(self, database):
        self.database = database

    def get_resource(self, id):
        raise NotImplemented

    def get(self, id):
        return self.get_resource(id)

    def put(self, id):
        resource = self.get_resource(id)
        resource.update(rebar.validated_body)
        return resource


class Todo(AbstractResource):
    def get_resource(self, id):
        return get_todo(database, id)


for method, request_body_schema in [
    ("get", None),
    ("put", UpdateTodoSchema()),
]:
    registry.add_handler(
        func=Todo.as_view(method + "_todo", database=database),
        rule="/todos/<id>",
        response_body_schema=TodoSchema(),  # for versions <= 1.7.0, use marshal_schema
        method=method,
        request_body_schema=request_body_schema,
    )

This isn’t a super slick, classed based interface for Flask-Rebar, but it is a way to use unadulterated Flask views to their full intent with minimal DRY violations.

Combining Security/Authentication

Authentication is hard, and complicated. Flask-Rebar supports custom Authenticator classes so that you can make your authentication as complicated as your heart desires.

Sometime though you want to combine security requirements. Maybe an endpoint should allow either an admin user or a user with an “edit” permission, maybe you want to allow requests to use Auth0 or an Api Key, maybe you want to only authenticate if it’s Sunday and Jupiter is in retrograde?

Here are some simple recipes for what Flask-Rebar currently supports:

Allow a user with either scope “A” OR scope “B”

from flask import g
from my_app import authenticator, registry
from my_app.scheme import EditStuffSchema, StuffSchema


# Allow a user with the "admin" scope OR the "edit:stuff" scope
@registry.handles(
        rule="/stuff/<uid:thing>/",
        method="POST",e
        request_body_schema=EditStuffSchema(),
        response_body_schema=StuffSchema(),
        authenticators=[authenticator.with_scope("admin"), authenticator.with_scope("edit:stuff")]
)
def edit_stuff(thing):
        update_stuff(thing, g.validated_body)
        return thing

Allow a request with either valid Auth0 OR an API-Key

from flask import g
from flask_rebar.authenticators import HeaderApiKeyAuthenticator
from flask_rebar_auth0 import get_authenticated_user
from my_app import authenticator, registry


# Allow Auth0 or API Key
@registry.handles(
    rule="/rate_limit/",
    method="GET",
    response_body_schema=RateLimitSchema(),
    authenticators=[authenticator, HeaderApiKeyAuthenticator("X-API-KEY")]
)
def get_limits():
        requester = g.authenticated_app_name or get_authenticated_user()
        rate_limit = get_limits_for_app_or_user(requester)
        return rate_limit

Allow a request with Auth0 AND an API-Key

Note

This currently requires some workarounds. Better support is planned.

from flask_rebar.authenticators import HeaderApiKeyAuthenticator
from flask_rebar_auth0 import Auth0Authenticator
from flask_rebar.swagger_generation.authenticator_to_swagger import (
        AuthenticatorConverter, authenticator_converter_registry
)
from my_app import app


class CombinedAuthenticator(Auth0Authenticator, HeaderApiKeyAuthenticator):

        def __init__(self, app, header):
                Auth0Authenticator.__init__(self, app)
                HeaderApiKeyAuthenticator.__init__(self, header)

        def authenticate(self):
                Auth0Authenticator.authenticate(self)
                HeaderApiKeyAuthenticator.authenticate(self)


# You need to make sure that the converters already exist before trying to access them
# Create mock/stub authenticators that are used only for lookup
auth0_converter = authenticator_converter_registry._get_converter_for_type(Auth0Authenticator(app))
header_api_converter = authenticator_converter_registry._get_converter_for_type(HeaderApiKeyAuthenticator("header"))

class CombinedAuthenticatorConverter(AuthenticatorConverter):

        AUTHENTICATOR_TYPE = CombinedAuthenticator

        def get_security_schemes(self, obj, context):
                definition = dict()
                definition.update(auth0_converter.get_security_schemes(obj, context))
                definition.update(header_api_converter.get_security_schemes(obj, context))
                return definition

        def get_security_requirements(self, obj, context):
                auth_requirement = auth0_converter.get_security_requirements(obj, context)[0]
                header_requirement = header_api_converter.get_security_requirements(obj, context)[0]
                combined_requirement = dict()
                combined_requirement.update(auth_requirement)
                combined_requirement.update(header_requirement)

                return [
                        combined_requirement
                ]


authenticator_converter_registry.register_type(CombinedAuthenticatorConverter())


@registry.handles(
    rule="/user/me/api_token",
    method="GET",
    authenticators=CombinedAuthenticator(app, "X-API-Key")
)
def check_token():
        return 200

Marshmallow Partial Schemas

Beginning with version 1.12, Flask-Rebar includes support for Marshmallow “partial” loading of schemas. This is particularly useful if you have a complicated schema with lots of required fields for creating an item (e.g., via a POST endpoint) and want to reuse the schema with some or all fields as optional for an update operation (e.g., via PATCH).

While you can accomplish this by simply adding a partial keyword argument when instantiating an existing schema, to avoid confusion in the generated OpenAPI model, we strongly recommend creating a derived schema class as illustrated in the following example:

class CreateTodoSchema(RequestSchema):
        complete = fields.Boolean(required=True)
        description = fields.String(required=True)
        created_by = fields.String(required=True)


class UpdateTodoSchema(CreateTodoSchema):
        def __init__(self, **kwargs):
                super_kwargs = dict(kwargs)
                partial_arg = super_kwargs.pop('partial', True)
                super(UpdateTodoSchema, self).__init__(partial=partial_arg, **super_kwargs)

The preceeding example makes all fields from CreateTodoSchema optional in the derived UpdateTodoSchema class by injecting partial=True as a keyword argument. Marshmallow also supports specifying only some fields as “partial” so if, for example, you wanted to use this approach but make only the description and created_by fields optional, you could use something like:

class UpdateTodoSchema(CreateTodoSchema):
        def __init__(self, **kwargs):
                super_kwargs = dict(kwargs)
                partial_arg = super_kwargs.pop('partial', ['description', 'created_by'])
                super(UpdateTodoSchema, self).__init__(partial=partial_arg, **super_kwargs)