python-jsonpath 1.3.0__tar.gz → 1.3.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 (36) hide show
  1. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/.gitignore +6 -1
  2. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/PKG-INFO +3 -1
  3. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/README.md +2 -0
  4. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/__about__.py +1 -1
  5. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/__init__.py +0 -2
  6. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/env.py +1 -1
  7. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/exceptions.py +2 -5
  8. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/filter.py +37 -7
  9. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/arguments.py +2 -1
  10. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/patch.py +8 -8
  11. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/pointer.py +73 -46
  12. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/LICENSE.txt +0 -0
  13. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/__main__.py +0 -0
  14. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/_data.py +0 -0
  15. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/cli.py +0 -0
  16. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/fluent_api.py +0 -0
  17. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/__init__.py +0 -0
  18. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/count.py +0 -0
  19. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/filter_function.py +0 -0
  20. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/is_instance.py +0 -0
  21. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/keys.py +0 -0
  22. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/length.py +0 -0
  23. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/match.py +0 -0
  24. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/search.py +0 -0
  25. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/typeof.py +0 -0
  26. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/function_extensions/value.py +0 -0
  27. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/lex.py +0 -0
  28. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/match.py +0 -0
  29. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/parse.py +0 -0
  30. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/path.py +0 -0
  31. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/py.typed +0 -0
  32. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/selectors.py +0 -0
  33. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/serialize.py +0 -0
  34. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/stream.py +0 -0
  35. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/jsonpath/token.py +0 -0
  36. {python_jsonpath-1.3.0 → python_jsonpath-1.3.2}/pyproject.toml +0 -0
@@ -77,6 +77,8 @@ ENV/
77
77
 
78
78
  # IDE
79
79
  .vscode/
80
+ .idea/
81
+ python-jsonpath.iml
80
82
 
81
83
  # Dev utils
82
84
  dev.py
@@ -88,4 +90,7 @@ comparison_regression_suite.yaml
88
90
  cts.json
89
91
 
90
92
  # System
91
- .DS_Store
93
+ .DS_Store
94
+
95
+ # Rob-specific
96
+ generated_temp/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jsonpath
3
- Version: 1.3.0
3
+ Version: 1.3.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
@@ -97,6 +97,8 @@ conda install -c conda-forge python-jsonpath
97
97
 
98
98
  - [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).
99
99
 
