python-jsonpath 2.0.0__tar.gz → 2.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/PKG-INFO +4 -3
  2. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/README.md +2 -2
  3. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/__about__.py +1 -1
  4. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/exceptions.py +3 -3
  5. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/lex.py +1 -1
  6. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/patch.py +8 -3
  7. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/pointer.py +52 -55
  8. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/pyproject.toml +2 -1
  9. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/.gitignore +0 -0
  10. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/LICENSE.txt +0 -0
  11. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/__init__.py +0 -0
  12. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/__main__.py +0 -0
  13. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/_data.py +0 -0
  14. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/_types.py +0 -0
  15. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/cli.py +0 -0
  16. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/env.py +0 -0
  17. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/filter.py +0 -0
  18. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/fluent_api.py +0 -0
  19. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/__init__.py +0 -0
  20. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/_pattern.py +0 -0
  21. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/arguments.py +0 -0
  22. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/count.py +0 -0
  23. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/filter_function.py +0 -0
  24. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/is_instance.py +0 -0
  25. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/keys.py +0 -0
  26. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/length.py +0 -0
  27. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/match.py +0 -0
  28. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/search.py +0 -0
  29. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/starts_with.py +0 -0
  30. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/typeof.py +0 -0
  31. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/function_extensions/value.py +0 -0
  32. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/lru_cache.py +0 -0
  33. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/match.py +0 -0
  34. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/parse.py +0 -0
  35. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/path.py +0 -0
  36. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/py.typed +0 -0
  37. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/segments.py +0 -0
  38. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/selectors.py +0 -0
  39. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/serialize.py +0 -0
  40. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/stream.py +0 -0
  41. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/token.py +0 -0
  42. {python_jsonpath-2.0.0 → python_jsonpath-2.0.2}/jsonpath/unescape.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jsonpath
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: JSONPath, JSON Pointer and JSON Patch for Python.
5
5
  Project-URL: Documentation, https://jg-rp.github.io/python-jsonpath/
6
6
  Project-URL: Issues, https://github.com/jg-rp/python-jsonpath/issues
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
22
23
  Classifier: Programming Language :: Python :: Implementation :: CPython
23
24
  Classifier: Programming Language :: Python :: Implementation :: PyPy
24
25
  Requires-Python: >=3.8
@@ -95,9 +96,9 @@ conda install -c conda-forge python-jsonpath
95
96
 
96
97
  ## Related projects
97
98
 
98
- - [JSONPath RFC 9535](https://github.com/jg-rp/python-jsonpath-rfc9535) - A Python implementation of JSONPath that follows RFC 9535 much more strictly. If you require maximum interoperability with JSONPath implemented in other languages - at the expense of extra features - choose [jsonpath-rfc9535](https://pypi.org/project/jsonpath-rfc9535/) over [python-jsonpath](https://pypi.org/project/python-jsonpath/).
99
+ - [JSONPath RFC 9535](https://github.com/jg-rp/python-jsonpath-rfc9535) - A minimal, slightly cleaner Python implementation of RFC 9535. If you're not interested JSONPath sytax beyond that defined in RFC 9535, you might choose [jsonpath-rfc9535](https://pypi.org/project/jsonpath-rfc9535/) over [python-jsonpath](https://pypi.org/project/python-jsonpath/).
99
100
 
100
- jsonpath-rfc9535 matches RFC 9535's JSONPath model internally and is careful to use the spec's terminology. It also includes utilities for verifying and testing the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite). Most notably the nondeterministic behavior of some JSONPath selectors.
101
+ jsonpath-rfc9535 also includes utilities for verifying and testing the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite). Most notably the nondeterministic behavior of some JSONPath selectors.
101
102
 
