Usage

Library for implementing configurable REST APIs.

A resource is a factory for creating Flask Blueprint that’s parameterized via a config. The main difference from a regular blueprint is:

  • Syntactical overlay - it creates a slightly different way of writing views and wiring them up with the Flask routing system. Flask-Resources is meant for REST APIs and thus puts emphasis on the HTTP method, and as apposed to a Flask MethodView, it allows keeping all view methods together for all endpoints.

  • Dependency injection - a resource enables easy dependency injection via a configuration object. The idea behind this is for instance you write a reusable application that you want to allow developers to customize. For instance you could allow a developer to accept and deserialize their custom XML instead of only JSON at a given endpoint while keeping the application view the same, or allow them to customize the URL routes via the Flask application config.

In addition, Flask-Resources provides basic utilities for developing REST APIs such as:

  • Content negotiation to support multiple response serializations (e.g. serving JSON, JSON-LD, XML from the same endpoint).

  • Request parsing (query string, headers, body) using Marshmallow and data deserialization.

  • Resource request context to enforce paradigm of passing only validated request data to the view function.

If you don’t need any of the above, you can simply use just a normal Flask Blueprint instead.

Below is small minimal example:

from flask import Flask
from flask_resources import Resource, ResourceConfig, route

class Config(ResourceConfig):
    blueprint_name = "hello"

class HelloWorldResource(Resource):
    def hello_world(self):
        return "Hello, World!"

    def create_url_rules(self):
        return [
            route("GET", "/", self.hello_world),
        ]

app = Flask('test')
app.config.update({
    "RESOURCE_CONFIG": Config()
})
resource = Resource(app.config["RESOURCE_CONFIG"])
app.register_blueprint(resource.as_blueprint())

Larger example

Below is a large example that demonstrates:

  • Response handling via content negotiation.

  • Error handling and mapping of business-level exceptions to JSON errors.

  • Request parsing from the body content, URL query string, headers and view args.

  • Accessing the resource request context

class Config(ResourceConfig):
    # Response handlers defines possible mimetypes for content
    # negotiation
    response_handlers = {
        "application/json: ResponseHandler(JSONSerializer()),
        # ...
    }

class MyResource(Resource):

    # Error handlers maps exceptions to JSON errors.
    error_handlers = {
        ma.ValidationError: create_error_handler(
            HTTPJSONException(code=400),
        )
    }

    decorators = [
        # You can apply decorators to all views
        login_required,
    ]

    @request_parser(
        {'q': ma.fields.String(required=True)},
        # Other locations include args, view_args, headers.
        location='args',
    )
    @response_handler(many=True)
    def search(self):
        # The validated data is available in the resource request context.
        if resource_requestctx.args['q']:
            # ...
        # From the view you can return an object which the response handler
        # will serialize.
        return [], 200

    # You can parse request body depending on the Content-Type header.
    @request_body(
        parsers={
            "application/json": RequestBodyParser(JSONDeserializer())
        }
    )
    @response_handler()
    def create(self):
        return {}, 201

    # All decorators all values to come from the conf.
    @request_parser(from_conf('update_args), location='args')
    def update(self):
        return {}, 201

    def create_url_rules(self):
        return [
            route('GET', "/", self.search),
            route('POST', "/", self.create),
            route('PUT', "/<pid_value>", self.update),
            # You can selectively disable global decorators.
            route('DELETE', "/<pid_value>", self.delete, apply_decorators=False),
        ]