python-jsonpath 1.3.2__tar.gz → 2.0.0__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 (50) hide show
  1. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/.gitignore +3 -1
  2. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/PKG-INFO +5 -1
  3. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/__about__.py +1 -1
  4. python_jsonpath-2.0.0/jsonpath/__init__.py +365 -0
  5. python_jsonpath-2.0.0/jsonpath/_types.py +31 -0
  6. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/cli.py +11 -1
  7. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/env.py +100 -47
  8. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/exceptions.py +75 -4
  9. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/filter.py +78 -84
  10. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/function_extensions/__init__.py +4 -2
  11. python_jsonpath-2.0.0/jsonpath/function_extensions/_pattern.py +112 -0
  12. python_jsonpath-2.0.0/jsonpath/function_extensions/keys.py +31 -0
  13. python_jsonpath-2.0.0/jsonpath/function_extensions/match.py +19 -0
  14. python_jsonpath-2.0.0/jsonpath/function_extensions/search.py +19 -0
  15. python_jsonpath-2.0.0/jsonpath/function_extensions/starts_with.py +21 -0
  16. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/lex.py +113 -64
  17. python_jsonpath-2.0.0/jsonpath/lru_cache.py +130 -0
  18. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/match.py +13 -0
  19. python_jsonpath-2.0.0/jsonpath/parse.py +888 -0
  20. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/path.py +48 -84
  21. python_jsonpath-2.0.0/jsonpath/segments.py +131 -0
  22. python_jsonpath-2.0.0/jsonpath/selectors.py +734 -0
  23. python_jsonpath-2.0.0/jsonpath/stream.py +97 -0
  24. python_jsonpath-2.0.0/jsonpath/token.py +120 -0
  25. python_jsonpath-2.0.0/jsonpath/unescape.py +134 -0
  26. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/pyproject.toml +27 -6
  27. python_jsonpath-1.3.2/jsonpath/__init__.py +0 -83
  28. python_jsonpath-1.3.2/jsonpath/function_extensions/keys.py +0 -12
  29. python_jsonpath-1.3.2/jsonpath/function_extensions/match.py +0 -21
  30. python_jsonpath-1.3.2/jsonpath/function_extensions/search.py +0 -21
  31. python_jsonpath-1.3.2/jsonpath/parse.py +0 -742
  32. python_jsonpath-1.3.2/jsonpath/selectors.py +0 -768
  33. python_jsonpath-1.3.2/jsonpath/stream.py +0 -99
  34. python_jsonpath-1.3.2/jsonpath/token.py +0 -122
  35. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/LICENSE.txt +0 -0
  36. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/README.md +0 -0
  37. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/__main__.py +0 -0
  38. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/_data.py +0 -0
  39. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/fluent_api.py +0 -0
  40. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/function_extensions/arguments.py +0 -0
  41. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/function_extensions/count.py +0 -0
  42. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/function_extensions/filter_function.py +0 -0
  43. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/function_extensions/is_instance.py +0 -0
  44. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/function_extensions/length.py +0 -0
  45. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/function_extensions/typeof.py +0 -0
  46. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/function_extensions/value.py +0 -0
  47. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/patch.py +0 -0
  48. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/pointer.py +0 -0
  49. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/py.typed +0 -0
  50. {python_jsonpath-1.3.2 → python_jsonpath-2.0.0}/jsonpath/serialize.py +0 -0
@@ -78,7 +78,9 @@ ENV/
78
78
  # IDE
79
79
  .vscode/
80
80
  .idea/
81
- python-jsonpath.iml
81
+ *.iml
82
+ *.ipr
83
+ .junie
82
84
 
83
85
  # Dev utils
84
86
  dev.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jsonpath
3
- Version: 1.3.2
3
+ Version: 2.0.0
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
@@ -8,6 +8,7 @@ Project-URL: Source, https://github.com/jg-rp/python-jsonpath
8
8
  Author-email: James Prior <jamesgr.prior@gmail.com>
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE.txt
11
+ Keywords: JSON,JSON Patch,JSON Path,JSON Pointer,JSONPath,RFC 9535
11
12
  Classifier: Development Status :: 5 - Production/Stable
12
13
  Classifier: Intended Audience :: Developers
