Callbacks#

While accessing and updating your models via the API flask-schema has created is great. Sometimes you need to perform some additional logic before or after a model is returned, created, updated, deleted or if an error is raised.

You might need to log API calls in the database, or send out an email when an error is raised. Possibly you need to perform some additional validation on the data before it is saved to the database.

flask-schema provides a way to do this by defining callbacks on your SQLAlchemy models or Flask configuration.

Callback Lifecycle#

Callbacks can fire at 3 different points in the API request lifecycle.

  1. Setup Callback - This is called before any database operation is executed. This could be used to perform any additional validation or logging before the database operation is executed.

  2. Return Callback - This is called before the completed API response is returned. Here you could intercept the response and perform any additional logic before the response is returned to the client.

  3. Error Callback - This is called when an exception is raised. This could be used to log the error, send an email or perform any additional logic when an exception is raised.

Configuring Callbacks#

flask-schema uses the Flask configuration to define global configuration values and these same configuration values can be applied to specific HTTP method’s or SQLAlchemy models.

Note

For comprehensive details on configuration, and where they can be applied visit our configuration page.

Configuration Keys#

SETUP_CALLBACK

Called before the database operation is executed.

Can be set in the Flask configuration or in SQLAlchemy models.

RETURN_CALLBACK

Called before the completed API response is returned.

Can be set in the Flask configuration or in SQLAlchemy models.

ERROR_CALLBACK

Called when an exception is raised.

Can only be set in the Flask configuration, not in models.

Configuration Placement#

Flask Global Configuration

These configuration values can be set in your Flask configuration and will apply to all endpoints unless a more specific value is set.

Using the structure of API_{configuration_key}

i.e API_SETUP_CALLBACK = my_setup_callback

  • API_SETUP_CALLBACK - callable object to be executed before any database operation is executed.

  • API_RETURN_CALLBACK - callable object to be executed before the completed API response is returned.

  • API_ERROR_CALLBACK - callable object to be executed when an exception is raised.

Flask Method Based Configuration

These methods will override any global configuration values for the specific HTTP method and are set in your Flask configuration.

Using the structure of API_{method}_{configuration_key}

i.e API_GET_SETUP_CALLBACK = my_get_setup_callback

SQLAlchemy Model Configuration

These override any of the previous configuration values, only overridden by method based model configuration. These functions will apply to ANY endpoint for the model.

Using the structure of {configuration_key} in lower case.

Applied to the Meta class of the model.

i.e

class MyModel(db.Model):
    class Meta:
        setup_callback = my_setup_callback

Example Configuration Values:

  • setup_callback - callable object to be executed before database operation is executed on the model.

  • return_callback - callable object to be executed before a completed request for this model is returned by the API.

  • error_callback - callable object to be executed when a API call has an exception raised for this models endpoint.

SQLAlchemy Model Method Based Configuration

These take the highest priority and will override all other configuration values, and are set directly in the models

Using the structure of {method}_{configuration_key} in lower case.

Applied to the Meta class of the model.

i.e

class MyModel(db.Model):
    class Meta:
        get_setup_callback = my_get_setup_callback
        post_error_callback = my_post_error_callback

Example Configuration Values:

  • get_setup_callback - callable object to be executed before any GET database operation is executed on the model.

  • post_return_callback - callable object to be executed before a completed POST request for this model is returned by the API.

  • delete_error_callback - callable object to be executed when a DELETE API call has an exception raised for this model’s endpoint.

Callback Examples#

To demonstrate how to use callbacks, please see the demo folder of our repo or view the demo code here.

Callback Signatures#

It’s probably best for your callback functions to accept **kwargs as the only argument. This will allow you to access any data you need from the request, response or error.

A selection of data is passed to the callback functions (where possible), and this can differ depending on the HTTP method or lifecycle position. It’s also possible the structure of this data could change in later versions.

Setup callback signature#

The setup callback function’s kwargs will accept data that could be needed to process the request.

{'model': "<class 'demo.model_extension.model.models.Author'>", 'id': 1, 'field': None, 'join_model': None, 'many': False, 'url': '/authors', 'name': 'author', 'output_schema': "<class 'abc.AuthorSchema'>", 'session': "<sqlalchemy.orm.scoping.scoped_session object at 0x7fbde078ae10>", 'input_schema': None, 'group_tag': 'People/Companies'}

The setup function should return the kwargs object with any changes made to the data.

def my_setup_callback(**kwargs):
    # Do some logic here
    return kwargs

Return callback signature#

The return callback function’s kwargs will house the data that will be returned to the client. This may be the SQLAlchemy query object or a dictionary of data depending on the query made.

