API

Default Settings

Define deault settings for all extensions.

Also define a function to load all settings as usual.

flask_tern.settings.SECRET_KEY = b'i\xfb\xea\xb1\x9be\xd0\xe1\xb3W&\xe7:4m\xe6'

value to use to sign / encrypt session cookie. If unset, the app will auto generate a value. This value needs to be configured if load balancing across multiple processes is used.

flask_tern.settings.SESSION_TYPE = None

Session backend type. The default is to use cookies as session storage.

flask_tern.settings.SESSION_FILE_DIR = '/Users/uqgweis/Documents/Projects/workspaces/bioimages/flask_tern/docs/flask_session'

If SESSION_TYPE = “filesystem”, this option specifies the storage location for session data. as default it uses the flask_session folder within current working directory.

flask_tern.settings.SESSION_FILE_THRESHOLD = 500

If SESSION_TYPE = “filesystem”, this option sets the maximum number of items the session stores before it starts deleting some. default is 500.

flask_tern.settings.SESSION_FILE_MODE = 384

If ‘SESSION_TYPE = “filesystem”` this option sets the file mode for the session files. as default it uses 0o600. If given as string it will be parsed as integer or if prefixed with 0o as octal number.

flask_tern.settings.SESSION_REDIS = None

If SESSION_TYPE = “redis”, this option should be a configured redis.Redis instance. If loaded from environment variables, it can be specified as connection url as described in redis:redis.from_url()

flask_tern.settings.SQLALCHEMY_DATABASE_URI = None

It is required to configure this setting if SQLAlchemy is being used.

flask_tern.settings.SQLALCHEMY_TRACK_MODIFICATIONS = False

avoid SQLAlchemy warning

flask_tern.settings.SQLALCHEMY_ENGINE_OPTIONS = {'pool_pre_ping': True, 'pool_recycle': 300, 'pool_size': 5}

default SQLAlchemy connection pool configuration (can only be overriden with a settings file)

flask_tern.settings.DOI_URL = None

deault DOI URL

flask_tern.settings.CACHE_TYPE = 'flask_caching.backends.nullcache.NullCache'

default CACHE_TYPE used for Flask-Caching extension.

Configuration

flask_tern.utils.config.load_settings(app: Flask, env_prefix: str = 'FLASK_TERN_', defaults: dict = None, config: dict | ModuleType | object = None)

Function intended to be used to configure a Flask app instance.

Parameters:
  • app (Flask) – The flask app to configure.

  • env_prefix (str) – Only env var prefixed with this are considered as configuration.

  • defaults (dict) – Only env var prefixed with this are considered as configuration.

  • config (dict | ModuleType | object) – object with highest proiority config values.

Options are applied in the following order to the app instance. Later steps override values form previous steps. This method loads settings via flask.Config.from_object().

  1. apply defaults from bundled etensions

  2. apply defaults in flask_tern.settings

  3. passed in defaults

  4. call flask.Config.from_prefixed_env()

  5. call convert_settngs()

  6. apply passed in config

Usage

app = Flask(__name__)
load_settings(app, "MY_APP_", {}, {})
flask_tern.utils.config.as_bool(value: str | bool) bool

Convert value to bool.

Accepts True, 1 and on as True and everything else as false. It uses case insensitive comparison.

Parameters:

value (str | bool) – string interpret

Returns:

Whether string is to be interpretede as True or False

Return type:

bool

flask_tern.utils.config.add_setting_parser(name: str, parser: Callable[[str], any])

Adds a parser from environmante variable to settings data type.

Parameters:
  • name (str) – setting name

  • parser (Callable[[str], any]) – function to convert string from environmet to required data type

This allows applications to easily add custom parsers for extra options. The provided parser function could do anything. E.g. parse the value as json, interpret it as filename, etc…

The parser method will be caled with the value of the environment variable (may be empty, but should never be None). The return value can be anything which will eventually be stored in Flask application configuration (Configuration Handling) under setting name.

Usage

# my_app.settings.py
from flask_tern.utils.config import add_setting_parser

# set default for MAX_COUNT
MAX_COUNT = 2
# add parser for this which converts ``str`` to ``int``
add_setting_parser("MAX_COUNT", int)
flask_tern.utils.config.convert_settings(app: ~flask.app.Flask, type_map: dict = {'ELASTICSEARCH_SSL_SHOW_WARN': <function as_bool>, 'ELASTICSEARCH_VERIFY_CERTS': <function as_bool>, 'OIDC_USE_REFRESH_TOKEN': <function as_bool>, 'SESSION_COOKIE_HTTPONLY': <function as_bool>, 'SESSION_COOKIE_SECURE': <function as_bool>, 'SESSION_FILE_MODE': <function <lambda>>, 'SESSION_FILE_THRESHOLD': <class 'int'>, 'SESSION_PERMANENT': <function as_bool>, 'SESSION_REDIS': <function from_url>, 'SQLALCHEMY_TRACK_MODIFICATIONS': <function as_bool>})

Goes through registered value converters and tries to parse into python type.

Parameters:
  • app (Flask) – The flask app to configure.

  • type_map (dict) – a mapping from option name to some method used to parse value, and convert to a python type.

Extensions:

flask_tern.init_app(app: Flask)

A helper method that initalises all included extensions based on config options.

Parameters:

app (Flask) – The Flask app to configure

  • Logging: always enabled

  • SQLAlchemy: enabled if SQLALCHEMY_DATABASE_URI is set

  • Elasticsearch: enabled if ELASTICSEARCH_URL is set

  • Session: enabled if SESSION_TYPE is set

  • Cache: always enabled

  • CORS: always enabled

  • ProxyFix: always enabled

  • HealthCheck: adds builtin check methods if the respective configuration is set

    • SQLALCHEMY_DATABASE_URI: add healthcheck.check.check_db()

    • ELASTICSEARCH_URL: add healthcheck.checks.check_es()

    • APIKEY_SERVICE: add healtcheck.checks.check_apikey()

    • OIDC_DISCOVERY_URL: add healthcheck.checks.check_keycloak`()

    • DOI_URL: add healthcheck.checks.check_doi`()

  • OIDC Auth: enabled if ODIC_CLIENT_ID is set.

SQLAlchemy

flask_tern.db.DeclarativeBase

We define our own declarative base class. Using this base class makes it possible to re-use db models outside of flask context

flask_tern.db.db = <SQLAlchemy>

global flask_sqlalchemy.SQLAlchemy object.

flask_tern.db.init_app(app)

Initialise Flask-SQLAlchemy extension.

Elasticsearch

flask_tern.elasticsearch.init_app(app: Flask)

Configure elasticsearch_dsl connections pool along a Flask application.

Main purpose is to have conistent place on how to set up extension and third party libraries along a Flask application, and also to unify configuration loading.

This method just uses app.config to create a default elasticsearch_dsl connection.

Parameters:

app (Flask) – Flask app to configure.

Usage

from flask_tern import elasticsearch

def create_app():
    app = Flask(__name__)
    elasticsearch.init_app(app)
    return app
flask_tern.elasticsearch.settings.ELASTICSEARCH_URL = None

Elasticsearch URL. This url should include credentials and index name if required.

flask_tern.elasticsearch.settings.ELASTICSEARCH_VERIFY_CERTS = True

Enable / disable certificate validation in case fol https connections to Elasticsearch.

flask_tern.elasticsearch.settings.ELASTICSEARCH_SSL_SHOW_WARN = True

Enable / disable untrusted SSL certificate varnings in case certificate verification das been disabled.

Caching

flask_tern.cache.cache = <flask_caching.Cache object>

global flaskcache:flask_caching.Cache instance

flask_tern.cache.init_app(app)

Configure Flask-Caching extension.

Authentication

flask_tern.auth.init_app(app)

This method is used to configure authentication via OIDC.

Parameters:

app – Flask app to configure

It configures authlib Flask integration to verify tokens against the provided OIDC provider.

Authlib’s Flask integration relies on session storage. Please make sure to use a secure session implementation to avoid any issues.

Typical usage would be:

from flask_tern import auth

def create_app():
    app = Flask(__name__)
    auth.init_app(app)
    return app
flask_tern.auth.require_login(view_func: Callable) Callable[[], Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | WSGIApplication]

Redirect to login page if not logged in.

If flask_tern.auth.current_user is not set, then it will generate a redirect to a named endpoint oidc_login.login (as defined in blueprint flask_tern.auth.login.oidc_login). The current url is passed as url parameter return_url to the endpoint. After successful login, the user can be redirected back to the original url. If the user is not viewing from the web browser, we can return a 403 response.

This wrapper can only be used with simple GET requests, as passing a post body on redirect may not work:

@require_login
def view():
    return "ok"

# noqa: DAR101, DAR201

Parameters:

view_func (Callable)

Return type:

Callable[[], Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], Headers | Mapping[str, str | List[str] | Tuple[str, …]] | Sequence[Tuple[str, str | List[str] | Tuple[str, …]]]] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int, Headers | Mapping[str, str | List[str] | Tuple[str, …]] | Sequence[Tuple[str, str | List[str] | Tuple[str, …]]]] | WSGIApplication]

flask_tern.auth.require_roles(roles: Iterable[str]) Callable[[Callable], Callable]

Require an authenticated user with a specific set of roles.

This wrapper ensures that there is an authenticated user, and all roles set in this decorator are present on the current users role attribute:

@require_roles(["admin", "writer"])
def view():
    return "ok"

# noqa: DAR101, DAR201

Parameters:

roles (Iterable[str])

Return type:

Callable[[Callable], Callable]

flask_tern.auth.require_user(view_func: Callable) Callable[[], Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | WSGIApplication]

View decorator which enforces an authenticated user.

If flask_tern.auth.current_user is not set, then call flask.abort(), which raises a 403 HTTP Exception. If we have a valid user, then execute the wrapped view method:

@require_user
def view():
    return "ok"

# noqa: DAR201, DAR101

Parameters:

view_func (Callable)

Return type:

Callable[[], Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], Headers | Mapping[str, str | List[str] | Tuple[str, …]] | Sequence[Tuple[str, str | List[str] | Tuple[str, …]]]] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int] | Tuple[Response | str | bytes | List[Any] | Mapping[str, Any] | Iterator[str] | Iterator[bytes], int, Headers | Mapping[str, str | List[str] | Tuple[str, …]] | Sequence[Tuple[str, str | List[str] | Tuple[str, …]]]] | WSGIApplication]

class flask_tern.auth.user.User(id: str, name: str, email: str | None = None, email_verified: bool = False, roles: List[str] | None = None, scopes: List[str] | None = None, claims: Dict[str, Any] | None = None)

A class representing an authenticated user.

id and name are required attributes.

Parameters:
id: str

Unique id identifying a user.

name: str

Users display name

email: str

Users email address

email_verified: bool

Flag whether users email address has been verified

roles: List[str]

List of roles within this app assigned to a user

scopes: List[str]

List of scopes in OAuth token

claims: dict

Dictionary of all claims taken from auth source (e.g. OAuth token)

has_role(role: str) bool

Test if a user has given role.

Parameters:

role (str) – role to test

Returns:

True if role is in self.roles otherwise False

Return type:

bool

has_roles(roles: list) bool

Test if a user has all roles assigend.

Parameters:

roles (list) – List of roles to test for.

Returns:

True if all roles are in self.roles otherwise False

Return type:

bool

get(key, default=None)

Backwards compatible methods to allow dict based access to attributes.

Supports dict methed like .get(key, default)

Parameters:
  • key – attribute to return

  • default – default value if not attribute is not available

Returns:

Value of attribute

flask_tern.auth.apikey.get_userinfo_from_apikey(auth_info: dict) User

Method used by APP to verify ApiKey against api key service.

Parameters:

auth_info (dict) – dictionary with details about credentials provided in request

Returns:

User object filled with details about user

Return type:

User

ApiKey service returns user info for given api key. This auth method passes the given apikey on to apikey service for validation.

flask_tern.auth.login.oidc_login = <Blueprint 'oidc_login'>

A blueprint providing views to handle browser session login

flask_tern.auth.login.get_userinfo_from_session(auth_info)

Return User object based on authinfo.

Parameters:

auth_info – dict, A dictionary as returned by flask_tern.auth.utils.get_authorization(). With keys type: bearer and token: the encoded access token.

Returns:

‘User’ object with attributes set from auth_info

flask_tern.auth.login.login()

Generate redirect url for oauth login.

accepts url parameter return_url or uses Referrer header to remember return_url

If there is no return_url set, it use the url for a route named root, which needs to be configured on the Flask app.

Returns:

Redirect to authorize url.

flask_tern.auth.login.authorize()

Parse and validate authorization response.

On success return redirect user back to original URL and set session cookie

If there is no return_url set, it use the url for a route named root, which needs to be configured on the Flask app.

Returns:

Redirect to stored return url, or application root route.

flask_tern.auth.login.logout()

Clear logged in session.

If there is no return_url set, it use the url for a route named root, which needs to be configured on the Flask app.

Returns:

Redirect to return url.

flask_tern.auth.oidc.get_userinfo_from_accesstoken(auth_info) User

Extract user information and roles from access token.

Parameters:

auth_info – dict, A dictionary as returned by flask_tern.auth.utils.get_authorization(). With keys type: bearer and token: the encoded access token.

Returns:

‘User’ object with attributes set from auth_info

Return type:

User

flask_tern.auth.utils.get_authorization() dict | None

Find and extract authentication information from current request.

This method looks for an Autharization header or a valid session. It returns either None or a dictionary with keys type describing the authentication method and additional keys depending on the value of ‘type’.

Returns:

Dictionary containinf information about credentials provided.

Return type:

dict | None

flask_tern.auth.settings.USERINFO_MAP = {'apikey-v1': <function get_userinfo_from_apikey>, 'bearer': <function get_userinfo_from_accesstoken>, 'session': <function get_userinfo_from_session>}

A set of methods to extract user information based on authentication method. E.g. bearer looks at HTTP Authorization header with bearer tokens, and session uses sessions and cookies to store curretn login status. Other options can easily be implemented. See Authentication for details

flask_tern.auth.settings.OIDC_DISCOVERY_URL = None

The URL which serves an OIDC discovery document. This usually ends with /.well-known/openid-configuration.

flask_tern.auth.settings.OIDC_CLIENT_ID = None

OIDC client id

flask_tern.auth.settings.OIDC_CLIENT_SECRET = None

OIDC client secret

flask_tern.auth.settings.OIDC_SCOPE = 'openid profile email'

OIDC scope. This needs to include openid to enable OpenID Connect protocol.

flask_tern.auth.settings.OIDC_USE_REFRESH_TOKEN = False

If set to true, the application will store a refresh token in the session, and will keep the access and id token valid (until refresh token or session timeout)

flask_tern.auth.settings.APIKEY_SERVICE = None

5000/api/v1.0. The URL must not end with /.

Type:

The API endpoint of the API Key service e.g. http

Type:

//localhost

Logging

flask_tern.logging.init_app(app: Flask)

Configure logging.

This method configures the logging for the Flask application. It configures a logger named app.name.

If LOG_LEVEL is set, it will be used. If LOG_LEVEL is not set, and the application runs in debug mode, then set log level to DEBUG. Otherwise fall back to INFO level.

LOG_LEVEL: configure root logger level. This is either a string or one of the constants from Logging Levels.

Parameters:

app (Flask) – Flask app to configure

from flask_tern import logging as app_logging

def create_app():
    app = Flask(__name__)
    app_logging.init_app(app)
    return app
flask_tern.logging.log_audit(*args, **kwargs)

Use this method to log audit messages.

Parameters are passed on to python log function. Usually the result of create_audit_event should be passed in as the only parameter.

Parameters:
  • args – positional args passed to log function.

  • kwargs – keyword args passed to log function.

flask_tern.logging.record_audit_target(id, **kw)

Store target information in current application context.

The audit_log() decorator will pick up this information and add it to the audit log object.

The information is stored in flask.g under key audit_target.

Parameters:
  • id – identifier for target

  • kw – additional properties added to target

flask_tern.logging.audit_ignore(audit_ignore=True)

Call to disable audit log via audit_log() decorator.

This is useful in case automatic audit logging should be turned off for this request.

Parameters:

audit_ignore – Sets the request global g.audit_ignore flag to True or False

flask_tern.logging.audit_success(success=True, reason=None)

Call to set audit_outcome manually.

Parameters:
  • success – If true outcome will be “success”, else outcome will be “failure”.

  • reason – A dictionary containing “reasonType” and “reasonCode” and/or “policyType” and “policyId”

flask_tern.logging.create_audit_event(action: str, outcome, target: str | dict = None) dict

Create an Audit Event object (dictionary).

The resulting object can be passed to log_audit directly.

Parameters:
  • action (str) – audit log action field

  • outcome – audit outcome

  • target (str | dict) – if a string it’s used as target id, if a dict, it’s the full target section in the audit log

Returns:

full audit message as dictionary

Return type:

dict

flask_tern.logging.audit_log(action: str, target: str | dict = None)

Wrap view methods to create audit logs.

Parameters:
  • action (str) – Sets the action field of the audit message

  • target (str | dict) – Either a target id or a dict with fields to put into target of audit message., optional

This method automatically sets the outcome field to success or in case the view method throws an exception to failure. Use record_audit_target() to add additional information about the target.

# noqa: DAR201 # don’t validate return

OpenAPI

flask_tern.openapi.OpenApiBlueprint(name: str, import_name: str) Blueprint

A Blueprint factory to serve APIs described by an OpenAPI spec document.

It provides a few routes per default:

  • /: A route named api_root which redicets to the Swagger UI endpoint.

  • /ui: a route to render a Swagger UI page

  • /openapi.yaml: serves the openapi document as yaml

  • /openapi.json: serves the openapi document as yaml

The openapi.yaml file is loaded via py:func:flask:flask.Blueprint.open_resource

Parameters:
  • name (str) – prefix for route names registered at blueprint

  • import_name (str) – module name used to locate resources

Returns:

Blueprint instance

Return type:

Blueprint

flask_tern.openapi.validate(validate_request=True, validate_response=True)

Decorate view methods to enable openapi request / response validation.

Using this decorator enforces request and/or response validation against the openapi spec.

Parameters:
  • validate_request – enable / disable request validation

  • validate_response – enable / disable response validation

# noqa: DAR201 ignore missing returns

flask_tern.openapi.settings.SWAGGER_UI_VERSION = '5.0.0-alpha.14'

The Swagger UI version used to show the openapi documentation.

ProxyFix

Setup ProxyFix middleware.

For details prease see Tell Flask it is Behind a Proxy.

Proxyfix middleware is wrapped here to make it configurable via environment variables.

See py:mod:flask_tern.proxyfix.settings.

The following headers are considered by ProxyFix middleware:

  • X-Forwarded-For sets REMOTE_ADDR.

  • X-Forwarded-Proto sets wsgi.url_scheme.

  • X-Forwarded-Host sets HTTP_HOST, SERVER_NAME, and SERVER_PORT.

  • X-Forwarded-Port sets HTTP_HOST and SERVER_PORT.

  • X-Forwarded-Prefix sets SCRIPT_NAME.

flask_tern.proxyfix.init_app(app: Flask)

This method is used to configure Tell Flask it is Behind a Proxy middleware.

It sets the number of values for various X-Forward-XXX header values considered by the middleware.

Usage

from flask_tern import proxyfix

def create_app():
    app = Flask(__name__)
    proxyfix.init_app(app)
    return app
Parameters:

app (Flask) – The flask app to configure.

flask_tern.proxyfix.settings.PROXYFIX_X_FOR = 1

Number of values to trust for X-Forwarded-For.

flask_tern.proxyfix.settings.PROXYFIX_X_PROTO = 1

Number of values to trust for X-Forwarded-Proto.

flask_tern.proxyfix.settings.PROXYFIX_X_HOST = 0

Number of values to trust for X-Forwarded-Host.

flask_tern.proxyfix.settings.PROXYFIX_X_PORT = 0

Number of values to trust for X-Forwarded-Port.

flask_tern.proxyfix.settings.PROXYFIX_X_PREFIX = 0

Number of values to trust for X-Forwarded-Prefix.

Testing

This module defines some re-usable test fixtures.

These test fixtures are useful to make testing apps built with this extension a little bit easier.

flask_tern.testing.fixtures.monkeypatch_session(request)

Experimental (https://github.com/pytest-dev/pytest/issues/363).

flask_tern.testing.fixtures.basic_auth(app) dict

A fixture to set up some well defined users which can be used with basic authentication during testing.

Returns:

Dictionary with users and their attributes available for testing.

Return type:

dict

Usage

def my_test_func(client, basic_auth):
    r = client.post(
        "/api/url",
        headers={"Authorization"}: basic_auth["user"]["auth"]},
    )
flask_tern.testing.fixtures.db(app)

Configure and return Flask-SQLAlchemy extesions.

If SQLALCHEMY_URL is configured globally, then this fixture is not needed. It is useful to enable SQLAlchemy extension conditionally for specific tests.

Todo

Check if this fixture is really useful in apps. (Seems like it’s only useful for this library itself.)

Returns:

Instance of Flask-SQLAlechym extension configured for testing.

flask_tern.testing.fixtures.cache(app)

Configure and return Flask-Caching extesions.

Configures CACHE_TYPE = "flask_caching.backends.SimpleCache" and returns the Flask-Caching instance to use in tests.

Todo

Check if this fixture is really useful in apps. (Seems like it’s only useful for this library itself.)

Returns:

Instance of Flask-Caching extension configured for testing.

flask_tern.testing.fixtures.views(app) dict

Add some simple views for testing to Flask app.

  • /public: returns ok

  • /: endpoint named root and returns ok

  • /private: requires valid user, and returns user dict

Todo

Check if this fixture is really useful in apps. (Seems like it’s only useful for this library itself.)

Usage

from flask_tern.testing.fixtures import vievs

def test_view(client, views):
    response = client.get(views["public"]) # root, private
    assert response.status_code = 200
    assert response.text = "ok"
Returns:

Mapping for view name to url to use in tests.

Return type:

dict