From 76b0f89d8fcbf88616f62c01551731cc043f86a3 Mon Sep 17 00:00:00 2001 From: Anthon van der Neut Date: Fri, 21 Sep 2018 20:39:48 +0200 Subject: fix #233 fix #231 reverting to subclass dict resp list for CommentedMap/Seq had to reimplement round-trippable mappings with merge-keys to get both JSON dumping and casting using dict(**some_commented_map) to work. (JSON dump of merge maps not tested) *When this change indeed resolves your problem, please **Close** this issue*. *(You can do so using the WorkFlow pull-down (close to the top right of this page))* --- CHANGES | 8 ++++++++ README.rst | 12 ++++++++++-- __init__.py | 4 ++-- _doc/_static/pypi.svg | 2 +- _test/test_issues.py | 21 +++++++++++++-------- comments.py | 50 +++++++++++++++++++++++++++----------------------- 6 files changed, 61 insertions(+), 36 deletions(-) diff --git a/CHANGES b/CHANGES index 6b8208e..2b64da8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +[0, 15, 70]: 2018-09-21 + - reverted CommentedMap and CommentedSeq to subclass ordereddict resp. list, + reimplemented merge maps so that both ``dict(**commented_map_instance)`` and JSON + dumping works. This also allows checking with ``isinstance()`` on ``dict`` resp. ``list``. + (Proposed by `Stuart Berg `__, with feedback + from `blhsing `__ on + `StackOverflow `__) + [0, 15, 69]: 2018-09-20 - fix issue with dump_all gobbling end-of-document comments on parsing (reported by `Pierre B. `__) diff --git a/README.rst b/README.rst index 607fd5e..04f6783 100644 --- a/README.rst +++ b/README.rst @@ -4,8 +4,8 @@ ruamel.yaml ``ruamel.yaml`` is a YAML 1.2 loader/dumper package for Python. -:version: 0.15.69 -:updated: 2018-09-20 +:version: 0.15.70 +:updated: 2018-09-21 :documentation: http://yaml.readthedocs.io :repository: https://bitbucket.org/ruamel/ :pypi: https://pypi.org/project/ruamel.yaml/ @@ -54,6 +54,14 @@ ChangeLog .. should insert NEXT: at the beginning of line for next key (with empty line) +0.15.70 (2018-09-21): + - reverted CommentedMap and CommentedSeq to subclass ordereddict resp. list, + reimplemented merge maps so that both ``dict(**commented_map_instance)`` and JSON + dumping works. This also allows checking with ``isinstance()`` on ``dict`` resp. ``list``. + (Proposed by `Stuart Berg `__, with feedback + from `blhsing `__ on + `StackOverflow `__) + 0.15.69 (2018-09-20): - fix issue with dump_all gobbling end-of-document comments on parsing (reported by `Pierre B. `__) diff --git a/__init__.py b/__init__.py index 16bfa0c..6ffb2d4 100644 --- a/__init__.py +++ b/__init__.py @@ -7,8 +7,8 @@ if False: # MYPY _package_data = dict( full_package_name='ruamel.yaml', - version_info=(0, 15, 69), - __version__='0.15.69', + version_info=(0, 15, 70), + __version__='0.15.70', author='Anthon van der Neut', author_email='a.van.der.neut@ruamel.eu', description='ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order', # NOQA diff --git a/_doc/_static/pypi.svg b/_doc/_static/pypi.svg index c73ced3..8b244c0 100644 --- a/_doc/_static/pypi.svg +++ b/_doc/_static/pypi.svg @@ -1 +1 @@ - pypipypi0.15.690.15.69 + pypipypi0.15.700.15.70 diff --git a/_test/test_issues.py b/_test/test_issues.py index 2651cff..80be4f4 100644 --- a/_test/test_issues.py +++ b/_test/test_issues.py @@ -187,13 +187,12 @@ class TestIssues: assert res == yaml_str.replace(' b ', ' B ').replace(' d\n', ' D\n') def test_issue_176_test_slicing(self): - from ruamel.yaml.comments import CommentedSeq + from ruamel.yaml.compat import PY2 mss = round_trip_load('[0, 1, 2, 3, 4]') assert len(mss) == 5 assert mss[2:2] == [] assert mss[2:4] == [2, 3] - assert isinstance(mss[2:4], CommentedSeq) assert mss[1::2] == [1, 3] # slice assignment @@ -219,10 +218,18 @@ class TestIssues: m[1::2] = [42, 43] assert m == [0, 42, 2, 43, 4] m = mss[:] - with pytest.raises(TypeError, match='too many'): - m[1::2] = [42, 43, 44] - with pytest.raises(TypeError, match='not enough'): - m[1::2] = [42] + if PY2: + with pytest.raises(ValueError, match='attempt to assign'): + m[1::2] = [42, 43, 44] + else: + with pytest.raises(TypeError, match='too many'): + m[1::2] = [42, 43, 44] + if PY2: + with pytest.raises(ValueError, match='attempt to assign'): + m[1::2] = [42] + else: + with pytest.raises(TypeError, match='not enough'): + m[1::2] = [42] m = mss[:] m += [5] m[1::2] = [42, 43, 44] @@ -408,7 +415,6 @@ class TestIssues: with pytest.raises(ruamel.yaml.parser.ParserError): yaml.safe_load('{]') - # @pytest.mark.xfail(strict=True, reason='not a dict subclass', raises=TypeError) def test_issue_233(self): from ruamel.yaml import YAML import json @@ -417,7 +423,6 @@ class TestIssues: data = yaml.load('{}') json_str = json.dumps(data) # NOQA - @pytest.mark.xfail(strict=True, reason='not a list subclass', raises=TypeError) def test_issue_233a(self): from ruamel.yaml import YAML import json diff --git a/comments.py b/comments.py index 9730f0a..b54f45a 100644 --- a/comments.py +++ b/comments.py @@ -385,16 +385,16 @@ class CommentedBase(object): raise NotImplementedError -class CommentedSeq(MutableSliceableSequence, CommentedBase): +class CommentedSeq(MutableSliceableSequence, list, CommentedBase): __slots__ = (Comment.attrib, '_lst') def __init__(self, *args, **kw): # type: (Any, Any) -> None - self._lst = list(*args, **kw) + list.__init__(self, *args, **kw) def __getsingleitem__(self, idx): # type: (Any) -> Any - return self._lst[idx] + return list.__getitem__(self, idx) def __setsingleitem__(self, idx, value): # type: (Any, Any) -> None @@ -406,11 +406,11 @@ class CommentedSeq(MutableSliceableSequence, CommentedBase): and isinstance(self[idx], ScalarString) ): value = type(self[idx])(value) - self._lst.__setitem__(idx, value) + list.__setitem__(self, idx, value) def __delsingleitem__(self, idx=None): # type: (Any) -> Any - del self._lst[idx] + list.__delitem__(self, idx) self.ca.items.pop(idx, None) # might not be there -> default value for list_index in sorted(self.ca.items): if list_index < idx: @@ -419,12 +419,12 @@ class CommentedSeq(MutableSliceableSequence, CommentedBase): def __len__(self): # type: () -> int - return len(self._lst) + return list.__len__(self) def insert(self, idx, val): # type: (Any, Any) -> None """the comments after the insertion have to move forward""" - self._lst.insert(idx, val) + list.insert(self, idx, val) for list_index in sorted(self.ca.items, reverse=True): if list_index < idx: break @@ -432,11 +432,11 @@ class CommentedSeq(MutableSliceableSequence, CommentedBase): def extend(self, val): # type: (Any) -> None - self._lst.extend(val) + list.extend(self, val) def __eq__(self, other): # type: (Any) -> bool - return bool(self._lst == other) + return list.__eq__(self, other) def _yaml_add_comment(self, comment, key=NoComment): # type: (Any, Optional[Any]) -> None @@ -494,16 +494,18 @@ class CommentedSeq(MutableSliceableSequence, CommentedBase): def __add__(self, other): # type: (Any) -> Any - return self._lst + other + return list.__add__(self, other) - def sort(self, key=None, reverse=False): + def sort(self, key=None, reverse=False): # type: ignore # type: (Any, bool) -> None if key is None: - tmp_lst = sorted(zip(self._lst, range(len(self._lst))), reverse=reverse) - self._lst = [x[0] for x in tmp_lst] + tmp_lst = sorted(zip(self, range(len(self))), reverse=reverse) + list.__init__(self, [x[0] for x in tmp_lst]) else: - tmp_lst = sorted(zip(map(key, self._lst), range(len(self._lst))), reverse=reverse) - self._lst = [self._lst[x[1]] for x in tmp_lst] + tmp_lst = sorted( + zip(map(key, list.__iter__(self)), range(len(self))), reverse=reverse + ) + list.__init__(self, [list.__getitem__(self, x[1]) for x in tmp_lst]) itm = self.ca.items self.ca._items = {} for idx, x in enumerate(tmp_lst): @@ -513,7 +515,7 @@ class CommentedSeq(MutableSliceableSequence, CommentedBase): def __repr__(self): # type: () -> Any - return self._lst.__repr__() + return list.__repr__(self) class CommentedKeySeq(tuple, CommentedBase): @@ -638,13 +640,13 @@ class CommentedMapValuesView(CommentedMapView): class CommentedMap(MutableMapping, ordereddict, CommentedBase): - __slots__ = (Comment.attrib, '_ok', '_ref') # own keys + __slots__ = (Comment.attrib, '_ok', '_ref') def __init__(self, *args, **kw): # type: (Any, Any) -> None - ordereddict.__init__(self, *args, **kw) - self._ok = set() - self._ref = [] + ordereddict.__init__(self, *args, **kw) # type: ignore + self._ok = set() # type: MutableSet[Any] # own keys + self._ref = [] # type: List[CommentedMap] def _yaml_add_comment(self, comment, key=NoComment, value=NoComment): # type: (Any, Optional[Any], Optional[Any]) -> None @@ -707,7 +709,7 @@ class CommentedMap(MutableMapping, ordereddict, CommentedBase): # type: (Any) -> None try: ordereddict.update(self, vals) - self._ok.update(vals.keys()) + self._ok.update(vals.keys()) # type: ignore except TypeError: # probably a dict that is used for x in vals: @@ -780,7 +782,7 @@ class CommentedMap(MutableMapping, ordereddict, CommentedBase): def __contains__(self, key): # type: (Any) -> bool - return ordereddict.__contains__(self, key) + return bool(ordereddict.__contains__(self, key)) def get(self, key, default=None): # type: (Any, Any) -> Any @@ -831,7 +833,7 @@ class CommentedMap(MutableMapping, ordereddict, CommentedBase): def __len__(self): # type: () -> int - return ordereddict.__len__(self) + return ordereddict.__len__(self) # type: ignore def __eq__(self, other): # type: (Any) -> bool @@ -922,6 +924,7 @@ class CommentedMap(MutableMapping, ordereddict, CommentedBase): return x def add_referent(self, cm): + # type: (Any) -> None if cm not in self._ref: self._ref.append(cm) @@ -936,6 +939,7 @@ class CommentedMap(MutableMapping, ordereddict, CommentedBase): self.merge.extend(value) def update_key_value(self, key): + # type: (Any) -> None if key in self._ok: return for v in self.merge: -- cgit v1.2.1