Skip to content

Commit 678f83a

Browse files
committed
Renamed internal AbstractYamlObject methods with dunder: from_yaml_dict becomes __from_yaml_dict__ and to_yaml_dict become __to_yaml_dict__. Fixed #3
(Legacy names will remain supported for a while, added a tet to check that.) `YamlCodec.create_from_yaml_dict` renamed `from_yaml_dict` for consistency.
1 parent fc35cdb commit 678f83a

7 files changed

Lines changed: 100 additions & 25 deletions

File tree

docs/usage.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Writing a codec is quite simple:
3232
- `get_known_types` to return all object types that can be encoded by this codec
3333
- `is_yaml_tag_supported` to return `True` if a yaml tag (suffix) is supported by this codec for decoding.
3434
- finally fill the encoder/decoder:
35-
- `create_from_yaml_dict` to decode
35+
- `from_yaml_dict` to decode
3636
- `to_yaml_dict` to encode
3737

3838
The example below shows how it can be done:
@@ -73,7 +73,7 @@ class MyCodec(YamlCodec):
7373
# ----
7474

7575
@classmethod
76-
def create_from_yaml_dict(cls, yaml_tag_suffix: str, dct, **kwargs):
76+
def from_yaml_dict(cls, yaml_tag_suffix: str, dct, **kwargs):
7777
# Create an object corresponding to the given tag, from the decoded dict
7878
typ = yaml_tags_to_types[yaml_tag_suffix]
7979
return typ(**dct)

yamlable/base.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from abc import ABC, abstractmethod
1+
from abc import ABC
22
from collections import OrderedDict
33
from io import TextIOBase, StringIO
44
from typing import Union, TypeVar, Dict, Any
5+
from warnings import warn
56

67
try:
78
from typing import Type
@@ -17,22 +18,28 @@ class AbstractYamlObject(ABC):
1718
Adds convenient methods load(s)_yaml/dump(s)_yaml to any object, to call pyyaml features directly on the object or
1819
on the object class.
1920
20-
Also adds the two methods to_yaml_dict / from_yaml_dict, that are common to YamlObject2 and YamlAble.
21+
Also adds the two methods __to_yaml_dict__ / __from_yaml_dict__, that are common to YamlObject2 and YamlAble.
2122
Default implementation uses vars(self) and cls(**dct), but subclasses can override.
2223
"""
2324

24-
def to_yaml_dict(self) -> Dict[str, Any]:
25+
def __to_yaml_dict__(self) -> Dict[str, Any]:
2526
"""
2627
Implementors should transform the object into a dictionary containing all information necessary to decode the
2728
object in the future. That dictionary will be serialized as a YAML mapping.
2829
2930
Default implementation returns vars(self). TODO maybe some day we'll need to rather make a copy...?
3031
:return:
3132
"""
33+
# Legacy compliance TODO remove in future version
34+
if 'to_yaml_dict' in dir(self):
35+
warn(type(self).__name__ + " still uses the legacy method name 'to_yaml_dict'. This name will not be "
36+
"supported in future version, please use '__to_yaml_dict__' instead")
37+
return self.to_yaml_dict()
38+
3239
return vars(self)
3340

3441
@classmethod
35-
def from_yaml_dict(cls: 'Type[Y]', dct: Dict[Any, Any], yaml_tag: str) -> Y:
42+
def __from_yaml_dict__(cls: 'Type[Y]', dct: Dict[Any, Any], yaml_tag: str) -> Y:
3643
"""
3744
Implementors should transform the given dictionary (read from yaml by the pyYaml stack) into an object instance.
3845
The yaml tag associated to this object, read in the yaml document, is provided in parameter.
@@ -47,6 +54,12 @@ def from_yaml_dict(cls: 'Type[Y]', dct: Dict[Any, Any], yaml_tag: str) -> Y:
4754
against is_json_schema_id_supported)
4855
:return:
4956
"""
57+
# Legacy compliance TODO remove in future version
58+
if 'from_yaml_dict' in dir(cls):
59+
warn(cls.__name__ + " still uses the legacy method name 'from_yaml_dict'. This name will not be "
60+
"supported in future version, please use '__from_yaml_dict__' instead")
61+
return cls.from_yaml_dict(dct, yaml_tag)
62+
5063
return cls(**dct)
5164

5265
def dump_yaml(self, file_path_or_stream: Union[str, TextIOBase], safe: bool = True, **pyyaml_kwargs):

yamlable/main.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def decode_yamlable(loader, yaml_tag, node, **kwargs):
201201
try:
202202
if clazz.is_yaml_tag_supported(yaml_tag):
203203
constructor_args = read_yaml_node_as_dict(loader, node)
204-
return clazz.from_yaml_dict(constructor_args, yaml_tag=yaml_tag)
204+
return clazz.__from_yaml_dict__(constructor_args, yaml_tag=yaml_tag)
205205
except Exception as e:
206206
errors[clazz.__name__] = e
207207

@@ -223,7 +223,7 @@ def encode_yamlable(dumper, obj, without_custom_tag: bool = False, **kwargs):
223223
:return:
224224
"""
225225
# Convert objects to a dictionary of their representation
226-
new_data = obj.to_yaml_dict()
226+
new_data = obj.__to_yaml_dict__()
227227