100
+ - [Ruby JSON P3](https://github.com/jg-rp/ruby-json-p3) - RFC 9535, RFC 6901 and RFC 6902 implemented in Ruby.
101
+
100
102
  ## Examples
101
103
 
102
104
  ### JSONPath
@@ -72,6 +72,8 @@ conda install -c conda-forge python-jsonpath
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
 
75
+ - [Ruby JSON P3](https://github.com/jg-rp/ruby-json-p3) - RFC 9535, RFC 6901 and RFC 6902 implemented in Ruby.
76
+
75
77
  ## Examples
76
78
 
77
79
  ### JSONPath
@@ -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__ = "1.3.0"
4
+ __version__ = "1.3.2"
@@ -10,7 +10,6 @@ from .exceptions import JSONPathIndexError
10
10
  from .exceptions import JSONPathNameError
11
11
  from .exceptions import JSONPathSyntaxError
12
12
  from .exceptions import JSONPathTypeError
13
- from .exceptions import JSONPointerEncodeError
14
13
  from .exceptions import JSONPointerError
15
14
  from .exceptions import JSONPointerIndexError
16
15
  from .exceptions import JSONPointerKeyError
@@ -52,7 +51,6 @@ __all__ = (
52
51
  "JSONPathSyntaxError",
53
52
  "JSONPathTypeError",
54
53
  "JSONPointer",
55
- "JSONPointerEncodeError",
56
54
  "JSONPointerError",
57
55
  "JSONPointerIndexError",
58
56
  "JSONPointerKeyError",
@@ -99,7 +99,7 @@ class JSONPathEnvironment:
99
99
  intersection_token (str): The pattern used as the intersection operator.
100
100
  Defaults to `"&"`.
101
101
  key_token (str): The pattern used to identify the current key or index when
102
- filtering a, mapping or sequence. Defaults to `"#"`.
102
+ filtering a mapping or sequence. Defaults to `"#"`.
103
103
  keys_selector_token (str): The pattern used as the "keys" selector. Defaults to
104
104
  `"~"`.
105
105
  lexer_class: The lexer to use when tokenizing path strings.
@@ -1,4 +1,5 @@
1
1
  """JSONPath exceptions."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from typing import TYPE_CHECKING
@@ -80,10 +81,6 @@ class JSONPointerError(Exception):
80
81
  """Base class for all JSON Pointer errors."""
81
82
 
82
83
 
83
- class JSONPointerEncodeError(JSONPointerError):
84
- """An exception raised when a JSONPathMatch can't be encoded to a JSON Pointer."""
85
-
86
-
87
84
  class JSONPointerResolutionError(JSONPointerError):
88
85
  """Base exception for those that can be raised during pointer resolution."""
89
86
 
@@ -145,7 +142,7 @@ class JSONPatchTestFailure(JSONPatchError): # noqa: N818
145
142
  def _truncate_message(value: str, num: int, end: str = "...") -> str:
146
143
  if len(value) < num:
147
144
  return value
148
- return f"{value[:num-len(end)]}{end}"
145
+ return f"{value[: num - len(end)]}{end}"
149
146
 
150
147
 
151
148
  def _truncate_words(val: str, num: int, end: str = "...") -> str:
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
30
30
  from .path import JSONPath
31
31
  from .selectors import FilterContext
32
32
 
33
- # ruff: noqa: D102
33
+ # ruff: noqa: D102, PLW1641
34
34
 
35
35
 
36
36
  class FilterExpression(ABC):
@@ -544,7 +544,12 @@ class SelfPath(Path):
544
544
  return context.current
545
545
  return NodeList()
546
546
 
547
- return NodeList(self.path.finditer(context.current))
547
+ return NodeList(
548
+ self.path.finditer(
549
+ context.current,
550
+ filter_context=context.extra_context,
551
+ )
552
+ )
548
553
 
549
554
  async def evaluate_async(self, context: FilterContext) -> object:
550
555
  if isinstance(context.current, str): # TODO: refactor
@@ -557,7 +562,13 @@ class SelfPath(Path):
557
562
  return NodeList()
558
563
 
559
564
  return NodeList(
560
- [match async for match in await self.path.finditer_async(context.current)]
565
+ [
566
+ match
567
+ async for match in await self.path.finditer_async(
568
+ context.current,
569
+ filter_context=context.extra_context,
570
+ )
571
+ ]
561
572
  )
562
573
 
563
574
 
@@ -576,11 +587,22 @@ class RootPath(Path):
576
587
  return str(self.path)
577
588
 
578
589
  def evaluate(self, context: FilterContext) -> object:
579
- return NodeList(self.path.finditer(context.root))
590
+ return NodeList(
591
+ self.path.finditer(
592
+ context.root,
593
+ filter_context=context.extra_context,
594
+ )
595
+ )
580
596
 
581
597
  async def evaluate_async(self, context: FilterContext) -> object:
582
598
  return NodeList(
583
- [match async for match in await self.path.finditer_async(context.root)]
599
+ [
600
+ match
601
+ async for match in await self.path.finditer_async(
602
+ context.root,
603
+ filter_context=context.extra_context,
604
+ )
605
+ ]
584
606
  )
585
607
 
586
608
 
@@ -600,13 +622,21 @@ class FilterContextPath(Path):
600
622
  return "_" + path_repr[1:]
601
623
 
602
624
  def evaluate(self, context: FilterContext) -> object:
603
- return NodeList(self.path.finditer(context.extra_context))
625
+ return NodeList(
626
+ self.path.finditer(
627
+ context.extra_context,
628
+ filter_context=context.extra_context,
629
+ )
630
+ )
604
631
 
605
632
  async def evaluate_async(self, context: FilterContext) -> object:
606
633
  return NodeList(
607
634
  [
608
635
  match
609
- async for match in await self.path.finditer_async(context.extra_context)
636
+ async for match in await self.path.finditer_async(
637
+ context.extra_context,
638
+ filter_context=context.extra_context,
639
+ )
610
640
  ]
611
641
  )
612
642
 
@@ -1,4 +1,5 @@
1
1
  """Class-based function extension base."""
2
+
2
3
  import inspect
3
4
  from typing import TYPE_CHECKING
4
5
  from typing import Any
@@ -26,7 +27,7 @@ def validate(
26
27
  params = list(inspect.signature(func).parameters.values())
27
28
 
28
29
  # Keyword only params are not supported
29
- if len([p for p in params if p.kind in (p.KEYWORD_ONLY, p.VAR_KEYWORD)]):
30
+ if [p for p in params if p.kind in (p.KEYWORD_ONLY, p.VAR_KEYWORD)]:
30
31
  raise JSONPathTypeError(
31
32
  f"function {token.value!r} requires keyword arguments",
32
33
  token=token,
@@ -74,7 +74,7 @@ class OpAdd(Op):
74
74
  else:
75
75
  parent.insert(int(target), self.value)
76
76
  elif isinstance(parent, MutableMapping):
77
- parent[target] = self.value
77
+ parent[str(target)] = self.value
78
78
  else:
79
79
  raise JSONPatchError(
80
80
  f"unexpected operation on {parent.__class__.__name__!r}"
@@ -124,7 +124,7 @@ class OpAddAp(OpAdd):
124
124
  """A non-standard add operation that appends to arrays/lists .
125
125
 
126
126
  This is like _OpAdd_, but assumes an index of "-" if the path can not
127
- be resolved.
127
+ be resolved rather than raising a JSONPatchError.
128
128
 
129
129
  **New in version 1.2.0**
130
130
  """
@@ -183,7 +183,7 @@ class OpRemove(Op):
183
183
  elif isinstance(parent, MutableMapping):
184
184
  if obj is UNDEFINED:
185
185
  raise JSONPatchError("can't remove nonexistent property")
186
- del parent[self.path.parts[-1]]
186
+ del parent[str(self.path.parts[-1])]
187
187
  else:
188
188
  raise JSONPatchError(
189
189
  f"unexpected operation on {parent.__class__.__name__!r}"
@@ -221,7 +221,7 @@ class OpReplace(Op):
221
221
  elif isinstance(parent, MutableMapping):
222
222
  if obj is UNDEFINED:
223
223
  raise JSONPatchError("can't replace nonexistent property")
224
- parent[self.path.parts[-1]] = self.value
224
+ parent[str(self.path.parts[-1])] = self.value
225
225
  else:
226
226
  raise JSONPatchError(
227
227
  f"unexpected operation on {parent.__class__.__name__!r}"
@@ -259,7 +259,7 @@ class OpMove(Op):
259
259
  if isinstance(source_parent, MutableSequence):
260
260
  del source_parent[int(self.source.parts[-1])]
261
261
  if isinstance(source_parent, MutableMapping):
262
- del source_parent[self.source.parts[-1]]
262
+ del source_parent[str(self.source.parts[-1])]
263
263
 
264
264
  dest_parent, _ = self.dest.resolve_parent(data)
265
265
 
@@ -270,7 +270,7 @@ class OpMove(Op):
270
270
  if isinstance(dest_parent, MutableSequence):
271
271
  dest_parent.insert(int(self.dest.parts[-1]), source_obj)
272
272
  elif isinstance(dest_parent, MutableMapping):
273
- dest_parent[self.dest.parts[-1]] = source_obj
273
+ dest_parent[str(self.dest.parts[-1])] = source_obj
274
274
  else:
275
275
  raise JSONPatchError(
276
276
  f"unexpected operation on {dest_parent.__class__.__name__!r}"
@@ -312,7 +312,7 @@ class OpCopy(Op):
312
312
  if isinstance(dest_parent, MutableSequence):
313
313
  dest_parent.insert(int(self.dest.parts[-1]), copy.deepcopy(source_obj))
314
314
  elif isinstance(dest_parent, MutableMapping):
315
- dest_parent[self.dest.parts[-1]] = copy.deepcopy(source_obj)
315
+ dest_parent[str(self.dest.parts[-1])] = copy.deepcopy(source_obj)
316
316
  else:
317
317
  raise JSONPatchError(
318
318
  f"unexpected operation on {dest_parent.__class__.__name__!r}"
@@ -419,7 +419,7 @@ class JSONPatch:
419
419
  value=self._op_value(operation, "value", "addne", i),
420
420
  )
421
421
  elif op == "addap":
422
- self.addne(
422
+ self.addap(
423
423
  path=self._op_pointer(operation, "path", "addap", i),
424
424
  value=self._op_value(operation, "value", "addap", i),
425
425
  )
@@ -32,7 +32,7 @@ if TYPE_CHECKING:
32
32
 
33
33
  class _Undefined:
34
34
  def __str__(self) -> str:
35
- return "<jsonpath.pointer.UNDEFINED>"
35
+ return "<jsonpath.pointer.UNDEFINED>" # pragma: no cover
36
36
 
37
37
 
38
38
  UNDEFINED = _Undefined()
@@ -115,59 +115,83 @@ class JSONPointer:
115
115
  try:
116
116
  index = int(s)
117
117
  if index < self.min_int_index or index > self.max_int_index:
118
- raise JSONPointerIndexError("index out of range")
118
+ raise JSONPointerError("index out of range")
119
119
  return index
120
120
  except ValueError:
121
121
  return s
122
122
 
123
- def _getitem(self, obj: Any, key: Any) -> Any: # noqa: PLR0912
123
+ def _getitem(self, obj: Any, key: Any) -> Any:
124
124
  try:
125
+ # Handle the most common cases. A mapping with a string key, or a sequence
126
+ # 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.
125
130
  return getitem(obj, key)
126
131
  except KeyError as err:
127
- # Try a string repr of the index-like item as a mapping key.
128
- if isinstance(key, int):
129
- try:
130
- return getitem(obj, str(key))
131
- except KeyError:
132
- raise JSONPointerKeyError(key) from err
133
- # Handle non-standard keys/property selector/pointer.
134
- if (
135
- isinstance(key, str)
136
- and isinstance(obj, Mapping)
137
- and key.startswith((self.keys_selector, "#"))
138
- and key[1:] in obj
139
- ):
140
- return key[1:]
141
- # Handle non-standard index/property pointer (`#`)
142
- raise JSONPointerKeyError(key) from err
132
+ return self._handle_key_error(obj, key, err)
143
133
  except TypeError as err:
144
- if isinstance(obj, Sequence) and not isinstance(obj, str):
145
- if key == "-":
146
- # "-" is a valid index when appending to a JSON array
147
- # with JSON Patch, but not when resolving a JSON Pointer.
148
- raise JSONPointerIndexError("index out of range") from None
149
- # Handle non-standard index pointer.
150
- if isinstance(key, str) and key.startswith("#"):
151
- _index = int(key[1:])
152
- if _index >= len(obj):
153
- raise JSONPointerIndexError(
154
- f"index out of range: {_index}"
155
- ) from err
156
- return _index
157
- # Try int index. Reject non-zero ints that start with a zero.
158
- if isinstance(key, str):
159
- index = self._index(key)
160
- if isinstance(index, int):
161
- try:
162
- return getitem(obj, int(key))
163
- except IndexError as index_err:
164
- raise JSONPointerIndexError(
165
- f"index out of range: {key}"
166
- ) from index_err
167
- raise JSONPointerTypeError(f"{key}: {err}") from err
134
+ return self._handle_type_error(obj, key, err)
168
135
  except IndexError as err:
169
136
  raise JSONPointerIndexError(f"index out of range: {key}") from err
170
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))
142
+
143
+ # Handle non-standard key/property selector/pointer.
144
+ #
145
+ # For the benefit of `RelativeJSONPointer.to()` and `JSONPathMatch.pointer()`,
146
+ # treat keys starting with a `#` or `~` as a "key pointer". If `key[1:]` is a
147
+ # key in `obj`, return the key.
148
+ #
149
+ # Note that if a key with a leading `#`/`~` exists in `obj`, it will have been
150
+ # handled by `_getitem`.
151
+ if (
152
+ isinstance(key, str)
153
+ and isinstance(obj, Mapping)
154
+ and key.startswith((self.keys_selector, "#"))
155
+ and key[1:] in obj
156
+ ):
157
+ return key[1:]
158
+
159
+ raise JSONPointerKeyError(key) from err
160
+
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
+ ):
167
+ raise JSONPointerTypeError(f"{key}: {err}") from err
168
+
169
+ # `obj` is array-like
170
+ # `key` is a string
171
+
172
+ if key == "-":
173
+ # "-" is a valid index when appending to a JSON array with JSON Patch, but
174
+ # not when resolving a JSON Pointer.
175
+ raise JSONPointerIndexError("index out of range") from None
176
+
177
+ # Handle non-standard index pointer.
178
+ #
179
+ # For the benefit of `RelativeJSONPointer.to()`, treat keys starting with a `#`
180
+ # and followed by a valid index as an "index pointer". If `int(key[1:])` is
181
+ # less than `len(obj)`, return the index.
182
+ if re.match(r"#[1-9]\d*", key):
183
+ _index = int(key[1:])
184
+ if _index >= len(obj):
185
+ raise JSONPointerIndexError(f"index out of range: {_index}") from err
186
+ return _index
187
+
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
+ raise JSONPointerTypeError(f"{key}: {err}") from err
194
+
171
195
  def resolve(
172
196
  self,
173
197
  data: Union[str, IOBase, Sequence[object], Mapping[str, object]],
@@ -263,7 +287,7 @@ class JSONPointer:
263
287
  pointer = cls._encode(match.parts)
264
288
  else:
265
289
  # This should not happen, unless the JSONPathMatch has been tampered with.
266
- pointer = ""
290
+ pointer = "" # pragma: no cover
267
291
 
268
292
  return cls(
269
293
  pointer,
@@ -328,10 +352,10 @@ class JSONPointer:
328
352
  return isinstance(other, JSONPointer) and self.parts == other.parts
329
353
 
330
354
  def __hash__(self) -> int:
331
- return hash(self.parts)
355
+ return hash(self.parts) # pragma: no cover
332
356
 
333
357
  def __repr__(self) -> str:
334
- return f"JSONPointer({self._s!r})"
358
+ return f"JSONPointer({self._s!r})" # pragma: no cover
335
359
 
336
360
  def exists(
337
361
  self, data: Union[str, IOBase, Sequence[object], Mapping[str, object]]
@@ -485,6 +509,9 @@ class RelativeJSONPointer:
485
509
  def __eq__(self, __value: object) -> bool:
486
510
  return isinstance(__value, RelativeJSONPointer) and str(self) == str(__value)
487
511
 
512
+ def __hash__(self) -> int:
513
+ return hash((self.origin, self.index, self.pointer)) # pragma: no cover
514
+
488
515
  def _parse(
489
516
  self,
490
517
  rel: str,