python-jsonpath 1.0.0__py3-none-any.whl → 1.1.1__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 +1 -1
- jsonpath/__init__.py +4 -0
- jsonpath/env.py +52 -7
- jsonpath/filter.py +8 -6
- jsonpath/fluent_api.py +161 -0
- jsonpath/match.py +5 -0
- jsonpath/parse.py +7 -5
- jsonpath/selectors.py +42 -18
- {python_jsonpath-1.0.0.dist-info → python_jsonpath-1.1.1.dist-info}/METADATA +1 -1
- {python_jsonpath-1.0.0.dist-info → python_jsonpath-1.1.1.dist-info}/RECORD +13 -12
- {python_jsonpath-1.0.0.dist-info → python_jsonpath-1.1.1.dist-info}/WHEEL +0 -0
- {python_jsonpath-1.0.0.dist-info → python_jsonpath-1.1.1.dist-info}/entry_points.txt +0 -0
- {python_jsonpath-1.0.0.dist-info → python_jsonpath-1.1.1.dist-info}/licenses/LICENSE.txt +0 -0
jsonpath/__about__.py
CHANGED
jsonpath/__init__.py
CHANGED
|
@@ -17,6 +17,7 @@ from .exceptions import RelativeJSONPointerError
|
|
|
17
17
|
from .exceptions import RelativeJSONPointerIndexError
|
|
18
18
|
from .exceptions import RelativeJSONPointerSyntaxError
|
|
19
19
|
from .filter import UNDEFINED
|
|
20
|
+
from .fluent_api import Query
|
|
20
21
|
from .lex import Lexer
|
|
21
22
|
from .match import JSONPathMatch
|
|
22
23
|
from .parse import Parser
|
|
@@ -52,6 +53,8 @@ __all__ = (
|
|
|
52
53
|
"Lexer",
|
|
53
54
|
"match",
|
|
54
55
|
"Parser",
|
|
56
|
+
"query",
|
|
57
|
+
"Query",
|
|
55
58
|
"RelativeJSONPointer",
|
|
56
59
|
"RelativeJSONPointerError",
|
|
57
60
|
"RelativeJSONPointerIndexError",
|
|
@@ -69,3 +72,4 @@ findall_async = DEFAULT_ENV.findall_async
|
|
|
69
72
|
finditer = DEFAULT_ENV.finditer
|
|
70
73
|
finditer_async = DEFAULT_ENV.finditer_async
|
|
71
74
|
match = DEFAULT_ENV.match
|
|
75
|
+
query = DEFAULT_ENV.query
|
jsonpath/env.py
CHANGED
|
@@ -27,6 +27,7 @@ from .filter import FilterExpression
|
|
|
27
27
|
from .filter import FunctionExtension
|
|
28
28
|
from .filter import InfixExpression
|
|
29
29
|
from .filter import Path
|
|
30
|
+
from .fluent_api import Query
|
|
30
31
|
from .function_extensions import ExpressionType
|
|
31
32
|
from .function_extensions import FilterFunction
|
|
32
33
|
from .function_extensions import validate
|
|
@@ -76,8 +77,6 @@ class JSONPathEnvironment:
|
|
|
76
77
|
- Hook in to mapping and sequence item getting by overriding `getitem()`.
|
|
77
78
|
- Change filter comparison operator behavior by overriding `compare()`.
|
|
78
79
|
|
|
79
|
-
## Class attributes
|
|
80
|
-
|
|
81
80
|
Arguments:
|
|
82
81
|
filter_caching (bool): If `True`, filter expressions will be cached
|
|
83
82
|
where possible.
|
|
@@ -89,6 +88,8 @@ class JSONPathEnvironment:
|
|
|
89
88
|
|
|
90
89
|
**New in version 0.10.0**
|
|
91
90
|
|
|
91
|
+
## Class attributes
|
|
92
|
+
|
|
92
93
|
Attributes:
|
|
93
94
|
fake_root_token (str): The pattern used to select a "fake" root node, one level
|
|
94
95
|
above the real root node.
|
|
@@ -229,9 +230,9 @@ class JSONPathEnvironment:
|
|
|
229
230
|
*,
|
|
230
231
|
filter_context: Optional[FilterContextVars] = None,
|
|
231
232
|
) -> List[object]:
|
|
232
|
-
"""Find all objects in
|
|
233
|
+
"""Find all objects in _data_ matching the JSONPath _path_.
|
|
233
234
|
|
|
234
|
-
If
|
|
235
|
+
If _data_ is a string or a file-like objects, it will be loaded
|
|
235
236
|
using `json.loads()` and the default `JSONDecoder`.
|
|
236
237
|
|
|
237
238
|
Arguments:
|
|
@@ -259,10 +260,10 @@ class JSONPathEnvironment:
|
|
|
259
260
|
*,
|
|
260
261
|
filter_context: Optional[FilterContextVars] = None,
|
|
261
262
|
) -> Iterable[JSONPathMatch]:
|
|
262
|
-
"""Generate `JSONPathMatch` objects for each match.
|
|
263
|
+
"""Generate `JSONPathMatch` objects for each match of _path_ in _data_.
|
|
263
264
|
|
|
264
|
-
If
|
|
265
|
-
|
|
265
|
+
If _data_ is a string or a file-like objects, it will be loaded using
|
|
266
|
+
`json.loads()` and the default `JSONDecoder`.
|
|
266
267
|
|
|
267
268
|
Arguments:
|
|
268
269
|
path: The JSONPath as a string.
|
|
@@ -310,6 +311,50 @@ class JSONPathEnvironment:
|
|
|
310
311
|
"""
|
|
311
312
|
return self.compile(path).match(data, filter_context=filter_context)
|
|
312
313
|
|
|
314
|
+
def query(
|
|
315
|
+
self,
|
|
316
|
+
path: str,
|
|
317
|
+
data: Union[str, IOBase, Sequence[Any], Mapping[str, Any]],
|
|
318
|
+
filter_context: Optional[FilterContextVars] = None,
|
|
319
|
+
) -> Query:
|
|
320
|
+
"""Return a `Query` object over matches found by applying _path_ to _data_.
|
|
321
|
+
|
|
322
|
+
`Query` objects are iterable.
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
for match in jsonpath.query("$.foo..bar", data):
|
|
326
|
+
...
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
You can skip and limit results with `Query.skip()` and `Query.limit()`.
|
|
330
|
+
|
|
331
|
+
```
|
|
332
|
+
matches = (
|
|
333
|
+
jsonpath.query("$.foo..bar", data)
|
|
334
|
+
.skip(5)
|
|
335
|
+
.limit(10)
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
for match in matches
|
|
339
|
+
...
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`Query.tail()` will get the last _n_ results.
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
for match in jsonpath.query("$.foo..bar", data).tail(5):
|
|
346
|
+
...
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Get values for each match using `Query.values()`.
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
for obj in jsonpath.query("$.foo..bar", data).limit(5).values():
|
|
353
|
+
...
|
|
354
|
+
```
|
|
355
|
+
"""
|
|
356
|
+
return Query(self.finditer(path, data, filter_context=filter_context))
|
|
357
|
+
|
|
313
358
|
async def findall_async(
|
|
314
359
|
self,
|
|
315
360
|
path: str,
|
jsonpath/filter.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Filter expression nodes."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import copy
|
|
@@ -317,7 +318,7 @@ class PrefixExpression(FilterExpression):
|
|
|
317
318
|
class InfixExpression(FilterExpression):
|
|
318
319
|
"""A pair of expressions and a comparison or logical operator."""
|
|
319
320
|
|
|
320
|
-
__slots__ = ("left", "operator", "right")
|
|
321
|
+
__slots__ = ("left", "operator", "right", "logical")
|
|
321
322
|
|
|
322
323
|
def __init__(
|
|
323
324
|
self,
|
|
@@ -328,10 +329,11 @@ class InfixExpression(FilterExpression):
|
|
|
328
329
|
self.left = left
|
|
329
330
|
self.operator = operator
|
|
330
331
|
self.right = right
|
|
332
|
+
self.logical = operator in ("&&", "||")
|
|
331
333
|
super().__init__()
|
|
332
334
|
|
|
333
335
|
def __str__(self) -> str:
|
|
334
|
-
if self.
|
|
336
|
+
if self.logical:
|
|
335
337
|
return f"({self.left} {self.operator} {self.right})"
|
|
336
338
|
return f"{self.left} {self.operator} {self.right}"
|
|
337
339
|
|
|
@@ -345,22 +347,22 @@ class InfixExpression(FilterExpression):
|
|
|
345
347
|
|
|
346
348
|
def evaluate(self, context: FilterContext) -> bool:
|
|
347
349
|
left = self.left.evaluate(context)
|
|
348
|
-
if isinstance(left, NodeList) and len(left) == 1:
|
|
350
|
+
if not self.logical and isinstance(left, NodeList) and len(left) == 1:
|
|
349
351
|
left = left[0].obj
|
|
350
352
|
|
|
351
353
|
right = self.right.evaluate(context)
|
|
352
|
-
if isinstance(right, NodeList) and len(right) == 1:
|
|
354
|
+
if not self.logical and isinstance(right, NodeList) and len(right) == 1:
|
|
353
355
|
right = right[0].obj
|
|
354
356
|
|
|
355
357
|
return context.env.compare(left, self.operator, right)
|
|
356
358
|
|
|
357
359
|
async def evaluate_async(self, context: FilterContext) -> bool:
|
|
358
360
|
left = await self.left.evaluate_async(context)
|
|
359
|
-
if isinstance(left, NodeList) and len(left) == 1:
|
|
361
|
+
if not self.logical and isinstance(left, NodeList) and len(left) == 1:
|
|
360
362
|
left = left[0].obj
|
|
361
363
|
|
|
362
364
|
right = await self.right.evaluate_async(context)
|
|
363
|
-
if isinstance(right, NodeList) and len(right) == 1:
|
|
365
|
+
if not self.logical and isinstance(right, NodeList) and len(right) == 1:
|
|
364
366
|
right = right[0].obj
|
|
365
367
|
|
|
366
368
|
return context.env.compare(left, self.operator, right)
|
jsonpath/fluent_api.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""A fluent API for managing JSONPathMatch iterators."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import collections
|
|
5
|
+
import itertools
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import Iterable
|
|
8
|
+
from typing import Iterator
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from typing import Tuple
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from jsonpath import JSONPathMatch
|
|
14
|
+
from jsonpath import JSONPointer
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Query:
|
|
18
|
+
"""A fluent API for managing `JSONPathMatch` iterators.
|
|
19
|
+
|
|
20
|
+
Usually you'll want to use `jsonpath.query()` or `JSONPathEnvironment.query()`
|
|
21
|
+
to create instances of `Query` rather than instantiating `Query` directly.
|
|
22
|
+
|
|
23
|
+
Arguments:
|
|
24
|
+
it: A `JSONPathMatch` iterable, as you'd get from `jsonpath.finditer()` or
|
|
25
|
+
`JSONPathEnvironment.finditer()`.
|
|
26
|
+
|
|
27
|
+
**New in version 1.1.0**
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, it: Iterable[JSONPathMatch]) -> None:
|
|
31
|
+
self._it = iter(it)
|
|
32
|
+
|
|
33
|
+
def __iter__(self) -> Iterator[JSONPathMatch]:
|
|
34
|
+
return self._it
|
|
35
|
+
|
|
36
|
+
def limit(self, n: int) -> Query:
|
|
37
|
+
"""Limit the query iterator to at most _n_ matches.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If _n_ < 0.
|
|
41
|
+
"""
|
|
42
|
+
if n < 0:
|
|
43
|
+
raise ValueError("can't limit by a negative number of matches")
|
|
44
|
+
|
|
45
|
+
self._it = itertools.islice(self._it, n)
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def head(self, n: int) -> Query:
|
|
49
|
+
"""Limit the query iterator to at most the first _n_ matches.
|
|
50
|
+
|
|
51
|
+
`head()` is an alias for `limit()`.
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If _n_ < 0.
|
|
55
|
+
"""
|
|
56
|
+
return self.limit(n)
|
|
57
|
+
|
|
58
|
+
def first(self, n: int) -> Query:
|
|
59
|
+
"""Limit the query iterator to at most the first _n_ matches.
|
|
60
|
+
|
|
61
|
+
`first()` is an alias for `limit()`.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValueError: If _n_ < 0.
|
|
65
|
+
"""
|
|
66
|
+
return self.limit(n)
|
|
67
|
+
|
|
68
|
+
def drop(self, n: int) -> Query:
|
|
69
|
+
"""Skip up to _n_ matches from the query iterator.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If _n_ < 0.
|
|
73
|
+
"""
|
|
74
|
+
if n < 0:
|
|
75
|
+
raise ValueError("can't drop a negative number of matches")
|
|
76
|
+
|
|
77
|
+
if n > 0:
|
|
78
|
+
next(itertools.islice(self._it, n, n), None)
|
|
79
|
+
|
|
80
|
+
return self
|
|
81
|
+
|
|
82
|
+
def skip(self, n: int) -> Query:
|
|
83
|
+
"""Skip up to _n_ matches from the query iterator.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ValueError: If _n_ < 0.
|
|
87
|
+
"""
|
|
88
|
+
return self.drop(n)
|
|
89
|
+
|
|
90
|
+
def tail(self, n: int) -> Query:
|
|
91
|
+
"""Drop matches up to the last _n_ matches from the iterator.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: If _n_ < 0.
|
|
95
|
+
"""
|
|
96
|
+
if n < 0:
|
|
97
|
+
raise ValueError("can't select a negative number of matches")
|
|
98
|
+
|
|
99
|
+
self._it = iter(collections.deque(self._it, maxlen=n))
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def last(self, n: int) -> Query:
|
|
103
|
+
"""Drop up to the last _n_ matches from the iterator.
|
|
104
|
+
|
|
105
|
+
`last()` is an alias for `tail()`.
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
ValueError: If _n_ < 0.
|
|
109
|
+
"""
|
|
110
|
+
return self.tail(n)
|
|
111
|
+
|
|
112
|
+
def values(self) -> Iterable[object]:
|
|
113
|
+
"""Return an iterable of objects associated with each match."""
|
|
114
|
+
return (m.obj for m in self._it)
|
|
115
|
+
|
|
116
|
+
def locations(self) -> Iterable[str]:
|
|
117
|
+
"""Return an iterable of normalized paths, one for each match."""
|
|
118
|
+
return (m.path for m in self._it)
|
|
119
|
+
|
|
120
|
+
def items(self) -> Iterable[Tuple[str, object]]:
|
|
121
|
+
"""Return an iterable of (object, path) tuples, one for each match."""
|
|
122
|
+
return ((m.path, m.obj) for m in self._it)
|
|
123
|
+
|
|
124
|
+
def pointers(self) -> Iterable[JSONPointer]:
|
|
125
|
+
"""Return an iterable of JSONPointers, one for each match."""
|
|
126
|
+
return (m.pointer() for m in self._it)
|
|
127
|
+
|
|
128
|
+
def first_one(self) -> Optional[JSONPathMatch]:
|
|
129
|
+
"""Return the first `JSONPathMatch` or `None` if there were no matches."""
|
|
130
|
+
try:
|
|
131
|
+
return next(self._it)
|
|
132
|
+
except StopIteration:
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def one(self) -> Optional[JSONPathMatch]:
|
|
136
|
+
"""Return the first `JSONPathMatch` or `None` if there were no matches.
|
|
137
|
+
|
|
138
|
+
`one()` is an alias for `first_one()`.
|
|
139
|
+
"""
|
|
140
|
+
return self.first_one()
|
|
141
|
+
|
|
142
|
+
def last_one(self) -> Optional[JSONPathMatch]:
|
|
143
|
+
"""Return the last `JSONPathMatch` or `None` if there were no matches."""
|
|
144
|
+
try:
|
|
145
|
+
return next(iter(self.tail(1)))
|
|
146
|
+
except StopIteration:
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
def tee(self, n: int = 2) -> Tuple[Query, ...]:
|
|
150
|
+
"""Return _n_ independent queries by teeing this query's iterator.
|
|
151
|
+
|
|
152
|
+
It is not safe to use a `Query` instance after calling `tee()`.
|
|
153
|
+
"""
|
|
154
|
+
return tuple(Query(it) for it in itertools.tee(self._it, n))
|
|
155
|
+
|
|
156
|
+
def take(self, n: int) -> Query:
|
|
157
|
+
"""Return a new query iterating over the next _n_ matches.
|
|
158
|
+
|
|
159
|
+
It is safe to continue using this query after calling take.
|
|
160
|
+
"""
|
|
161
|
+
return Query(list(itertools.islice(self._it, n)))
|
jsonpath/match.py
CHANGED
|
@@ -76,6 +76,11 @@ class JSONPathMatch:
|
|
|
76
76
|
"""Return a `JSONPointer` pointing to this match's path."""
|
|
77
77
|
return JSONPointer.from_match(self)
|
|
78
78
|
|
|
79
|
+
@property
|
|
80
|
+
def value(self) -> object:
|
|
81
|
+
"""Return the value associated with this match/node."""
|
|
82
|
+
return self.obj
|
|
83
|
+
|
|
79
84
|
|
|
80
85
|
def _truncate(val: str, num: int, end: str = "...") -> str:
|
|
81
86
|
# Replaces consecutive whitespace with a single newline.
|
jsonpath/parse.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""The default JSONPath parser."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import json
|
|
@@ -142,14 +143,15 @@ class Parser:
|
|
|
142
143
|
"""A JSONPath parser bound to a JSONPathEnvironment."""
|
|
143
144
|
|
|
144
145
|
PRECEDENCE_LOWEST = 1
|
|
145
|
-
PRECEDENCE_LOGICALRIGHT =
|
|
146
|
-
|
|
146
|
+
PRECEDENCE_LOGICALRIGHT = 2
|
|
147
|
+
PRECEDENCE_LOGICAL_OR = 3
|
|
148
|
+
PRECEDENCE_LOGICAL_AND = 4
|
|
147
149
|
PRECEDENCE_RELATIONAL = 5
|
|
148
150
|
PRECEDENCE_MEMBERSHIP = 6
|
|
149
151
|
PRECEDENCE_PREFIX = 7
|
|
150
152
|
|
|
151
153
|
PRECEDENCES = {
|
|
152
|
-
TOKEN_AND:
|
|
154
|
+
TOKEN_AND: PRECEDENCE_LOGICAL_AND,
|
|
153
155
|
TOKEN_CONTAINS: PRECEDENCE_MEMBERSHIP,
|
|
154
156
|
TOKEN_EQ: PRECEDENCE_RELATIONAL,
|
|
155
157
|
TOKEN_GE: PRECEDENCE_RELATIONAL,
|
|
@@ -160,7 +162,7 @@ class Parser:
|
|
|
160
162
|
TOKEN_LT: PRECEDENCE_RELATIONAL,
|
|
161
163
|
TOKEN_NE: PRECEDENCE_RELATIONAL,
|
|
162
164
|
TOKEN_NOT: PRECEDENCE_PREFIX,
|
|
163
|
-
TOKEN_OR:
|
|
165
|
+
TOKEN_OR: PRECEDENCE_LOGICAL_OR,
|
|
164
166
|
TOKEN_RE: PRECEDENCE_RELATIONAL,
|
|
165
167
|
TOKEN_RPAREN: PRECEDENCE_LOWEST,
|
|
166
168
|
}
|
|
@@ -563,9 +565,9 @@ class Parser:
|
|
|
563
565
|
|
|
564
566
|
def parse_regex(self, stream: TokenStream) -> FilterExpression:
|
|
565
567
|
pattern = stream.current.value
|
|
568
|
+
flags = 0
|
|
566
569
|
if stream.peek.kind == TOKEN_RE_FLAGS:
|
|
567
570
|
stream.next_token()
|
|
568
|
-
flags = 0
|
|
569
571
|
for flag in set(stream.current.value):
|
|
570
572
|
flags |= self.RE_FLAG_MAP[flag]
|
|
571
573
|
return RegexLiteral(value=re.compile(pattern, flags))
|
jsonpath/selectors.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""JSONPath
|
|
1
|
+
"""JSONPath segments and selectors, as returned from `Parser.parse`."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
from abc import ABC
|
|
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class JSONPathSelector(ABC):
|
|
31
|
-
"""Base class for all JSONPath selectors."""
|
|
31
|
+
"""Base class for all JSONPath segments and selectors."""
|
|
32
32
|
|
|
33
33
|
__slots__ = ("env", "token")
|
|
34
34
|
|
|
@@ -38,7 +38,17 @@ class JSONPathSelector(ABC):
|
|
|
38
38
|
|
|
39
39
|
@abstractmethod
|
|
40
40
|
def resolve(self, matches: Iterable[JSONPathMatch]) -> Iterable[JSONPathMatch]:
|
|
41
|
-
"""
|
|
41
|
+
"""Apply the segment/selector to each node in _matches_.
|
|
42
|
+
|
|
43
|
+
Arguments:
|
|
44
|
+
matches: Nodes matched by preceding segments/selectors. This is like
|
|
45
|
+
a lazy _NodeList_, as described in RFC 9535, but each match carries
|
|
46
|
+
more than the node's value and location.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The `JSONPathMatch` instances created by applying this selector to each
|
|
50
|
+
preceding node.
|
|
51
|
+
"""
|
|
42
52
|
|
|
43
53
|
@abstractmethod
|
|
44
54
|
def resolve_async(
|
|
@@ -48,7 +58,7 @@ class JSONPathSelector(ABC):
|
|
|
48
58
|
|
|
49
59
|
|
|
50
60
|
class PropertySelector(JSONPathSelector):
|
|
51
|
-
"""A
|
|
61
|
+
"""A shorthand or bracketed property selector."""
|
|
52
62
|
|
|
53
63
|
__slots__ = ("name", "shorthand")
|
|
54
64
|
|
|
@@ -115,7 +125,12 @@ class PropertySelector(JSONPathSelector):
|
|
|
115
125
|
|
|
116
126
|
|
|
117
127
|
class IndexSelector(JSONPathSelector):
|
|
118
|
-
"""
|
|
128
|
+
"""Select an element from an array by index.
|
|
129
|
+
|
|
130
|
+
Considering we don't require mapping (JSON object) keys/properties to
|
|
131
|
+
be quoted, and that we support mappings with numeric keys, we also check
|
|
132
|
+
to see if the "index" is a mapping key, which is non-standard.
|
|
133
|
+
"""
|
|
119
134
|
|
|
120
135
|
__slots__ = ("index", "_as_key")
|
|
121
136
|
|
|
@@ -213,7 +228,10 @@ class IndexSelector(JSONPathSelector):
|
|
|
213
228
|
|
|
214
229
|
|
|
215
230
|
class KeysSelector(JSONPathSelector):
|
|
216
|
-
"""Select
|
|
231
|
+
"""Select mapping/object keys/properties.
|
|
232
|
+
|
|
233
|
+
NOTE: This is a non-standard selector.
|
|
234
|
+
"""
|
|
217
235
|
|
|
218
236
|
__slots__ = ("shorthand",)
|
|
219
237
|
|
|
@@ -354,7 +372,7 @@ class SliceSelector(JSONPathSelector):
|
|
|
354
372
|
|
|
355
373
|
|
|
356
374
|
class WildSelector(JSONPathSelector):
|
|
357
|
-
"""
|
|
375
|
+
"""Select all items from a sequence/array or values from a mapping/object."""
|
|
358
376
|
|
|
359
377
|
__slots__ = ("shorthand",)
|
|
360
378
|
|
|
@@ -433,7 +451,10 @@ class WildSelector(JSONPathSelector):
|
|
|
433
451
|
|
|
434
452
|
|
|
435
453
|
class RecursiveDescentSelector(JSONPathSelector):
|
|
436
|
-
"""A JSONPath selector that visits all
|
|
454
|
+
"""A JSONPath selector that visits all nodes recursively.
|
|
455
|
+
|
|
456
|
+
NOTE: Strictly this is a "segment", not a "selector".
|
|
457
|
+
"""
|
|
437
458
|
|
|
438
459
|
def __str__(self) -> str:
|
|
439
460
|
return ".."
|
|
@@ -504,7 +525,10 @@ async def _alist(it: List[T]) -> AsyncIterable[T]:
|
|
|
504
525
|
|
|
505
526
|
|
|
506
527
|
class ListSelector(JSONPathSelector):
|
|
507
|
-
"""A
|
|
528
|
+
"""A bracketed list of selectors, the results of which are concatenated together.
|
|
529
|
+
|
|
530
|
+
NOTE: Strictly this is a "segment", not a "selector".
|
|
531
|
+
"""
|
|
508
532
|
|
|
509
533
|
__slots__ = ("items",)
|
|
510
534
|
|
|
@@ -541,21 +565,21 @@ class ListSelector(JSONPathSelector):
|
|
|
541
565
|
return hash((self.items, self.token))
|
|
542
566
|
|
|
543
567
|
def resolve(self, matches: Iterable[JSONPathMatch]) -> Iterable[JSONPathMatch]:
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
568
|
+
for match_ in matches:
|
|
569
|
+
for item in self.items:
|
|
570
|
+
yield from item.resolve([match_])
|
|
547
571
|
|
|
548
572
|
async def resolve_async(
|
|
549
573
|
self, matches: AsyncIterable[JSONPathMatch]
|
|
550
574
|
) -> AsyncIterable[JSONPathMatch]:
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
575
|
+
async for match_ in matches:
|
|
576
|
+
for item in self.items:
|
|
577
|
+
async for m in item.resolve_async(_alist([match_])):
|
|
578
|
+
yield m
|
|
555
579
|
|
|
556
580
|
|
|
557
581
|
class Filter(JSONPathSelector):
|
|
558
|
-
"""
|
|
582
|
+
"""Filter sequence/array items or mapping/object values with a filter expression."""
|
|
559
583
|
|
|
560
584
|
__slots__ = ("expression", "cacheable_nodes")
|
|
561
585
|
|
|
@@ -713,7 +737,7 @@ class Filter(JSONPathSelector):
|
|
|
713
737
|
|
|
714
738
|
|
|
715
739
|
class FilterContext:
|
|
716
|
-
"""
|
|
740
|
+
"""Contextual information and data for evaluating a filter expression."""
|
|
717
741
|
|
|
718
742
|
__slots__ = (
|
|
719
743
|
"current_key",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-jsonpath
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
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
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
jsonpath/__about__.py,sha256=
|
|
2
|
-
jsonpath/__init__.py,sha256=
|
|
1
|
+
jsonpath/__about__.py,sha256=E4FhSU883i2VaRcAnHWVbCWT0excoHeyG4xrr6DJ54M,132
|
|
2
|
+
jsonpath/__init__.py,sha256=oMtSyvfoxP5zIiSvlLHXSG716i-zZ1R5ZtqDortONC0,2104
|
|
3
3
|
jsonpath/__main__.py,sha256=6Y5wOE7U-MHymopXOsxofaY30tVZYPGTJO0L4vytoUw,61
|
|
4
4
|
jsonpath/_data.py,sha256=JEpu5Kg0_kgxYKUilBcHVdTmPxf3-Vc0NgaW6olsqyY,577
|
|
5
5
|
jsonpath/cli.py,sha256=scpWfJXl1jLQ80ZyXqMCu8JtRgZUXluC11x6OkC4oeA,10026
|
|
6
|
-
jsonpath/env.py,sha256=
|
|
6
|
+
jsonpath/env.py,sha256=kxlYqOIagW6arm4tinlinTkk_opy9ClTgWMJH8B5r3Y,22240
|
|
7
7
|
jsonpath/exceptions.py,sha256=5n-9vaKTu5asWllHT0IN-824ewcNCyQIyKvQ58TERVk,4351
|
|
8
|
-
jsonpath/filter.py,sha256=
|
|
8
|
+
jsonpath/filter.py,sha256=ocrU4l1QwYquf-GQzkRClbX1mGcdRwqg_hbdQ08DV-I,20638
|
|
9
|
+
jsonpath/fluent_api.py,sha256=59MB8h3Q23oxpMrLTBYCVVpAGIdqd-OYamDOB9yw6m0,4795
|
|
9
10
|
jsonpath/lex.py,sha256=7Nt7l_VyfkgDdtdDSJXP91uJRTAFH2pktm_j0W6oQDQ,10265
|
|
10
|
-
jsonpath/match.py,sha256=
|
|
11
|
-
jsonpath/parse.py,sha256=
|
|
11
|
+
jsonpath/match.py,sha256=b8fkPHsYjJSrTEOi68MFEyOCj0-FoQjFmMq-Ex4qvo0,3503
|
|
12
|
+
jsonpath/parse.py,sha256=66ZcMACyf6yesClpjj0RY0O_YPjHZ5eikVxDFROXnkU,23601
|
|
12
13
|
jsonpath/patch.py,sha256=V_BiRr20qyByBNppBeA0e2I1nfbPJ_svPspDubMzA20,21350
|
|
13
14
|
jsonpath/path.py,sha256=SEGCzoIpWWuUaMyVs2zHD4U1zUCtw3OSJgNHUMY6fys,14645
|
|
14
15
|
jsonpath/pointer.py,sha256=YvfNAp5qADiqJGuTE1jvPrtCTVyyDk9rlT2xJB9SjtE,22234
|
|
15
16
|
jsonpath/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
jsonpath/selectors.py,sha256=
|
|
17
|
+
jsonpath/selectors.py,sha256=u7YcHBt_yEQv0CeTtsOwVQuP5bHLT0_lnulUwShCKdQ,27370
|
|
17
18
|
jsonpath/stream.py,sha256=S1xxgOsJlivu6Y8-kejMoYm7MgSlhjnoX_H5cem5Yhk,2815
|
|
18
19
|
jsonpath/token.py,sha256=2xD2Wrxj7TgyREUhQxzcBcOWLhjz8demYBJ7BqaNJMA,3938
|
|
19
20
|
jsonpath/function_extensions/__init__.py,sha256=wrUayhwlm6iNLnSIx_ER6EeGiqR91T_SIjNBUr42nX0,548
|
|
@@ -27,8 +28,8 @@ jsonpath/function_extensions/match.py,sha256=KjsH33fCFGonp2RV__FuaeIOTwLLcvgaaCi
|
|
|
27
28
|
jsonpath/function_extensions/search.py,sha256=O11fnkHlbvf0QPrLISYfhlPXBvVPBr-U8V0dGbd614Y,710
|
|
28
29
|
jsonpath/function_extensions/typeof.py,sha256=yCAj9zOqSnam1mfHCGolNHWDmsBOvU3rAhbZDYycx50,1780
|
|
29
30
|
jsonpath/function_extensions/value.py,sha256=fQMbPUV87Jn1nOwAlBpTeLmLIG5ejH0XQBOM_SR-Us4,721
|
|
30
|
-
python_jsonpath-1.
|
|
31
|
-
python_jsonpath-1.
|
|
32
|
-
python_jsonpath-1.
|
|
33
|
-
python_jsonpath-1.
|
|
34
|
-
python_jsonpath-1.
|
|
31
|
+
python_jsonpath-1.1.1.dist-info/METADATA,sha256=Kfwo1Zy1v-NjBzt9af3ac9fKclvAiudFYx3E59_wYYY,5346
|
|
32
|
+
python_jsonpath-1.1.1.dist-info/WHEEL,sha256=TJPnKdtrSue7xZ_AVGkp9YXcvDrobsjBds1du3Nx6dc,87
|
|
33
|
+
python_jsonpath-1.1.1.dist-info/entry_points.txt,sha256=xvbWnAebJyOMI_9ugK0xrpFRlwmEsAJD2kNHU0Dvscc,43
|
|
34
|
+
python_jsonpath-1.1.1.dist-info/licenses/LICENSE.txt,sha256=u7PksAQGI1QYWcERHeauMseZ4XAzDKUrKW8Z4wbeU1k,1101
|
|
35
|
+
python_jsonpath-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|