13
14
  Classifier: License :: OSI Approved :: MIT License
@@ -21,6 +22,9 @@ Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Programming Language :: Python :: Implementation :: CPython
22
23
  Classifier: Programming Language :: Python :: Implementation :: PyPy
23
24
  Requires-Python: >=3.8
25
+ Provides-Extra: strict
26
+ Requires-Dist: iregexp-check>=0.1.4; extra == 'strict'
27
+ Requires-Dist: regex; extra == 'strict'
24
28
  Description-Content-Type: text/markdown
25
29
 
26
30
  <h1 align="center">Python JSONPath</h1>
@@ -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.2"
4
+ __version__ = "2.0.0"
@@ -0,0 +1,365 @@
1
+ # SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ from __future__ import annotations
5
+
6
+ from typing import TYPE_CHECKING
7
+ from typing import AsyncIterable
8
+ from typing import Iterable
9
+ from typing import List
10
+ from typing import Optional
11
+ from typing import Union
12
+
13
+ from ._types import JSON
14
+ from ._types import JSONData
15
+ from ._types import JSONScalar
16
+ from .env import JSONPathEnvironment
17
+ from .exceptions import JSONPatchError
18
+ from .exceptions import JSONPatchTestFailure
19
+ from .exceptions import JSONPathError
20
+ from .exceptions import JSONPathIndexError
21
+ from .exceptions import JSONPathNameError
22
+ from .exceptions import JSONPathSyntaxError
23
+ from .exceptions import JSONPathTypeError
24
+ from .exceptions import JSONPointerError
25
+ from .exceptions import JSONPointerIndexError
26
+ from .exceptions import JSONPointerKeyError
27
+ from .exceptions import JSONPointerResolutionError
28
+ from .exceptions import JSONPointerTypeError
29
+ from .exceptions import RelativeJSONPointerError
30
+ from .exceptions import RelativeJSONPointerIndexError
31
+ from .exceptions import RelativeJSONPointerSyntaxError
32
+ from .filter import UNDEFINED
33
+ from .fluent_api import Projection
34
+ from .fluent_api import Query
35
+ from .lex import Lexer
36
+ from .match import JSONPathMatch
37
+ from .match import NodeList
38
+ from .parse import Parser
39
+ from .patch import JSONPatch
40
+ from .path import CompoundJSONPath
41
+ from .path import JSONPath
42
+ from .pointer import JSONPointer
43
+ from .pointer import RelativeJSONPointer
44
+ from .pointer import resolve
45
+
46
+ if TYPE_CHECKING:
47
+ from .match import FilterContextVars
48
+
49
+
50
+ __all__ = (
51
+ "compile",
52
+ "CompoundJSONPath",
53
+ "findall_async",
54
+ "findall",
55
+ "finditer_async",
56
+ "finditer",
57
+ "JSONPatch",
58
+ "JSONPatchError",
59
+ "JSONPatchTestFailure",
60
+ "JSONPath",
61
+ "JSONPathEnvironment",
62
+ "JSONPathError",
63
+ "JSONPathIndexError",
64
+ "JSONPathMatch",
65
+ "JSONPathNameError",
66
+ "JSONPathSyntaxError",
67
+ "JSONPathTypeError",
68
+ "JSONPointer",
69
+ "JSONPointerError",
70
+ "JSONPointerIndexError",
71
+ "JSONPointerKeyError",
72
+ "JSONPointerResolutionError",
73
+ "JSONPointerTypeError",
74
+ "Lexer",
75
+ "NodeList",
76
+ "match",
77
+ "Parser",
78
+ "Projection",
79
+ "query",
80
+ "Query",
81
+ "RelativeJSONPointer",
82
+ "RelativeJSONPointerError",
83
+ "RelativeJSONPointerIndexError",
84
+ "RelativeJSONPointerSyntaxError",
85
+ "resolve",
86
+ "JSON",
87
+ "JSONData",
88
+ "JSONScalar",
89
+ "UNDEFINED",
90
+ )
91
+
92
+
93
+ # For convenience and to delegate to strict or non-strict environments.
94
+ DEFAULT_ENV = JSONPathEnvironment()
95
+ _STRICT_ENV = JSONPathEnvironment(strict=True)
96
+
97
+
98
+ def compile(path: str, *, strict: bool = False) -> Union[JSONPath, CompoundJSONPath]: # noqa: A001
99
+ """Prepare a path string ready for repeated matching against different data.
100
+
101
+ Arguments:
102
+ path: A JSONPath as a string.
103
+ strict: When `True`, compile the path for strict compliance with RFC 9535.
104
+
105
+ Returns:
106
+ A `JSONPath` or `CompoundJSONPath`, ready to match against some data.
107
+ Expect a `CompoundJSONPath` if the path string uses the _union_ or
108
+ _intersection_ operators.
109
+
110
+ Raises:
111
+ JSONPathSyntaxError: If _path_ is invalid.
112
+ JSONPathTypeError: If filter functions are given arguments of an
113
+ unacceptable type.
114
+ """
115
+ return _STRICT_ENV.compile(path) if strict else DEFAULT_ENV.compile(path)
116
+
117
+
118
+ def findall(
119
+ path: str,
120
+ data: JSONData,
121
+ *,
122
+ filter_context: Optional[FilterContextVars] = None,
123
+ strict: bool = False,
124
+ ) -> List[object]:
125
+ """Find all objects in _data_ matching the JSONPath _path_.
126
+
127
+ If _data_ is a string or a file-like objects, it will be loaded
128
+ using `json.loads()` and the default `JSONDecoder`.
129
+
130
+ Arguments:
131
+ path: The JSONPath as a string.
132
+ data: A JSON document or Python object implementing the `Sequence`
133
+ or `Mapping` interfaces.
134
+ filter_context: Arbitrary data made available to filters using
135
+ the _filter context_ selector.
136
+ strict: When `True`, compile and evaluate with strict compliance with
137
+ RFC 9535.
138
+
139
+ Returns:
140
+ A list of matched objects. If there are no matches, the list will
141
+ be empty.
142
+
143
+ Raises:
144
+ JSONPathSyntaxError: If the path is invalid.
145
+ JSONPathTypeError: If a filter expression attempts to use types in
146
+ an incompatible way.
147
+ """
148
+ return (
149
+ _STRICT_ENV.findall(path, data, filter_context=filter_context)
150
+ if strict
151
+ else DEFAULT_ENV.findall(path, data, filter_context=filter_context)
152
+ )
153
+
154
+
155
+ async def findall_async(
156
+ path: str,
157
+ data: JSONData,
158
+ *,
159
+ filter_context: Optional[FilterContextVars] = None,
160
+ strict: bool = False,
161
+ ) -> List[object]:
162
+ """Find all objects in _data_ matching the JSONPath _path_.
163
+
164
+ If _data_ is a string or a file-like objects, it will be loaded
165
+ using `json.loads()` and the default `JSONDecoder`.
166
+
167
+ Arguments:
168
+ path: The JSONPath as a string.
169
+ data: A JSON document or Python object implementing the `Sequence`
170
+ or `Mapping` interfaces.
171
+ filter_context: Arbitrary data made available to filters using
172
+ the _filter context_ selector.
173
+ strict: When `True`, compile and evaluate with strict compliance with
174
+ RFC 9535.
175
+
176
+ Returns:
177
+ A list of matched objects. If there are no matches, the list will
178
+ be empty.
179
+
180
+ Raises:
181
+ JSONPathSyntaxError: If the path is invalid.
182
+ JSONPathTypeError: If a filter expression attempts to use types in
183
+ an incompatible way.
184
+ """
185
+ return (
186
+ await _STRICT_ENV.findall_async(path, data, filter_context=filter_context)
187
+ if strict
188
+ else await DEFAULT_ENV.findall_async(path, data, filter_context=filter_context)
189
+ )
190
+
191
+
192
+ def finditer(
193
+ path: str,
194
+ data: JSONData,
195
+ *,
196
+ filter_context: Optional[FilterContextVars] = None,
197
+ strict: bool = False,
198
+ ) -> Iterable[JSONPathMatch]:
199
+ """Generate `JSONPathMatch` objects for each match of _path_ in _data_.
200
+
201
+ If _data_ is a string or a file-like objects, it will be loaded using
202
+ `json.loads()` and the default `JSONDecoder`.
203
+
204
+ Arguments:
205
+ path: The JSONPath as a string.
206
+ data: A JSON document or Python object implementing the `Sequence`
207
+ or `Mapping` interfaces.
208
+ filter_context: Arbitrary data made available to filters using
209
+ the _filter context_ selector.
210
+ strict: When `True`, compile and evaluate with strict compliance with
211
+ RFC 9535.
212
+
213
+ Returns:
214
+ An iterator yielding `JSONPathMatch` objects for each match.
215
+
216
+ Raises:
217
+ JSONPathSyntaxError: If the path is invalid.
218
+ JSONPathTypeError: If a filter expression attempts to use types in
219
+ an incompatible way.
220
+ """
221
+ return (
222
+ _STRICT_ENV.finditer(path, data, filter_context=filter_context)
223
+ if strict
224
+ else DEFAULT_ENV.finditer(path, data, filter_context=filter_context)
225
+ )
226
+
227
+
228
+ async def finditer_async(
229
+ path: str,
230
+ data: JSONData,
231
+ *,
232
+ filter_context: Optional[FilterContextVars] = None,
233
+ strict: bool = False,
234
+ ) -> AsyncIterable[JSONPathMatch]:
235
+ """Find all objects in _data_ matching the JSONPath _path_.
236
+
237
+ If _data_ is a string or a file-like objects, it will be loaded
238
+ using `json.loads()` and the default `JSONDecoder`.
239
+
240
+ Arguments:
241
+ path: The JSONPath as a string.
242
+ data: A JSON document or Python object implementing the `Sequence`
243
+ or `Mapping` interfaces.
244
+ filter_context: Arbitrary data made available to filters using
245
+ the _filter context_ selector.
246
+ strict: When `True`, compile and evaluate with strict compliance with
247
+ RFC 9535.
248
+
249
+ Returns:
250
+ A list of matched objects. If there are no matches, the list will
251
+ be empty.
252
+
253
+ Raises:
254
+ JSONPathSyntaxError: If the path is invalid.
255
+ JSONPathTypeError: If a filter expression attempts to use types in
256
+ an incompatible way.
257
+ """
258
+ return (
259
+ await _STRICT_ENV.finditer_async(path, data, filter_context=filter_context)
260
+ if strict
261
+ else await DEFAULT_ENV.finditer_async(path, data, filter_context=filter_context)
262
+ )
263
+
264
+
265
+ def match(
266
+ path: str,
267
+ data: JSONData,
268
+ *,
269
+ filter_context: Optional[FilterContextVars] = None,
270
+ strict: bool = False,
271
+ ) -> Union[JSONPathMatch, None]:
272
+ """Return a `JSONPathMatch` instance for the first object found in _data_.
273
+
274
+ `None` is returned if there are no matches.
275
+
276
+ Arguments:
277
+ path: The JSONPath as a string.
278
+ data: A JSON document or Python object implementing the `Sequence`
279
+ or `Mapping` interfaces.
280
+ filter_context: Arbitrary data made available to filters using
281
+ the _filter context_ selector.
282
+ strict: When `True`, compile and evaluate with strict compliance with
283
+ RFC 9535.
284
+
285
+ Returns:
286
+ A `JSONPathMatch` object for the first match, or `None` if there were
287
+ no matches.
288
+
289
+ Raises:
290
+ JSONPathSyntaxError: If the path is invalid.
291
+ JSONPathTypeError: If a filter expression attempts to use types in
292
+ an incompatible way.
293
+ """
294
+ return (
295
+ _STRICT_ENV.match(path, data, filter_context=filter_context)
296
+ if strict
297
+ else DEFAULT_ENV.match(path, data, filter_context=filter_context)
298
+ )
299
+
300
+
301
+ def query(
302
+ path: str,
303
+ data: JSONData,
304
+ *,
305
+ filter_context: Optional[FilterContextVars] = None,
306
+ strict: bool = False,
307
+ ) -> Query:
308
+ """Return a `Query` iterator over matches found by applying _path_ to _data_.
309
+
310
+ `Query` objects are iterable.
311
+
312
+ ```
313
+ for match in jsonpath.query("$.foo..bar", data):
314
+ ...
315
+ ```
316
+
317
+ You can skip and limit results with `Query.skip()` and `Query.limit()`.
318
+
319
+ ```
320
+ matches = (
321
+ jsonpath.query("$.foo..bar", data)
322
+ .skip(5)
323
+ .limit(10)
324
+ )
325
+
326
+ for match in matches
327
+ ...
328
+ ```
329
+
330
+ `Query.tail()` will get the last _n_ results.
331
+
332
+ ```
333
+ for match in jsonpath.query("$.foo..bar", data).tail(5):
334
+ ...
335
+ ```
336
+
337
+ Get values for each match using `Query.values()`.
338
+
339
+ ```
340
+ for obj in jsonpath.query("$.foo..bar", data).limit(5).values():
341
+ ...
342
+ ```
343
+
344
+ Arguments:
345
+ path: The JSONPath as a string.
346
+ data: A JSON document or Python object implementing the `Sequence`
347
+ or `Mapping` interfaces.
348
+ filter_context: Arbitrary data made available to filters using
349
+ the _filter context_ selector.
350
+ strict: When `True`, compile and evaluate with strict compliance with
351
+ RFC 9535.
352
+
353
+ Returns:
354
+ A query iterator.
355
+
356
+ Raises:
357
+ JSONPathSyntaxError: If the path is invalid.
358
+ JSONPathTypeError: If a filter expression attempts to use types in
359
+ an incompatible way.
360
+ """
361
+ return (
362
+ _STRICT_ENV.query(path, data, filter_context=filter_context)
363
+ if strict
364
+ else DEFAULT_ENV.query(path, data, filter_context=filter_context)
365
+ )
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from io import IOBase
4
+ from typing import Any
5
+ from typing import Mapping
6
+ from typing import Sequence
7
+ from typing import Union
8
+
9
+ JSONScalar = Union[str, int, float, bool, None]
10
+ """A scalar JSON-like value.
11
+
12
+ This includes primitive types that can appear in JSON:
13
+ string, number, boolean, or null.
14
+ """
15
+
16
+ JSON = Union[JSONScalar, Sequence[Any], Mapping[str, Any]]
17
+ """A JSON-like data structure.
18
+
19
+ This covers scalars, sequences (e.g. lists, tuples), and mappings (e.g.
20
+ dictionaries with string keys). Values inside may be untyped (`Any`) rather
21
+ than recursively constrained to `JSON` for flexibility.
22
+ """
23
+
24
+ JSONData = Union[str, IOBase, JSON]
25
+ """Input representing JSON content.
26
+
27
+ Accepts:
28
+ - a JSON-like object (`JSON`),
29
+ - a raw JSON string,
30
+ - or a file-like object containing JSON data.
31
+ """
@@ -1,4 +1,5 @@
1
1
  """JSONPath, JSON Pointer and JSON Patch command line interface."""
