Coverage for yamlable/yaml_objects.py: 95%
30 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-07-06 08:57 +0000
« prev ^ index » next coverage.py v6.4.1, created at 2022-07-06 08:57 +0000
1# Authors: Sylvain MARIE <sylvain.marie@se.com>
2# + All contributors to <https://github.com/smarie/python-yamlable>
3#
4# License: 3-clause BSD, <https://github.com/smarie/python-yamlable/blob/master/LICENSE>
6from abc import ABCMeta
8import six
10try: # python 3.5+
11 from typing import TypeVar
13 YO2 = TypeVar('YO2', bound='YamlObject2')
14except ImportError:
15 pass
16try: # python 3.5.4+
17 from typing import Type
18except ImportError:
19 pass # normal for old versions of typing
21from yaml import YAMLObjectMetaclass, YAMLObject, SafeLoader, MappingNode
23from yamlable.base import AbstractYamlObject, read_yaml_node_as_yamlobject
26class YAMLObjectMetaclassStrict(YAMLObjectMetaclass):
27 """
28 Improved metaclass for YAMLObject, that raises an error if yaml_tag is not defined
29 """
30 def __init__(cls, # type: Type[YO2] # type: ignore
31 name, bases, kwds):
33 # construct as usual
34 super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
36 # if yaml_tag is provided
37 if 'yaml_tag' in kwds:
38 # if cls.yaml_tag != NONE_IGNORE_CHECKS:
39 if kwds['yaml_tag'] is not None:
40 cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
41 cls.yaml_dumper.add_representer(cls, cls.to_yaml)
42 else:
43 if 'yaml_tag' in cls.__dict__: 43 ↛ 48line 43 didn't jump to line 48, because the condition on line 43 was never false
44 # this is an explicitly disabled class (yaml_tag=None is set on it), ok
45 pass
46 else:
47 # this class inherits from the yaml_tag=None and does not redefine it, not ok
48 raise TypeError("`yaml_tag` field is not redefined by class {}, cannot inherit from YAMLObject "
49 "properly. Note that abstract classes can set the tag explicitly to `None` to skip "
50 "this check. It won't disable the check for their subclasses".format(cls))
52 else:
53 raise TypeError("`yaml_tag` field is not redefined by class {}, cannot inherit from YAMLObject properly"
54 "".format(cls))
57class ABCYAMLMeta(YAMLObjectMetaclassStrict, ABCMeta):
58 """The subclass of both YAMLObjectMetaclass and ABCMeta, to be used in YamlObject2"""
59 pass
62class YamlObject2(six.with_metaclass(ABCYAMLMeta, AbstractYamlObject, YAMLObject)):
63 """
64 A helper class to register a class as able to dump instances to yaml and to load them back from yaml.
66 This class relies on the `YAMLObject` class provided in pyyaml, and implements the to_yaml/from_yaml methods by
67 leveraging the AbstractYamlObject class (__to_yaml_dict__ / __from_yaml_dict__).
69 It is basically an extension of YAMLObject
70 - mapping the methods to methods in AbstractYamlObject (for consistency with YamlAble) so that you only have to
71 provide a mapping to dictionary and do not have to care about pyyaml internals
72 - and raising an error if the `yaml_tag` class field is not properly set.
74 You still have to
75 - define `yaml_tag` either directly or using the @yaml_info() decorator
76 - optionally override methods from AbstractYamlObject: __to_yaml_dict__ and __from_yaml_dict__
78 Note: since this class extends YAMLObject, it relies on metaclass. You might therefore prefer to extend YamlAble
79 instead.
80 """
81 yaml_loader = SafeLoader # explicitly use SafeLoader by default
82 # yaml_dumper = Dumper
83 yaml_tag = None
84 # yaml_flow_style = ...
86 @classmethod
87 def to_yaml(cls, # type: Type[YamlObject2]
88 dumper,
89 data # type: AbstractYamlObject
90 ):
91 # type: (...) -> MappingNode
92 """
93 Default implementation: relies on AbstractYamlObject API to serialize all public variables
95 :param dumper:
96 :param data:
97 :return:
98 """
99 new_data = data.__to_yaml_dict__()
100 return dumper.represent_mapping(cls.yaml_tag, new_data, flow_style=cls.yaml_flow_style)
102 @classmethod
103 def from_yaml(cls, # type: Type[YamlObject2]
104 loader,
105 node # type: MappingNode
106 ):
107 # type: (...) -> YamlObject2
108 """
109 Default implementation: relies on AbstractYamlObject API to load the node as a dictionary/sequence/scalar
111 :param loader:
112 :param node:
113 :return:
114 """
115 return read_yaml_node_as_yamlobject(cls=cls, loader=loader, node=node, yaml_tag=cls.yaml_tag)