102
103
  - [JSON P3](https://github.com/jg-rp/json-p3) - RFC 9535 implemented in TypeScript. JSON P3 does not include all the non-standard features of Python JSONPath, but does define some optional [extra syntax](https://jg-rp.github.io/json-p3/guides/jsonpath-extra).
103
104
 
@@ -66,9 +66,9 @@ conda install -c conda-forge python-jsonpath
66
66
 
67
67
  ## Related projects
68
68
 
69
- - [JSONPath RFC 9535](https://github.com/jg-rp/python-jsonpath-rfc9535) - A Python implementation of JSONPath that follows RFC 9535 much more strictly. If you require maximum interoperability with JSONPath implemented in other languages - at the expense of extra features - choose [jsonpath-rfc9535](https://pypi.org/project/jsonpath-rfc9535/) over [python-jsonpath](https://pypi.org/project/python-jsonpath/).
69
+ - [JSONPath RFC 9535](https://github.com/jg-rp/python-jsonpath-rfc9535) - A minimal, slightly cleaner Python implementation of RFC 9535. If you're not interested JSONPath sytax beyond that defined in RFC 9535, you might choose [jsonpath-rfc9535](https://pypi.org/project/jsonpath-rfc9535/) over [python-jsonpath](https://pypi.org/project/python-jsonpath/).
70
70
 
71
- jsonpath-rfc9535 matches RFC 9535's JSONPath model internally and is careful to use the spec's terminology. It also includes utilities for verifying and testing the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite). Most notably the nondeterministic behavior of some JSONPath selectors.
71
+ jsonpath-rfc9535 also includes utilities for verifying and testing the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite). Most notably the nondeterministic behavior of some JSONPath selectors.
72
72
 
73
73
  - [JSON P3](https://github.com/jg-rp/json-p3) - RFC 9535 implemented in TypeScript. JSON P3 does not include all the non-standard features of Python JSONPath, but does define some optional [extra syntax](https://jg-rp.github.io/json-p3/guides/jsonpath-extra).
74
74
 
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "2.0.0"
4
+ __version__ = "2.0.2"
@@ -160,21 +160,21 @@ class JSONPointerIndexError(JSONPointerResolutionError, IndexError):
160
160
  """An exception raised when an array index is out of range."""
161
161
 
162
162
  def __str__(self) -> str:
163
- return f"pointer index error {super().__str__()}"
163
+ return f"pointer index error: {super().__str__()}"
164
164
 
165
165
 
166
166
  class JSONPointerKeyError(JSONPointerResolutionError, KeyError):
167
167
  """An exception raised when a pointer references a mapping with a missing key."""
168
168
 
169
169
  def __str__(self) -> str:
170
- return f"pointer key error {super().__str__()}"
170
+ return f"pointer key error: {super().__str__()}"
171
171
 
172
172
 
173
173
  class JSONPointerTypeError(JSONPointerResolutionError, TypeError):
174
174
  """An exception raised when a pointer resolves a string against a sequence."""
175
175
 
176
176
  def __str__(self) -> str:
177
- return f"pointer type error {super().__str__()}"
177
+ return f"pointer type error: {super().__str__()}"
178
178
 
179
179
 
180
180
  class RelativeJSONPointerError(Exception):
@@ -113,7 +113,7 @@ class Lexer:
113
113
  )
114
114
 
115
115
  # /pattern/ or /pattern/flags
116
- self.re_pattern = r"/(?P<G_RE>.+?)/(?P<G_RE_FLAGS>[aims]*)"
116
+ self.re_pattern = r"/(?P<G_RE>(?:(?!(?<!\\)/).)*)/(?P<G_RE_FLAGS>[aims]*)"
117
117
 
118
118
  # func(
119
119
  self.function_pattern = r"(?P<G_FUNC>[a-z][a-z_0-9]+)(?P<G_FUNC_PAREN>\()"
@@ -7,6 +7,7 @@ import json
7
7
  from abc import ABC
8
8
  from abc import abstractmethod
9
9
  from io import IOBase
10
+ from typing import Any
10
11
  from typing import Dict
11
12
  from typing import Iterable
12
13
  from typing import List
@@ -70,7 +71,11 @@ class OpAdd(Op):
70
71
  if target == "-":
71
72
  parent.append(self.value)
72
73
  else:
73
- raise JSONPatchError("index out of range")
74
+ index = self.path._index(target) # noqa: SLF001
75
+ if index == len(parent):
76
+ parent.append(self.value)
77
+ else:
78
+ raise JSONPatchError("index out of range")
74
79
  else:
75
80
  parent.insert(int(target), self.value)
76
81
  elif isinstance(parent, MutableMapping):
@@ -628,7 +633,7 @@ class JSONPatch:
628
633
 
629
634
  def apply(
630
635
  self,
631
- data: Union[str, IOBase, MutableSequence[object], MutableMapping[str, object]],
636
+ data: Union[str, IOBase, MutableSequence[Any], MutableMapping[str, Any]],
632
637
  ) -> object:
633
638
  """Apply all operations from this patch to _data_.
634
639
 
@@ -676,7 +681,7 @@ class JSONPatch:
676
681
 
677
682
  def apply(
678
683
  patch: Union[str, IOBase, Iterable[Mapping[str, object]], None],
679
- data: Union[str, IOBase, MutableSequence[object], MutableMapping[str, object]],
684
+ data: Union[str, IOBase, MutableSequence[Any], MutableMapping[str, Any]],
680
685
  *,
681
686
  unicode_escape: bool = True,
682
687
  uri_decode: bool = False,
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING
10
10
  from typing import Any
11
11
  from typing import Iterable
12
12
  from typing import Mapping
13
+ from typing import Optional
13
14
  from typing import Sequence
14
15
  from typing import Tuple
15
16
  from typing import Union
@@ -58,14 +59,14 @@ class JSONPointer:
58
59
  max_int_index (int): The maximum integer allowed when resolving array
59
60
  items by index. Defaults to `(2**53) - 1`.
60
61
  min_int_index (int): The minimum integer allowed when resolving array
61
- items by index. Defaults to `-(2**53) + 1`.
62
+ items by index. Defaults to `0`.
62
63
  """
63
64
 
64
65
  __slots__ = ("_s", "parts")
65
66
 
66
67
  keys_selector = "~"
67
68
  max_int_index = (2**53) - 1
68
- min_int_index = -(2**53) + 1
69
+ min_int_index = 0
69
70
 
70
71
  def __init__(
71
72
  self,
@@ -75,11 +76,15 @@ class JSONPointer:
75
76
  unicode_escape: bool = True,
76
77
  uri_decode: bool = False,
77
78
  ) -> None:
78
- self.parts = parts or self._parse(
79
- pointer,
80
- unicode_escape=unicode_escape,
81
- uri_decode=uri_decode,
82
- )
79
+ if parts:
80
+ self.parts = tuple(str(part) for part in parts)
81
+ else:
82
+ self.parts = self._parse(
83
+ pointer,
84
+ unicode_escape=unicode_escape,
85
+ uri_decode=uri_decode,
86
+ )
87
+
83
88
  self._s = self._encode(self.parts)
84
89
 
85
90
  def __str__(self) -> str:
@@ -91,7 +96,7 @@ class JSONPointer:
91
96
  *,
92
97
  unicode_escape: bool,
93
98
  uri_decode: bool,
94
- ) -> Tuple[Union[int, str], ...]:
99
+ ) -> Tuple[str, ...]:
95
100
  if uri_decode:
