Cock stands for «configuration file with click». It is a configuration aggregator, which stands on shiny click
library.
No module for click with flat configuration file, which will mimic actual click options. There are click-config
and click-config-file
, but they targets another goals.
- Aggregate configuration file and cli options into flat configuration object.
- Respect all click checks and conversions (
required
attribute forbidden, since it breaks internal logic). dict
-like, flat, sorted, dot-accessed configuration object.- Entrypoint builder.
cock
is offered under MIT license.
- python 3.7+
from cock import build_entrypoint, Config, Option
def main(config: Config):
print(config)
options = [
Option("a_b_c", default="foo"),
Option("b_c_d", default="bar"),
]
entrypoint = build_entrypoint(main, options, auto_envvar_prefix="EXAMPLE", show_default=True)
if __name__ == "__main__":
entrypoint(prog_name="example")
This is almost pure click setup:
$ python example.py --help
Usage: example [OPTIONS] [CONFIGURATION_FILE]
Options:
--a-b-c TEXT [default: foo]
--b-c-d TEXT [default: bar]
--help Show this message and exit. [default: False]
But there is a CONFIGURATION_FILE
argument. Lets see use cases.
$ python example.py
{'configuration_file': None, 'a_b_c': 'foo', 'b_c_d': 'bar'}
$ EXAMPLE_A_B_C=foo-env python example.py
{'configuration_file': None, 'a_b_c': 'foo-env', 'b_c_d': 'bar'}
$ EXAMPLE_A_B_C=foo-env python example.py --a-b-c foo-cli
{'a_b_c': 'foo-cli', 'configuration_file': None, 'b_c_d': 'bar'}
a-b-c: foo-file
$ EXAMPLE_A_B_C=foo-env python example.py --a-b-c foo-cli config-example.yml
{'a_b_c': 'foo-file', 'configuration_file': '/absolute/path/to/config-example.yml', 'b_c_d': 'bar'}
Priority is obvious: file > cli arguments > env variables
Note: for file a-b-c
is the same as a_b_c
or a-b_c
. Use whatever you prefer.
As described in features paragraph, configuration is flattened before chaining with click options. So all configuration files listed below are equal:
a_b_c: foo-file
a:
b:
c: foo-file
a-b:
c: foo-file
If provided file have key crossings:
a-b_c: foo-file1
a:
b-c: foo-file2
Then RuntimeError
will be raised.
cock
uses pyyaml
library for config loading, so it supports yaml
and json
formats, but this can be improved later if someone will need more configuration file types.
Configuration can be defined as dictionary too:
from cock import build_entrypoint, Option, Config
def main(config: Config):
print(config)
options = {
"a": {
"b": {
"c": Option(default="foo"),
},
},
"a-b_d": Option(default="bar"),
}
entrypoint = build_entrypoint(main, options, auto_envvar_prefix="EXAMPLE", show_default=True)
if __name__ == "__main__":
entrypoint(prog_name="example")
Note: for dictionaries you can use same rules in naming and structure as for files.
Configuration can be defined as multiple sources:
from cock import build_entrypoint, Option, Config
def main(config: Config):
print(config)
dict_options = {"a-b-c": Option(default="foo")}
list_options = [Option("b_c-d", default="bar")]
entrypoint = build_entrypoint(main, dict_options, list_options,
auto_envvar_prefix="EXAMPLE", show_default=True)
if __name__ == "__main__":
entrypoint(prog_name="example")
You can also gather all defaults from options as a Config
:
from cock import get_options_defaults, Option
options = {
"a": {
"b": {
"c": Option(default="foo"),
},
},
}
config = get_options_defaults(options)
assert config == {"a_b_c": "foo"}
assert config.a_b_c == "foo"
Config
is an extended (with dot-access) version of sortedcontainers.SortedDict
:
>>> from cock import Config
>>> c = Config(b=1, a=2)
Config({'a': 2, 'b': 1})
>>> c["a"], c.b
(2, 1)
>>> c.z
...
KeyError: 'z'
>>> c.items()
SortedItemsView(Config({'a': 2, 'b': 1}))
>>> c["0"] = 0
>>> c
Config({'0': 0, 'a': 2, 'b': 1})
def build_entrypoint(
main: Callable[[Config], Any],
*options_stack: Union[dict, List[Union[Option, click.option]]],
**context_settings
) -> Callable[..., Any]:
main
is a user-space function of exactly one argument, a dot-accessed config wrapper.*options_stack
is a sequence of dicts and/or lists described above.**context_settings
propagated toclick.command
decorator.
def get_options_defaults(
*options_stack: Union[dict, List[Union[Option, click.option]]]
) -> Config:
*options_stack
is a sequence of dicts and/or lists described above.
class Option(
name: Optional[str] = None,
**attributes)
name
is a name in resultingConfig
object, which passed tomain
.name
can be set only once, further set will lead to exception.name
field will be converted to «underscore» view (e.g.a-b_c
internaly will be converted toa_b_c
).
**attributes
propagated toclick.option
decorator.required
attribute forbidden, since it breaks internal logic.
Deprecations:
- Usage of
click.option
as option. build_options_from_dict
function, since it is obsolete with new api.