diff --git a/.gitignore b/.gitignore index b6659a1..f147690 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,9 @@ coverage.xml .pytest_cache/ cov.xml +# Profiling data +mprofile* + # Translations *.mo *.pot @@ -81,6 +84,7 @@ ENV/ # Dev utils dev.py profile_.py +tests/test_dev.py # Test fixtures comparison_regression_suite.yaml diff --git a/jsonpath_rfc9535/node.py b/jsonpath_rfc9535/node.py index 3aa291f..4d9dd54 100644 --- a/jsonpath_rfc9535/node.py +++ b/jsonpath_rfc9535/node.py @@ -39,7 +39,15 @@ def __init__( def path(self) -> str: """Return the normalized path to this node.""" return "$" + "".join( - (f"['{p}']" if isinstance(p, str) else f"[{p}]" for p in self.location) + f"['{p}']" if isinstance(p, str) else f"[{p}]" for p in self.location + ) + + def new_child(self, value: object, key: Union[int, str]) -> JSONPathNode: + """Return a new node using this node's location.""" + return JSONPathNode( + value=value, + location=self.location + (key,), + root=self.root, ) def __str__(self) -> str: diff --git a/jsonpath_rfc9535/segments.py b/jsonpath_rfc9535/segments.py index b5a8b17..d5813bf 100644 --- a/jsonpath_rfc9535/segments.py +++ b/jsonpath_rfc9535/segments.py @@ -12,10 +12,10 @@ from typing import Tuple from .exceptions import JSONPathRecursionError -from .node import JSONPathNode if TYPE_CHECKING: from .environment import JSONPathEnvironment + from .node import JSONPathNode from .selectors import JSONPathSelector from .tokens import Token @@ -88,20 +88,12 @@ def _visit(self, node: JSONPathNode, depth: int = 1) -> Iterable[JSONPathNode]: if isinstance(node.value, dict): for name, val in node.value.items(): if isinstance(val, (dict, list)): - _node = JSONPathNode( - value=val, - location=node.location + (name,), - root=node.root, - ) + _node = node.new_child(val, name) yield from self._visit(_node, depth + 1) elif isinstance(node.value, list): for i, element in enumerate(node.value): if isinstance(element, (dict, list)): - _node = JSONPathNode( - value=element, - location=node.location + (i,), - root=node.root, - ) + _node = node.new_child(element, i) yield from self._visit(_node, depth + 1) def _nondeterministic_visit( @@ -175,15 +167,7 @@ def _nondeterministic_children(node: JSONPathNode) -> Iterable[JSONPathNode]: items = list(node.value.items()) random.shuffle(items) for name, val in items: - yield JSONPathNode( - value=val, - location=node.location + (name,), - root=node.root, - ) + yield node.new_child(val, name) elif isinstance(node.value, list): for i, element in enumerate(node.value): - yield JSONPathNode( - value=element, - location=node.location + (i,), - root=node.root, - ) + yield node.new_child(element, i) diff --git a/jsonpath_rfc9535/selectors.py b/jsonpath_rfc9535/selectors.py index cebd2a8..184ef50 100644 --- a/jsonpath_rfc9535/selectors.py +++ b/jsonpath_rfc9535/selectors.py @@ -15,11 +15,11 @@ from .exceptions import JSONPathIndexError from .exceptions import JSONPathTypeError from .filter_expressions import FilterContext -from .node import JSONPathNode if TYPE_CHECKING: from .environment import JSONPathEnvironment from .filter_expressions import FilterExpression + from .node import JSONPathNode from .tokens import Token @@ -76,11 +76,7 @@ def resolve(self, node: JSONPathNode) -> Iterable[JSONPathNode]: """Select a value from a dict/object by its property/key.""" if isinstance(node.value, dict): with suppress(KeyError): - yield JSONPathNode( - value=node.value[self.name], - location=node.location + (self.name,), - root=node.root, - ) + yield node.new_child(node.value[self.name], self.name) class IndexSelector(JSONPathSelector): @@ -125,12 +121,7 @@ def resolve(self, node: JSONPathNode) -> Iterable[JSONPathNode]: if isinstance(node.value, list): norm_index = self._normalized_index(node.value) with suppress(IndexError): - _node = JSONPathNode( - value=node.value[self.index], - location=node.location + (norm_index,), - root=node.root, - ) - yield _node + yield node.new_child(node.value[self.index], norm_index) class SliceSelector(JSONPathSelector): @@ -185,13 +176,7 @@ def resolve(self, node: JSONPathNode) -> Iterable[JSONPathNode]: idx = self.slice.start or 0 step = self.slice.step or 1 for element in node.value[self.slice]: - norm_index = self._normalized_index(node.value, idx) - _node = JSONPathNode( - value=element, - location=node.location + (norm_index,), - root=node.root, - ) - yield _node + yield node.new_child(element, self._normalized_index(node.value, idx)) idx += step @@ -221,21 +206,11 @@ def resolve(self, node: JSONPathNode) -> Iterable[JSONPathNode]: members = node.value.items() for name, val in members: - _node = JSONPathNode( - value=val, - location=node.location + (name,), - root=node.root, - ) - yield _node + yield node.new_child(val, name) elif isinstance(node.value, list): for i, element in enumerate(node.value): - _node = JSONPathNode( - value=element, - location=node.location + (i,), - root=node.root, - ) - yield _node + yield node.new_child(element, i) class Filter(JSONPathSelector): @@ -284,11 +259,7 @@ def resolve(self, node: JSONPathNode) -> Iterable[JSONPathNode]: # noqa: PLR091 ) try: if self.expression.evaluate(context): - yield JSONPathNode( - value=val, - location=node.location + (name,), - root=node.root, - ) + yield node.new_child(val, name) except JSONPathTypeError as err: if not err.token: err.token = self.token @@ -303,11 +274,7 @@ def resolve(self, node: JSONPathNode) -> Iterable[JSONPathNode]: # noqa: PLR091 ) try: if self.expression.evaluate(context): - yield JSONPathNode( - value=element, - location=node.location + (i,), - root=node.root, - ) + yield node.new_child(element, i) except JSONPathTypeError as err: if not err.token: err.token = self.token diff --git a/tests/test_cts_nondeterminism.py b/tests/test_cts_nondeterminism.py index 91a4b55..0d97522 100644 --- a/tests/test_cts_nondeterminism.py +++ b/tests/test_cts_nondeterminism.py @@ -72,7 +72,7 @@ def _result_repr(rv: List[object]) -> Tuple[str, ...]: env = MockEnv() - # Repeat enough times to has high probability that we've covered all + # Repeat enough times so as to have high probability that we've covered all # valid permutations. results = { _result_repr(env.find(case.selector, case.document).values())