What I want
From a yaml config I get a python dictionary that looks like this:
conf = {
'cc0': {
'subselect':
{'roi_spectra': [0], 'roi_x_pixel_spec': 'slice(400, 1200)'},
'spec':
{'subselect': {'x_property': 'wavenumber'}},
'trace':
{'subselect': {'something': 'jaja', 'roi_spectra': [1, 2]}}
}
}
As you see the keyword 'subselect' is common to all sub level and its value is always a dict, but its existance is optional. The amount of nesting might change. I'm searching for a function, that allows me to do the following:
# desired function that uses recursion I belive.
collect_config(conf, 'trace', 'subselect')
where 'trace' is the key of a dict of dicts, with possibly a 'subselect' dict as value.
and It should return
{'subselect':{
'something': 'jaja',
'roi_spectra': [1, 2],
'roi_x_pixel_spec':
'slice(400, 1200)'
}
or if I ask for
collect_config(conf, "spec", "subselect")
It should return
{'subselect':{
'roi_spectra': [0],
'roi_x_pixel_spec':
'slice(400, 1200)',
'x_property': 'wavenumber'
}
What I basically want, is a way of passing the values of a key from the top level down to the lower levels and have the lower levels overwrite the top level values. Much like inheritance for a class, but with a dictionary.
So I need a function that transverses the dict, finds a path to the desired key (here "trace" or "spec" and fills up its value (here "subselect") with the values of the higher levels, but only if the heigher level values are not existing.
A crappy solution
I currently have a kind of implementation that looks the following.
# This traverses the dict and gives me the path to get there as a list.
def traverse(dic, path=None):
if not path:
path=[]
if isinstance(dic, dict):
for x in dic.keys():
local_path = path[:]
local_path.append(x)
for b in traverse(dic[x], local_path):
yield b
else:
yield path, dic
# Traverses through config and searches for the property(keyword) prop.
# higher levels will update the return
# once we reached the level of the name (max_depth) only
# the path with name in it is of interes. All other paths are to
# be ignored.
def collect_config(config, name, prop, max_depth):
ret = {}
for x in traverse(config):
path = x[0]
kwg = path[-1]
value = x[1]
current_depth = len(path)
# We only care about the given property.
if prop not in path:
continue
if current_depth < max_depth or (current_depth == max_depth and name in path):
ret.update({kwg: value})
return ret
and I could then call it with
read_config(conf, "trace", 'subselect', 4)
and get
{'roi_spectra': [0],
'roi_x_pixel_spec': 'slice(400, 1200)',
'something': 'jaja'}
Update
jdehesa is almost there, but I could also have a config that looks like:
conf = {
'subselect': {'test': 'jaja'}
'bg0': {
'subselect': {'roi_spectra': [0, 1, 2]}},
'bg1': {
'subselect': {'test': 'nene'}},
}
collect_config(conf, 'bg0', 'subselect')
{'roi_spectra': [0, 1, 2]}
instead of
{'roi_spectra': [0, 1, 2], 'test': 'jaja'}