python-jsonpath 1.3.1__py3-none-any.whl → 2.0.0__py3-none-any.whl

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.
jsonpath/__about__.py CHANGED
@@ -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.1"
4
+ __version__ = "2.0.0"
jsonpath/__init__.py CHANGED
@@ -1,7 +1,18 @@
1
1
  # SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
+ from __future__ import annotations
4
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
5
16
  from .env import JSONPathEnvironment
6
17
  from .exceptions import JSONPatchError
7
18
  from .exceptions import JSONPatchTestFailure
@@ -10,7 +21,6 @@ from .exceptions import JSONPathIndexError
10
21
  from .exceptions import JSONPathNameError
11
22
  from .exceptions import JSONPathSyntaxError
12
23
  from .exceptions import JSONPathTypeError
13
- from .exceptions import JSONPointerEncodeError
14
24
  from .exceptions import JSONPointerError
15
25
  from .exceptions import JSONPointerIndexError
16
26
  from .exceptions import JSONPointerKeyError
@@ -33,6 +43,10 @@ from .pointer import JSONPointer
33
43
  from .pointer import RelativeJSONPointer
34
44
  from .pointer import resolve
35
45
 
46
+ if TYPE_CHECKING:
47
+ from .match import FilterContextVars
48
+
49
+
36
50
  __all__ = (
37
51
  "compile",
38
52
  "CompoundJSONPath",
@@ -52,7 +66,6 @@ __all__ = (
52
66
  "JSONPathSyntaxError",
53
67
  "JSONPathTypeError",
54
68
  "JSONPointer",
55
- "JSONPointerEncodeError",
56
69
  "JSONPointerError",
57
70
  "JSONPointerIndexError",
58
71
  "JSONPointerKeyError",
@@ -70,16 +83,283 @@ __all__ = (
70
83
  "RelativeJSONPointerIndexError",
71
84
  "RelativeJSONPointerSyntaxError",
72
85
  "resolve",
86
+ "JSON",
87
+ "JSONData",
88
+ "JSONScalar",
73
89
  "UNDEFINED",
74
90
  )
75
91
 
76
92
 
77
- # For convenience
93
+ # For convenience and to delegate to strict or non-strict environments.
78
94
  DEFAULT_ENV = JSONPathEnvironment()
79
- compile = DEFAULT_ENV.compile # noqa: A001
80
- findall = DEFAULT_ENV.findall
81
- findall_async = DEFAULT_ENV.findall_async
82
- finditer = DEFAULT_ENV.finditer
83
- finditer_async = DEFAULT_ENV.finditer_async
84
- match = DEFAULT_ENV.match
85
- query = DEFAULT_ENV.query
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
+ )
jsonpath/_types.py ADDED
@@ -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
+ """
jsonpath/cli.py CHANGED
@@ -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: