Skip to content

Commit

Permalink
Finished adding RegEx and updated yamlpath tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wwkimball committed May 9, 2019
1 parent 7393ca3 commit b872a90
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 69 deletions.
104 changes: 102 additions & 2 deletions tests/test_yamlpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@
from distutils.util import strtobool

from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedSeq, CommentedMap
from ruamel.yaml.scalarstring import PlainScalarString
from ruamel.yaml.scalarbool import ScalarBoolean
from ruamel.yaml.scalarfloat import ScalarFloat
from ruamel.yaml.scalarint import ScalarInt

from yamlpath import YAMLPath
from yamlpath.exceptions import YAMLPathException
from yamlpath.enums import YAMLValueFormats
from yamlpath.enums import (
YAMLValueFormats,
PathSegmentTypes,
PathSearchMethods,
)
from yamlpath.wrappers import ConsolePrinter

@pytest.fixture
Expand Down Expand Up @@ -123,7 +132,7 @@ def yamldata():
children:
first: ju
second: ju ichi
third: ji ni
third: ji ni # Deliberate typo for testing (should be "ju ni")
hash_of_hashes:
child1:
id: 1
Expand Down Expand Up @@ -285,6 +294,11 @@ def test_empty_set_nodes(yamlpath, yamldata):
("namespaced::hash.with_array_of_hashes[id>=3].name", "san"),
(r"namespaced::hash.with_array_of_hashes[name!%i].id", 3),
(r"[.^top_][.^key][.^child][attr_tst=child\ 2]", "child 2"),
(r"complex.hash_of_hashes[.=~/^child\d+/].children[third=~/^j[^u]\s\w+$/]", "ji ni"),
(r"complex.hash_of_hashes[.=~/^child[0-9]+/].children[third=~/^j[^u] \w+$/]", "ji ni"),
(r"complex.hash_of_hashes[.=~_^child\d+_].children[third=~#^j[^u] \w+$#]", "ji ni"),
(r"complex.hash_of_hashes[ . =~ !^child\d+! ].children[ third =~ a^j[^u] \w+$a ]", "ji ni"),
(r"complex.hash_of_hashes[.=~ -^child\d+-].children[third =~ $^j[^u] \w+$]", "ji ni"),
])
def test_happy_singular_get_leaf_nodes(yamlpath, yamldata, search, compare):
for node in yamlpath.get_nodes(yamldata, search):
Expand Down Expand Up @@ -431,3 +445,89 @@ def test_yamlpatherror_str(yamlpath, yamldata, search, compare, vformat, mexist)
def test_bad_value_format(yamlpath, yamldata):
with pytest.raises(NameError):
yamlpath.set_value(yamldata, "aliases[&test_scalarstring]", "Poorly formatted value.", value_format="no_such_format", mustexist=False)

@pytest.mark.parametrize("val,typ", [
([], CommentedSeq),
({}, CommentedMap),
("", PlainScalarString),
(1, ScalarInt),
(1.1, ScalarFloat),
(True, ScalarBoolean),
(SimpleNamespace(), SimpleNamespace),
])
def test_wrap_type(val, typ):
assert isinstance(YAMLPath.wrap_type(val), typ)

def test_default_for_child_none(yamlpath):
assert isinstance(yamlpath._default_for_child(None, ""), str)

@pytest.mark.parametrize("path,typ", [
([(True, False)], ScalarBoolean),
([(str, "")], PlainScalarString),
([(int, 1)], ScalarInt),
([(float, 1.1)], ScalarFloat),
])
def test_default_for_child(yamlpath, path, typ):
assert isinstance(yamlpath._default_for_child(path, path[0][1]), typ)

def test_notimplementeds(yamlpath, yamldata):
with pytest.raises(NotImplementedError):
yamlpath.set_value(yamldata, "namespaced::hash[&newAnchor]", "New Value")

def test_scalar_search(yamlpath, yamldata):
for node in yamlpath._search(yamldata["top_scalar"], [True, PathSearchMethods.EQUALS, ".", "top_scalar"]):
assert node is not None

def test_nonexistant_path_search_method(yamlpath, yamldata):
from enum import Enum
from yamlpath.enums import PathSearchMethods
names = [m.name for m in PathSearchMethods] + ['DNF']
PathSearchMethods = Enum('PathSearchMethods', names)

