Skip to content

Commit 887b193

Browse files
committed
It should now be easier to define an abstract yaml-able class for several object types.
Deprecated `NONE_IGNORE_CHECKS`. Fixes #7 Added corresponding tests.
1 parent ffc0446 commit 887b193

5 files changed

Lines changed: 153 additions & 15 deletions

File tree

yamlable/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ def load_yaml(cls, # type: Type[Y]
189189
"".format(cls.__name__, type(res).__name__))
190190

191191

192-
NONE_IGNORE_CHECKS = object()
193-
""" A tag to be used as yaml tag for abstract classes, to indicate that they are abstract (checks disabled) """
192+
NONE_IGNORE_CHECKS = None
193+
# """Tag to be used as yaml tag for abstract classes, to indicate that
194+
# they are abstract (checks disabled). Not used anymore, kept for legacy reasons"""
194195

195196

196197
def read_yaml_node_as_dict(loader, node):

yamlable/main.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from yaml import Loader, SafeLoader, Dumper, SafeDumper, MappingNode
1919

20-
from yamlable.base import AbstractYamlObject, NONE_IGNORE_CHECKS, read_yaml_node_as_dict
20+
from yamlable.base import AbstractYamlObject, read_yaml_node_as_dict
2121
from yamlable.yaml_objects import YamlObject2
2222

2323

