From 68ea6b9ef755296c169b5416fe46ac48250e3c62 Mon Sep 17 00:00:00 2001 From: Anthon van der Neut Date: Fri, 9 Nov 2018 10:47:11 +0100 Subject: fix issue #194 object attributes always sorted Test for this issue in: https://bitbucket.org/ruamel/yaml.data/src/default/object/control_base_representer_mapping_sort.yaml *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 | 7 +++++++ README.rst | 11 +++++++++-- __init__.py | 4 ++-- _doc/_static/pypi.svg | 2 +- _test/test_z_data.py | 40 +++++++++++++++++++++++++++++++++++++++- main.py | 16 ++++++++-------- nodes.py | 15 ++++++++------- representer.py | 13 +++++++------ scalarstring.py | 23 ++++++++++++++++++++++- 9 files changed, 103 insertions(+), 28 deletions(-) diff --git a/CHANGES b/CHANGES index 2c8e973..528789a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,10 @@ +[0, 15, 77]: 2018-11-09 + - setting `yaml.sort_base_mapping_type_on_output = False`, will prevent + explicit sorting by keys in the base representer of mappings. Roundtrip + already did not do this. Usage only makes real sense for Python 3.6+ + (feature request by `Sebastian Gerber `__). + - implement Python version check in YAML metadata in ``_test/test_z_data.py`` + [0, 15, 76]: 2018-11-01 - fix issue with empty mapping and sequence loaded as flow-style (mapping reported by `Min RK `__, sequence diff --git a/README.rst b/README.rst index 09960a4..a9a645b 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.76 -:updated: 2018-11-01 +:version: 0.15.77 +:updated: 2018-11-09 :documentation: http://yaml.readthedocs.io :repository: https://bitbucket.org/ruamel/ :pypi: https://pypi.org/project/ruamel.yaml/ @@ -54,6 +54,13 @@ ChangeLog .. should insert NEXT: at the beginning of line for next key (with empty line) +0.15.77 (2018-11-09): + - setting `yaml.sort_base_mapping_type_on_output = False`, will prevent + explicit sorting by keys in the base representer of mappings. Roundtrip + already did not do this. Usage only makes real sense for Python 3.6+ + (feature request by `Sebastian Gerber `__). + - implement Python version check in YAML metadata in ``_test/test_z_data.py`` + 0.15.76 (2018-11-01): - fix issue with empty mapping and sequence loaded as flow-style (mapping reported by `Min RK `__, sequence diff --git a/__init__.py b/__init__.py index 9b28251..bdc47aa 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, 76), - __version__='0.15.76', + version_info=(0, 15, 77), + __version__='0.15.77', 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 ef9b8ff..b48d281 100644 --- a/_doc/_static/pypi.svg +++ b/_doc/_static/pypi.svg @@ -1 +1 @@ - pypipypi0.15.760.15.76 + pypipypi0.15.770.15.77 diff --git a/_test/test_z_data.py b/_test/test_z_data.py index d106a2a..b0464ba 100644 --- a/_test/test_z_data.py +++ b/_test/test_z_data.py @@ -2,6 +2,7 @@ from __future__ import print_function, unicode_literals +import sys import pytest # NOQA import warnings # NOQA @@ -83,7 +84,10 @@ def pytest_generate_tests(metafunc): paths = sorted(base_path.glob('**/*.yaml')) idlist = [] for path in paths: - idlist.append(path.stem) + stem = path.stem + if stem.startswith('.#'): # skip emacs temporary file + continue + idlist.append(stem) test_yaml.append([path]) metafunc.parametrize(['yaml'], test_yaml, ids=idlist, scope='class') @@ -160,6 +164,9 @@ class TestYAMLData(object): d = docs[0] typ = d.get('type') yaml_version = d.get('yaml_version') + if 'python' in d: + if not check_python_version(d['python']): + pytest.skip("unsupported version") idx += 1 data = output = confirm = python = None for doc in docs[idx:]: @@ -196,3 +203,34 @@ class TestYAMLData(object): else: print('\nrun type unknown:', typ) assert False + + +def check_python_version(match, current=None): + """ + version indication, return True if version matches. + match should be something like 3.6+, or [2.7, 3.3] etc. Floats + are converted to strings. Single values are made into lists. + """ + if current is None: + current = list(sys.version_info[:3]) + if not isinstance(match, list): + match = [match] + for m in match: + minimal = False + if isinstance(m, float): + m = str(m) + if m.endswith('+'): + minimal = True + m = m[:-1] + # assert m[0].isdigit() + # assert m[-1].isdigit() + m = [int(x) for x in m.split('.')] + current_len = current[:len(m)] + # print(m, current, current_len) + if minimal: + if current_len >= m: + return True + else: + if current_len == m: + return True + return False diff --git a/main.py b/main.py index 35613ef..851ae83 100644 --- a/main.py +++ b/main.py @@ -148,6 +148,7 @@ class YAML(object): self.sequence_dash_offset = 0 self.compact_seq_seq = None self.compact_seq_map = None + self.sort_base_mapping_type_on_output = None # default: sort self.top_level_colon_align = None self.prefix_colon = None @@ -289,15 +290,14 @@ class YAML(object): # type: () -> Any attr = '_' + sys._getframe().f_code.co_name if not hasattr(self, attr): - setattr( - self, - attr, - self.Representer( - default_style=self.default_style, - default_flow_style=self.default_flow_style, - dumper=self, - ), + repres = self.Representer( + default_style=self.default_style, + default_flow_style=self.default_flow_style, + dumper=self, ) + if self.sort_base_mapping_type_on_output is not None: + repres.sort_base_mapping_type_on_output = self.sort_base_mapping_type_on_output + setattr(self, attr, repres) return getattr(self, attr) # separate output resolver? diff --git a/nodes.py b/nodes.py index 0a35752..d99e909 100644 --- a/nodes.py +++ b/nodes.py @@ -12,14 +12,14 @@ if False: # MYPY class Node(object): __slots__ = 'tag', 'value', 'start_mark', 'end_mark', 'comment', 'anchor' - def __init__(self, tag, value, start_mark, end_mark, comment=None): - # type: (Any, Any, Any, Any, Any) -> None + def __init__(self, tag, value, start_mark, end_mark, comment=None, anchor=None): + # type: (Any, Any, Any, Any, Any, Any) -> None self.tag = tag self.value = value self.start_mark = start_mark self.end_mark = end_mark self.comment = comment - self.anchor = None + self.anchor = anchor def __repr__(self): # type: () -> str @@ -78,14 +78,15 @@ class ScalarNode(Node): __slots__ = ('style',) id = 'scalar' - def __init__(self, tag, value, start_mark=None, end_mark=None, style=None, comment=None): - # type: (Any, Any, Any, Any, Any, Any) -> None - Node.__init__(self, tag, value, start_mark, end_mark, comment=comment) + def __init__(self, tag, value, start_mark=None, end_mark=None, style=None, comment=None, + anchor=None): + # type: (Any, Any, Any, Any, Any, Any, Any) -> None + Node.__init__(self, tag, value, start_mark, end_mark, comment=comment, anchor=anchor) self.style = style class CollectionNode(Node): - __slots__ = 'flow_style', 'anchor' + __slots__ = ('flow_style', ) def __init__( self, diff --git a/representer.py b/representer.py index e0ac31d..143b7d5 100644 --- a/representer.py +++ b/representer.py @@ -65,6 +65,7 @@ class BaseRepresenter(object): self.represented_objects = {} # type: Dict[Any, Any] self.object_keeper = [] # type: List[Any] self.alias_key = None # type: Optional[int] + self.sort_base_mapping_type_on_output = True @property def serializer(self): @@ -209,10 +210,11 @@ class BaseRepresenter(object): best_style = True if hasattr(mapping, 'items'): mapping = list(mapping.items()) - try: - mapping = sorted(mapping) - except TypeError: - pass + if self.sort_base_mapping_type_on_output: + try: + mapping = sorted(mapping) + except TypeError: + pass for item_key, item_value in mapping: node_key = self.represent_key(item_key) node_value = self.represent_data(item_value) @@ -986,8 +988,7 @@ class RoundTripRepresenter(SafeRepresenter): best_style = False value.append((node_key, node_value)) if flow_style is None: - if ((item_count != 0) or bool(merge_list)) \ - and self.default_flow_style is not None: + if ((item_count != 0) or bool(merge_list)) and self.default_flow_style is not None: node.flow_style = self.default_flow_style else: node.flow_style = best_style diff --git a/scalarstring.py b/scalarstring.py index 03db72d..c7b1a78 100644 --- a/scalarstring.py +++ b/scalarstring.py @@ -3,6 +3,7 @@ from __future__ import print_function, absolute_import, division, unicode_literals from ruamel.yaml.compat import text_type +from ruamel.yaml.anchor import Anchor if False: # MYPY from typing import Text, Any, Dict, List # NOQA @@ -20,7 +21,7 @@ __all__ = [ class ScalarString(text_type): - __slots__ = () + __slots__ = (Anchor.attrib) def __new__(cls, *args, **kw): # type: (Any, Any) -> Any @@ -30,6 +31,26 @@ class ScalarString(text_type): # type: (Any, Any, int) -> Any return type(self)((text_type.replace(self, old, new, maxreplace))) + @property + def anchor(self): + # type: () -> Any + if not hasattr(self, Anchor.attrib): + setattr(self, Anchor.attrib, Anchor()) + return getattr(self, Anchor.attrib) + + def yaml_anchor(self): + # type: () -> Any + if not hasattr(self, Anchor.attrib): + return None + if not self.anchor.always_dump: + return None + return self.anchor + + def yaml_set_anchor(self, value, always_dump=False): + # type: (Any, bool) -> None + self.anchor.value = value + self.anchor.always_dump = always_dump + class LiteralScalarString(ScalarString): __slots__ = 'comment' # the comment after the | on the first line -- cgit v1.2.1