flask-smorest Documentation

repository·dev·Indexed 20 days ago

https://github.com/marshmallow-code/flask-smorest

A REST API framework built upon Flask and marshmallow (version 0.47.0). It simplifies API development by providing automatic OpenAPI specification generation, schema-based validation, and pagination. The framework integrates webargs for argument extraction and apispec for documentation, offering specialized tools like the flask_smorest.Blueprint class for routing, ETag support for cache validation, and support for multiple independent APIs within a single application.

Tokens
18K
Snippets
56
Records
76
Agent score
69%

What's inside flask-smorest

  1. Overview of flask-smorest

    dev

    flask-smorest is a database-agnostic framework for creating REST APIs. It integrates several key libraries to handle the API lifecycle:

    • Flask: Serves as the underlying webserver.
    • Marshmallow: Handles data serialization and deserialization.
    • webargs: Used to extract and validate arguments from incoming requests.
    • apispec: Automatically generates OpenAPI specification files.
  2. Overview of flask-smorest features

    dev

    flask-smorest is a REST API framework built on top of Flask and marshmallow. It provides the following core capabilities:

    • Serialization, deserialization, and validation: Uses marshmallow.Schema to handle data transformation and validation.
    • Validation Error Handling: Returns explicit validation error messages in API responses.
    • OpenAPI (Swagger) Integration: Automatically generates OpenAPI specifications. These can be exposed using tools like ReDoc, Swagger UI, or RapiDoc.
    • Pagination: Built-in support for paginating API results.
    • ETag Support: Support for ETag headers for efficient caching and concurrency control.
    • Database Agnostic: Works with any database backend.
  3. Document Operations, Parameters, and Responses

    dev

    Documentation for schemas and parameters is largely automated:

    • Arguments: Schemas passed to Blueprint.arguments are automatically parsed for documentation. You can provide additional example or examples (OpenAPI v3 only) to enrich this.
    • Responses: Schemas passed to Blueprint.response are automatically parsed. You can provide example, examples (OpenAPI v3 only), and headers to document response metadata.
    • Default Errors: A default error response named "DEFAULT_ERROR" is automatically added to all resources. You can customize this name via Api.DEFAULT_ERROR_RESPONSE_NAME or disable it by setting it to None.
    • Path Parameters: These are automatically documented based on the Flask path converter. For shared path parameters, use the parameters argument in Blueprint.route or Api.register_blueprint (for url_prefix parameters).
  4. Handle unknown arguments in schemas

    dev

    When input data contains fields not defined in the marshmallow Schema, you can control the behavior using the unknown attribute.

    1. Via Schema Meta: Set unknown to RAISE, EXCLUDE, or INCLUDE in the Schema.Meta class.
    2. Via FlaskParser customization: For locations like query, headers, cookies, and files, flask-smorest defaults to unknown=EXCLUDE. To change this globally for specific locations, subclass webargs.flaskparser.FlaskParser and assign it to your Blueprint.ARGUMENTS_PARSER.

    Note: For locations that support nested schemas (json, form, json_or_form), it is recommended to set the unknown behavior in the Schema.Meta class, as the FlaskParser override only applies to the top-level schema and does not propagate to nested ones.

    import marshmallow as ma
    from webargs.flaskparser import FlaskParser
    from flask_smorest import Blueprint
    
    class MyFlaskParser(FlaskParser):
        DEFAULT_UNKNOWN_BY_LOCATION = {
            "query": ma.RAISE,
        }
    
    class MyBlueprint(Blueprint):
        ARGUMENTS_PARSER = MyFlaskParser()
  5. Core architectural assumptions of flask-smorest

    dev

    To use flask-smorest effectively, your application should follow these structural patterns:

    • Blueprints: Split your application into Blueprint objects.
    • MethodViews: While basic Flask view functions work, it is recommended to use Flask MethodView classes to organize resources.
    • Marshmallow Schemas: Use marshmallow.Schema to handle both parameter serialization (input) and response serialization (output).
    • JSON: Request and response bodies are serialized as JSON.
    • Single Success Response: A view function should define exactly one successful response type and status code. All other outcomes are treated as errors.
  6. Expose multiple APIs in a single application

    dev

    To host multiple independent APIs within a single Flask application, you must instantiate multiple Api objects, each with a unique config_prefix.

    When a config_prefix is provided, all configuration keys for that specific API instance must be prefixed with that string in your application configuration. This allows you to isolate settings like OPENAPI_VERSION and OPENAPI_URL_PREFIX for different API versions or namespaces.

    If you only have one API, you can omit the config_prefix (it defaults to an empty string).

    api_1 = Api(config_prefix="V1_")
    
    class Config:
        V1_OPENAPI_VERSION = "3.0.2"
        V1_OPENAPI_URL_PREFIX = "/v1/"
  7. Use Pagination in View Functions

    dev

    When you want the view function to be responsible for selecting specific elements (e.g., using LIMIT and OFFSET in a database query), use the 'Pagination in View Function' mode.

    To implement this:

    1. Decorate the view method with @blp.paginate().
    2. Accept a pagination_parameters argument in your view method (of type PaginationParameters).
    3. Set the item_count attribute on the pagination_parameters object to the total number of available items.
    4. Use the first_item and last_item attributes from pagination_parameters to slice your data source.
    @blp.route("/")
    class Pets(MethodView):
        @blp.response(200, PetSchema(many=True))
        @blp.paginate()
        def get(self, pagination_parameters):
            # Set the total count of items available
            pagination_parameters.item_count = Pet.size
            # Return only the slice requested by the client
            return Pet.get_elements(
                first_item=pagination_parameters.first_item,
                last_item=pagination_parameters.last_item,
            )
  8. Stack multiple argument schemas

    dev

    You can call the @blp.arguments decorator multiple times on a single resource function to accept parameters from different locations (e.g., both the request body and the query string). The order of the decorators determines the order in which arguments are passed to the view function.

    @blp.route("/")
    class Pets(MethodView):
        # pet_data comes from the first decorator (body)
        # query_args comes from the second decorator (query string)
        @blp.arguments(PetSchema)
        @blp.arguments(QueryArgsSchema, location="query")
        def post(self, pet_data, query_args):
            return Pet.create(pet_data, **query_args)
  9. Add Summary and Description to View Functions

    dev

    By default, flask-smorest uses the docstrings of your view functions to populate the summary and description fields in the OpenAPI documentation.

    • Summary: The first line(s) of the docstring.
    • Description: All lines following the first empty line.
    • Delimiter: You can use a delimiter line (defaulting to ---) to separate documentation from internal comments. Everything after the delimiter is ignored.

    You can customize the delimiter by subclassing Blueprint and overriding DOCSTRING_INFO_DELIMITER. Setting it to None includes the entire docstring in the documentation.

    def get(pet_id):
        """Find pets by ID
    
        Return pets based on ID.
        ---
        Internal comment not meant to be exposed.
        """
  10. Create a basic API with flask-smorest

    dev

    To set up a flask-smorest API, follow these steps:

    1. Initialize the API: Instantiate the Api class with your Flask app and configure API metadata using app.config.
    2. Define Schemas: Create Marshmallow schemas for your data models and for validating query arguments.
    3. Create a Blueprint: Use Blueprint to define a logical grouping of routes, including a url_prefix and description.
    4. Implement MethodViews: Use @blp.arguments to handle request deserialization and @blp.response to handle response serialization.
    5. Handle Errors: Use the abort function to return errors, passing a status code and a message (which is passed to the error handler).
    6. Register the Blueprint: Register your blueprint with the Api instance using api.register_blueprint(blp).
    from flask import Flask
    from flask.views import MethodView
    import marshmallow as ma
    from flask_smorest import Api, Blueprint, abort
    
    # 1. Initialize API
    app = Flask(__name__)
    app.config["API_TITLE"] = "My API"
    app.config["API_VERSION"] = "v1"
    app.config["OPENAPI_VERSION"] = "3.0.2"
    api = Api(app)
    
    # 2. Define Schemas
    class PetSchema(ma.Schema):
        id = ma.fields.Int(dump_only=True)
        name = ma.fields.String()
    
    class PetQueryArgsSchema(ma.Schema):
        name = ma.fields.String()
    
    # 3. Create Blueprint
    blp = Blueprint("pets", "pets", url_prefix="/pets", description="Operations on pets")
    
    # 4. Implement MethodViews
    @blp.route("/")
    class Pets(MethodView):
        @blp.arguments(PetQueryArgsSchema, location="query")
        @blp.response(200, PetSchema(many=True))
        def get(self, args):
            """List pets"""
            return Pet.get(filters=args)
    
        @blp.arguments(PetSchema)
        @blp.response(201, PetSchema)
        def post(self, new_data):
            """Add a new pet"""
            item = Pet.create(**new_data)
            return item
    
    @blp.route("/<pet_id>")
    class PetsById(MethodView):
        @blp.response(200, PetSchema)
        def get(self, pet_id):
            """Get pet by ID"""
            try:
                item = Pet.get_by_id(pet_id)
            except ItemNotFoundError:
                # 5. Handle Errors
                abort(404, message="Item not found.")
            return item
    
        @blp.arguments(PetSchema)
        @blp.response(200, PetSchema)
        def put(self, update_data, pet_id):
            """Update existing pet"""
            try:
                item = Pet.get_by_id(pet_id)
            except ItemNotFoundError:
                abort(404, message="Item not found.")
            item.update(update_data)
            item.commit()
            return item
    
        @blp.response(204)
        def delete(self, pet_id):
            """Delete pet"""
            try:
                Pet.delete(pet_id)
            except ItemNotFoundError:
                abort(404, message="Item not found.")
    
    # 6. Register Blueprint
    api.register_blueprint(blp)