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:

  1. Centralizing configuration specification for your application into a single class.
  2. 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:

  1. 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.

  2. Your application configuration is centralized in one place instead of spread out across your code base.

  3. You can automatically document your configuration using the everett.sphinxext Sphinx extension and autocomponentconfig 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: Type[CT_co]) → Dict[str, Tuple[everett.manager.Option, Type[CT_co]]]

Roll up configuration options for this class and parent classes.

This handles subclasses overriding configuration options in parent classes.

Parameters:cls – the component class to return configuration options for
Returns:final dict of configuration options for this class in key -> (option, cls) form

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: everett.manager.ConfigManager, component: Any, traverse: Callable = <function traverse_tree>) → List[Tuple[List[str], str, Any, everett.manager.Option]]

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 – a configuration manager instance
  • component – a component or tree of components
  • traverse – the function for traversing the component tree; see everett.manager.traverse_tree() for signature
Returns:

a list of (namespace, key, value, option) tuples