96
101
  s = unquote(s)
97
102
  if unicode_escape:
@@ -103,43 +108,49 @@ class JSONPointer:
103
108
  "pointer must start with a slash or be the empty string"
104
109
  )
105
110
 
106
- return tuple(
107
- self._index(p.replace("~1", "/").replace("~0", "~")) for p in s.split("/")
108
- )[1:]
111
+ return tuple(p.replace("~1", "/").replace("~0", "~") for p in s.split("/"))[1:]
109
112
 
110
- def _index(self, s: str) -> Union[str, int]:
111
- # Reject non-zero ints that start with a zero.
112
- if len(s) > 1 and s.startswith("0"):
113
- return s
113
+ def _index(self, key: str) -> Optional[int]:
114
+ """Return an array index for `key`.
115
+
116
+ Return `None` if key can't be converted to an index.
117
+ """
118
+ # Reject indexes that start with a zero.
119
+ if len(key) > 1 and key.startswith("0"):
120
+ return None
114
121
 
115
122
  try:
116
- index = int(s)
117
- if index < self.min_int_index or index > self.max_int_index:
118
- raise JSONPointerError("index out of range")
119
- return index
123
+ index = int(key)
120
124
  except ValueError:
121
- return s
125
+ return None
122
126
 
123
- def _getitem(self, obj: Any, key: Any) -> Any:
127
+ if index < self.min_int_index or index > self.max_int_index:
128
+ raise JSONPointerIndexError(
129
+ f"array indices must be between {self.min_int_index}"
130
+ f" and {self.max_int_index}"
131
+ )
132
+
133
+ return index
134
+
135
+ def _getitem(self, obj: Any, key: str) -> Any:
124
136
  try:
125
137
  # Handle the most common cases. A mapping with a string key, or a sequence
126
138
  # with an integer index.
127
- #
128
- # Note that `obj` does not have to be a Mapping or Sequence here. Any object
129
- # implementing `__getitem__` will do.
139
+ if isinstance(obj, Sequence) and not isinstance(obj, str):
140
+ index = self._index(key)
141
+ if isinstance(index, int):
142
+ return getitem(obj, index)
130
143
  return getitem(obj, key)
131
144
  except KeyError as err:
132
145
  return self._handle_key_error(obj, key, err)
133
146
  except TypeError as err:
134
147
  return self._handle_type_error(obj, key, err)
135
148
  except IndexError as err:
136
- raise JSONPointerIndexError(f"index out of range: {key}") from err
137
-
138
- def _handle_key_error(self, obj: Any, key: Any, err: Exception) -> object:
139
- if isinstance(key, int):
140
- # Try a string repr of the index-like item as a mapping key.
141
- return self._getitem(obj, str(key))
149
+ if not isinstance(err, JSONPointerIndexError):
150
+ raise JSONPointerIndexError(f"index out of range: {key}") from err
151
+ raise
142
152
 
153
+ def _handle_key_error(self, obj: Any, key: str, err: Exception) -> object:
143
154
  # Handle non-standard key/property selector/pointer.
144
155
  #
145
156
  # For the benefit of `RelativeJSONPointer.to()` and `JSONPathMatch.pointer()`,
@@ -149,8 +160,7 @@ class JSONPointer:
149
160
  # Note that if a key with a leading `#`/`~` exists in `obj`, it will have been
150
161
  # handled by `_getitem`.
151
162
  if (
152
- isinstance(key, str)
153
- and isinstance(obj, Mapping)
163
+ isinstance(obj, Mapping)
154
164
  and key.startswith((self.keys_selector, "#"))
155
165
  and key[1:] in obj
156
166
  ):
@@ -158,17 +168,10 @@ class JSONPointer:
158
168
 
159
169
  raise JSONPointerKeyError(key) from err
160
170
 
161
- def _handle_type_error(self, obj: Any, key: Any, err: Exception) -> object:
162
- if (
163
- isinstance(obj, str)
164
- or not isinstance(obj, Sequence)
165
- or not isinstance(key, str)
166
- ):
171
+ def _handle_type_error(self, obj: Any, key: str, err: Exception) -> object:
172
+ if not isinstance(obj, Sequence) or not isinstance(key, str):
167
173
  raise JSONPointerTypeError(f"{key}: {err}") from err
168
174
 