with pytest.raises(NotImplementedError):
for _ in yamlpath._search(yamldata["top_scalar"], [True, PathSearchMethods.DNF, ".", "top_scalar"]):
pass

def test_nonexistant_path_segment_types(yamlpath, yamldata):
from enum import Enum
from yamlpath.enums import PathSegmentTypes
names = [m.name for m in PathSegmentTypes] + ['DNF']
PathSegmentTypes = Enum('PathSegmentTypes', names)

with pytest.raises(NotImplementedError):
for _ in yamlpath._get_elements_by_ref(yamldata, (PathSegmentTypes.DNF, False)):
pass

def test_append_list_element_value_error(yamlpath):
with pytest.raises(ValueError):
yamlpath._append_list_element([], PathSearchMethods, "anchor")

def test_get_elements_by_bad_ref(yamlpath, yamldata):
with pytest.raises(YAMLPathException):
for _ in yamlpath._get_elements_by_ref(yamldata, (PathSegmentTypes.INDEX, "4F")):
pass

def test_get_elements_by_none_refs(yamlpath, yamldata):
tally = 0
for _ in yamlpath._get_elements_by_ref(None, (PathSegmentTypes.INDEX, "4F")):
tally += 1

for _ in yamlpath._get_elements_by_ref(yamldata, None):
tally += 1

assert tally == 0

@pytest.mark.parametrize("newval,newform", [
("new value", YAMLValueFormats.LITERAL),
(1.1, YAMLValueFormats.FLOAT),
])
def test_update_value(yamlpath, yamldata, newval, newform):
yamlpath._update_value(yamldata, yamldata["top_scalar"], newval, newform)

@pytest.mark.parametrize("newval,newform", [
("4F", YAMLValueFormats.INT),
("4.F", YAMLValueFormats.FLOAT),
])
def test_bad_update_value(yamlpath, yamldata, newval, newform):
with pytest.raises(SystemExit):
yamlpath._update_value(yamldata, yamldata["top_scalar"], newval, newform)
106 changes: 39 additions & 67 deletions yamlpath/yamlpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,9 @@ def set_value(self, data, yaml_path, value, **kwargs):
)
found_nodes = 0
for node in self._get_nodes(data, path):
if node is None:
continue

found_nodes += 1
self._update_value(data, node, value, value_format)
if node is not None:
found_nodes += 1
self._update_value(data, node, value, value_format)

if found_nodes < 1:
raise YAMLPathException(
Expand All @@ -147,9 +145,8 @@ def set_value(self, data, yaml_path, value, **kwargs):
.format(self.parser.str_path(path))
)
for node in self._ensure_path(data, path, value):
if node is None:
continue
self._update_value(data, node, value, value_format)
if node is not None:
self._update_value(data, node, value, value_format)

def _get_nodes(self, data, yaml_path):
"""Generates zero or more matching, pre-existing nodes from YAML data
Expand Down Expand Up @@ -180,18 +177,16 @@ def _get_nodes(self, data, yaml_path):

# The next element must already exist
for node in self._get_elements_by_ref(data, curref):
if node is None:
continue

matches += 1
self.log.debug(
("YAMLPath::_get_nodes: Found element {} in the data;"
+ " recursing into it..."
).format(curele)
)
for epn in self._get_nodes(node, yaml_path.copy()):
if epn is not None:
yield epn
if node is not None:
matches += 1
self.log.debug(
("YAMLPath::_get_nodes: Found element {} in the data;"
+ " recursing into it..."
).format(curele)
)
for epn in self._get_nodes(node, yaml_path.copy()):
if epn is not None:
yield epn

if not matches:
return None
Expand Down Expand Up @@ -321,7 +316,7 @@ def update_refs(data, reference_node, replacement_node):
try:
newval = int(value)
except ValueError:
self.log.error("Not an integer: {}".format(value), 1)
self.log.critical("Not an integer: {}".format(value), 1)

if new_node is None:
if hasattr(source_node, "anchor"):
Expand All @@ -346,16 +341,13 @@ def _get_elements_by_ref(self, data, ref):
PathSegmentTypes value.
"""
if data is None or ref is None:
return None
return

