Assume I created a @dataclass class Foo
, and added a __post_init__
to perform type checking and processing.
When I attempt to yaml.load
a !Foo
object, __post_init__
is not called.
from dataclasses import dataclass, fieldsfrom ruamel.yaml import yaml_object, YAMLyaml = YAML()@yaml_object(yaml)
@dataclass
class Foo:foo: intbar: intdef __post_init__(self):raise Exceptionfor field in fields(self):value = getattr(self, field.name)typ = field.typeif not isinstance(value, typ):raise Exceptions = '''\
!Foo
foo: "foo"
bar: "bar"
'''
yaml.load(s)
How do I perform parameter checking when loading dataclasses via ruamel.yaml?
This behavior occurs in Python 3.7 as well as 3.6 with pip install dataclasses
.
This is now supported in 0.17.34, where you can do
from dataclasses import dataclass
from ruamel.yaml import YAMLyaml = ruamel.yaml.YAML()@yaml.register_class
@dataclass
class Foo:....
The reason why __post_init__
is not called, is because ruamel.yaml
(and the PyYAML code in its Constructor
s), was created long before dataclasses
was created.
Of course code for making a call to __post_init_()
could be added to ruamel.yaml
's Python object constructors, preferably after a test if something was created using @dataclass
, as otherwise a non Data-Class class, that happens to have such a method named __post_init_
, will all of a sudden have that method called during loading.
If you have no such classes, you can add your own, smarter, constructor to the YAML()
instance before first loading/dumping (at which moment the constructor is instantiated) using yaml.Constructor = MyConstructor
. But adding a constructor is not as trivial as subclassing the RoundTripConstructor
, because all supported node types need to be registered on such a new constructor type.
Most of the time I find it easier to just patch the appropriate method on the RoundTripConstructor
:
from dataclasses import dataclass, fields
from ruamel.yaml import yaml_object, YAML, RoundTripConstructordef my_construct_yaml_object(self, node, cls):for data in self.org_construct_yaml_object(node, cls):yield data# not doing a try-except, in case `__post_init__` does catch the AttributeErrorpost_init = getattr(data, '__post_init__', None)if post_init:post_init()RoundTripConstructor.org_construct_yaml_object = RoundTripConstructor.construct_yaml_object
RoundTripConstructor.construct_yaml_object = my_construct_yaml_objectyaml = YAML()
yaml.preserve_quotes = True@yaml_object(yaml)
@dataclass
class Foo:foo: intbar: intdef __post_init__(self):for field in fields(self):value = getattr(self, field.name)typ = field.typeif not isinstance(value, typ):raise Exceptions = '''\
!Foo
foo: "foo"
bar: "bar"
'''
d = yaml.load(s)
throws an exception:
Traceback (most recent call last):File "try.py", line 36, in <module>d = yaml.load(s)File "/home/venv/tmp-46489abf428c4cd4/lib/python3.7/site-packages/ruamel/yaml/main.py", line 266, in loadreturn constructor.get_single_data()File "/home/venv/tmp-46489abf428c4cd4/lib/python3.7/site-packages/ruamel/yaml/constructor.py", line 105, in get_single_datareturn self.construct_document(node)File "/home/venv/tmp-46489abf428c4cd4/lib/python3.7/site-packages/ruamel/yaml/constructor.py", line 115, in construct_documentfor dummy in generator:File "try.py", line 10, in my_construct_yaml_objectpost_init()File "try.py", line 29, in __post_init__raise Exception
Exception
Please note that the double quotes in your YAML are superfluous, so if you want to preserve these on round-trip you need to do yaml.preserve_quotes = True