{'model': "<class 'demo.model_extension.model.models.Book'>", 'output': {'query': "<Book 137>"}, 'id': None, 'field': None, 'join_model': None, 'deserialized_data': {'title': 'The Crimson Beacon', 'isbn': '9782227215', 'publication_date': "datetime.date(2024, 4, 19)", 'author_id': 1, 'publisher_id': 12}}

The return function should return the kwargs object with any changes made to the data.

def my_return_callback(**kwargs):
    # Do some logic here
    return kwargs

Post dump callback signature#

The post dump callback function accepts two arguments, the data (the serialized model in dict form) and **kwargs passed to the schemas dump method.

You must return the data after any changes have been made or the api will return None.

def my_dump_function_callback(data, **kwargs):
    if data.get("name") == "John":
        data["name"] = "Johnathon"
    return data
def my_dump_function_callback(data, **kwargs):
    if not validate.email(data.get("email")):
        raise CustomHTTPException(400, "Invalid email")
    return data

Error callback signature#

The error callback function accepts the exception and traceback as arguments. There is no need to return anything.

def error_callback(e, traceback):
    # Do some logic here

Custom Exceptions#

Raising a custom exception in your callback will cause the API to return a custom error response. This can be useful for following the same error response structure as the rest of your API.

This is simple and can be achieved with the custom exception class provided by flask-schema.

from flask_schema import CustomHTTPException

def my_error_callback(**kwargs):
    raise CustomHTTPException(400, "My custom error message")

class MyModel(db.Model):
    class Meta:
        error_callback = my_error_callback

Extending Query Params#

If you are hoping to extend a endpoints by adding additional query params to your endpoints defining the function is beyond the scope of flask-scheema.

Note

If you are looking to add aditional filters…

The return callback is the best place to handle this, as it will have access to the SQLAlchemy Query object when in the kwargs passed to the function.

From here you can quite easily add additional filters.

def my_return_callback(**kwargs):
    query = kwargs.get("output")
    query = query.filter_by(my_field=kwargs.get("my_query_param"))
    kwargs["output"] = query
    return kwargs

However, you’ll like want to document any changes to the available query params in Redoc. This can be achieved with the ADDITIONAL_QUERY_PARAMS configuration key.

This key can be set in the Flask configuration or in SQLAlchemy models (globally or by Http method). This means you can apply new query params to specific models, or across the API as a whole.

The expected value is a list[dict] of the query params you want to add to the endpoint. Please use the below code examples as a guide for the expected structure.

Consider the below example where we add a new query param to the Flask configuration (which is applied globally) to every model and endpoint in the documentation.

class Config:

    API_ADDITIONAL_QUERY_PARAMS = [{
        "name": "log",
        "in": "query",
        "description": "Log call into the database", # optional
        "required": False, # optional
        "deprecated": False, # optional
        "schema": {
            "type": "string", # see below for options available
            "format": "password", # see below for options available ... optional
            "example": 1  # optional
        }
    }]

Or set to a specific HTTP method - GET on the model level.

class Author(db.Model):
    class Meta:
        get_additional_query_params = [{
                "name": "log",
                "in": "query",
                "description": "Log call into the database", # optional
                "required": False, # optional
                "deprecated": False, # optional
                "schema": {
                    "type": "string", # see below for options available
                    "format": "password", # see below for options available ... optional
                    "example": 1  # optional
                }
            }]

Acceptable Types#

Below is a list of acceptable types for the schema key in the ADDITIONAL_QUERY_PARAMS configuration key.

string: For string values.

number: For floating-point numbers.

integer: For whole numbers.

boolean: For true or false values.

array: For arrays or lists of values.

object: For JSON objects.

Acceptable Formats#

Below is a list of acceptable formats for the schema key in the ADDITIONAL_QUERY_PARAMS configuration key.

string formats

date: Full-date according to RFC3339 (e.g., 2020-01-01).

date-time: The date-time notation as defined by RFC 3339, section 5.6 (e.g., 2020-01-01T12:00:00Z).

password: A hint to UIs to mask the input.

byte: Base64-encoded characters, for binary data carried in JSON strings.

binary: Binary data not encoded in a string, used for file uploads.

email: String must be in email format.

uuid: String must be a UUID.

uri: String must be a URI.

hostname: String must be a hostname.

ipv4: String must be an IPv4.

ipv6: String must be an IPv6.

integer formats

int32: Signed 32-bit integers.

int64: Signed 64-bit integers (long).

number formats

float: Floating-point numbers.

double: Double-precision floating-point numbers.