Setting up aiohttp.web applications

There are many ways on setting up aiohttp.web application, as well as there are many ways of defining settings for them.

Most notable ways of managing settings are:

  • Importing Python settings module and store it within aiohttp.web.Application instance

  • Reading settings from .yaml or .json file and store it again, in application instance

With that in mind, rororo insists of their way for setting up aiohttp.web application.

Step 1. Settings data structure

The main part of setup is Settings data structure backed by brilliant attrs library. It stores all settings values, as well as any other settings related data.

Example below illustrates how given Settings data structure may look like,

import attr


@attr.dataclass(frozen=True, slots=True)
class Settings:
    level: str
    debug: bool

rororo provides basic data structure, which covers most of common settings, used withing aiohttp.web app. Given data structure available as: rororo.settings.BaseSettings and it contains next fields,

Attrib

Env var

Description

host

AIOHTTP_HOST

Host, where aiohttp.web application expected to run. By default: "localhost"

port

AIOHTTP_PORT

Port to run aiohttp.web application on. By default: 8080

debug

DEBUG

When enabled, means that aiohttp.web application run in debug mode. By default: False

level

LEVEL

Application level (can be used as SENTRY_ENVIRONMENT, for example). One of: "test", "dev", "staging", "prod". By default: "dev"

time_zone

TIME_ZONE

Time zone to use within application. By default: "UTC"

first_weekday

FIRST_WEEKDAY

First weekday for calendar and other modules. By default: 0 (Monday)

locale

LOCALE

Locale to use within app. By default: "en_US.UTF-8"

sentry_dsn

SENTRY_DSN

Sentry DSN to use. By default: None

sentry_release

SENTRY_RELEASE

Sentry release. By default: None

rororo elevates on 12factor configuration principles, which means all values for Settings data structure should come from environment.

Step 2. Instantiating settings

After you declare your Settings data structure it is needed to:

  1. Instantiate it

  2. Assign to the aiohttp.web.Application instance

In most cases, it should looks like,

from aiohttp import web

from .settings import Settings


def create_app(argv: List[str] = None, **options: Any) -> web.Application:
    # Instantiate settings
    settings = Settings(**options)

    # Instantiate app
    app = web.Application(...)

    # Assign settings to the application for later reusage
    app["settings"] = settings

    # Return app
    return app

You also, might want to “apply” settings by running,

settings.apply()

right after instantiation. In that case rororo.settings.BaseSettings.apply() will call,

functions with respected settings values.

Configuring Sentry SDK

Even rororo.settings.BaseSettings contains values to configure Sentry, it is not designed to call sentry_sdk.init on rororo.settings.BaseSettings.apply() run.

You must need to setup Sentry SDK by yourself like,

import logging

import sentry_sdk
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
from sentry_sdk.integrations.logging import LoggingIntegartion


def create_app(argv: List[str] = None) -> web.Application:
    settings = Settings()

    if settings.sentry_dsn:
        sentry_sdk.init(
            settings.sentry_dsn,
            environment=settings.level,
            release=settings.sentry_release,
            integrations=(
                AioHttpIntegration(),
                LoggingIntegration(event_level=logging.WARNING)
            )
        )

    ...

Setup shortcut

There is a rororo.settings.setup_settings() shortcut, which apply given Settings data structure and put given instance into aiohttp.web.Application dict as "settings" key.

In other words given function is a literally shortcut to,

settings.apply(...)
app["settings"] = settings

Step 3. Using the settings

In app.__main__ script

If you run your app not via python -m aiohttp.web, but via application own __main__.py, it is OK to,

  1. Run create_app factory function

  2. Read settings from resulted app

  3. Pass host / port and other values to aiohttp.web.run_app() function

In most cases that __main__.py will look like,

from aiohttp import web
from rororo.aio import ACCESS_LOG_FORMAT
from rororo.settings import APP_SETTINGS_KEY

from app.app import create_app, logger
from app.settings import Settings


if __name__ == "__main__":
    app = create_app()

    settings: Settings = app[APP_SETTINGS_KEY]
    is_dev = settings.level == "dev"

    if is_dev:
        import aiohttp_autoreload

        aiohttp_autoreload.start()

    web.run_app(
        host=settings.host,
        port=settings.port,
        access_log=logger if is_dev else None,
        access_log_format=ACCESS_LOG_FORMAT
    )

Within view functions

As aiohttp.web.Request instance contains link to app, which requests given view handler, it is straight forward to read the settings within the view as,

from aiohttp import web
from rororo.settings import APP_SETTINGS_KEY


async def index(request: web.Request) -> web.Response:
    if request.app[APP_SETTINGS_KEY].debug:
        print("Hello, world!")
    return web.json_response(True)

However, as aiohttp>=3 supports sub-apps it is considred to more robust using aiohttp.web.Request.config_dict for accessing Settings data structure,

if request.config_dict[APP_SETTINGS_KEY].debug:
    print("Hello, world!")