cmdkit.config#


Classes and interfaces for managing application level configuration parameters. Get a runtime configuration with a namespace-like interface from both local files and environment variables with appropriate precedent.



Reference#


class Configuration(**namespaces: Namespace)[source]#

Bases: NSCoreMixin

An ordered collection of Namespace dictionaries. The update behavior of Namespace is used to provide a layering effect for configuration parameters.

Example:
>>> conf = Configuration(one=Namespace({'x': 1, 'y': 2}),
...                      two=Namespace({'x': 3, 'z': 4})
>>> conf
Configuration(one=Namespace({'x': 1, 'y': 2}), two=Namespace({'x': 3, 'z': 4}))
>>> conf.x, conf.y, conf.z
(3, 2, 4)
>>> conf.namespaces.keys()
dict_keys(['one', 'two'])
>>> conf.namespaces.one.x
1

namespaces: Namespace#

extend(**others: Namespace | Environ) None[source]#

Extend the configuration by adding namespaces.

Example:
>>> conf = Configuration(one=Namespace({'x': 1, 'y': 2}),
...                      two=Namespace({'x': 3, 'z': 4})
>>> conf.extend(three=Namespace({'y': 5, 'u': {'i': 6, 'j': 7}}))
>>> conf
Configuration(one=Namespace({'x': 1, 'y': 2}),
              two=Namespace({'x': 3, 'z': 4}),
              three=Namespace({'y': 5, 'u': {'i': 6, 'j': 7}})

classmethod from_local(*, env: bool = False, prefix: str | None = None, default: dict | None = None, **files: str) Configuration[source]#

Create configuration from a cascade of files. Optionally include env.

Example:
>>> import os
>>> HOME, CWD = os.getenv('HOME'), os.getcwd()
>>> conf = Configuration.from_local(default=Namespace(),
...                                 env=True, prefix='MYAPP',
...                                 system='/etc/myapp.yml',
...                                 user=f'{HOME}/.myapp.yml',
...                                 local=f'{CWD}/.myapp.yml')

classmethod from_context(name: str, create_dirs: bool = True, config_format: str = 'toml', default_config: dict | None = None) Tuple[AppContext, Configuration][source]#

Build default configuration from application context.

This builder method is shorthand for calling default() on AppContext and passing it into Configuration.from_local() with environment variables enabled and system, user, and local passed accordingly.

Example:
>>> default_cfg = Namespace(logging={'level': 'info'}, server={'port': 54321})
>>> ctx, cfg = Configuration.from_context('myapp', default_config=default_cfg)

On Linux, you would get /etc/myapp.toml as a system configuration, ~/.myapp.toml as a user configuration and $MYAPP_SITE/config.toml as the local site.


which(*path: str) str[source]#

Derive which member namespace takes precedent for the given variable.

Example:
>>> conf = Configuration(one=Namespace({'x': 1, 'y': 2}),
...                      two=Namespace({'x': 3, 'z': 4}))
>>> conf.extend(three=Namespace({'y': 5, 'u': {'i': 6, 'j': 7}}))
>>> conf.which('x')
'two'
>>> conf.which('y')
'three'
>>> conf.which('u', 'i')
'three'
Note:

Care needs to be taken when used for mutable variables in the stack as the returned precedent does not reflect that the variable at that level may be a depth-first-merge of several sources.

>>> conf = Configuration(one=Namespace({'a': {'x': 1, 'y': 2}}),
...                      two=Namespace({'a': {'y': 3}}))
>>> conf.which('a')
'two'
>>> conf.a
Namespace({'x': 1, 'y': 3})

duplicates() Dict[str, Dict[str, List[Tuple[str, ...]]]][source]#

Find all the repeated leaves.

Example:
>>> one = Namespace({'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'z': 4}})
>>> two = Namespace({'b': {'x': 4, 'z': 2}, 'c': {'j': True, 'k': 3.14}})
>>> cfg = Configuration(one=one, two=two)
>>> cfg.duplicates()
{'x': {'one': [('a',), ('b',)], 'two': [('b',)]}, 'z': {'one': [('b',)], 'two': [('b',)]}}

whereis(leaf: str, value: ~typing.Callable[[~cmdkit.config.T], bool] | ~cmdkit.config.T = <function Configuration.<lambda>>) Dict[str, List[Tuple[str, ...]]][source]#

Find paths to leaf, optionally filtered on value, for each member namespace.

Example:
>>> one = Namespace({'a': {'x': 1, 'y': 2}, 'b': {'x': 3, 'z': 4}})
>>> two = Namespace({'b': {'x': 4}, 'c': {'j': True, 'k': 3.14}})
>>> cfg = Configuration(one=one, two=two)
>>> cfg.whereis('x')
{'one': [('a',), ('b',)], 'two': [('b',)]}
>>> cfg.whereis('x', 1)
{'one': [('a',)], 'two': []}
>>> cfg.whereis('x', lambda v: v % 3 == 0)
{'one': [('b',)], 'two': []}

update(*args, **kwargs) None[source]#

Update current namespace directly.

Note:

The Configuration class is itself a Namespace-like object. Doing any in-place changes to its underlying self does not change its member namespaces. This may otherwise cause confusion about the provenance of those parameters. Instead, overrides have been implemented to capture these changes in a local namespace. If you ask which() namespace a parameter has come from, and it was an in-place change, it will be considered a member of the “_” namespace.

Example:
>>> conf = Configuration(a=Namespace(x=1))
>>> conf
Configuration(a=Namespace({'x': 1}))
>>> conf.update(y=2)
>>> conf
Configuration(a=Namespace({'x': 1}), _=Namespace({'y': 2}))
>>> conf.x = 2
>>> conf
Configuration(a=Namespace({'x': 1}), _=Namespace({'x': 2, 'y': 2}))
>>> conf.update(y=3)
>>> conf
Configuration(a=Namespace({'x': 1}), _=Namespace({'x': 2, 'y': 3}))
>>> dict(conf)
{'x': 2, 'y': 3}