How To Auto-dump Modified Values In Nested Dictionaries Using Ruamel.yaml?
Solution 1:
You will need to make a secondary class SubConfig
that behaves similar to Config
.
It is probably a good idea to get rid of the old style super(Config, self)
before that.
Change __setitem__
to check that the value is a dict, and if so
instantiate SubConfig
and then setting the individual items (the
SubConfig needs to do that as well, so you can have arbitrary nesting).
The SubConfig, on __init__
, doesn't take a filename, but it takes a
parent (of type Config
or SubConfig
). Subconfig
itself shouldn't
dump, and its updated
should call the parents updated
(eventually
bubbling up to Config
that then does a save).
In order to support doing cfg['a'] = dict(c=1)
you need to implement __getitem__
, and
similar for del cfg['a']
implement __delitem__
, to make it write the updated file.
I thought you could subclass one file fromt the other as several methods are the same,
but couldn't get this to work with super()
properly.
If you ever assign lists to (nested) keys, and want to autodump on updating an element
in such a list you'll need to implement some SubConfigList
and handle those in __setitem__
import sys
import os
from pathlib import Path
import ruamel.yaml
classSubConfig(dict):
def__init__(self, parent):
self.parent = parent
defupdated(self):
self.parent.updated()
def__setitem__(self, key, value):
ifisinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def__getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
returnsuper().__getitem__(key)
return res
def__delitem__(self, key):
res = super().__delitem__(key)
self.updated()
defupdate(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
self.updated()
return
_SR = ruamel.yaml.representer.SafeRepresenter
_SR.add_representer(SubConfig, _SR.represent_dict)
classConfig(dict):
def__init__(self, filename, auto_dump=True):
self.filename = filename ifhasattr(filename, 'open') else Path(filename)
self.auto_dump = auto_dump
self.changed = False
self.yaml = ruamel.yaml.YAML(typ='safe')
self.yaml.default_flow_style = Falseif self.filename.exists():
withopen(filename) as f:
self.update(self.yaml.load(f) or {})
defupdated(self):
if self.auto_dump:
self.dump(force=True)
else:
self.changed = Truedefdump(self, force=False):
ifnot self.changed andnot force:
returnwithopen(self.filename, "w") as f:
self.yaml.dump(dict(self), f)
self.changed = Falsedef__setitem__(self, key, value):
ifisinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def__getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
returnsuper().__getitem__(key)
def__delitem__(self, key):
res = super().__delitem__(key)
self.updated()
defupdate(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
self.updated()
config_file = Path('config.yaml')
cfg = Config(config_file)
cfg['a'] = 1
cfg['b']['x'] = 2
cfg['c']['y']['z'] = 42print(f'{config_file} 1:')
print(config_file.read_text())
cfg['b']['x'] = 3
cfg['a'] = 4print(f'{config_file} 2:')
print(config_file.read_text())
cfg.update(a=9, d=196)
cfg['c']['y'].update(k=11, l=12)
print(f'{config_file} 3:')
print(config_file.read_text())
# reread config from file
cfg = Config(config_file)
assertisinstance(cfg['c']['y'], SubConfig)
assert cfg['c']['y']['z'] == 42del cfg['c']
print(f'{config_file} 4:')
print(config_file.read_text())
# start from scratch immediately use updating
config_file.unlink()
cfg = Config(config_file)
cfg.update(a=dict(b=4))
cfg.update(c=dict(b=dict(e=5)))
assertisinstance(cfg['a'], SubConfig)
assertisinstance(cfg['c']['b'], SubConfig)
cfg['c']['b']['f'] = 22print(f'{config_file} 5:')
print(config_file.read_text())
which gives:
config.yaml 1:a:1b:x:2c:y:z:42config.yaml 2:a:4b:x:3c:y:z:42config.yaml 3:a:9b:x:3c:y:k:11l:12z:42d:196config.yaml 4:a:9b:x:3d:196config.yaml 5:a:b:4c:b:e:5f:22
You should consider not making these classes a subclass of dict
, but have the dict as an attribute ._d
(and replace super().
with self._d.
). This would require a specific representer function/method.
The advantage of that is that you don't get some dict functionality unexpectedly. E.g. in the above subclassing implementation, if I hadn't implemented __delitem__
, you could still do del cfg['c']
without an error, but the YAML file would not be written automatically. If the dict is an attribute, you'll get an error until you implement __delitem__
.
Post a Comment for "How To Auto-dump Modified Values In Nested Dictionaries Using Ruamel.yaml?"