API
Configuration
tree-config
provides the tools to configure applications where the
objects to be configured are nested in a tree-like fashion such that they
can be represented as nested dicts in e.g. a yaml file.
See the guide for complete examples.
- class tree_config.Configurable
Bases:
object
tree_config
uses a duck typing approach. E.g. when applying config,apply_config()
will callapply_config_property
if it is defined for the objects being configured, otherwise it directly sets the properties.The
Configurable
can be used as a base-class instead of the duck typing approach and it defines all the special config methods. It can also be used as a guide to the names of the special config hooks available when using the duck typing approach.There are only two possible benefits to using
Configurable
. (1) it defines_config_props
/_config_children
which caches the names of the configurable properties / children on a per-class basis. Unlike the duck typing approach that gathers them afresh every time. (2) You can usesuper
for properties you want to use the default handling approach as in the following example.However, the main reason for the class’ existance is to define the configuration API in one place so we can refer to the method names.
Consider a duck typing example and the inheritance based example:
class DuckApp: _config_props_ = ('frame', 'color') frame = 'square' color = 'blue' class ConfigurableApp(Configurable): _config_props_ = ('frame', 'color') frame = 'square' color = 'blue'
Both behave identically with respect to configuration:
>>> read_config_from_object(DuckApp()) {'frame': 'square', 'color': 'blue'} >>> read_config_from_object(ConfigurableApp()) {'frame': 'square', 'color': 'blue'}
However, if we wanted to customize setting the
frame
property, usingConfigurable
will be slighlty simpler since you can call super to set the remaining properties in the default way. E.g.:class DuckApp: _config_props_ = ('frame', 'color') frame = 'square' color = 'blue' def apply_config_property(self, name, value): if name == 'frame': self.frame = value * 2 else: setattr(self, name, value) class ConfigurableApp(Configurable): _config_props_ = ('frame', 'color') frame = 'square' color = 'blue' def apply_config_property(self, name, value): if name == 'frame': self.frame = value * 2 else: super().apply_config_property(name, value)
- property _config_children: Dict[str, str]
A property, which if defined will be used to get all the configurable children instead of using
_config_children_
. It returns the dict of configurable children like_config_children_
.
- property _config_props: List[str]
A property, which if defined will be used to get all the configurable properties instead of using
_config_props_
. It returns the list of configurable properties like_config_props_
.
- apply_config_child(name: str, prop: str, obj: Any, config: Union[Dict[str, Any], List[Dict[str, Any]]]) Any
If defined, it is called to apply the configuration for all the properties of the child, for each child.
When defined, it must either call
apply_config()
which also implicitly dispatchespost_config_applied()
. If you manually configure the child, you also have to callpost_config_applied()
manually.- Parameters
name – The human friendly name of the child that will be configured. It is the same as the keys in
_config_children_
.prop – The property name storing the child that will be configured. It is the same as the values in
_config_children_
.obj – The configurable child object or a list of objects if the property is a list.
config – The config dict or list of config dicts to be applied to the child(ern).
- apply_config_property(name: str, value: Any) None
If defined, it is called to set the value of a configurable property of the class.
- Parameters
name – The name of the property.
value – The value of the property.
- get_config_property(name: str) Any
If defined, it is called by the configuration system to get the value of the named property.
- Parameters
name – The name of the property to get.
- post_config_applied() None
If defined, it is called by the configuration system when it finishes applying all the configuration properties/children of this object.
- tree_config.apply_config(obj, config: typing.Dict[str, typing.Any], get_attr=<built-in function getattr>, set_attr=<built-in function setattr>) None
Takes the config data read with e.g.
read_config_from_object()
orread_config_from_file()
and applies them to the object and its children.Calls
post_config_applied
on the object and/or its children after they are configured if all/any have such a method.- Parameters
obj – The object to which to apply the config.
config – The config dict.
get_attr – The function to use to get the child value from the object and its children. defaults to
getattr
.set_attr – The function to use to set the property values for the object and its children. defaults to
setattr
.
The object’s config is applied before its children’s config is applied.
E.x.:
class App: _config_props_ = ('name', ) _config_children_ = {'the box': 'box'} name = 'chair' box = None class Box: _config_props_ = ('volume', ) volume = 12 def apply_config_property(self, name, value): print('applying child', name) setattr(self, name, value) def post_config_applied(self): print('done applying child')
then:
>>> app = App() >>> app.box = Box() >>> d = read_config_from_object(app) >>> d {'the box': {'volume': 12}, 'name': 'chair'} >>> d['name'] = 'bed' >>> d['the box']['volume'] = 34 >>> apply_config(app, d) applying child volume done applying child >>> app.name 'bed' >>> app.box.volume 34
- tree_config.dump_config(filename: typing.Union[str, pathlib.Path], data: typing.Dict[str, typing.Any], yaml_dump_str=<function yaml_dumps>) None
Dumps config data gotten with e.g.
read_config_from_object()
to a yaml file.- Parameters
filename – The yaml filename.
data – The config data.
yaml_dump_str – The function to encode the config to yaml. Defaults to
yaml_dumps()
.
E.g.:
>>> class App: >>> _config_props_ = ('name', ) >>> name = 'chair' >>> app = App() >>> d = read_config_from_object(app) >>> dump_config('config.yaml', d) >>> with open('config.yaml') as fh: ... print(fh.read())
Which prints
name: chair
.
- tree_config.get_config_children_items(obj_or_cls, get_attr=<built-in function getattr>) List[Tuple[str, str, Any]]
Returns a list of 3-tuples of all the configurable children of
obj_or_cls
. Each item is is a 3-tuple of(friendly_name, prop_name, value)
, wherefriendly_name
is the name as listed in the keys of_config_children_
,prop_name
is the property name in the class as listed in the values of_config_children_
, andvalue
is the value taken fromobj_or_cls
.We get the list of children of the object from
get_config_children_names()
.- Parameters
obj_or_cls – The object or class on which to search for children.
get_attr – The function to use to get the child value from the object. defaults to
getattr
.
E.x.:
class App: _config_children_ = {'the box': 'box'} box = None class Box: pass
then:
>>> get_config_children_items(App()) [('the box', 'box', None)] >>> get_config_children_items(App) [('the box', 'box', None)] >>> app = App() >>> app.box = Box() >>> get_config_children_items(app) [('the box', 'box', <Box at 0x264581a2a90>)]
- tree_config.get_config_children_names(obj_or_cls) Dict[str, str]
Returns a dict of the names friendly and property names of all the configurable children of
obj_or_cls
.If
obj_or_cls
has a_config_children
property, that is used to get the list of configurable children, otherwise we walk the class and its super classes hierarchy using_config_children_
to get the configurable children names.- Parameters
obj_or_cls – The object or class on which to search for children.
E.x.:
class App: _config_children_ = {'the box': 'box'} box = None class Box: pass
then:
>>> get_config_children_names(App()) {'the box': 'box'} >>> get_config_children_names(App) {'the box': 'box'}
- tree_config.get_config_prop_items(obj_or_cls, get_attr=<built-in function getattr>) Dict[str, Any]
Returns a dict mapping the names of all the configurable properties of
obj_or_cls
to its values as gotten from the object or class.We get the list of properties of the object from
get_config_prop_names()
. For each property, if the object has aget_config_property
method, that is used to get the value. Otherwise, or if it’s a classget_attr
is used instead.- Parameters
obj_or_cls – The object or class on which to search for properties.
get_attr – The function to use to get the property value from the object. defaults to
getattr
.
E.x.:
class App: _config_props_ = ('name', ) name = 'chair' class MyApp(App): _config_props_ = ('location', ) location = 0 def get_config_property(self, name): if name == 'location': return len(name) return getattr(self, name)
then:
>>> get_config_prop_items(MyApp()) {'location': 8, 'name': 'chair'} >>> get_config_prop_items(MyApp) {'location': 0, 'name': 'chair'}
- tree_config.get_config_prop_names(obj_or_cls) List[str]
Returns a list of the names of all the configurable properties of
obj_or_cls
.If
obj_or_cls
has a_config_props
property, that is used to get the list of configurable properties, otherwise we walk the class and its super classes hierarchy using_config_props_
to get the configurable property names.- Parameters
obj_or_cls – The object or class on which to search for properties.
E.x.:
class App: _config_props_ = ('name', ) name = 'chair' class MyApp(App): _config_props_ = ('location', ) location = 0 def get_config_property(self, name): if name == 'location': return len(name) return getattr(self, name)
then:
>>> get_config_prop_names(MyApp()) ['location', 'name'] >>> get_config_prop_names(MyApp) ['location', 'name']
- tree_config.load_apply_save_config(obj, filename: typing.Union[str, pathlib.Path], get_attr=<built-in function getattr>, set_attr=<built-in function setattr>, yaml_dump_str=<function yaml_dumps>, yaml_load_str=<function yaml_loads>) Dict[str, Any]
Applies the config to the object from the yaml file (if the file doesn’t exist it creates it), and then dumps to the yaml file the current config from the object. It also returns the final config dict.
This can be used to set the object from the config, but also making sure the file contains the current config including any new properties not previously there or properties that changed during config application.
- Parameters
obj – The configurable object.
filename – The yaml filename.
get_attr – The function to use to get the property/children values from the object. defaults to
getattr
.set_attr – The function to use to set the property values of the object and its children. defaults to
setattr
.yaml_dump_str – The function to encode the config to yaml. Defaults to
yaml_dumps()
.yaml_load_str – The function to parse the yaml string read from the file. Defaults to
yaml_loads()
.
E.x.:
class App: _config_props_ = ('name', ) name = 'chair' class AppV2: _config_props_ = ('name', 'side') name = 'chair' side = 'left'
then:
>>> app = App() >>> app.name = 'tree' >>> load_apply_save_config(app, 'config_app.yaml') {'name': 'tree'} >>> app.name 'tree' >>> # then later for v2 of the app >>> app_v2 = AppV2() >>> app_v2.name 'chair' >>> load_apply_save_config(app_v2, 'config_app.yaml') {'name': 'tree', 'side': 'left'} >>> app_v2.name 'tree' >>> with open('config_app.yaml') as fh: ... print(fh.read())
this prints:
name: tree side: left
- tree_config.load_config(obj, filename: typing.Union[str, pathlib.Path], get_attr=<built-in function getattr>, yaml_dump_str=<function yaml_dumps>, yaml_load_str=<function yaml_loads>) Dict[str, Any]
Loads and decodes the config from a yaml file. If the config file doesn’t exist, it first dumps the config to the file using
read_config_from_object()
anddump_config()
before loading it.- Parameters
obj – The object from which to dump the config when the files doesn’t exist.
filename – The yaml filename.
get_attr – The function to use to get the property/children values from the object. defaults to
getattr
.yaml_dump_str – The function to encode the config to yaml. Defaults to
yaml_dumps()
.yaml_load_str – The function to parse the yaml string read from the file. Defaults to
yaml_loads()
.
E.g.:
>>> class App: >>> _config_props_ = ('name', ) >>> name = 'chair' >>> load_config(App(), 'app_config.yaml') {'name': 'chair'}
- tree_config.read_config_from_file(filename: typing.Union[str, pathlib.Path], yaml_load_str=<function yaml_loads>) Dict[str, Any]
Reads and returns the yaml config data dict from a file that was previously dumped with
dump_config()
.- Parameters
filename – The yaml filename.
yaml_load_str – The function to parse the yaml string read from the file. Defaults to
yaml_loads()
.
E.g.:
>>> class App: >>> _config_props_ = ('name', ) >>> name = 'chair' >>> app = App() >>> d = read_config_from_object(app) >>> dump_config('config.yaml', d) >>> read_config_from_file('config.yaml') {'name': 'chair'}
- tree_config.read_config_from_object(obj, get_attr=<built-in function getattr>) Dict[str, Any]
Returns a recursive dict containing all the configuration options of the obj and its configurable children.
- Parameters
obj – The object from which to get the config.
get_attr – The function to use to get the child value from the object and its children. defaults to
getattr
.
E.x.:
class App: _config_props_ = ('name', ) _config_children_ = {'the box': 'box'} name = 'chair' box = None class Box: _config_props_ = ('volume', ) volume = 12
then:
>>> app = App() >>> app.box = Box() >>> read_config_from_object(app) {'the box': {'volume': 12}, 'name': 'chair'}
Yaml module
Provides the functions to create yaml objects, and to dump and load yaml using these objects.
- tree_config.yaml.get_yaml() ruamel.yaml.main.YAML
Creates and returns a yaml object that can be used by
yaml_dumps()
andyaml_loads()
to dump and load from yaml files.The default flow style (
default_flow_style
) is set to False so the file is formatted as expanded. The yaml type instantiated issafe
.
- tree_config.yaml.register_kivy_yaml_support() None
Registers the Kivy properties so it can be encoded to yaml.
E.g. Some properties create custom list sub-classes.
- tree_config.yaml.register_numpy_yaml_support() None
Registers the numpy data types so it can be encoded to yaml and then decoded.
When it is encountered in an object it is converted to a list with a special numpy tag. When decoded, an appropriate numpy type is constructed and returned.
- tree_config.yaml.register_pathlib_yaml_support() None
Registers pathlib.Path so it can be encoded and decoded back to Path.
- tree_config.yaml.register_torch_yaml_support() None
Registers the torch data types so it can be encoded to yaml and then decoded.
When it is encountered in an object it is converted to a list with a special torch tag. When decoded, an appropriate torch type is constructed and returned.
- tree_config.yaml.yaml_dumps(value: typing.Any, get_yaml_obj: typing.Callable[[], ruamel.yaml.main.YAML] = <function get_yaml>) str
Converts the object to yaml.
- Parameters
value – the object to convert.
get_yaml_obj – A function such as
get_yaml()
that will be called to get a yaml object. Defaults toget_yaml()
.
- Returns
a string yaml representation.
- tree_config.yaml.yaml_loads(value: str, get_yaml_obj: typing.Callable[[], ruamel.yaml.main.YAML] = <function get_yaml>) Any
Loads a yaml file and converts it to the objects it represents.
- Parameters
value – the string to decode.
get_yaml_obj – A function such as
get_yaml()
that will be called to get a yaml object. Defaults toget_yaml()
.
- Returns
the decoded object.
Documentation generation
When generating docs, the documentation of the properties listed in
__config_props__
can dumped to a yaml file using
create_doc_listener()
.
write_config_props_rst()
can load the docstrings from this yaml file
and then generate a nicely formatted rst file listing all the configurable
properties of the project. See the user guide for details and complete examples.
Command line
This module provides two command line options that can call either
download_merge_yaml_doc()
or merge_yaml_doc()
and forwards the
arguments to these functions. See the guide for examples.
- tree_config.doc_gen.create_doc_listener(sphinx_app, package_name, filename, yaml_dump_str=<function yaml_dumps>)
Creates a listener for the
__config_props__
attributes and dumps the docs of any props listed, tofilename
. If the file already exists, it extends it with new data and overwrites any exiting properties that we see again in this run.To use, in the sphinx conf.py file do something like:
def setup(app): import package create_doc_listener(app, package, 'config_attrs.yaml')
where
package
is the package for which the docs are generated.See the guide for a full example.
- tree_config.doc_gen.download_merge_yaml_doc(filename, url, out_filename)
Downloads a yaml file with previously saved configurable properties docstrings, optionally merges it with an existing docstrings yaml file, and outputs the merged yaml file. See the guide for an example.
- Parameters
filename – The optional yaml filename into which to merge the remote yaml docstrings.
url – A url to a docstrings containing yaml file that will be downloaded and optionally merged into
filename
and output toout_filename
.out_filename – The filename of the output yaml file.
On the command line it’s invocated as:
$ python -m tree_config.doc_gen download --help usage: Config Docs generation download [-h] [-f FILENAME] -u URL -o OUT_FILENAME optional arguments: -h, --help show this help message and exit -f FILENAME, --filename FILENAME The optional yaml filename with which to merge the downloaded file. -u URL, --url URL -o OUT_FILENAME, --out_filename OUT_FILENAME
- tree_config.doc_gen.merge_yaml_doc(filename1, filename2, out_filename)
Merges two yaml files containing configurable properties docstrings into a single output yaml file.
- Parameters
filename1 – First yaml input filename into which the second is merged.
filename2 – Second yaml input filename.
out_filename – The output filename of the resulting yaml file.
On the command line it’s invocated as:
$ python -m tree_config.doc_gen merge --help usage: Config Docs generation merge [-h] -f1 FILENAME1 -f2 FILENAME2 -o OUT_FILENAME optional arguments: -h, --help show this help message and exit -f1 FILENAME1, --filename1 FILENAME1 -f2 FILENAME2, --filename2 FILENAME2 -o OUT_FILENAME, --out_filename OUT_FILENAME
- tree_config.doc_gen.write_config_props_rst(obj, project, app, exception, filename, rst_filename, get_attr=<built-in function getattr>, yaml_dump_str=<function yaml_dumps>)
Walks through all the configurable classes of
obj
, recursively by looking at_config_props_
and_config_children_
and using the type hints of these children properties if they are None (e.g. if obj is a class). For each property it loads their docs from the yaml filefilename
and it generates a rst output file atrst_filename
with all the tokens.For example in the sphinx conf.py file do:
def setup(app): app.connect('build-finished', partial(write_config_props_rst, ProjectApp, project_name, filename='config_prop_docs.yaml', rst_filename='source/config.rst'))
where project_name is the name of project and ProjectApp is the App of the package the contains all the configurable objects.
See the guide for a complete example.
Utils
- class tree_config.utils.class_property(fget=None, fset=None, fdel=None, doc=None)
Bases:
property
Can be used as
@class_property
on a method to make it a class property in the same way@property
can be used on a normal method to make it a object property.
- tree_config.utils.get_class_annotations(obj_or_cls: Union[Any, Type[Any]]) Dict[str, Any]
Gets a dict of all the annotations of the object/class, including from all superclasses. The values are actual object, even if the type is given as a string (for forward declarations).
- tree_config.utils.get_class_bases(cls: Type) Generator[Type, None, None]
Yields all the base-classes of the class, not including the passed class or object.