reftyp = ref[0]
refele = ref[1]

if reftyp == PathSegmentTypes.KEY:
if isinstance(data, dict) and refele in data:
yield data[refele]
else:
return None
elif reftyp == PathSegmentTypes.INDEX:
try:
intele = int(refele)
Expand All @@ -367,8 +359,6 @@ def _get_elements_by_ref(self, data, ref):

if isinstance(data, list) and len(data) > intele:
yield data[intele]
else:
return None
elif reftyp == PathSegmentTypes.ANCHOR:
if isinstance(data, list):
for ele in data:
Expand All @@ -378,15 +368,10 @@ def _get_elements_by_ref(self, data, ref):
for _, val in data.items():
if hasattr(val, "anchor") and refele == val.anchor.value:
yield val
else:
return None
elif reftyp == PathSegmentTypes.SEARCH:
for match in self._search(data, refele):
if match is None:
continue
else:
if match is not None:
yield match
return None
else:
raise NotImplementedError

Expand All @@ -405,12 +390,6 @@ def _append_list_element(self, data, value=None, anchor=None):
"""

if anchor is not None and value is not None:
self.log.debug(
("YAMLPath::_append_list_element: Ensuring {}{} is a"
+ " PlainScalarString."
).format(type(value), value)
)

value = YAMLPath.wrap_type(value)
if not hasattr(value, "anchor"):
raise ValueError(
Expand Down Expand Up @@ -565,8 +544,6 @@ def search_matches(method, needle, haystack):
if (matches and not invert) or (invert and not matches):
yield data

yield None

def _ensure_path(self, data, path, value=None):
"""Returns zero or more pre-existing nodes matching a YAML Path, or
exactly one new node at the end of the YAML Path if it had to be
Expand Down Expand Up @@ -605,18 +582,16 @@ def _ensure_path(self, data, path, value=None):
# The next element may not exist; this method ensures that it does
matched_nodes = 0
for node in self._get_elements_by_ref(data, curref):
if node is None:
continue

matched_nodes += 1
self.log.debug(
("YAMLPath::_ensure_path: Found element {} in the data;"
+ " recursing into it..."
).format(curele)
)
for epn in self._ensure_path(node, path.copy(), value):
if epn is not None:
yield epn
if node is not None:
matched_nodes += 1
self.log.debug(
("YAMLPath::_ensure_path: Found element {} in the"
+ " data; recursing into it..."
).format(curele)
)
for epn in self._ensure_path(node, path.copy(), value):
if epn is not None:
yield epn

if (
matched_nodes < 1
Expand All @@ -638,21 +613,19 @@ def _ensure_path(self, data, path, value=None):
data, new_val, curele
)
for node in self._ensure_path(new_ele, path, value):
if node is None:
continue
matched_nodes += 1
yield node
if node is not None:
matched_nodes += 1
yield node
elif curtyp is PathSegmentTypes.INDEX:
for _ in range(len(data) - 1, curele):
new_val = self._default_for_child(path, value)
self._append_list_element(data, new_val)
for node in self._ensure_path(
data[curele], path, value
):
if node is None:
continue
matched_nodes += 1
yield node
if node is not None:
matched_nodes += 1
yield node
else:
restore_path = path.copy()
restore_path.appendleft(curref)
Expand All @@ -677,10 +650,9 @@ def _ensure_path(self, data, path, value=None):
for node in self._ensure_path(
data[curele], path, value
):
if node is None:
continue
matched_nodes += 1
yield node
if node is not None:
matched_nodes += 1
yield node
else:
restore_path = path.copy()
restore_path.appendleft(curref)
Expand Down Expand Up @@ -744,12 +716,12 @@ def _default_for_child(yaml_path, value=None):
default_value = CommentedMap()
elif isinstance(value, str):
default_value = PlainScalarString("")
elif isinstance(value, bool):
default_value = ScalarBoolean(False)
elif isinstance(value, int):
default_value = ScalarInt(maxsize)
elif isinstance(value, float):
default_value = ScalarFloat("inf")
elif isinstance(value, bool):
default_value = ScalarBoolean(False)

return default_value

Expand Down

0 comments on commit b872a90

Please sign in to comment.