Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-jsonpath-ng for openSUSE:Factory checked in at 2023-09-20 13:27:06 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-jsonpath-ng (Old) and /work/SRC/openSUSE:Factory/.python-jsonpath-ng.new.16627 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jsonpath-ng" Wed Sep 20 13:27:06 2023 rev:3 rq:1112029 version:1.6.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-jsonpath-ng/python-jsonpath-ng.changes 2021-11-03 17:27:18.205371269 +0100 +++ /work/SRC/openSUSE:Factory/.python-jsonpath-ng.new.16627/python-jsonpath-ng.changes 2023-09-20 13:29:00.450253981 +0200 @@ -1,0 +2,43 @@ +Fri Sep 15 08:51:59 UTC 2023 - [email protected] + +- version update to 1.6.0 + 1.6.0 / 2023-09-13 + * Enclose field names containing literals in quotes + * Add note about extensions + * Remove documentation status link + * Update supported versions in setup.py + * Add LICENSE file + * Code cleanup + * Remove dependency on six + * Update build status badge + * (origin/github-actions, github-actions) Remove testscenarios dependency + * Remove pytest version constraints + * Add testing with GitHub actions + * Escape back slashes in tests to avoid DeprecationWarning. + * Use raw strings for regular expressions to avoid DeprecationWarning. + * refactor(package): remove dependency for decorator + * Merge pull request #128 from michaelmior/hashable + * (origin/hashable, hashable) Make path instances hashable + * Merge pull request #122 from snopoke/snopoke-patch-1 + * Add more detail to filter docs. + * remove incorrect parenthesis in filter examples + * Merge pull request #119 from snopoke/patch-1 + * add 'sub' line with function param names + * readme formatting fixes + * chore(history): update + * Update __init__.py + 1.5.3 / 2021-07-05 + * Update __init__.py + * Update setup.py + * Merge pull request #72 from kaapstorm/find_or_create + * Tests + * Add `update_or_create()` method + * Merge pull request #68 from kaapstorm/example_tests + * Merge pull request #70 from kaapstorm/exceptions + * Add/fix `__eq__()` + * Add tests based on Stefan Goessner's examples + * Tests + * Allow callers to catch JSONPathErrors +- six and decorator are not required + +------------------------------------------------------------------- Old: ---- jsonpath-ng-1.5.2.tar.gz New: ---- jsonpath-ng-1.6.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jsonpath-ng.spec ++++++ --- /var/tmp/diff_new_pack.MLnPK8/_old 2023-09-20 13:29:01.614295682 +0200 +++ /var/tmp/diff_new_pack.MLnPK8/_new 2023-09-20 13:29:01.614295682 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-jsonpath-ng # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,9 +16,8 @@ # -%{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-jsonpath-ng -Version: 1.5.2 +Version: 1.6.0 Release: 0 Summary: JSONPath for Python License: Apache-2.0 @@ -31,13 +30,13 @@ Requires: python-decorator Requires: python-ply Requires: python-six +Requires(post): update-alternatives +Requires(postun):update-alternatives BuildArch: noarch # SECTION test requirements -BuildRequires: %{python_module decorator} BuildRequires: %{python_module ply} # BuildRequires: %{python_module pytest} -BuildRequires: %{python_module six} BuildRequires: python3-oslotest # /SECTION %python_subpackages ++++++ jsonpath-ng-1.5.2.tar.gz -> jsonpath-ng-1.6.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/.github/workflows/ci.yml new/jsonpath-ng-1.6.0/.github/workflows/ci.yml --- old/jsonpath-ng-1.5.2/.github/workflows/ci.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpath-ng-1.6.0/.github/workflows/ci.yml 2023-09-13 21:46:25.000000000 +0200 @@ -0,0 +1,28 @@ +name: CI +on: + push: + branches: + - '*' + tags: + - 'v*' + pull_request: + branches: + - main +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install -r requirements-dev.txt + - name: Run tests + run: make test diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/History.md new/jsonpath-ng-1.6.0/History.md --- old/jsonpath-ng-1.5.2/History.md 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/History.md 2023-09-13 21:46:25.000000000 +0200 @@ -1,3 +1,44 @@ +1.6.0 / 2023-09-13 +=================== + * Enclose field names containing literals in quotes + * Add note about extensions + * Remove documentation status link + * Update supported versions in setup.py + * Add LICENSE file + * Code cleanup + * Remove dependency on six + * Update build status badge + * (origin/github-actions, github-actions) Remove testscenarios dependency + * Remove pytest version constraints + * Add testing with GitHub actions + * Escape back slashes in tests to avoid DeprecationWarning. + * Use raw strings for regular expressions to avoid DeprecationWarning. + * refactor(package): remove dependency for decorator + * Merge pull request #128 from michaelmior/hashable + * (origin/hashable, hashable) Make path instances hashable + * Merge pull request #122 from snopoke/snopoke-patch-1 + * Add more detail to filter docs. + * remove incorrect parenthesis in filter examples + * Merge pull request #119 from snopoke/patch-1 + * add 'sub' line with function param names + * readme formatting fixes + * chore(history): update + * Update __init__.py + +1.5.3 / 2021-07-05 +================== + + * Update __init__.py + * Update setup.py + * Merge pull request #72 from kaapstorm/find_or_create + * Tests + * Add `update_or_create()` method + * Merge pull request #68 from kaapstorm/example_tests + * Merge pull request #70 from kaapstorm/exceptions + * Add/fix `__eq__()` + * Add tests based on Stefan Goessner's examples + * Tests + * Allow callers to catch JSONPathErrors v1.5.2 / 2020-09-07 =================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/MANIFEST.in new/jsonpath-ng-1.6.0/MANIFEST.in --- old/jsonpath-ng-1.5.2/MANIFEST.in 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/MANIFEST.in 2023-09-13 21:46:25.000000000 +0200 @@ -1 +1,2 @@ recursive-include tests *.json *.py +include LICENSE diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/README.rst new/jsonpath-ng-1.6.0/README.rst --- old/jsonpath-ng-1.5.2/README.rst 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/README.rst 2023-09-13 21:46:25.000000000 +0200 @@ -187,29 +187,42 @@ Extensions ---------- -+--------------+----------------------------------------------+ -| name | Example | -+==============+==============================================+ -| len | - $.objects.`len` | -+--------------+----------------------------------------------+ -| sub | - $.field.`sub(/foo\\\\+(.*)/, \\\\1)` | -+--------------+----------------------------------------------+ -| split | - $.field.`split(+, 2, -1)` | -| | - $.field.`split(sep, segement, maxsplit)` | -+--------------+----------------------------------------------+ -| sorted | - $.objects.`sorted` | -| | - $.objects[\\some_field] | -| | - $.objects[\\some_field,/other_field] | -+--------------+----------------------------------------------+ -| filter | - $.objects[?(@some_field > 5)] | -| | - $.objects[?some_field = "foobar")] | -| | - $.objects[?some_field =~ "foobar")] | -| | - $.objects[?some_field > 5 & other < 2)] | -+--------------+----------------------------------------------+ -| arithmetic | - $.foo + "_" + $.bar | -| (-+*/) | - $.foo * 12 | -| | - $.objects[*].cow + $.objects[*].cat | -+--------------+----------------------------------------------+ +To use the extensions below you must import from `jsonpath_ng.ext`. + ++--------------+-----------------------------------------------+ +| name | Example | ++==============+===============================================+ +| len | - ``$.objects.`len``` | ++--------------+-----------------------------------------------+ +| sub | - ``$.field.`sub(/foo\\\\+(.*)/, \\\\1)``` | +| | - ``$.field.`sub(/regex/, replacement)``` | ++--------------+-----------------------------------------------+ +| split | - ``$.field.`split(+, 2, -1)``` | +| | - ``$.field.`split(sep, segement, maxsplit)```| ++--------------+-----------------------------------------------+ +| sorted | - ``$.objects.`sorted``` | +| | - ``$.objects[\\some_field]`` | +| | - ``$.objects[\\some_field,/other_field]`` | ++--------------+-----------------------------------------------+ +| filter | - ``$.objects[?(@some_field > 5)]`` | +| | - ``$.objects[?some_field = "foobar"]`` | +| | - ``$.objects[?some_field =~ "foobar"]`` | +| | - ``$.objects[?some_field > 5 & other < 2]`` | +| | | +| | Supported operators: | +| | - Equality: ==, =, != | +| | - Comparison: >, >=, <, <= | +| | - Regex match: =~ | +| | | +| | Combine multiple criteria with '&'. | +| | | +| | Properties can only be compared to static | +| | values. | ++--------------+-----------------------------------------------+ +| arithmetic | - ``$.foo + "_" + $.bar`` | +| (-+*/) | - ``$.foo * 12`` | +| | - ``$.objects[*].cow + $.objects[*].cat`` | ++--------------+-----------------------------------------------+ About arithmetic and string --------------------------- @@ -228,10 +241,10 @@ 'fish': 'bar' } -| **cow + fish** returns **cowfish** -| **$.cow + $.fish** returns **foobar** -| **$.cow + "_" + $.fish** returns **foo_bar** -| **$.cow + "_" + fish** returns **foo_fish** +| ``cow + fish`` returns ``cowfish`` +| ``$.cow + $.fish`` returns ``foobar`` +| ``$.cow + "_" + $.fish`` returns ``foo_bar`` +| ``$.cow + "_" + fish`` returns ``foo_fish`` About arithmetic and list ------------------------- @@ -245,7 +258,7 @@ {'cow': 4, 'cat': 6} ]} -| **$.objects[\*].cow + $.objects[\*].cat** returns **[6, 9]** +| ``$.objects[\*].cow + $.objects[\*].cat`` returns ``[6, 9]`` More to explore --------------- @@ -322,9 +335,7 @@ .. |PyPi downloads| image:: https://pypip.in/d/jsonpath-ng/badge.png :target: https://pypi.python.org/pypi/jsonpath-ng -.. |Build Status| image:: https://travis-ci.org/h2non/jsonpath-ng.svg?branch=master - :target: https://travis-ci.org/h2non/jsonpath-ng +.. |Build Status| image:: https://github.com/h2non/jsonpath-ng/actions/workflows/ci.yml/badge.svg + :target: https://github.com/h2non/jsonpath-ng/actions/workflows/ci.yml .. |PyPI| image:: https://img.shields.io/pypi/v/jsonpath-ng.svg?maxAge=2592000?style=flat-square :target: https://pypi.python.org/pypi/jsonpath-ng -.. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-green.svg?style=flat - :target: http://jsonpath-ng.readthedocs.io/en/latest/?badge=latest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/jsonpath_ng/__init__.py new/jsonpath-ng-1.6.0/jsonpath_ng/__init__.py --- old/jsonpath-ng-1.5.2/jsonpath_ng/__init__.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/jsonpath_ng/__init__.py 2023-09-13 21:46:25.000000000 +0200 @@ -3,4 +3,4 @@ # Current package version -__version__ = '1.5.2' +__version__ = '1.6.0' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/jsonpath_ng/exceptions.py new/jsonpath-ng-1.6.0/jsonpath_ng/exceptions.py --- old/jsonpath-ng-1.5.2/jsonpath_ng/exceptions.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpath-ng-1.6.0/jsonpath_ng/exceptions.py 2023-09-13 21:46:25.000000000 +0200 @@ -0,0 +1,10 @@ +class JSONPathError(Exception): + pass + + +class JsonPathLexerError(JSONPathError): + pass + + +class JsonPathParserError(JSONPathError): + pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/jsonpath_ng/ext/filter.py new/jsonpath-ng-1.6.0/jsonpath_ng/ext/filter.py --- old/jsonpath-ng-1.5.2/jsonpath_ng/ext/filter.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/jsonpath_ng/ext/filter.py 2023-09-13 21:46:25.000000000 +0200 @@ -13,7 +13,6 @@ import operator import re -from six import moves from .. import JSONPath, DatumInContext, Index @@ -49,7 +48,7 @@ return [] return [DatumInContext(datum.value[i], path=Index(i), context=datum) - for i in moves.range(0, len(datum.value)) + for i in range(0, len(datum.value)) if (len(self.expressions) == len(list(filter(lambda x: x.find(datum.value[i]), self.expressions))))] @@ -71,6 +70,10 @@ def __str__(self): return '[?%s]' % self.expressions + def __eq__(self, other): + return (isinstance(other, Filter) + and self.expressions == other.expressions) + class Expression(JSONPath): """The JSONQuery expression""" @@ -108,7 +111,7 @@ return found def __eq__(self, other): - return (isinstance(other, Filter) and + return (isinstance(other, Expression) and self.target == other.target and self.op == other.op and self.value == other.value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/jsonpath_ng/ext/string.py new/jsonpath-ng-1.6.0/jsonpath_ng/ext/string.py --- old/jsonpath-ng-1.5.2/jsonpath_ng/ext/string.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/jsonpath_ng/ext/string.py 2023-09-13 21:46:25.000000000 +0200 @@ -15,9 +15,9 @@ from .. import DatumInContext, This -SUB = re.compile("sub\(/(.*)/,\s+(.*)\)") -SPLIT = re.compile("split\((.),\s+(\d+),\s+(\d+|-1)\)") -STR = re.compile("str\(\)") +SUB = re.compile(r"sub\(/(.*)/,\s+(.*)\)") +SPLIT = re.compile(r"split\((.),\s+(\d+),\s+(\d+|-1)\)") +STR = re.compile(r"str\(\)") class DefintionInvalid(Exception): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/jsonpath_ng/jsonpath.py new/jsonpath-ng-1.6.0/jsonpath_ng/jsonpath.py --- old/jsonpath-ng-1.5.2/jsonpath_ng/jsonpath.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/jsonpath_ng/jsonpath.py 2023-09-13 21:46:25.000000000 +0200 @@ -1,8 +1,9 @@ -from __future__ import unicode_literals, print_function, absolute_import, division, generators, nested_scopes +from __future__ import (absolute_import, division, generators, nested_scopes, + print_function, unicode_literals) + import logging -import six -from six.moves import xrange from itertools import * # noqa +from jsonpath_ng.lexer import JsonPathLexer # Get logger name logger = logging.getLogger(__name__) @@ -11,6 +12,9 @@ # ... could be a kwarg pervasively but uses are rare and simple today auto_id_field = None +NOT_SET = object() +LIST_KEY = object() + class JSONPath(object): """ @@ -27,6 +31,9 @@ """ raise NotImplementedError() + def find_or_create(self, data): + return self.find(data) + def update(self, data, val): """ Returns `data` with the specified path replaced by `val`. Only updates @@ -35,6 +42,9 @@ raise NotImplementedError() + def update_or_create(self, data, val): + return self.update(data, val) + def filter(self, fn, data): """ Returns `data` with the specified path filtering nodes according @@ -210,6 +220,9 @@ def __eq__(self, other): return isinstance(other, Root) + def __hash__(self): + return hash('$') + class This(JSONPath): """ @@ -234,6 +247,9 @@ def __eq__(self, other): return isinstance(other, This) + def __hash__(self): + return hash('this') + class Child(JSONPath): """ @@ -261,6 +277,23 @@ self.right.update(datum.value, val) return data + def find_or_create(self, datum): + datum = DatumInContext.wrap(datum) + submatches = [] + for subdata in self.left.find_or_create(datum): + if isinstance(subdata, AutoIdForDatum): + # Extra special case: auto ids do not have children, + # so cut it off right now rather than auto id the auto id + continue + for submatch in self.right.find_or_create(subdata): + submatches.append(submatch) + return submatches + + def update_or_create(self, data, val): + for datum in self.left.find_or_create(data): + self.right.update_or_create(datum.value, val) + return _clean_list_keys(data) + def filter(self, fn, data): for datum in self.left.find(data): self.right.filter(fn, datum.value) @@ -275,6 +308,9 @@ def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.left, self.right) + def __hash__(self): + return hash((self.left, self.right)) + class Parent(JSONPath): """ @@ -296,6 +332,9 @@ def __repr__(self): return 'Parent()' + def __hash__(self): + return hash('parent') + class Where(JSONPath): """ @@ -330,6 +369,9 @@ def __eq__(self, other): return isinstance(other, Where) and other.left == self.left and other.right == self.right + def __hash__(self): + return hash((self.left, self.right)) + class Descendants(JSONPath): """ JSONPath that matches first the left expression then any descendant @@ -439,6 +481,13 @@ def __eq__(self, other): return isinstance(other, Descendants) and self.left == other.left and self.right == other.right + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.left, self.right) + + def __hash__(self): + return hash((self.left, self.right)) + + class Union(JSONPath): """ JSONPath that returns the union of the results of each match. @@ -459,6 +508,12 @@ def find(self, data): return self.left.find(data) + self.right.find(data) + def __eq__(self, other): + return isinstance(other, Union) and self.left == other.left and self.right == other.right + + def __hash__(self): + return hash((self.left, self.right)) + class Intersect(JSONPath): """ JSONPath for bits that match *both* patterns. @@ -480,6 +535,12 @@ def find(self, data): raise NotImplementedError() + def __eq__(self, other): + return isinstance(other, Intersect) and self.left == other.left and self.right == other.right + + def __hash__(self): + return hash((self.left, self.right)) + class Fields(JSONPath): """ @@ -493,15 +554,20 @@ def __init__(self, *fields): self.fields = fields - def get_field_datum(self, datum, field): + @staticmethod + def get_field_datum(datum, field, create): if field == auto_id_field: return AutoIdForDatum(datum) - else: - try: - field_value = datum.value[field] # Do NOT use `val.get(field)` since that confuses None as a value and None due to `get` - return DatumInContext(value=field_value, path=Fields(field), context=datum) - except (TypeError, KeyError, AttributeError): - return None + try: + field_value = datum.value.get(field, NOT_SET) + if field_value is NOT_SET: + if create: + datum.value[field] = field_value = {} + else: + return None + return DatumInContext(field_value, path=Fields(field), context=datum) + except (TypeError, AttributeError): + return None def reified_fields(self, datum): if '*' not in self.fields: @@ -514,15 +580,28 @@ return () def find(self, datum): - datum = DatumInContext.wrap(datum) + return self._find_base(datum, create=False) + + def find_or_create(self, datum): + return self._find_base(datum, create=True) - return [field_datum - for field_datum in [self.get_field_datum(datum, field) for field in self.reified_fields(datum)] - if field_datum is not None] + def _find_base(self, datum, create): + datum = DatumInContext.wrap(datum) + field_data = [self.get_field_datum(datum, field, create) + for field in self.reified_fields(datum)] + return [fd for fd in field_data if fd is not None] def update(self, data, val): + return self._update_base(data, val, create=False) + + def update_or_create(self, data, val): + return self._update_base(data, val, create=True) + + def _update_base(self, data, val, create): if data is not None: for field in self.reified_fields(DatumInContext.wrap(data)): + if field not in data and create: + data[field] = {} if field in data: if hasattr(val, '__call__'): val(data[field], data, field) @@ -539,7 +618,14 @@ return data def __str__(self): - return ','.join(map(str, self.fields)) + # If any JsonPathLexer.literals are included in field name need quotes + # This avoids unnecessary quotes to keep strings short. + # Test each field whether it contains a literal and only then add quotes + # The test loops over all literals, could possibly optimize to short circuit if one found + fields_as_str = ("'" + str(f) + "'" if any([l in f for l in JsonPathLexer.literals]) else + str(f) for f in self.fields) + return ','.join(fields_as_str) + def __repr__(self): return '%s(%s)' % (self.__class__.__name__, ','.join(map(repr, self.fields))) @@ -547,6 +633,9 @@ def __eq__(self, other): return isinstance(other, Fields) and tuple(self.fields) == tuple(other.fields) + def __hash__(self): + return hash(tuple(self.fields)) + class Index(JSONPath): """ @@ -561,14 +650,33 @@ self.index = index def find(self, datum): - datum = DatumInContext.wrap(datum) + return self._find_base(datum, create=False) + + def find_or_create(self, datum): + return self._find_base(datum, create=True) + def _find_base(self, datum, create): + datum = DatumInContext.wrap(datum) + if create: + if datum.value == {}: + datum.value = _create_list_key(datum.value) + self._pad_value(datum.value) if datum.value and len(datum.value) > self.index: return [DatumInContext(datum.value[self.index], path=self, context=datum)] else: return [] def update(self, data, val): + return self._update_base(data, val, create=False) + + def update_or_create(self, data, val): + return self._update_base(data, val, create=True) + + def _update_base(self, data, val, create): + if create: + if data == {}: + data = _create_list_key(data) + self._pad_value(data) if hasattr(val, '__call__'): val.__call__(data[self.index], data, self.index) elif len(data) > self.index: @@ -586,6 +694,17 @@ def __str__(self): return '[%i]' % self.index + def __repr__(self): + return '%s(index=%r)' % (self.__class__.__name__, self.index) + + def _pad_value(self, value): + if len(value) <= self.index: + pad = self.index - len(value) + 1 + value += [{} for __ in range(pad)] + + def __hash__(self): + return hash(self.index) + class Slice(JSONPath): """ @@ -624,13 +743,13 @@ return [] # Here's the hack. If it is a dictionary or some kind of constant, # put it in a single-element list - if (isinstance(datum.value, dict) or isinstance(datum.value, six.integer_types) or isinstance(datum.value, six.string_types)): + if (isinstance(datum.value, dict) or isinstance(datum.value, int) or isinstance(datum.value, str)): return self.find(DatumInContext([datum.value], path=datum.path, context=datum.context)) # Some iterators do not support slicing but we can still # at least work for '*' - if self.start == None and self.end == None and self.step == None: - return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in xrange(0, len(datum.value))] + if self.start is None and self.end is None and self.step is None: + return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))] else: return [DatumInContext(datum.value[i], path=Index(i), context=datum) for i in range(0, len(datum.value))[self.start:self.end:self.step]] @@ -652,7 +771,7 @@ return data def __str__(self): - if self.start == None and self.end == None and self.step == None: + if self.start is None and self.end is None and self.step is None: return '[*]' else: return '[%s%s%s]' % (self.start or '', @@ -664,3 +783,35 @@ def __eq__(self, other): return isinstance(other, Slice) and other.start == self.start and self.end == other.end and other.step == self.step + + def __hash__(self): + return hash((self.start, self.end, self.step)) + + +def _create_list_key(dict_): + """ + Adds a list to a dictionary by reference and returns the list. + + See `_clean_list_keys()` + """ + dict_[LIST_KEY] = new_list = [{}] + return new_list + + +def _clean_list_keys(dict_): + """ + Replace {LIST_KEY: ['foo', 'bar']} with ['foo', 'bar']. + + >>> _clean_list_keys({LIST_KEY: ['foo', 'bar']}) + ['foo', 'bar'] + + """ + for key, value in dict_.items(): + if isinstance(value, dict): + dict_[key] = _clean_list_keys(value) + elif isinstance(value, list): + dict_[key] = [_clean_list_keys(v) if isinstance(v, dict) else v + for v in value] + if LIST_KEY in dict_: + return dict_[LIST_KEY] + return dict_ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/jsonpath_ng/lexer.py new/jsonpath-ng-1.6.0/jsonpath_ng/lexer.py --- old/jsonpath-ng-1.5.2/jsonpath_ng/lexer.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/jsonpath_ng/lexer.py 2023-09-13 21:46:25.000000000 +0200 @@ -4,11 +4,9 @@ import ply.lex -logger = logging.getLogger(__name__) - +from jsonpath_ng.exceptions import JsonPathLexerError -class JsonPathLexerError(Exception): - pass +logger = logging.getLogger(__name__) class JsonPathLexer(object): @@ -18,7 +16,7 @@ def __init__(self, debug=False): self.debug = debug - if self.__doc__ == None: + if self.__doc__ is None: raise JsonPathLexerError('Docstrings have been removed! By design of PLY, jsonpath-rw requires docstrings. You must not use PYTHONOPTIMIZE=2 or python -OO.') def tokenize(self, string): @@ -33,7 +31,8 @@ while True: t = new_lexer.token() - if t is None: break + if t is None: + break t.col = t.lexpos - new_lexer.latest_newline yield t diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/jsonpath_ng/parser.py new/jsonpath-ng-1.6.0/jsonpath_ng/parser.py --- old/jsonpath-ng-1.5.2/jsonpath_ng/parser.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/jsonpath_ng/parser.py 2023-09-13 21:46:25.000000000 +0200 @@ -1,18 +1,27 @@ -from __future__ import print_function, absolute_import, division, generators, nested_scopes +from __future__ import ( + print_function, + absolute_import, + division, + generators, + nested_scopes, +) +import logging import sys import os.path -import logging import ply.yacc +from jsonpath_ng.exceptions import JsonPathParserError from jsonpath_ng.jsonpath import * from jsonpath_ng.lexer import JsonPathLexer logger = logging.getLogger(__name__) + def parse(string): return JsonPathParser().parse(string) + class JsonPathParser(object): ''' An LALR-parser for JsonPath @@ -21,8 +30,12 @@ tokens = JsonPathLexer.tokens def __init__(self, debug=False, lexer_class=None): - if self.__doc__ == None: - raise Exception('Docstrings have been removed! By design of PLY, jsonpath-rw requires docstrings. You must not use PYTHONOPTIMIZE=2 or python -OO.') + if self.__doc__ is None: + raise JsonPathParserError( + 'Docstrings have been removed! By design of PLY, ' + 'jsonpath-rw requires docstrings. You must not use ' + 'PYTHONOPTIMIZE=2 or python -OO.' + ) self.debug = debug self.lexer_class = lexer_class or JsonPathLexer # Crufty but works around statefulness in PLY @@ -43,7 +56,8 @@ parsing_table_module = '_'.join([module_name, start_symbol, 'parsetab']) - # And we regenerate the parse table every time; it doesn't actually take that long! + # And we regenerate the parse table every time; + # it doesn't actually take that long! new_parser = ply.yacc.yacc(module=self, debug=self.debug, tabmodule = parsing_table_module, @@ -66,7 +80,8 @@ ] def p_error(self, t): - raise Exception('Parse error at %s:%s near token %s (%s)' % (t.lineno, t.col, t.value, t.type)) + raise JsonPathParserError('Parse error at %s:%s near token %s (%s)' + % (t.lineno, t.col, t.value, t.type)) def p_jsonpath_binop(self, p): """jsonpath : jsonpath '.' jsonpath @@ -98,7 +113,8 @@ elif p[1] == 'parent': p[0] = Parent() else: - raise Exception('Unknown named operator `%s` at %s:%s' % (p[1], p.lineno(1), p.lexpos(1))) + raise JsonPathParserError('Unknown named operator `%s` at %s:%s' + % (p[1], p.lineno(1), p.lexpos(1))) def p_jsonpath_root(self, p): "jsonpath : '$'" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/requirements-dev.txt new/jsonpath-ng-1.6.0/requirements-dev.txt --- old/jsonpath-ng-1.5.2/requirements-dev.txt 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/requirements-dev.txt 2023-09-13 21:46:25.000000000 +0200 @@ -1,7 +1,6 @@ oslotest -pytest~=3.0.5 +pytest flake8 -testscenarios coverage coveralls -pytest-cov~=2.3.1 +pytest-cov diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/requirements.txt new/jsonpath-ng-1.6.0/requirements.txt --- old/jsonpath-ng-1.5.2/requirements.txt 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/requirements.txt 2023-09-13 21:46:25.000000000 +0200 @@ -1,4 +1,2 @@ ply -decorator -six setuptools>=18.5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/setup.py new/jsonpath-ng-1.6.0/setup.py --- old/jsonpath-ng-1.5.2/setup.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/setup.py 2023-09-13 21:46:25.000000000 +0200 @@ -4,9 +4,9 @@ setuptools.setup( name='jsonpath-ng', - version='1.5.2', + version='1.6.0', description=( - 'A final implementation of JSONPath for Python that aims to be ' + 'A final implementation of JSONPath for Python that aims to be ' 'standard compliant, including arithmetic and binary comparison ' 'operators and providing clear AST for metaprogramming.' ), @@ -23,17 +23,17 @@ }, test_suite='tests', install_requires=[ - 'ply', 'decorator', 'six' + 'ply' ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11' ], ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/tests/test_create.py new/jsonpath-ng-1.6.0/tests/test_create.py --- old/jsonpath-ng-1.5.2/tests/test_create.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpath-ng-1.6.0/tests/test_create.py 2023-09-13 21:46:25.000000000 +0200 @@ -0,0 +1,177 @@ +import doctest +from collections import namedtuple + +import pytest + +import jsonpath_ng +from jsonpath_ng.ext import parse + +Params = namedtuple('Params', 'string initial_data insert_val target') + + [email protected]('string, initial_data, insert_val, target', [ + + Params(string='$.foo', + initial_data={}, + insert_val=42, + target={'foo': 42}), + + Params(string='$.foo.bar', + initial_data={}, + insert_val=42, + target={'foo': {'bar': 42}}), + + Params(string='$.foo[0]', + initial_data={}, + insert_val=42, + target={'foo': [42]}), + + Params(string='$.foo[1]', + initial_data={}, + insert_val=42, + target={'foo': [{}, 42]}), + + Params(string='$.foo[0].bar', + initial_data={}, + insert_val=42, + target={'foo': [{'bar': 42}]}), + + Params(string='$.foo[1].bar', + initial_data={}, + insert_val=42, + target={'foo': [{}, {'bar': 42}]}), + + Params(string='$.foo[0][0]', + initial_data={}, + insert_val=42, + target={'foo': [[42]]}), + + Params(string='$.foo[1][1]', + initial_data={}, + insert_val=42, + target={'foo': [{}, [{}, 42]]}), + + Params(string='foo[0]', + initial_data={}, + insert_val=42, + target={'foo': [42]}), + + Params(string='foo[1]', + initial_data={}, + insert_val=42, + target={'foo': [{}, 42]}), + + Params(string='foo', + initial_data={}, + insert_val=42, + target={'foo': 42}), + + # Initial data can be a list if we expect a list back + Params(string='[0]', + initial_data=[], + insert_val=42, + target=[42]), + + Params(string='[1]', + initial_data=[], + insert_val=42, + target=[{}, 42]), + + # Converts initial data to a list if necessary + Params(string='[0]', + initial_data={}, + insert_val=42, + target=[42]), + + Params(string='[1]', + initial_data={}, + insert_val=42, + target=[{}, 42]), + + Params(string='foo[?bar="baz"].qux', + initial_data={'foo': [ + {'bar': 'baz'}, + {'bar': 'bizzle'}, + ]}, + insert_val=42, + target={'foo': [ + {'bar': 'baz', 'qux': 42}, + {'bar': 'bizzle'} + ]}), +]) +def test_update_or_create(string, initial_data, insert_val, target): + jsonpath = parse(string) + result = jsonpath.update_or_create(initial_data, insert_val) + assert result == target + + [email protected]('string, initial_data, insert_val, target', [ + # Slice not supported + Params(string='foo[0:1]', + initial_data={}, + insert_val=42, + target={'foo': [42, 42]}), + # result is {'foo': {}} + + # Filter does not create items to meet criteria + Params(string='foo[?bar="baz"].qux', + initial_data={}, + insert_val=42, + target={'foo': [{'bar': 'baz', 'qux': 42}]}), + # result is {'foo': {}} + + # Does not convert initial data to a dictionary + Params(string='foo', + initial_data=[], + insert_val=42, + target={'foo': 42}), + # raises TypeError + +]) [email protected] +def test_unsupported_classes(string, initial_data, insert_val, target): + jsonpath = parse(string) + result = jsonpath.update_or_create(initial_data, insert_val) + assert result == target + + [email protected]('string, initial_data, insert_val, target', [ + + Params(string='$.name[0].text', + initial_data={}, + insert_val='Sir Michael', + target={'name': [{'text': 'Sir Michael'}]}), + + Params(string='$.name[0].given[0]', + initial_data={'name': [{'text': 'Sir Michael'}]}, + insert_val='Michael', + target={'name': [{'text': 'Sir Michael', + 'given': ['Michael']}]}), + + Params(string='$.name[0].prefix[0]', + initial_data={'name': [{'text': 'Sir Michael', + 'given': ['Michael']}]}, + insert_val='Sir', + target={'name': [{'text': 'Sir Michael', + 'given': ['Michael'], + 'prefix': ['Sir']}]}), + + Params(string='$.birthDate', + initial_data={'name': [{'text': 'Sir Michael', + 'given': ['Michael'], + 'prefix': ['Sir']}]}, + insert_val='1943-05-05', + target={'name': [{'text': 'Sir Michael', + 'given': ['Michael'], + 'prefix': ['Sir']}], + 'birthDate': '1943-05-05'}), +]) +def test_build_doc(string, initial_data, insert_val, target): + jsonpath = parse(string) + result = jsonpath.update_or_create(initial_data, insert_val) + assert result == target + + +def test_doctests(): + results = doctest.testmod(jsonpath_ng) + assert results.failed == 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/tests/test_examples.py new/jsonpath-ng-1.6.0/tests/test_examples.py --- old/jsonpath-ng-1.5.2/tests/test_examples.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpath-ng-1.6.0/tests/test_examples.py 2023-09-13 21:46:25.000000000 +0200 @@ -0,0 +1,74 @@ +import pytest + +from jsonpath_ng.ext.filter import Filter, Expression +from jsonpath_ng.ext import parse +from jsonpath_ng.jsonpath import * + + [email protected]('string, parsed', [ + # The authors of all books in the store + ("$.store.book[*].author", + Child(Child(Child(Child(Root(), Fields('store')), Fields('book')), + Slice()), Fields('author'))), + + # All authors + ("$..author", Descendants(Root(), Fields('author'))), + + # All things in the store + ("$.store.*", Child(Child(Root(), Fields('store')), Fields('*'))), + + # The price of everything in the store + ("$.store..price", + Descendants(Child(Root(), Fields('store')), Fields('price'))), + + # The third book + ("$..book[2]", + Child(Descendants(Root(), Fields('book')),Index(2))), + + # The last book in order + # ("$..book[(@.length-1)]", # Not implemented + # Child(Descendants(Root(), Fields('book')), Slice(start=-1))), + ("$..book[-1:]", + Child(Descendants(Root(), Fields('book')), Slice(start=-1))), + + # The first two books + # ("$..book[0,1]", # Not implemented + # Child(Descendants(Root(), Fields('book')), Slice(end=2))), + ("$..book[:2]", + Child(Descendants(Root(), Fields('book')), Slice(end=2))), + + # Filter all books with ISBN number + ("$..book[?(@.isbn)]", + Child(Descendants(Root(), Fields('book')), + Filter([Expression(Child(This(), Fields('isbn')), None, None)]))), + + # Filter all books cheaper than 10 + ("$..book[?(@.price<10)]", + Child(Descendants(Root(), Fields('book')), + Filter([Expression(Child(This(), Fields('price')), '<', 10)]))), + + # All members of JSON structure + ("$..*", Descendants(Root(), Fields('*'))), +]) +def test_goessner_examples(string, parsed): + """ + Test Stefan Goessner's `examples`_ + + .. _examples: https://goessner.net/articles/JsonPath/index.html#e3 + """ + assert parse(string, debug=True) == parsed + + [email protected]('string, parsed', [ + # Navigate objects + ("$.store.book[0].title", + Child(Child(Child(Child(Root(), Fields('store')), Fields('book')), + Index(0)), Fields('title'))), + + # Navigate dictionaries + ("$['store']['book'][0]['title']", + Child(Child(Child(Child(Root(), Fields('store')), Fields('book')), + Index(0)), Fields('title'))), +]) +def test_obj_v_dict(string, parsed): + assert parse(string, debug=True) == parsed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/tests/test_exceptions.py new/jsonpath-ng-1.6.0/tests/test_exceptions.py --- old/jsonpath-ng-1.5.2/tests/test_exceptions.py 1970-01-01 01:00:00.000000000 +0100 +++ new/jsonpath-ng-1.6.0/tests/test_exceptions.py 2023-09-13 21:46:25.000000000 +0200 @@ -0,0 +1,20 @@ +import pytest + +from jsonpath_ng import parse as rw_parse +from jsonpath_ng.exceptions import JSONPathError, JsonPathParserError +from jsonpath_ng.ext import parse as ext_parse + + +def test_rw_exception_class(): + with pytest.raises(JSONPathError): + rw_parse('foo.bar.`grandparent`.baz') + + +def test_rw_exception_subclass(): + with pytest.raises(JsonPathParserError): + rw_parse('foo.bar.`grandparent`.baz') + + +def test_ext_exception_subclass(): + with pytest.raises(JsonPathParserError): + ext_parse('foo.bar.`grandparent`.baz') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/tests/test_jsonpath.py new/jsonpath-ng-1.6.0/tests/test_jsonpath.py --- old/jsonpath-ng-1.5.2/tests/test_jsonpath.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/tests/test_jsonpath.py 2023-09-13 21:46:25.000000000 +0200 @@ -136,7 +136,7 @@ def test_slice_value(self): self.check_cases([('[*]', [1, 2, 3], [1, 2, 3]), - ('[*]', xrange(1, 4), [1, 2, 3]), + ('[*]', range(1, 4), [1, 2, 3]), ('[1:]', [1, 2, 3, 4], [2, 3, 4]), ('[:2]', [1, 2, 3, 4], [1, 2])]) @@ -183,6 +183,7 @@ for string, data, target in test_cases: print('parse("%s").find(%s).paths =?= %s' % (string, data, target)) + assert hash(parse(string)) == hash(parse(string)) result = parse(string).find(data) if isinstance(target, list): assert [str(r.full_path) for r in result] == target @@ -232,6 +233,8 @@ def test_descendants_paths(self): self.check_paths([('foo..baz', {'foo': {'baz': 1, 'bing': {'baz': 2}}}, ['foo.baz', 'foo.bing.baz'] )]) + def test_literals_in_field_names(self): + self.check_paths([("A.'a.c'", {'A' : {'a.c': 'd'}}, ["A.'a.c'"])]) # # Check the "auto_id_field" feature diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jsonpath-ng-1.5.2/tests/test_jsonpath_rw_ext.py new/jsonpath-ng-1.6.0/tests/test_jsonpath_rw_ext.py --- old/jsonpath-ng-1.5.2/tests/test_jsonpath_rw_ext.py 2020-09-07 15:37:49.000000000 +0200 +++ new/jsonpath-ng-1.6.0/tests/test_jsonpath_rw_ext.py 2023-09-13 21:46:25.000000000 +0200 @@ -21,14 +21,23 @@ from jsonpath_ng import jsonpath # For setting the global auto_id_field flag from oslotest import base -from six import moves -import testscenarios from jsonpath_ng.ext import parser -class Testjsonpath_ng_ext(testscenarios.WithScenarios, - base.BaseTestCase): +# Example from https://docs.pytest.org/en/7.1.x/example/parametrize.html#a-quick-port-of-testscenarios +def pytest_generate_tests(metafunc): + idlist = [] + argvalues = [] + for scenario in metafunc.cls.scenarios: + idlist.append(scenario[0]) + items = scenario[1].items() + argnames = [x[0] for x in items] + argvalues.append([x[1] for x in items]) + metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class") + + +class Testjsonpath_ng_ext: scenarios = [ ('sorted_list', dict(string='objects.`sorted`', data={'objects': ['alpha', 'gamma', 'beta']}, @@ -126,14 +135,14 @@ {'cat': 2, 'cow': 1}, {'cat': 3, 'cow': 3}]}, target=2)), - ('sort2', dict(string='objects[\cat]', + ('sort2', dict(string='objects[\\cat]', data={'objects': [{'cat': 2}, {'cat': 1}, {'cat': 3}]}, target=[[{'cat': 3}, {'cat': 2}, {'cat': 1}]])), - ('sort2_indexed', dict(string='objects[\cat][-1].cat', + ('sort2_indexed', dict(string='objects[\\cat][-1].cat', data={'objects': [{'cat': 2}, {'cat': 1}, {'cat': 3}]}, target=1)), - ('sort3', dict(string='objects[/cow,\cat]', + ('sort3', dict(string='objects[/cow,\\cat]', data={'objects': [{'cat': 1, 'cow': 2}, {'cat': 2, 'cow': 1}, {'cat': 3, 'cow': 1}, @@ -142,7 +151,7 @@ {'cat': 2, 'cow': 1}, {'cat': 1, 'cow': 2}, {'cat': 3, 'cow': 3}]])), - ('sort3_indexed', dict(string='objects[/cow,\cat][0].cat', + ('sort3_indexed', dict(string='objects[/cow,\\cat][0].cat', data={'objects': [{'cat': 1, 'cow': 2}, {'cat': 2, 'cow': 1}, {'cat': 3, 'cow': 1}, @@ -334,17 +343,17 @@ )), ] - def test_fields_value(self): + def test_fields_value(self, string, data, target): jsonpath.auto_id_field = None - result = parser.parse(self.string, debug=True).find(self.data) - if isinstance(self.target, list): - self.assertEqual(self.target, [r.value for r in result]) - elif isinstance(self.target, set): - self.assertEqual(self.target, set([r.value for r in result])) - elif isinstance(self.target, (int, float)): - self.assertEqual(self.target, result[0].value) + result = parser.parse(string, debug=True).find(data) + if isinstance(target, list): + assert target == [r.value for r in result] + elif isinstance(target, set): + assert target == set([r.value for r in result]) + elif isinstance(target, (int, float)): + assert target == result[0].value else: - self.assertEqual(self.target, result[0].value) + assert target == result[0].value # NOTE(sileht): copy of tests/test_jsonpath.py # to ensure we didn't break jsonpath_ng @@ -406,7 +415,7 @@ def test_slice_value(self): self.check_cases([('[*]', [1, 2, 3], [1, 2, 3]), - ('[*]', moves.range(1, 4), [1, 2, 3]), + ('[*]', range(1, 4), [1, 2, 3]), ('[1:]', [1, 2, 3, 4], [2, 3, 4]), ('[:2]', [1, 2, 3, 4], [1, 2])])