@@ -79,12 +79,19 @@ def is_yaml_tag_supported(cls,
7979
:return:
8080
"""
8181
if hasattr(cls, '__yaml_tag_suffix__') and cls.__yaml_tag_suffix__ is not None:
82-
return cls.__yaml_tag_suffix__ == yaml_tag and cls.__yaml_tag_suffix__ is not NONE_IGNORE_CHECKS
82+
if '__yaml_tag_suffix__' in cls.__dict__:
83+
# this is an explicitly configured class (__yaml_tag_suffix__ is set on it), ok
84+
return cls.__yaml_tag_suffix__ == yaml_tag
85+
else:
86+
# this class inherits from the __yaml_tag_suffix__ and does not redefine it, not ok
87+
raise TypeError("`__yaml_tag_suffix__` field is not redefined by class {}, cannot inherit from YamlAble"
88+
"properly.".format(cls))
89+
8390
else:
8491
raise NotImplementedError("class {} does not seem to have a non-None '__yaml_tag_suffix__' field. You can "
8592
"either create one manually or by decorating your class with @yaml_info. "
86-
"Alternately you should override the 'is_yaml_tag_supported' abstract method "
87-
"from YamlAble".format(cls))
93+
"Alternately you should override the 'is_yaml_tag_supported' method "
94+
"from YamlAble.".format(cls))
8895

8996

9097
def yaml_info(yaml_tag=None, # type: str
@@ -247,7 +254,7 @@ def encode_yamlable(dumper,
247254
return dumper.represent_mapping(None, new_data, flow_style=None)
248255
else:
249256
# Add the tag information
250-
if not hasattr(obj, '__yaml_tag_suffix__') or obj.__yaml_tag_suffix__ in {None, NONE_IGNORE_CHECKS}:
257+
if not hasattr(obj, '__yaml_tag_suffix__') or obj.__yaml_tag_suffix__ is None:
251258
raise NotImplementedError("object {} does not seem to have a non-None '__yaml_tag_suffix__' field. You "
252259
"can either create one manually or by decorating your class with @yaml_info."
253260
"".format(obj))

yamlable/tests/test_yamlable.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def __from_yaml_dict__(cls, # type: Type[Y]
5252

5353

5454
def test_yamlable():
55-
""" Tests that YamlAbleMixIn works correctly """
55+
""" Tests that YamlAble works correctly """
5656

5757
@yaml_info(yaml_tag_ns='yaml.tests')
5858
class Foo(YamlAble):
@@ -250,3 +250,77 @@ class Foo(YamlAble):
250250

251251
assert Foo().dumps_yaml() == """!yamlable/com.example.Foo {}
252252
"""
253+
254+
255+
def test_abstract_parent_error():
256+
"""This tests that we can define an abstract parent class with the YamlAble behaviour and inherit it"""
257+
258+
class AbstractFooE(YamlAble):
259+
pass
260+
261+
class FooError(AbstractFooE):
262+
"""
263+
This class inherits from the parent without redefining a yaml tag
264+
"""
265+
def __init__(self, a, b):
266+
self.a = a
267+
self.b = b
268+
269+
def __eq__(self, other):
270+
return vars(self) == vars(other)
271+
272+
# instantiate
273+
e = FooError(1, 'hello')
274+
275+
# dump
276+
with pytest.raises(NotImplementedError):
277+
e.dumps_yaml()
278+
279+
280+
def test_abstract_parent():
281+
"""This tests that we can define an abstract parent class with the YamlAble behaviour and inherit it"""
282+
283+
class AbstractFooV(YamlAble):
284+
pass
285+
286+
@yaml_info(yaml_tag_ns='yaml.tests')
287+
class FooValid(AbstractFooV):
288+
289+
def __init__(self, a, b):
290+
self.a = a
291+
self.b = b
292+
293+
def __eq__(self, other):
294+
return vars(self) == vars(other)
295+
296+
# instantiate
297+
f = FooValid(1, 'hello') # note:
298+
299+
# dump
300+
y = f.dumps_yaml()
301+
assert y == "!yamlable/yaml.tests.FooValid {a: 1, b: hello}\n"
302+
303+
# dump io
304+
class MemorizingStringIO(StringIO):
305+
""" A StringIO object that memorizes its buffer when it is closed (as opposed to the standard StringIO) """
306+
307+
def close(self):
308+
self.value = self.getvalue()
309+
# super(StringIO, self).close() # this does not work with python 2 old-style classes (StringIO is one)
310+
StringIO.close(self)
311+
312+
s = MemorizingStringIO()
313+
f.dump_yaml(s)
314+
assert s.value == y
315+
316+
# dump pyyaml
317+
assert dump(f) == y
318+
319+
# load
320+
assert f == FooValid.loads_yaml(y)
321+
322+
# load io
323+
assert f == FooValid.load_yaml(StringIO(y))
324+
325+
# load pyyaml
326+
assert f == load(y)

yamlable/tests/test_yamlobjects.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,58 @@ def __from_yaml_dict__(cls, # type: Type[Y]
5555
# load from yaml
5656
g = Foo.loads_yaml(o)
5757
assert f == g
58+
59+
60+
def test_abstract_parent_error():
61+
"""This tests that we can define an abstract parent class with the YamlAble behaviour and inherit it"""
62+
63+
with pytest.raises(TypeError):
64+
class AbstractFooE(YamlObject2):
65+
pass
66+
67+
# class FooError(AbstractFooE):
68+
# """
69+
# This class inherits from the parent without redefining a yaml tag
70+
# """
71+
# def __init__(self, a, b):
72+
# self.a = a
73+
# self.b = b
74+
#
75+
# def __eq__(self, other):
76+
# return vars(self) == vars(other)
77+
#
78+
# # instantiate
79+
# e = FooError(1, 'hello')
80+
#
81+
# # dump
82+
# with pytest.raises(NotImplementedError):
83+
# e.dumps_yaml()
84+
85+
86+
def test_abstract_parent():
87+
"""This tests that we can define an abstract parent class with the YamlAble behaviour and inherit it"""
88+
89+
class AbstractFooV(YamlObject2):
90+
# With YamlObject2 as opposed to YamlAble, we have to explicitly disable the yaml_tag field
91+
yaml_tag = None
92+
93+
class FooValid(AbstractFooV):
94+
yaml_tag = '!foo'
95+
96+
def __init__(self, a, b):
97+
self.a = a
98+
self.b = b
99+
100+
def __eq__(self, other):
101+
return vars(self) == vars(other)
102+
103+
# instantiate
104+
f = FooValid(1, 'hello')
105+
106+
# dump to yaml
107+
o = f.dumps_yaml(safe=False)
108+
assert o == "!foo {a: 1, b: hello}\n"
109+
110+
# load from yaml
111+
g = FooValid.loads_yaml(o)
112+
assert f == g

yamlable/yaml_objects.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
from yaml import YAMLObjectMetaclass, YAMLObject, SafeLoader, MappingNode
1717

18-
from yamlable.base import NONE_IGNORE_CHECKS, AbstractYamlObject, read_yaml_node_as_dict
18+
from yamlable.base import AbstractYamlObject, read_yaml_node_as_dict
1919

2020

2121
class YAMLObjectMetaclassStrict(YAMLObjectMetaclass):
@@ -29,18 +29,19 @@ def __init__(cls, # type: Type[YO2]
2929
super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
3030

3131
# if yaml_tag is provided
32-
if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
33-
if cls.yaml_tag != NONE_IGNORE_CHECKS:
32+
if 'yaml_tag' in kwds:
33+
# if cls.yaml_tag != NONE_IGNORE_CHECKS:
34+
if kwds['yaml_tag'] is not None:
3435
cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
3536
cls.yaml_dumper.add_representer(cls, cls.to_yaml)
3637
else:
3738
if 'yaml_tag' in cls.__dict__:
38-
# this is an explicitly disabled class (yaml_tag=NONE_IGNORE_CHECK is set on it), ok
39+
# this is an explicitly disabled class (yaml_tag=None is set on it), ok
3940
pass
4041
else:
41-
# this class inherits from the yaml_tag=NONE_IGNORE_CHECK and does not redefine it, not ok
42+
# this class inherits from the yaml_tag=None and does not redefine it, not ok
4243
raise TypeError("`yaml_tag` field is not redefined by class {}, cannot inherit from YAMLObject "
43-
"properly. Note that abstract classes can use the tag NONE_IGNORE_CHECKS to skip "
44+
"properly. Note that abstract classes can set the tag explicitly to `None` to skip "
4445
"this check. It won't disable the check for their subclasses".format(cls))
4546

4647
else:
@@ -74,7 +75,7 @@ class YamlObject2(six.with_metaclass(ABCYAMLMeta, AbstractYamlObject, YAMLObject
7475
"""
7576
yaml_loader = SafeLoader # explicitly use SafeLoader by default
7677
# yaml_dumper = Dumper
77-
yaml_tag = NONE_IGNORE_CHECKS
78+
yaml_tag = None
7879
# yaml_flow_style = ...
7980

8081
@classmethod

0 commit comments

Comments
 (0)