Attribute Forwarding with ConfigAttribute
Flask uses the ConfigAttribute descriptor to create a seamless link between the application object and its configuration dictionary. While the primary way to store and retrieve settings is through the app.config dictionary, certain core settings are exposed as direct attributes on the application instance (e.g., app.testing or app.secret_key) to provide a cleaner and more discoverable API.
The ConfigAttribute Descriptor
The ConfigAttribute class, defined in src/flask/config.py, implements the Python descriptor protocol (__get__ and __set__). It acts as a proxy: when you access an attribute defined as a ConfigAttribute, it fetches the value from the instance's config dictionary using a pre-defined key.
class ConfigAttribute(t.Generic[T]):
def __init__(
self, name: str, get_converter: t.Callable[[t.Any], T] | None = None
) -> None:
self.__name__ = name
self.get_converter = get_converter
def __get__(self, obj: App | None, owner: type[App] | None = None) -> T | te.Self:
if obj is None:
return self
rv = obj.config[self.__name__]
if self.get_converter is not None:
rv = self.get_converter(rv)
return rv
def __set__(self, obj: App, value: t.Any) -> None:
obj.config[self.__name__] = value
Attribute Proxying
When a ConfigAttribute is defined on a class, accessing it on an instance triggers __get__. This method looks up the value in obj.config[self.__name__]. Similarly, assigning a value to the attribute triggers __set__, which updates the corresponding key in the config dictionary.
This ensures that the application object and the configuration dictionary remain perfectly in sync, as they both point to the same underlying data store.
Value Conversion
A powerful feature of ConfigAttribute is the get_converter parameter. This allows Flask to store raw data types (like integers or strings) in the configuration while exposing rich objects to the developer.
The most prominent example in Flask is permanent_session_lifetime. In the configuration dictionary, this might be stored as an integer representing seconds. However, when accessed via the attribute, it is converted into a timedelta object.
In src/flask/sansio/app.py, the converter is defined as:
def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
if value is None or isinstance(value, timedelta):
return value
return timedelta(seconds=value)
This converter is then passed to the ConfigAttribute definition:
permanent_session_lifetime = ConfigAttribute[timedelta](
"PERMANENT_SESSION_LIFETIME",
get_converter=_make_timedelta,
)
Implementation in the Application Class
Flask defines several core attributes using this pattern in the App class (found in src/flask/sansio/app.py). This base class provides the foundation for the standard Flask application object.
class App(Scaffold):
# ...
testing = ConfigAttribute[bool]("TESTING")
secret_key = ConfigAttribute[str | bytes | None]("SECRET_KEY")
permanent_session_lifetime = ConfigAttribute[timedelta](
"PERMANENT_SESSION_LIFETIME",
get_converter=_make_timedelta,
)
By defining these at the class level, every instance of a Flask application automatically gains these proxied attributes.
Design Tradeoffs and Constraints
Dependency on the Config Attribute
The ConfigAttribute descriptor assumes that the object it is attached to has a config attribute that supports dictionary-style access (obj.config[key]). If this attribute is missing or does not behave like a dictionary, the descriptor will raise an AttributeError or KeyError.
Read-Only Conversion
The get_converter is only applied during retrieval (__get__). It is not applied during assignment (__set__). This means that if you assign an integer to app.permanent_session_lifetime, the value stored in app.config["PERMANENT_SESSION_LIFETIME"] will be that integer. The conversion to a timedelta only occurs when you subsequently read the attribute.
Missing Keys
If a key is missing from the config dictionary, ConfigAttribute will raise a KeyError upon access. In practice, Flask avoids this by ensuring that default values for these keys are populated in app.default_config during application initialization.
Custom Usage Example
Developers can use ConfigAttribute in their own subclasses or extensions to expose custom configuration keys. This is useful for creating a more "native" feeling API for an extension.
from flask import Flask
from flask.config import ConfigAttribute
class MyCustomApp(Flask):
# Proxy 'MY_EXTENSION_TIMEOUT' config key to 'timeout' attribute
timeout = ConfigAttribute[int]("MY_EXTENSION_TIMEOUT")
app = MyCustomApp(__name__)
app.config["MY_EXTENSION_TIMEOUT"] = 30
# Accessing the attribute proxies to the config
assert app.timeout == 30
# Setting the attribute updates the config
app.timeout = 60
assert app.config["MY_EXTENSION_TIMEOUT"] == 60