169
- # `obj` is array-like
170
- # `key` is a string
171
-
172
175
  if key == "-":
173
176
  # "-" is a valid index when appending to a JSON array with JSON Patch, but
174
177
  # not when resolving a JSON Pointer.
@@ -185,11 +188,6 @@ class JSONPointer:
185
188
  raise JSONPointerIndexError(f"index out of range: {_index}") from err
186
189
  return _index
187
190
 
188
- # Try int index. Reject non-zero ints that start with a zero.
189
- index = self._index(key)
190
- if isinstance(index, int):
191
- return self._getitem(obj, index)
192
-
193
191
  raise JSONPointerTypeError(f"{key}: {err}") from err
194
192
 
195
193
  def resolve(
@@ -349,13 +347,13 @@ class JSONPointer:
349
347
  )
350
348
 
351
349
  def __eq__(self, other: object) -> bool:
352
- return isinstance(other, JSONPointer) and self.parts == other.parts
350
+ return isinstance(other, self.__class__) and self.parts == other.parts
353
351
 
354
352
  def __hash__(self) -> int:
355
- return hash(self.parts) # pragma: no cover
353
+ return hash((self.__class__, self.parts)) # pragma: no cover
356
354
 
357
355
  def __repr__(self) -> str:
358
- return f"JSONPointer({self._s!r})" # pragma: no cover
356
+ return f"{self.__class__.__name__}({self._s!r})" # pragma: no cover
359
357
 
360
358
  def exists(
361
359
  self, data: Union[str, IOBase, Sequence[object], Mapping[str, object]]
@@ -391,7 +389,7 @@ class JSONPointer:
391
389
  if not self.parts:
392
390
  return self
393
391
  parent_parts = self.parts[:-1]
394
- return JSONPointer(
392
+ return self.__class__(
395
393
  self._encode(parent_parts),
396
394
  parts=parent_parts,
397
395
  unicode_escape=False,
@@ -415,14 +413,13 @@ class JSONPointer:
415
413
 
416
414
  other = self._unicode_escape(other.lstrip())
417
415
  if other.startswith("/"):
418
- return JSONPointer(other, unicode_escape=False, uri_decode=False)
416
+ return self.__class__(other, unicode_escape=False, uri_decode=False)
419
417
 
420
418
  parts = self.parts + tuple(
421
- self._index(p.replace("~1", "/").replace("~0", "~"))
422
- for p in other.split("/")
419
+ p.replace("~1", "/").replace("~0", "~") for p in other.split("/")
423
420
  )
424
421
 
425
- return JSONPointer(
422
+ return self.__class__(
426
423
  self._encode(parts), parts=parts, unicode_escape=False, uri_decode=False
427
424
  )
428
425
 
@@ -612,7 +609,7 @@ class RelativeJSONPointer:
612
609
  raise RelativeJSONPointerIndexError(
613
610
  f"index offset out of range {new_index}"
614
611
  )
615
- parts[-1] = int(parts[-1]) + self.index
612
+ parts[-1] = str(int(parts[-1]) + self.index)
616
613
 
617
614
  # Pointer or index/property
618
615
  if isinstance(self.pointer, JSONPointer):
@@ -21,6 +21,7 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.11",
22
22
  "Programming Language :: Python :: 3.12",
23
23
  "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
24
25
  "Programming Language :: Python :: Implementation :: CPython",
25
26
  "Programming Language :: Python :: Implementation :: PyPy",
26
27
  ]
@@ -76,7 +77,7 @@ lint = "ruff check ."
76
77
  typing = "mypy"
77
78
 
78
79
  [[tool.hatch.envs.test.matrix]]
79
- python = ["38", "39", "310", "311", "312", "pypy38", "pypy39"]
80
+ python = ["38", "39", "310", "311", "312", "313", "314", "pypy38", "pypy39"]
80
81
 
81
82
  [tool.hatch.envs.test.scripts]
82
83
  test = "pytest {args}"