Skip to content

Instantly share code, notes, and snippets.

@kgriffs
Last active April 24, 2019 18:46
Show Gist options
  • Save kgriffs/ea37ede4480ac5e080018eed81d292af to your computer and use it in GitHub Desktop.
Save kgriffs/ea37ede4480ac5e080018eed81d292af to your computer and use it in GitHub Desktop.
Falcon Application State

There are at least three or four different approaches you can take to dealing with state in a Falcon app.

The first one is to have a module that exposes a function or attribute with read-only global state that doesn't change in between requests. This is typically used for app configuration that is loaded at startup. There are many variations on this theme, but here is an example showing one approach:

import os

import aumbry


class S3Config(aumbry.JsonConfig):
    __mapping__ = {
        'bucket': ['bucket', str],
        'region': ['region', str],
        'access_key': ['access_key', str],
    }


class AssetCacheConfig(aumbry.JsonConfig):
    __mapping__ = {
        's3': ['s3', S3Config],
    }


class AppConfig(aumbry.JsonConfig):
    __mapping__ = {
        'env': ['env', str],
        'asset_cache': ['asset_cache', AssetCacheConfig],
    }

    @property
    def dev(self):
        return self.env == 'dev'


def config(cache=True):
    if not cache:
        return _config()

    if not config._cached:
        config._cached = _config()

    return config._cached


config._cached = None


def _config():
    cfg = aumbry.load(
        aumbry.FILE,
        AppConfig,
        {'CONFIG_FILE_PATH': os.environ['MYAPP_CONFIG_PATH']},
    )

    cfg.asset_cache.s3.secret_key = os.environ.get('MYAPP_CACHE_S3_SECRET_KEY', None)

    return cfg

The second approach is a variation on the above. You still expose a module-level function or attribute but this time it is tied to thread-local storage. You set the attributes from a middleware component and then access them elsewhere as needed. Here is an example object-oriented implementation:

# context.py

import threading


class _Context:
    def __init__(self):
        self._thread_local = threading.local()

    @property
    def request_id(self):
        return getattr(self._thread_local, 'request_id', None)

    @request_id.setter
    def request_id(self, value):
        self._thread_local.request_id = value


ctx = _Context()
# middleware.py

from uuid import uuid4
from context import ctx

class ExampleComponent:
    def process_request(self, req, resp):
        ctx.request_id = str(uuid4())

The third approach involves using the req.context object. This works well if you only need the data in places where you already have a reference to the Request object:

# middleware.py

from uuid import uuid4
from context import ctx

class ExampleComponent:
    def process_request(self, req, resp):
        # Using Falcon 2.0 syntax
        req.context.request_id = str(uuid4())

        # Or if your logger has built-in support for contexts
        req.context.log = structlog.get_logger(request_id=str(uuid4()))

Finally, you could inject the request ID into the WSGI environ and then pull it out by subclassing Request and overriding __init__, setting it to a member variable (thus making it accessible via self._request_id, for example). That would be more of a round-about way of doing it, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment