Components
Changed in version 2.0: This is redone for v2.0.0 and simplified.
Configuration components
Everett supports configuration components.
There are two big use cases for this:
Centralizing configuration specification for your application into a single class.
Component architectures.
Centralizing configuration
Instead of having configuration-related bits defined across your codebase, you can define it in a class.
Here’s an example with an AppConfig
:
# component_appconfig.py
from everett.manager import ConfigManager, Option
# Central class holding configuration information
class AppConfig:
class Config:
debug = Option(
parser=bool,
default="false",
doc="Switch debug mode on and off.",
)
# Build a ConfigManger to look at the process environment for
# configuration and bound to the configuration options specified in
# AppConfig
def get_config():
manager = ConfigManager.basic_config(
doc="Check https://example.com/configuration for docs."
)
# Bind the configuration manager to the AppConfig component so that
# it handles option properties like defaults, parsers, documentation,
# and so on.
return manager.with_options(AppConfig())
config = get_config()
debug = config("debug")
print(f"debug: {debug}")
Let’s run it:
$ python component_appconfig.py
debug: False
$ DEBUG=true python component_appconfig.py
debug: True
Let’s run a Python shell and do some other things with it:
>>> import component_appconfig
debug: False
>>> config = component_appconfig.get_config()
>>> config("badkey")
Traceback (most recent call last):
...
everett.InvalidKeyError: 'badkey' is not a valid key for this component
Notice how you can’t use configuration keys that aren’t specified in the bound component.
Centrally defining configuration like this helps in a few ways:
You can reduce some bugs that occur as your application evolves over time. Every time you use configuration, the
ConfigManager
will enforce that the key is a valid option.Your application configuration is centralized in one place instead of spread out across your code base.
You can automatically document your configuration using the
everett.sphinxext
Sphinx extension andautocomponentconfig
directive:.. autocomponentconfig:: path.to.AppConfig
Because it’s automatically documented, your documentation is always up-to-date.
Component architectures
Everett configuration supports component architectures. Say your app needs to connect to RabbitMQ. With Everett, you can define the component’s configuration needs in the component class.
Here’s an example:
# componentapp.py
from everett.manager import ConfigManager, Option
class S3Bucket:
class Config:
region = Option(doc="AWS S3 region")
bucket_name = Option(doc="AWS S3 bucket name")
def __init__(self, config):
# Bind the configuration to just the configuration this component
# requires such that this component is self-contained
self.config = config.with_options(self)
self.region = self.config("region")
self.bucket_name = self.config("bucket_name")
def repr(self):
return f"<S3Bucket {self.region} {self.bucket_name}>"
config = ConfigManager.from_dict(
{
"S3_SOURCE_REGION": "us-east-1",
"S3_SOURCE_BUCKET_NAME": "mycompany_oldbucket",
"S3_DEST_REGION": "us-east-1",
"S3_DEST_BUCKET_NAME": "mycompany_newbucket",
}
)
s3_config = config.with_namespace("s3")
source_bucket = S3Bucket(s3_config.with_namespace("source"))
dest_bucket = S3Bucket(s3_config.with_namespace("dest"))
print(repr(source_bucket))
print(repr(dest_bucket))
That’s not wildly exciting, but if the component was in a library of components, then you can string them together using configuraiton.
For example, what if the destination wasn’t a single bucket, but rather a set of buckets?
dest_config = config("pipeline", default="dest", parser=ListOf(str))
dest_buckets = []
for name in dest_config:
dest_buckets.append(S3Bucket(s3_config.with_namespace(name)))
You can autogenerate configuration documentation for this component in your
Sphinx docs by including the everett.sphinxext
Sphinx extension and
using the autocomponentconfig
directive:
.. autocomponentconfig:: myapp.S3Bucket
Subclassing
You can subclass components and override configuration options.
For example:
# components_subclass.py
from everett.manager import ConfigManager, Option
class ComponentA:
class Config:
foo = Option(default="foo_from_a")
bar = Option(default="bar_from_a")
class ComponentB(ComponentA):
class Config:
foo = Option(default="foo_from_b")
def __init__(self, config):
self.config = config.with_options(self)
config = ConfigManager.basic_config()
compb = ComponentB(config)
print(compb.config("foo"))
print(compb.config("bar"))
That prints:
$ python components_subclass.py
foo_from_b
bar_from_a
Getting configuration information for components
You can get the configuration options for a component class using
everett.manager.get_config_for_class()
. This returns a dict of
configuration key -> (option, class)
. This helps with debugging which
option came from which class.
- everett.manager.get_config_for_class(cls)
Roll up configuration options for this class and parent classes.
This handles subclasses overriding configuration options in parent classes.
- Parameters:
cls (Type) – the component class to return configuration options for
- Returns:
final dict of configuration options for this class in
key -> (option, cls)
form- Return type:
Dict[str, Tuple[Option, Type]]
You can get the runtime configuration for a component or tree of components
using everett.manager.get_runtime_config()
. This returns a list of
(namespace, key, value, option, class)
tuples. The value is the computed
runtime value taking into account the environments specified in the
ConfigManager
and class hierarchies.
It’ll traverse any instance attributes that are components with options.
- everett.manager.get_runtime_config(config, component, traverse=<function traverse_tree>)
Returns configuration specification and values for a component tree
For example, if you had a tree of components instantiated, you could traverse the tree and log the configuration:
from everett.manager import ( ConfigManager, generate_uppercase_key, get_runtime_config, Option, parse_class, ) class App: class Config: debug = Option(default="False", parser=bool) reader = Option(parser=parse_class) writer = Option(parser=parse_class) def __init__(self, config): self.config = config.with_options(self) # App has a reader and a writer each of which has configuration # options self.reader = self.config("reader")(config.with_namespace("reader")) self.writer = self.config("writer")(config.with_namespace("writer")) class Reader: class Config: input_file = Option() def __init__(self, config): self.config = config.with_options(self) class Writer: class Config: output_file = Option() def __init__(self, config): self.config = config.with_options(self) cm = ConfigManager.from_dict( { # This specifies which reader component to use. Because we # specified this one, we need to define a READER_INPUT_FILE # value. "READER": "__main__.Reader", "READER_INPUT_FILE": "input.txt", # Same thing for the writer component. "WRITER": "__main__.Writer", "WRITER_OUTPUT_FILE": "output.txt", } ) my_app = App(cm) # This traverses the component tree starting with my_app and then # traversing .reader and .writer attributes. for namespace, key, value, option in get_runtime_config(cm, my_app): full_key = generate_uppercase_key(key, namespace) print(f"{full_key.upper()}={value or ''}") # This should print out: # DEBUG=False # READER=__main__.Reader # READER_INPUT_FILE=input.txt # WRITER=__main__.Writer # WRITER_OUTPUT_FILE=output.txt
- Parameters:
config (ConfigManager) – a configuration manager instance
component (Any) – a component or tree of components
traverse (Callable) – the function for traversing the component tree; see
everett.manager.traverse_tree()
for signature
- Returns:
a list of (namespace, key, value, option) tuples
- Return type:
List[Tuple[List[str], str, Any, Option]]