This contains some ways of solving problems I’ve had with applications I use
Everett in. These use cases help me to shape the Everett architecture such that
it’s convenient and flexible, but not big and overbearing.
Hopefully they help you, too.
If there are things you’re trying to solve and you’re using Everett that aren’t
covered here, add an item to the issue tracker.
It’s easy to set up a everett.manager.ConfigManager and then call it
for configuration. However, with any non-trivial application, it’s likely you’re
going to refer to configuration options multiple times in different parts of the
code.
One way to do this is to pull out the configuration value and store it in a
global constant or an attribute somewhere and pass that around.
Another way to do this is to create a configuration component, define all the
configuration options there and then pass that component around.
For example, this creates an AppConfig component which has configuration
for the application:
# recipes_appconfig.pyimportloggingfromeverett.managerimportConfigManager,OptionTEXT_TO_LOGGING_LEVEL={"CRITICAL":50,"ERROR":40,"WARNING":30,"INFO":20,"DEBUG":10,}defparse_loglevel(value):try:returnTEXT_TO_LOGGING_LEVEL[value.upper()]exceptKeyErrorasexc:raiseValueError(f'"{value}" is not a valid logging level. Try CRITICAL, ERROR, '"WARNING, INFO, DEBUG")fromexcclassAppConfig:classConfig:debug=Option(parser=bool,default="false",doc="Turns on debug mode for the application",)loglevel=Option(parser=parse_loglevel,default="INFO",doc=("Log level for the application; CRITICAL, ERROR, WARNING, INFO, ""DEBUG"),)definit_app():manager=ConfigManager.from_dict({})config=manager.with_options(AppConfig())logging.basicConfig(level=config("loglevel"))ifconfig("debug"):logging.info("debug mode!")if__name__=="__main__":init_app()
Couple of nice things here. First, is that if you do Sphinx documentation, you
can use autocomponentconfig to automatically document your configuration
based on the code. Second, you can use
everett.manager.get_runtime_config() to print out the runtime
configuration at startup.
In this scenario, the basedir is defined at the app-scope and is passed to
the reader and writer components when they’re created. In this way, basedir
is app configuration, but not reader/writer configuration.
Say we have two components that share a set of credentials. We don’t want to
have to specify the same set of credentials twice, so instead, we use alternate
keys which let you specify other keys to look at for a configuration value.
This lets us have both components look at the same keys for their credentials
and then we only have to define them once.
Let’s create a db reader and a db writer component:
# recipes_alternate_keys.pyfromeverett.managerimportConfigManager,OptionclassDatabaseReader:classConfig:username=Option(alternate_keys=["root:db_username"])password=Option(alternate_keys=["root:db_password"])def__init__(self,config):self.config=config.with_options(self)classDatabaseWriter:classConfig:username=Option(alternate_keys=["root:db_username"])password=Option(alternate_keys=["root:db_password"])def__init__(self,config):self.config=config.with_options(self)# Define a shared configurationconfig=ConfigManager.from_dict({"DB_USERNAME":"foo","DB_PASSWORD":"bar"})reader=DatabaseReader(config.with_namespace("reader"))assertreader.config("username")=="foo"assertreader.config("password")=="bar"writer=DatabaseWriter(config.with_namespace("writer"))assertwriter.config("username")=="foo"assertwriter.config("password")=="bar"# Or define different credentialsconfig=ConfigManager.from_dict({"READER_USERNAME":"joe","READER_PASSWORD":"foo","WRITER_USERNAME":"pete","WRITER_PASSWORD":"bar",})reader=DatabaseReader(config.with_namespace("reader"))assertreader.config("username")=="joe"assertreader.config("password")=="foo"writer=DatabaseWriter(config.with_namespace("writer"))assertwriter.config("username")=="pete"assertwriter.config("password")=="bar"