Hello community, here is the log from the commit of package python-strictyaml for openSUSE:Factory checked in at 2019-10-10 11:53:30 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-strictyaml (Old) and /work/SRC/openSUSE:Factory/.python-strictyaml.new.2352 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-strictyaml" Thu Oct 10 11:53:30 2019 rev:3 rq:736798 version:1.0.5 Changes: -------- --- /work/SRC/openSUSE:Factory/python-strictyaml/python-strictyaml.changes 2019-09-30 15:58:44.201322373 +0200 +++ /work/SRC/openSUSE:Factory/.python-strictyaml.new.2352/python-strictyaml.changes 2019-10-10 11:53:30.847310271 +0200 @@ -1,0 +2,8 @@ +Thu Oct 10 08:20:48 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 1.0.5: + * BUGFIX : Fixed python 2 bug introduced when fixing #72. + * FEATURE : Include tests / stories in package. + * BUG: issue #72. Now setitem uses schema. + +------------------------------------------------------------------- Old: ---- strictyaml-1.0.3.tar.gz New: ---- strictyaml-1.0.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-strictyaml.spec ++++++ --- /var/tmp/diff_new_pack.CsSoUF/_old 2019-10-10 11:53:31.415308763 +0200 +++ /var/tmp/diff_new_pack.CsSoUF/_new 2019-10-10 11:53:31.419308753 +0200 @@ -18,11 +18,10 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-strictyaml -Version: 1.0.3 +Version: 1.0.5 Release: 0 Summary: Strict, typed YAML parser License: MIT -Group: Development/Languages/Python URL: https://hitchdev.com/strictyaml Source: https://github.com/crdoconnor/strictyaml/archive/%{version}.tar.gz#/strictyaml-%{version}.tar.gz BuildRequires: %{python_module setuptools} ++++++ strictyaml-1.0.3.tar.gz -> strictyaml-1.0.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/MANIFEST.in new/strictyaml-1.0.5/MANIFEST.in --- old/strictyaml-1.0.3/MANIFEST.in 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/MANIFEST.in 2019-10-05 17:01:06.000000000 +0200 @@ -1,3 +1,6 @@ include VERSION include LICENSE.txt include README.md +recursive-include hitch * +prune hitch/__pycache__ +prune hitch/gen diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/VERSION new/strictyaml-1.0.5/VERSION --- old/strictyaml-1.0.3/VERSION 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/VERSION 2019-10-05 17:01:06.000000000 +0200 @@ -1 +1 @@ -1.0.3 \ No newline at end of file +1.0.5 \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/hitch/engine.py new/strictyaml-1.0.5/hitch/engine.py --- old/strictyaml-1.0.3/hitch/engine.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/hitch/engine.py 2019-10-05 17:01:06.000000000 +0200 @@ -2,6 +2,7 @@ from hitchstory import GivenDefinition, GivenProperty, InfoDefinition, InfoProperty from templex import Templex from strictyaml import Optional, Str, Map, Int, Bool, Enum, load +from path import Path import hitchpylibrarytoolkit from hitchrunpy import ( ExamplePythonCode, @@ -61,12 +62,16 @@ if not self.path.profile.exists(): self.path.profile.mkdir() - self.python = hitchpylibrarytoolkit.project_build( - "strictyaml", - self.path, - self.given["python version"], - {"ruamel.yaml": self.given["ruamel version"]}, - ).bin.python + if not self.settings.get("python_path"): + self.python = hitchpylibrarytoolkit.project_build( + "strictyaml", + self.path, + self.given["python version"], + {"ruamel.yaml": self.given["ruamel version"]}, + ).bin.python + else: + self.python = Path(self.settings.get("python_path")) + assert self.python.exists() self.example_py_code = ( ExamplePythonCode(self.python, self.path.gen) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/hitch/key.py new/strictyaml-1.0.5/hitch/key.py --- old/strictyaml-1.0.3/hitch/key.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/hitch/key.py 2019-10-05 17:01:06.000000000 +0200 @@ -111,6 +111,16 @@ storybook.with_params(**{"python version": "3.7.0"}).ordered_by_name().play() +@expected(HitchStoryException) +def regression_on_python_path(python_path, python_version): + """ + Run regression tests - e.g. hk regression_on_python_path /usr/bin/python 3.7.0 + """ + _storybook({ + "python_path": python_path, + }).with_params(**{"python version": python_version}).only_uninherited().ordered_by_name().play() + + def reformat(): """ Reformat using black and then relint. @@ -167,12 +177,12 @@ @expected(CommandError) -def rerun(version="3.7.0"): +def rerun(): """ Rerun last example code block with specified version of python. """ from commandlib import Command - + version = _personal_settings().data['params']['python version'] Command(DIR.gen.joinpath("py{0}".format(version), "bin", "python"))( - DIR.gen.joinpath("state", "examplepythoncode.py") - ).in_dir(DIR.gen.joinpath("state")).run() + DIR.gen.joinpath("working", "examplepythoncode.py") + ).in_dir(DIR.gen.joinpath("working")).run() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/hitch/story/update-with-schema.story new/strictyaml-1.0.5/hitch/story/update-with-schema.story --- old/strictyaml-1.0.3/hitch/story/update-with-schema.story 1970-01-01 01:00:00.000000000 +0100 +++ new/strictyaml-1.0.5/hitch/story/update-with-schema.story 2019-10-05 17:01:06.000000000 +0200 @@ -0,0 +1,93 @@ +Updating document with a schema: + docs: compound/update + based on: strictyaml + description: | + When StrictYAML loads a document with a schema, it checks that future + updates to that document follow the original schema. + given: + setup: | + import strictyaml as s + from ensure import Ensure + variations: + GitHub \#72: + steps: + - Run: |- + doc = s.load('a: 9', s.Map({ + 'a': s.Str(), + s.Optional('b'): s.Int(), + })) + doc['b'] = 9 + assert doc['b'] == 9 + + Works on empty mapping: + steps: + - Run: |- + doc = s.load('', s.EmptyDict() | s.Map({ + 'a': s.Int(), + })) + doc['a'] = 9 + assert doc['a'] == 9, doc.as_yaml() + + Works on complex types: + steps: + - Run: |- + doc = s.load('a: 8', s.Map({'a': s.Int() | s.Float()})) + assert type(doc['a'].data) == int, repr(doc.data) + doc['a'] = '5.' + assert type(doc['a'].data) == float, repr(doc.data) + assert doc['a'] == 5. + + Will not work on empty sequence: + steps: + - Run: + code: | + doc = s.load('', s.EmptyList() | s.Seq(s.Int())) + doc[0] = 9 + raises: + type: strictyaml.exceptions.YAMLSerializationError + message: |- + cannot extend list via __setitem__. Instead, replace whole list on parent node. + + Works on map with setting, updating, and then setting multiple keys (regression): + steps: + - Run: + code: | + doc = s.load('', s.EmptyDict() | s.MapPattern( + s.Str(), + s.EmptyDict() | s.Map({ + s.Optional('b'): s.Seq(s.Int()), + }) + )) + doc['a'] = {} + doc['a']['b'] = ['9'] + assert doc.data == {'a': {'b': [9]}}, doc.data + assert doc.as_yaml() == 'a:\n b:\n - 9\n', doc.as_yaml() + # Second assignment doesn't occur... + doc['a']['b'] = ['9', '10'] + assert doc.data == {'a': {'b': [9, 10]}}, doc.data + assert doc.as_yaml() == 'a:\n b:\n - 9\n - 10\n', doc.as_yaml() + # If and only if another node is overwritten. This was a bug due + # to mismatched _ruamelparsed objects. + doc['b'] = {'b': ['11']} + assert doc['a']['b'].data == [9, 10], doc.data + assert doc['b']['b'].data == [11], doc.data + assert doc.as_yaml() == 'a:\n b:\n - 9\n - 10\nb:\n b:\n - 11\n', doc.as_yaml() + + For empty sequence, must instead assign whole sequence as key: + steps: + - Run: |- + doc = s.load('a:', s.Map({'a': s.EmptyList() | s.Seq(s.Int())})) + doc['a'] = [1, 2, 3] + assert doc['a'].data == [1, 2, 3], repr(doc.data) + + + Can assign from string: + steps: + - Run: |- + doc = s.load('a: 9', s.Map({ + 'a': s.Str(), + s.Optional('b'): s.Int(), + })) + doc['b'] = '9' + assert doc['b'] == 9 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/strictyaml/any_validator.py new/strictyaml-1.0.5/strictyaml/any_validator.py --- old/strictyaml-1.0.3/strictyaml/any_validator.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/strictyaml/any_validator.py 2019-10-05 17:01:06.000000000 +0200 @@ -2,7 +2,7 @@ from strictyaml.compound import FixedSeq, Map from strictyaml.validators import Validator from strictyaml.exceptions import YAMLSerializationError -from strictyaml.scalar import Str +from strictyaml.scalar import Bool, EmptyDict, EmptyList, Float, Int, Str def schema_from_document(document): @@ -16,19 +16,29 @@ return Str() -def schema_from_data(data): +def schema_from_data(data, allow_empty): if isinstance(data, dict): if len(data) == 0: + if allow_empty: + return EmptyDict() raise YAMLSerializationError( "Empty dicts are not serializable to StrictYAML unless schema is used." ) - return Map({key: schema_from_data(value) for key, value in data.items()}) + return Map({key: schema_from_data(value, allow_empty) for key, value in data.items()}) elif isinstance(data, list): if len(data) == 0: + if allow_empty: + return EmptyList() raise YAMLSerializationError( "Empty lists are not serializable to StrictYAML unless schema is used." ) - return FixedSeq([schema_from_data(item) for item in data]) + return FixedSeq([schema_from_data(item, allow_empty) for item in data]) + elif isinstance(data, bool): + return Bool() + elif isinstance(data, int): + return Int() + elif isinstance(data, float): + return Float() else: return Str() @@ -41,8 +51,13 @@ def validate(self, chunk): return schema_from_document(chunk.contents)(chunk) - def to_yaml(self, data): - return schema_from_data(data).to_yaml(data) + def to_yaml(self, data, allow_empty=False): + """ + Args: + allow_empty (bool): True to allow EmptyDict and EmptyList in the + schema generated from the data. + """ + return schema_from_data(data, allow_empty=allow_empty).to_yaml(data) @property def key_validator(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/strictyaml/compound.py new/strictyaml-1.0.5/strictyaml/compound.py --- old/strictyaml-1.0.3/strictyaml/compound.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/strictyaml/compound.py 2019-10-05 17:01:06.000000000 +0200 @@ -1,6 +1,7 @@ from strictyaml.exceptions import YAMLSerializationError, InvalidOptionalDefault from strictyaml.validators import Validator, MapValidator, SeqValidator from ruamel.yaml.comments import CommentedMap, CommentedSeq +from strictyaml.representation import YAML from strictyaml.scalar import ScalarValidator, Str from strictyaml.yamllocation import YAMLChunk import sys @@ -176,7 +177,17 @@ # marked_up = new_value.as_marked_up() # chunk.contents[chunk.ruamelindex(strictindex)] = marked_up chunk.add_key_association(default_key, strictindex) - chunk.strictparsed()[yaml_key] = updated_value + sp = chunk.strictparsed() + if isinstance(sp, YAML): + # Do not trigger __setitem__ validation at this point, as + # we just ran the validator, and + # representation.py:revalidate() doesn't overwrite the + # _validator property until after all values are checked, + # which leads to an exception being raised if it is + # re-checked. + sp._value[yaml_key] = updated_value + else: + sp[yaml_key] = updated_value if not set(self._required_keys).issubset(found_keys): chunk.while_parsing_found( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/strictyaml/representation.py new/strictyaml-1.0.5/strictyaml/representation.py --- old/strictyaml-1.0.3/strictyaml/representation.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/strictyaml/representation.py 2019-10-05 17:01:06.000000000 +0200 @@ -1,6 +1,5 @@ from ruamel.yaml.comments import CommentedSeq, CommentedMap -from ruamel.yaml.scalarstring import PreservedScalarString -from strictyaml.exceptions import raise_type_error +from strictyaml.exceptions import raise_type_error, YAMLSerializationError from strictyaml.yamllocation import YAMLChunk from strictyaml.dumper import StrictYAMLDumper from ruamel.yaml import dump @@ -192,43 +191,50 @@ def __setitem__(self, index, value): strictindex = self._strictindex(index) - try: - value_validator = self._value[strictindex].validator - except KeyError: - # TODO: What if value isn't a YAML object? - value_validator = value.validator - - new_value = ( - value_validator(value._chunk) - if isinstance(value, YAML) - else value_validator(YAMLChunk(value_validator.to_yaml(value))) - ) - # Fork the value - forked_chunk = self._chunk.fork(strictindex, new_value) - - # Validate and attach to current structure - if self.is_mapping(): - updated_value = value_validator(forked_chunk.val(strictindex)) - updated_value._chunk.make_child_of(self._chunk.val(strictindex)) + # Generate nice error messages - first, copy our whole node's data + # and use ``to_yaml()`` to determine if the resulting data would + # validate our schema. Must replace whole current node to support + # complex types, e.g. ``EmptyList() | Seq(Str())``. + if isinstance(value, YAML): + yaml_value = self._chunk.fork(strictindex, value) + new_value = self._validator(yaml_value) else: - updated_value = value_validator(forked_chunk.index(strictindex)) - updated_value._chunk.make_child_of(self._chunk.index(strictindex)) - - marked_up = new_value.as_marked_up() - - # So that the nicer x: | style of text is used instead of - # x: "text\nacross\nlines" - if isinstance(marked_up, (str, unicode)): - if u"\n" in marked_up: - marked_up = PreservedScalarString(marked_up) - - self._chunk.contents[self._chunk.ruamelindex(strictindex)] = marked_up - self._value[ - YAML(forked_chunk.ruamelindex(strictindex)) - if self.is_mapping() - else forked_chunk.ruamelindex(strictindex) - ] = new_value + old_data = self.data + if isinstance(old_data, dict): + old_data[index] = value + elif isinstance(old_data, list): + if len(old_data) <= index: + raise YAMLSerializationError('cannot extend list via __setitem__. ' + 'Instead, replace whole list on parent ' + 'node.') + old_data[index] = value + else: + raise NotImplementedError(repr(old_data)) + yaml_value = YAMLChunk(self._validator.to_yaml(old_data)) + yaml_value_repr = self._validator(yaml_value) + + # Now that the new content is properly validated, create a valid + # chunk with the new information. + forked_chunk = self._chunk.fork(strictindex, yaml_value_repr[strictindex]) + new_value = self._validator(forked_chunk) + + # Now, overwrite our chunk and value with the new information. + old_chunk = self._chunk # Needed for reference to pre-fork ruamel + self._chunk = new_value._chunk + self._value = new_value._value + self._text = new_value._text + self._selected_validator = new_value._selected_validator + # Update any parent ruamel links to point to our new chunk. + self._chunk.pointer.set(old_chunk, '_ruamelparsed', + new_value._chunk.contents) + self._chunk.pointer.set(old_chunk, '_strictparsed', + self, strictdoc=True) + # forked chunk made a deep copy of the original document, but we just + # updated pointers in the original document. So, restore our chunk to + # pointing at the original document. + self._chunk._ruamelparsed = old_chunk._ruamelparsed + self._chunk._strictparsed = old_chunk._strictparsed def __delitem__(self, index): strictindex = self._strictindex(index) @@ -329,9 +335,7 @@ return isinstance(self._value, CommentedSeq) def is_scalar(self): - return not isinstance(self._value, CommentedSeq) and not isinstance( - self._value, CommentedMap - ) + return not self.is_mapping() and not self.is_sequence() @property def scalar(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/strictyaml/scalar.py new/strictyaml-1.0.5/strictyaml/scalar.py --- old/strictyaml-1.0.3/strictyaml/scalar.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/strictyaml/scalar.py 2019-10-05 17:01:06.000000000 +0200 @@ -8,6 +8,7 @@ import decimal import sys import re +from ruamel.yaml.scalarstring import PreservedScalarString if sys.version_info[0] == 3: @@ -148,7 +149,9 @@ def to_yaml(self, data): if not utils.is_string(data): raise YAMLSerializationError("'{}' is not a string".format(data)) - return str(data) + if "\n" in data: + return PreservedScalarString(data) + return data class Int(ScalarValidator): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/strictyaml/utils.py new/strictyaml-1.0.5/strictyaml/utils.py --- old/strictyaml-1.0.3/strictyaml/utils.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/strictyaml/utils.py 2019-10-05 17:01:06.000000000 +0200 @@ -77,10 +77,13 @@ >>> is_decimal("3.5") True + >>> is_decimal("4.") + True + >>> is_decimal("blah") False """ - return compile(r"^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$").match(value) is not None + return compile(r"^[-+]?[0-9]*(\.[0-9]*)?([eE][-+]?[0-9]+)?$").match(value) is not None def comma_separated_positions(text): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/strictyaml/yamllocation.py new/strictyaml-1.0.5/strictyaml/yamllocation.py --- old/strictyaml-1.0.3/strictyaml/yamllocation.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/strictyaml/yamllocation.py 2019-10-05 17:01:06.000000000 +0200 @@ -149,6 +149,12 @@ label=self.label, key_association=copy(self._key_association), ) + if self.is_scalar(): + # Necessary for e.g. EmptyDict, which reports as a scalar. + forked_chunk.pointer.set(forked_chunk, '_ruamelparsed', + CommentedMap()) + forked_chunk.pointer.set(forked_chunk, '_strictparsed', + CommentedMap(), strictdoc=True) forked_chunk.contents[self.ruamelindex(strictindex)] = new_value.as_marked_up() forked_chunk.strictparsed()[strictindex] = deepcopy(new_value.as_marked_up()) return forked_chunk diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/strictyaml-1.0.3/strictyaml/yamlpointer.py new/strictyaml-1.0.5/strictyaml/yamlpointer.py --- old/strictyaml-1.0.3/strictyaml/yamlpointer.py 2019-09-12 20:21:25.000000000 +0200 +++ new/strictyaml-1.0.5/strictyaml/yamlpointer.py 2019-10-05 17:01:06.000000000 +0200 @@ -187,5 +187,43 @@ raise RuntimeError("Invalid state") return segment + def set(self, src_obj, src_attr, new_ruamel, strictdoc=False): + """Since set() needs to overwrite what this pointer points to, it + affects the parent object. Therefore, rather than taking "document" + as get(), it takes the object which holds the document and the name + of the property which is the document. + """ + obj_last = src_obj + key_last = src_attr + r = getattr(src_obj, src_attr) + for index_type, index in self._indices: + obj_last = r + if index_type == "val": + key_last = index[1] if strictdoc else index[0] + r = r[key_last] + elif index_type == "index": + key_last = index + r = r[key_last] + elif index_type == "textslice": + key_last = None + r = r[index[0] : index[1]] + elif index_type == "key": + key_last = None + r = index[1] if strictdoc else index[0] + else: + raise RuntimeError("Invalid state") + if obj_last is src_obj: + # Starts with an attribute set + setattr(src_obj, src_attr, new_ruamel) + elif key_last is not None: + # Others are item set + if hasattr(obj_last, '_value'): + # Only want to overwrite value, do NOT re-validate schema... + obj_last._value[key_last] = new_ruamel + else: + obj_last[key_last] = new_ruamel + else: + raise NotImplementedError("invalid key, cannot set") + def __repr__(self): return "<YAMLPointer: {0}>".format(self._indices)