228228
if without_custom_tag:
229229
# TODO check that it works
@@ -323,7 +323,7 @@ class YamlCodec(ABC):
323323
- Decoding:
324324
- fill get_yaml_prefix
325325
- fill is_yaml_tag_supported to declare if a given yaml tag is supported or not
326-
- fill create_from_yaml_dict to create new instances of objects from a dictionary, according to the yaml tag
326+
- fill from_yaml_dict to create new instances of objects from a dictionary, according to the yaml tag
327327
- Encoding:
328328
- fill get_known_types
329329
- fill the to_yaml_dict
@@ -352,7 +352,7 @@ def decode(cls, loader, yaml_tag_suffix, node, **kwargs):
352352
"""
353353
if cls.is_yaml_tag_supported(yaml_tag_suffix):
354354
constructor_args = read_yaml_node_as_dict(loader, node)
355-
return cls.create_from_yaml_dict(yaml_tag_suffix, constructor_args, **kwargs)
355+
return cls.from_yaml_dict(yaml_tag_suffix, constructor_args, **kwargs)
356356

357357
@classmethod
358358
@abstractmethod
@@ -366,7 +366,7 @@ def is_yaml_tag_supported(cls, yaml_tag_suffix: str) -> bool:
366366

367367
@classmethod
368368
@abstractmethod
369-
def create_from_yaml_dict(cls, yaml_tag_suffix: str, dct, **kwargs):
369+
def from_yaml_dict(cls, yaml_tag_suffix: str, dct, **kwargs):
370370
"""
371371
Implementing classes should create an object corresponding to the given yaml tag, using the given constructor
372372
arguments.

yamlable/tests/test_yamlable.py

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ def test_yamlable_incomplete_description():
1313
with pytest.raises(NotImplementedError) as err_info:
1414
class Foo(YamlAble):
1515
# __yaml_tag_suffix__ = 'foo'
16-
def to_yaml_dict(self) -> Dict[str, Any]:
16+
def __to_yaml_dict__(self) -> Dict[str, Any]:
1717
return copy(vars(self))
1818