2
+
2
3
  import argparse
3
4
  import json
4
5
  import sys
@@ -59,6 +60,15 @@ def path_sub_command(parser: argparse.ArgumentParser) -> None: # noqa: D103
59
60
  help="Disables filter expression well-typedness checks.",
60
61
  )
61
62
 
63
+ parser.add_argument(
64
+ "--strict",
65
+ action="store_true",
66
+ help=(
67
+ "Compile and evaluate JSONPath expressions with strict "
68
+ "compliance with RFC 9535."
69
+ ),
70
+ )
71
+
62
72
 
63
73
  def pointer_sub_command(parser: argparse.ArgumentParser) -> None: # noqa: D103
64
74
  parser.set_defaults(func=handle_pointer_command)
@@ -248,6 +258,7 @@ def handle_path_command(args: argparse.Namespace) -> None: # noqa: PLR0912
248
258
  path = jsonpath.JSONPathEnvironment(
249
259
  unicode_escape=not args.no_unicode_escape,
250
260
  well_typed=not args.no_type_checks,
261
+ strict=args.strict,
251
262
  ).compile(query)
252
263
  except JSONPathSyntaxError as err:
253
264
  if args.debug:
@@ -289,7 +300,6 @@ def handle_pointer_command(args: argparse.Namespace) -> None:
289
300
  if args.pointer is not None:
290
301
  pointer = args.pointer
291
302
  else:
292
- # TODO: is a property with a trailing newline OK?
293
303
  pointer = args.pointer_file.read().strip()
294
304
 
295
305
  try: