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.