Flask

A key annoyance with how flask typically recommends your applications be set up is that things like your app instance, plugins, database handles, etc tend to be module-level attributes and it’s tempting or even encouraged to import and use them in code elsewhere (which often leads to gnarnly circular import issues).

The pattern that Strapp tries to encourage, encapsulates as much of that as possible, so that the “only” (read easy/obvious) way to do things avoids these problems entirely.

Setup

A typical ls of a project directory might look something like

app.py (generally, either this or __main__.py, and not both)
src/
    project/
        __main__.py
        routes.py
        errors.py
        views/
            ...
        ...

All app setup, and references to things like plugins, and config are encouraged to exist in either something like the app.py above, or the __main__.py. The idea being that its location encourages you to not try to import it.

There your file contents might look like so:

app.py or __main__.py
from configly import Config
from setuplog import setup_logging
from strapp.flask import create_app, callback_factory, default_error_handlers, handler_exception, sqlalchemy_database

from project.routes import routes
from project.errors import CustomErrorType

config = Config.from_yaml('config.yml')

setup_logging(**config.logging.to_dict())

app = create_app(
    routes,
    config=config.flask,
    error_handlers=[
        *default_error_handlers,
        handler_exception(CustomErrorType),
    ],
    plugins=[
        # FlaskCORS(),
        # FlaskWhateverPlugin(),
    ]
    callbacks=[
        callback_factory(sqlalchemy_database, config.database)
    ),
 )

A couple of notes:

  • As we’ll see below, routes can be directly imported because the routes argument does not require a reference to app (as it would normally, though, you can always put this somewhere importable and do route setup in a more typical way).

  • the intent is to centralize and pre-instantiate any shared objects which views might otherwise try to import, and make them available to views through means other than direct import.

  • This callback mechanism, (along with strapp.flask.inject_db() below), are recommended rather than using e.g. FlaskSQLAlchemy (which encourages and/or requires a circular dependence of your models on a flask-specific plugin), though once again you can use whatever you’d like.

Routes

routes.py
from strapp.flask import Route

from project.views import x, bar

routes = [
    ('GET', '/foo', x.get_x),
    Route.to('POST', '/bar', y.create_bar, endpoint='create_bar'),
    dict(method='GET', path='/foo', view=x.get_x),
]

We try to be as flexible as possible in allowing the routes to be defined concisely. Ultimately, all the arguments boil down to the arguments sent into flask.Flask.route(), however the actual reference (and attachment) to the app is delayed until the call to strapp.flask.create_app().

Views

Finally, for defining actual view functions, there are additional decorators which can be used to simplify a typical (usually json) route.