1919
@classmethod
20-
def from_yaml_dict(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
20+
def __from_yaml_dict__(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
2121
return Foo(**dct)
2222

2323
# instantiate
@@ -43,11 +43,11 @@ def __init__(self, a, b):
4343
def __eq__(self, other):
4444
return vars(self) == vars(other)
4545

46-
def to_yaml_dict(self) -> Dict[str, Any]:
46+
def __to_yaml_dict__(self) -> Dict[str, Any]:
4747
return copy(vars(self))
4848

4949
@classmethod
50-
def from_yaml_dict(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
50+
def __from_yaml_dict__(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
5151
return Foo(**dct)
5252

5353
# instantiate
@@ -80,6 +80,68 @@ def close(self):
8080
assert f == load(y)
8181

8282

83+
def test_yamlable_legacy_method_names():
84+
""" Tests that YamlAbleMixIn works correctly """
85+
86+
global enc
87+
global dec
88+
enc, dec = False, False
89+
90+
@yaml_info(yaml_tag_ns='yaml.tests')
91+
class FooLegacy(YamlAble):
92+
# __yaml_tag_suffix__ = 'foo' not needed: we used @yaml_info
93+
94+
def __init__(self, a, b):
95+
self.a = a
96+
self.b = b
97+
98+
def __eq__(self, other):
99+
return vars(self) == vars(other)
100+
101+
def to_yaml_dict(self) -> Dict[str, Any]:
102+
global enc
103+
enc = True
104+
return copy(vars(self))
105+
106+
@classmethod
107+
def from_yaml_dict(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
108+
global dec
109+
dec = True
110+
return FooLegacy(**dct)
111+
112+
# instantiate
113+
f = FooLegacy(1, 'hello')
114+
115+
# dump
116+
y = f.dumps_yaml()
117+
assert y == "!yamlable/yaml.tests.FooLegacy {a: 1, b: hello}\n"
118+
119+
# dump io
120+
class MemorizingStringIO(StringIO):
121+
""" A StringIO object that memorizes its buffer when it is closed (as opposed to the standard StringIO) """
122+
def close(self):
123+
self.value = self.getvalue()
124+
super(StringIO, self).close()
125+
s = MemorizingStringIO()
126+
f.dump_yaml(s)
127+
assert s.value == y
128+
129+
# dump pyyaml
130+
assert dump(f) == y
131+
132+
# load
133+
assert f == FooLegacy.loads_yaml(y)
134+
135+
# load io
136+
assert f == FooLegacy.load_yaml(StringIO(y))
137+
138+
# load pyyaml
139+
assert f == load(y)
140+
141+
assert enc
142+
assert dec
143+
144+
83145
# TODO override so that tag is not supported, to check error message
84146
def test_yamlable_not_supported():
85147

@@ -94,11 +156,11 @@ def __init__(self, a, b):
94156
def __eq__(self, other):
95157
return vars(self) == vars(other)
96158

97-
def to_yaml_dict(self) -> Dict[str, Any]:
159+
def __to_yaml_dict__(self) -> Dict[str, Any]:
98160
return copy(vars(self))
99161

100162
@classmethod
101-
def from_yaml_dict(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
163+
def __from_yaml_dict__(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
102164
return Foo_Err(**dct)
103165

104166
@classmethod

yamlable/tests/test_yamlcodec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def is_yaml_tag_supported(cls, yaml_tag_suffix: str) -> bool:
4444
return yaml_tag_suffix in yaml_tags_to_types.keys()
4545

4646
@classmethod
47-
def create_from_yaml_dict(cls, yaml_tag_suffix: str, dct, **kwargs):
47+
def from_yaml_dict(cls, yaml_tag_suffix: str, dct, **kwargs):
4848
typ = yaml_tags_to_types[yaml_tag_suffix]
4949
return typ(**dct)
5050

yamlable/tests/test_yamlobjects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ def __init__(self, a, b):
2929
def __eq__(self, other):
3030
return vars(self) == vars(other)
3131

32-
def to_yaml_dict(self) -> Dict[str, Any]:
32+
def __to_yaml_dict__(self) -> Dict[str, Any]:
3333
return copy(vars(self))
3434

3535
@classmethod
36-
def from_yaml_dict(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
36+
def __from_yaml_dict__(cls: 'Type[Y]', dct: Dict, yaml_tag: str) -> Y:
3737
return Foo(**dct)
3838

3939
# instantiate

yamlable/yaml_objects.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class YamlObject2(AbstractYamlObject, YAMLObject, metaclass=ABCYAMLMeta):
5151
A helper class to register a class as able to dump instances to yaml and to load them back from yaml.
5252
5353
This class relies on the `YAMLObject` class provided in pyyaml, and implements the to_yaml/from_yaml methods by
54-
leveraging the VarsExposer/BuildableFromVars mix-ins.
54+
leveraging the AbstractYamlObject class (__to_yaml_dict__ / __from_yaml_dict__).
5555
5656
It is basically an extension of YAMLObject
5757
- mapping the methods to methods in AbstractYamlObject (for consistency with YamlAble) so that you only have to
@@ -60,7 +60,7 @@ class YamlObject2(AbstractYamlObject, YAMLObject, metaclass=ABCYAMLMeta):
6060
6161
You still have to
6262
- define `yaml_tag` either directly or using the @yaml_info() decorator
63-
- implement both methods from AbstractYamlObject: to_yaml_dict and from_yaml_dict
63+
- optionally override methods from AbstractYamlObject: __to_yaml_dict__ and __from_yaml_dict__
6464
6565
Note: since this class extends YAMLObject, it relies on metaclass. You might therefore prefer to extend YamlAble
6666
instead.
@@ -79,17 +79,17 @@ def to_yaml(cls, dumper, data: AbstractYamlObject):
7979
:param data:
8080
:return:
8181
"""
82-
new_data = data.to_yaml_dict()
82+
new_data = data.__to_yaml_dict__()
8383
return dumper.represent_mapping(cls.yaml_tag, new_data, flow_style=cls.yaml_flow_style)
8484

8585
@classmethod
8686
def from_yaml(cls, loader, node):
8787
"""
88-
Default implementation: loads the node as a dictionary and calls from_yaml_dict with this dictionary
88+
Default implementation: loads the node as a dictionary and calls __from_yaml_dict__ with this dictionary
8989
9090
:param loader:
9191
:param node:
9292
:return:
9393
"""
9494
constructor_args = read_yaml_node_as_dict(loader, node)
95-
return cls.from_yaml_dict(constructor_args, yaml_tag=cls.yaml_tag)
95+
return cls.__from_yaml_dict__(constructor_args, yaml_tag=cls.yaml_tag)

0 commit comments

Comments
 (0)