Concepts¶
The pycnfg.Handler
contains class to read and execute configuration.
The purpose of any configuration is to produced result (object) by combining resources (steps). Pycnfg offers unified patten to create arbitrary Python objects pipeline-wise. That naturally allows to control all parameters via single configuration.
Configuration is a python dictionary. It supports multiple sections. Each
section specify set of sub-configurations. Each sub-configuration provide steps
to construct an object, that can be utilize as argument in other sections.
Whole configuration could be passed to pycnfg.run
or to user-defined
wrapper around pycnfg.Handler
, that builds target sub-configurations
objects one by one.
For each section there is common logic:
{'section_id':
'configuration_id 1': {
'init': Initial object state.
'producer': Factory class, contained methods to run steps.
'patch': Add custom methods to class.
'steps': [
('method_id 1', {'kwarg_id': value, ..}),
('method_id 2', {'kwarg_id': value, ..}),
],
'global': Shortcut to common parameters.
'priority': Execution priority (integer).
}
'configuration_id 2':{
...
}
}
The target for each sub-configuration is to create an object.
init
is the template for future object (for example empty dict).
producer
works as factory, it should contain run()
method that:
takes init
and consecutive pass it and kwargs to steps
methods and
returns resulting object.
# pseudocode
def run(self, init, steps):
obj = init
for step in steps:
obj = decorators(getattr(self, step_id))(obj, objects, **kwargs)
return obj
That object will be store in objects
and can be used as kwargs for any
step in others sections.
objects['section_id__conf_id'] = obj
To specify the order in which sections handled, the priority
key is
available in each sub-configuration.
For flexibility, it is possible:
Specify default configuration for section(s).
Specify global value for common kwargs in steps via
global
key.Create separate section for arbitrary parameter in steps.
Monkey-patch
producer
object with custom functions viapatch
key.Set
init
as an instance, a class or a functionSet decorators for any step.
The whole configuration could be considered as sub-configuration in some higher level section. That potentially allows arbitrary levels of nesting.
Sub-configuration keys¶
- initcallable or instance, optional (default={})
Initial state for constructing object. Will be passed consecutive in steps as argument. If set as callable ``init``(), auto called.
- producerclass, optional (default=pycnfg.Producer)
The factory to construct an object:
producer.run(init, steps)
. Class auto initialized:producer(objects, 'section_id__configuration_id', **kwargs)
, whereobjects
is a dictionary with previously created objects {section_id__configuration_id
: object}. If (‘__init__’, kwargs) step provided insteps
, kwargs will be passed to initializer.- patchdict {‘method_id’function}, optional (default={})
Monkey-patching the
producer
object with custom functions.- stepslist of tuples, optional (default=[])
List of
producer
methods with kwargs to run consecutive. Each step should be a tuple:('method_id', kwargs, decorators)
, where ‘method_id’ should match toproducer
functions’ names. In case of omitting any kwargs step executed with default, set in corresponding producer method.- kwargsdict, optional (default={})
Step arguments: {‘kwarg’: value, …}.
It is possible to create separate section for any argument. Set
section_id__configuration_id
as kwarg value, then it would be auto-filled with corresponding object fromobjects
storage before step execution. List ofsection_id__configuration_id
is also possible. To prevent auto substitution, set special ‘_id’ postfixkwarg_id
.It`is possible to turn on resolving
None
values for explicitly set kwargs insteps
. Seeresolve_none
argument inpycnfg. Handler.read()
: If value is set to None, parser try to resolve it. First searches for value in sub-cconfigurationglobal
. Then resolver looks up ‘kwarg’ in section names. If such section exist, there are two possibilities: if ‘kwarg’ name contains ‘_id’ postfix, resolver substitutes None with availablesection_id__configuration_id
, otherwise with configuration object. If fails to find resolution,value
is remained None. In case of resolution plurality, ValueError is raised.- decorators: list, optional (default=[])
Step decorators from most inner to outer: [decorator,]
- prioritynon-negative integer, optional (default=1)
Priority of configuration execution. The more the higher priority. For two conf with same priority order is not guaranteed. If zero, not execute configuration.
- globaldict , optional (default={})
Specify values to substitute for any kwargs:
{'kwarg_name': value, }
. This is convenient for example when the same kwarg used in all methods. It is possible to specify more targeted path with step_id in it:{'step_id__kwarg_name': value, }
. If global contains multiple resolutions for some kwarg, targeted path has priority. By default global don`t replace explicitly set values insteps
. Seeupdate_expl
flag inpycnfg.Handler.read()
to change this behavior. See belowglabal
usage in other levels of configuration dictionary.- **keysdict {‘kwarg_name’: value, …}, optional (default={})
All additional keys in configuration are moving to
global
automatically (rewrites already existed keys from the sub-conf level).
Notes
Any configuration key above could be set in the most outer configuration level
or section level to specify common values either for all sections or all
sub-sections in some section (Examples below). The more inner level has
the higher priority, levels priority in ascending: cnfg level (0) => section
level (1) => sub-conf level (2)
. Supported keys: global, init, producer,
patch, steps, priority
.
global
keys from levels are merged according to level priorities.
Targeted path also possible, in priority:
section_id__conf_id__step_id__kwarg_name
on level 0.
section_id__step_id__kwarg_name
on level 0.
section_id__conf_id__kwarg_name
on level 0.
section_id__kwarg_name
on level 0.
conf_id__step_id__kwarg_name
on levels 0,1.
conf_id__kwarg_name
on levels 0,1.
step_id__kwarg_name
on levels 0,1,2.
kwarg_name
on levels 0,1,2.
On the same level “more” targeted path priority > non-targeted.
other keys` value are replaced in level priority.
section_id
/configuration_id
/step_id
/kwargs_id
should not
contain double underscore ‘__’ (except magic methods in step_id
).
Default configurations can be set in pycnfg.Handler.read()
dcnfg
argument. Arbitrary objects could be pre-accommodated objects
argument in
pycnfg.Handler.exec()
, so no need to specify configurations for them.
If any provided it has priority over sub-configuration with the same id.
To add functionality to producer use patch
key or inheritance from
pycnfg.Producer
.
Examples
Patching producer with custom function.
def my_func(self, key, val):
# ... custom logic ...
return res
CNFG = {..{'patch': {'func': my_func,},}}
global
on the most outer level to set kwargs in ‘func’ step and patch
to add functionality. Section level init
to set in both configuration
simultaneously:
CNFG = {
'global': {
'section_1__set__val': 42,
'val': 99,
},
'patch': {'func': my_func},
'section_1': {
'init': {'a': 7},
'conf_1': {
'steps': [
('func', {'key': 'b', 'val': 24},),
],
},
'conf_2': {
'steps': [
('func', {'key': 'b', 'val': 24},),
],
},
},
}
# Result (update_expl=True):
{
'section_1__conf_1': {'a': 7, 'b': 42},
'section_1__conf_2': {'a': 7, 'b': 99},
}
There are two ways to set second level kwargs for some step, for example kwarg ‘c’ for step ‘func’:
def func(a, b=7, **kwargs):
print(kwargs['c'])
# **kwargs could be set:
# Implicitly via 'global'.
CNFG = {..{
'global': {'kwargs':{'c': 3}}
'steps': [('func', {'a': 1)],
}}
# Explicitly in 'steps' (only if no 'c' in the first level).
CNFG = {..{
'steps': [('func', {'a': 1, 'c': 3)],
}}
# In the last case, 'global' can address 'c' directly:
# 'global': {'c': 4}
See more detailed examples in Examples .
See also
pycnfg.Handler
Read configurations, execute steps.
pycnfg.CNFG
Default configurations.