python-jsonpath 1.3.2__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 +1 -1
- jsonpath/__init__.py +290 -8
- jsonpath/_types.py +31 -0
- jsonpath/cli.py +11 -1
- jsonpath/env.py +100 -47
- jsonpath/exceptions.py +75 -4
- jsonpath/filter.py +78 -84
- jsonpath/function_extensions/__init__.py +4 -2
- jsonpath/function_extensions/_pattern.py +112 -0
- jsonpath/function_extensions/keys.py +27 -8
- jsonpath/function_extensions/match.py +11 -13
- jsonpath/function_extensions/search.py +11 -13
- jsonpath/function_extensions/starts_with.py +21 -0
- jsonpath/lex.py +113 -64
- jsonpath/lru_cache.py +130 -0
- jsonpath/match.py +13 -0
- jsonpath/parse.py +448 -302
- jsonpath/path.py +48 -84
- jsonpath/segments.py +131 -0
- jsonpath/selectors.py +448 -482
- jsonpath/stream.py +68 -70
- jsonpath/token.py +59 -61
- jsonpath/unescape.py +134 -0
- {python_jsonpath-1.3.2.dist-info → python_jsonpath-2.0.0.dist-info}/METADATA +5 -1
- python_jsonpath-2.0.0.dist-info/RECORD +42 -0
- python_jsonpath-1.3.2.dist-info/RECORD +0 -36
- {python_jsonpath-1.3.2.dist-info → python_jsonpath-2.0.0.dist-info}/WHEEL +0 -0
- {python_jsonpath-1.3.2.dist-info → python_jsonpath-2.0.0.dist-info}/entry_points.txt +0 -0
- {python_jsonpath-1.3.2.dist-info → python_jsonpath-2.0.0.dist-info}/licenses/LICENSE.txt +0 -0
jsonpath/filter.py
CHANGED
|
@@ -23,17 +23,14 @@ from .exceptions import JSONPathTypeError
|
|
|
23
23
|
from .function_extensions import FilterFunction
|
|
24
24
|
from .match import NodeList
|
|
25
25
|
from .selectors import Filter as FilterSelector
|
|
26
|
-
from .selectors import ListSelector
|
|
27
26
|
from .serialize import canonical_string
|
|
28
27
|
|
|
29
28
|
if TYPE_CHECKING:
|
|
30
29
|
from .path import JSONPath
|
|
31
30
|
from .selectors import FilterContext
|
|
32
31
|
|
|
33
|
-
# ruff: noqa: D102, PLW1641
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
class FilterExpression(ABC):
|
|
33
|
+
class BaseExpression(ABC):
|
|
37
34
|
"""Base class for all filter expression nodes."""
|
|
38
35
|
|
|
39
36
|
__slots__ = ("volatile",)
|
|
@@ -60,11 +57,11 @@ class FilterExpression(ABC):
|
|
|
60
57
|
"""An async version of `evaluate`."""
|
|
61
58
|
|
|
62
59
|
@abstractmethod
|
|
63
|
-
def children(self) -> List[
|
|
60
|
+
def children(self) -> List[BaseExpression]:
|
|
64
61
|
"""Return a list of direct child expressions."""
|
|
65
62
|
|
|
66
63
|
@abstractmethod
|
|
67
|
-
def set_children(self, children: List[
|
|
64
|
+
def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
|
|
68
65
|
"""Update this expression's child expressions.
|
|
69
66
|
|
|
70
67
|
_children_ is assumed to have the same number of items as is returned
|
|
@@ -72,7 +69,7 @@ class FilterExpression(ABC):
|
|
|
72
69
|
"""
|
|
73
70
|
|
|
74
71
|
|
|
75
|
-
class Nil(
|
|
72
|
+
class Nil(BaseExpression):
|
|
76
73
|
"""The constant `nil`.
|
|
77
74
|
|
|
78
75
|
Also aliased as `null` and `None`, sometimes.
|
|
@@ -95,10 +92,10 @@ class Nil(FilterExpression):
|
|
|
95
92
|
async def evaluate_async(self, _: FilterContext) -> None:
|
|
96
93
|
return None
|
|
97
94
|
|
|
98
|
-
def children(self) -> List[
|
|
95
|
+
def children(self) -> List[BaseExpression]:
|
|
99
96
|
return []
|
|
100
97
|
|
|
101
|
-
def set_children(self, children: List[
|
|
98
|
+
def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
|
|
102
99
|
return
|
|
103
100
|
|
|
104
101
|
|
|
@@ -126,7 +123,7 @@ class _Undefined:
|
|
|
126
123
|
UNDEFINED = _Undefined()
|
|
127
124
|
|
|
128
125
|
|
|
129
|
-
class Undefined(
|
|
126
|
+
class Undefined(BaseExpression):
|
|
130
127
|
"""The constant `undefined`."""
|
|
131
128
|
|
|
132
129
|
__slots__ = ()
|
|
@@ -147,10 +144,10 @@ class Undefined(FilterExpression):
|
|
|
147
144
|
async def evaluate_async(self, _: FilterContext) -> object:
|
|
148
145
|
return UNDEFINED
|
|
149
146
|
|
|
150
|
-
def children(self) -> List[
|
|
147
|
+
def children(self) -> List[BaseExpression]:
|
|
151
148
|
return []
|
|
152
149
|
|
|
153
|
-
def set_children(self, children: List[
|
|
150
|
+
def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
|
|
154
151
|
return
|
|
155
152
|
|
|
156
153
|
|
|
@@ -159,7 +156,7 @@ UNDEFINED_LITERAL = Undefined()
|
|
|
159
156
|
LITERAL_EXPRESSION_T = TypeVar("LITERAL_EXPRESSION_T")
|
|
160
157
|
|
|
161
158
|
|
|
162
|
-
class
|
|
159
|
+
class FilterExpressionLiteral(BaseExpression, Generic[LITERAL_EXPRESSION_T]):
|
|
163
160
|
"""Base class for filter expression literals."""
|
|
164
161
|
|
|
165
162
|
__slots__ = ("value",)
|
|
@@ -183,14 +180,14 @@ class Literal(FilterExpression, Generic[LITERAL_EXPRESSION_T]):
|
|
|
183
180
|
async def evaluate_async(self, _: FilterContext) -> LITERAL_EXPRESSION_T:
|
|
184
181
|
return self.value
|
|
185
182
|
|
|
186
|
-
def children(self) -> List[
|
|
183
|
+
def children(self) -> List[BaseExpression]:
|
|
187
184
|
return []
|
|
188
185
|
|
|
189
|
-
def set_children(self, children: List[
|
|
186
|
+
def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
|
|
190
187
|
return
|
|
191
188
|
|
|
192
189
|
|
|
193
|
-
class BooleanLiteral(
|
|
190
|
+
class BooleanLiteral(FilterExpressionLiteral[bool]):
|
|
194
191
|
"""A Boolean `True` or `False`."""
|
|
195
192
|
|
|
196
193
|
__slots__ = ()
|
|
@@ -202,7 +199,7 @@ TRUE = BooleanLiteral(value=True)
|
|
|
202
199
|
FALSE = BooleanLiteral(value=False)
|
|
203
200
|
|
|
204
201
|
|
|
205
|
-
class StringLiteral(
|
|
202
|
+
class StringLiteral(FilterExpressionLiteral[str]):
|
|
206
203
|
"""A string literal."""
|
|
207
204
|
|
|
208
205
|
__slots__ = ()
|
|
@@ -211,19 +208,19 @@ class StringLiteral(Literal[str]):
|
|
|
211
208
|
return canonical_string(self.value)
|
|
212
209
|
|
|
213
210
|
|
|
214
|
-
class IntegerLiteral(
|
|
211
|
+
class IntegerLiteral(FilterExpressionLiteral[int]):
|
|
215
212
|
"""An integer literal."""
|
|
216
213
|
|
|
217
214
|
__slots__ = ()
|
|
218
215
|
|
|
219
216
|
|
|
220
|
-
class FloatLiteral(
|
|
217
|
+
class FloatLiteral(FilterExpressionLiteral[float]):
|
|
221
218
|
"""A float literal."""
|
|
222
219
|
|
|
223
220
|
__slots__ = ()
|
|
224
221
|
|
|
225
222
|
|
|
226
|
-
class RegexLiteral(
|
|
223
|
+
class RegexLiteral(FilterExpressionLiteral[Pattern[str]]):
|
|
227
224
|
"""A regex literal."""
|
|
228
225
|
|
|
229
226
|
__slots__ = ()
|
|
@@ -246,12 +243,12 @@ class RegexLiteral(Literal[Pattern[str]]):
|
|
|
246
243
|
return f"/{self.value.pattern}/{''.join(flags)}"
|
|
247
244
|
|
|
248
245
|
|
|
249
|
-
class ListLiteral(
|
|
246
|
+
class ListLiteral(BaseExpression):
|
|
250
247
|
"""A list literal."""
|
|
251
248
|
|
|
252
249
|
__slots__ = ("items",)
|
|
253
250
|
|
|
254
|
-
def __init__(self, items: List[
|
|
251
|
+
def __init__(self, items: List[BaseExpression]) -> None:
|
|
255
252
|
self.items = items
|
|
256
253
|
super().__init__()
|
|
257
254
|
|
|
@@ -268,19 +265,19 @@ class ListLiteral(FilterExpression):
|
|
|
268
265
|
async def evaluate_async(self, context: FilterContext) -> object:
|
|
269
266
|
return [await item.evaluate_async(context) for item in self.items]
|
|
270
267
|
|
|
271
|
-
def children(self) -> List[
|
|
268
|
+
def children(self) -> List[BaseExpression]:
|
|
272
269
|
return self.items
|
|
273
270
|
|
|
274
|
-
def set_children(self, children: List[
|
|
271
|
+
def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
|
|
275
272
|
self.items = children
|
|
276
273
|
|
|
277
274
|
|
|
278
|
-
class PrefixExpression(
|
|
275
|
+
class PrefixExpression(BaseExpression):
|
|
279
276
|
"""An expression composed of a prefix operator and another expression."""
|
|
280
277
|
|
|
281
278
|
__slots__ = ("operator", "right")
|
|
282
279
|
|
|
283
|
-
def __init__(self, operator: str, right:
|
|
280
|
+
def __init__(self, operator: str, right: BaseExpression):
|
|
284
281
|
self.operator = operator
|
|
285
282
|
self.right = right
|
|
286
283
|
super().__init__()
|
|
@@ -306,24 +303,24 @@ class PrefixExpression(FilterExpression):
|
|
|
306
303
|
async def evaluate_async(self, context: FilterContext) -> object:
|
|
307
304
|
return self._evaluate(context, await self.right.evaluate_async(context))
|
|
308
305
|
|
|
309
|
-
def children(self) -> List[
|
|
306
|
+
def children(self) -> List[BaseExpression]:
|
|
310
307
|
return [self.right]
|
|
311
308
|
|
|
312
|
-
def set_children(self, children: List[
|
|
309
|
+
def set_children(self, children: List[BaseExpression]) -> None:
|
|
313
310
|
assert len(children) == 1
|
|
314
311
|
self.right = children[0]
|
|
315
312
|
|
|
316
313
|
|
|
317
|
-
class InfixExpression(
|
|
314
|
+
class InfixExpression(BaseExpression):
|
|
318
315
|
"""A pair of expressions and a comparison or logical operator."""
|
|
319
316
|
|
|
320
317
|
__slots__ = ("left", "operator", "right", "logical")
|
|
321
318
|
|
|
322
319
|
def __init__(
|
|
323
320
|
self,
|
|
324
|
-
left:
|
|
321
|
+
left: BaseExpression,
|
|
325
322
|
operator: str,
|
|
326
|
-
right:
|
|
323
|
+
right: BaseExpression,
|
|
327
324
|
):
|
|
328
325
|
self.left = left
|
|
329
326
|
self.operator = operator
|
|
@@ -366,10 +363,10 @@ class InfixExpression(FilterExpression):
|
|
|
366
363
|
|
|
367
364
|
return context.env.compare(left, self.operator, right)
|
|
368
365
|
|
|
369
|
-
def children(self) -> List[
|
|
366
|
+
def children(self) -> List[BaseExpression]:
|
|
370
367
|
return [self.left, self.right]
|
|
371
368
|
|
|
372
|
-
def set_children(self, children: List[
|
|
369
|
+
def set_children(self, children: List[BaseExpression]) -> None:
|
|
373
370
|
assert len(children) == 2 # noqa: PLR2004
|
|
374
371
|
self.left = children[0]
|
|
375
372
|
self.right = children[1]
|
|
@@ -381,19 +378,19 @@ PRECEDENCE_LOGICAL_AND = 4
|
|
|
381
378
|
PRECEDENCE_PREFIX = 7
|
|
382
379
|
|
|
383
380
|
|
|
384
|
-
class
|
|
385
|
-
"""An expression that
|
|
381
|
+
class FilterExpression(BaseExpression):
|
|
382
|
+
"""An expression that evaluates to `True` or `False`."""
|
|
386
383
|
|
|
387
384
|
__slots__ = ("expression",)
|
|
388
385
|
|
|
389
|
-
def __init__(self, expression:
|
|
386
|
+
def __init__(self, expression: BaseExpression):
|
|
390
387
|
self.expression = expression
|
|
391
388
|
super().__init__()
|
|
392
389
|
|
|
393
|
-
def cache_tree(self) ->
|
|
390
|
+
def cache_tree(self) -> FilterExpression:
|
|
394
391
|
"""Return a copy of _self.expression_ augmented with caching nodes."""
|
|
395
392
|
|
|
396
|
-
def _cache_tree(expr:
|
|
393
|
+
def _cache_tree(expr: BaseExpression) -> BaseExpression:
|
|
397
394
|
children = expr.children()
|
|
398
395
|
if expr.volatile:
|
|
399
396
|
_expr = copy.copy(expr)
|
|
@@ -404,7 +401,7 @@ class BooleanExpression(FilterExpression):
|
|
|
404
401
|
_expr.set_children([_cache_tree(child) for child in children])
|
|
405
402
|
return _expr
|
|
406
403
|
|
|
407
|
-
return
|
|
404
|
+
return FilterExpression(_cache_tree(copy.copy(self.expression)))
|
|
408
405
|
|
|
409
406
|
def cacheable_nodes(self) -> bool:
|
|
410
407
|
"""Return `True` if there are any cacheable nodes in this expression tree."""
|
|
@@ -418,11 +415,11 @@ class BooleanExpression(FilterExpression):
|
|
|
418
415
|
|
|
419
416
|
def __eq__(self, other: object) -> bool:
|
|
420
417
|
return (
|
|
421
|
-
isinstance(other,
|
|
418
|
+
isinstance(other, FilterExpression) and self.expression == other.expression
|
|
422
419
|
)
|
|
423
420
|
|
|
424
421
|
def _canonical_string(
|
|
425
|
-
self, expression:
|
|
422
|
+
self, expression: BaseExpression, parent_precedence: int
|
|
426
423
|
) -> str:
|
|
427
424
|
if isinstance(expression, InfixExpression):
|
|
428
425
|
if expression.operator == "&&":
|
|
@@ -454,15 +451,15 @@ class BooleanExpression(FilterExpression):
|
|
|
454
451
|
async def evaluate_async(self, context: FilterContext) -> bool:
|
|
455
452
|
return context.env.is_truthy(await self.expression.evaluate_async(context))
|
|
456
453
|
|
|
457
|
-
def children(self) -> List[
|
|
454
|
+
def children(self) -> List[BaseExpression]:
|
|
458
455
|
return [self.expression]
|
|
459
456
|
|
|
460
|
-
def set_children(self, children: List[
|
|
457
|
+
def set_children(self, children: List[BaseExpression]) -> None:
|
|
461
458
|
assert len(children) == 1
|
|
462
459
|
self.expression = children[0]
|
|
463
460
|
|
|
464
461
|
|
|
465
|
-
class CachingFilterExpression(
|
|
462
|
+
class CachingFilterExpression(BaseExpression):
|
|
466
463
|
"""A FilterExpression wrapper that caches the result."""
|
|
467
464
|
|
|
468
465
|
__slots__ = (
|
|
@@ -472,7 +469,7 @@ class CachingFilterExpression(FilterExpression):
|
|
|
472
469
|
|
|
473
470
|
_UNSET = object()
|
|
474
471
|
|
|
475
|
-
def __init__(self, expression:
|
|
472
|
+
def __init__(self, expression: BaseExpression):
|
|
476
473
|
self.volatile = False
|
|
477
474
|
self._expr = expression
|
|
478
475
|
self._cached: object = self._UNSET
|
|
@@ -487,14 +484,14 @@ class CachingFilterExpression(FilterExpression):
|
|
|
487
484
|
self._cached = await self._expr.evaluate_async(context)
|
|
488
485
|
return self._cached
|
|
489
486
|
|
|
490
|
-
def children(self) -> List[
|
|
487
|
+
def children(self) -> List[BaseExpression]:
|
|
491
488
|
return self._expr.children()
|
|
492
489
|
|
|
493
|
-
def set_children(self, children: List[
|
|
490
|
+
def set_children(self, children: List[BaseExpression]) -> None:
|
|
494
491
|
self._expr.set_children(children)
|
|
495
492
|
|
|
496
493
|
|
|
497
|
-
class
|
|
494
|
+
class FilterQuery(BaseExpression, ABC):
|
|
498
495
|
"""Base expression for all _sub paths_ found in filter expressions."""
|
|
499
496
|
|
|
500
497
|
__slots__ = ("path",)
|
|
@@ -504,25 +501,22 @@ class Path(FilterExpression, ABC):
|
|
|
504
501
|
super().__init__()
|
|
505
502
|
|
|
506
503
|
def __eq__(self, other: object) -> bool:
|
|
507
|
-
return isinstance(other,
|
|
508
|
-
|
|
509
|
-
def children(self) -> List[
|
|
510
|
-
_children: List[
|
|
511
|
-
for segment in self.path.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
selector.expression
|
|
515
|
-
for selector in segment.items
|
|
516
|
-
if isinstance(selector, FilterSelector)
|
|
517
|
-
)
|
|
504
|
+
return isinstance(other, FilterQuery) and str(self) == str(other)
|
|
505
|
+
|
|
506
|
+
def children(self) -> List[BaseExpression]:
|
|
507
|
+
_children: List[BaseExpression] = []
|
|
508
|
+
for segment in self.path.segments:
|
|
509
|
+
for selector in segment.selectors:
|
|
510
|
+
if isinstance(selector, FilterSelector):
|
|
511
|
+
_children.append(selector.expression)
|
|
518
512
|
return _children
|
|
519
513
|
|
|
520
|
-
def set_children(self, children: List[
|
|
514
|
+
def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
|
|
521
515
|
# self.path has its own cache
|
|
522
516
|
return
|
|
523
517
|
|
|
524
518
|
|
|
525
|
-
class
|
|
519
|
+
class RelativeFilterQuery(FilterQuery):
|
|
526
520
|
"""A JSONPath starting at the current node."""
|
|
527
521
|
|
|
528
522
|
__slots__ = ()
|
|
@@ -535,11 +529,9 @@ class SelfPath(Path):
|
|
|
535
529
|
return "@" + str(self.path)[1:]
|
|
536
530
|
|
|
537
531
|
def evaluate(self, context: FilterContext) -> object:
|
|
538
|
-
if isinstance(context.current, str)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
return NodeList()
|
|
542
|
-
if not isinstance(context.current, (Sequence, Mapping)):
|
|
532
|
+
if isinstance(context.current, str) or not isinstance(
|
|
533
|
+
context.current, (Sequence, Mapping)
|
|
534
|
+
):
|
|
543
535
|
if self.path.empty():
|
|
544
536
|
return context.current
|
|
545
537
|
return NodeList()
|
|
@@ -552,11 +544,9 @@ class SelfPath(Path):
|
|
|
552
544
|
)
|
|
553
545
|
|
|
554
546
|
async def evaluate_async(self, context: FilterContext) -> object:
|
|
555
|
-
if isinstance(context.current, str)
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return NodeList()
|
|
559
|
-
if not isinstance(context.current, (Sequence, Mapping)):
|
|
547
|
+
if isinstance(context.current, str) or not isinstance(
|
|
548
|
+
context.current, (Sequence, Mapping)
|
|
549
|
+
):
|
|
560
550
|
if self.path.empty():
|
|
561
551
|
return context.current
|
|
562
552
|
return NodeList()
|
|
@@ -572,7 +562,7 @@ class SelfPath(Path):
|
|
|
572
562
|
)
|
|
573
563
|
|
|
574
564
|
|
|
575
|
-
class
|
|
565
|
+
class RootFilterQuery(FilterQuery):
|
|
576
566
|
"""A JSONPath starting at the root node."""
|
|
577
567
|
|
|
578
568
|
__slots__ = ()
|
|
@@ -606,7 +596,7 @@ class RootPath(Path):
|
|
|
606
596
|
)
|
|
607
597
|
|
|
608
598
|
|
|
609
|
-
class FilterContextPath(
|
|
599
|
+
class FilterContextPath(FilterQuery):
|
|
610
600
|
"""A JSONPath starting at the root of any extra context data."""
|
|
611
601
|
|
|
612
602
|
__slots__ = ()
|
|
@@ -641,12 +631,12 @@ class FilterContextPath(Path):
|
|
|
641
631
|
)
|
|
642
632
|
|
|
643
633
|
|
|
644
|
-
class FunctionExtension(
|
|
634
|
+
class FunctionExtension(BaseExpression):
|
|
645
635
|
"""A filter function."""
|
|
646
636
|
|
|
647
637
|
__slots__ = ("name", "args")
|
|
648
638
|
|
|
649
|
-
def __init__(self, name: str, args: Sequence[
|
|
639
|
+
def __init__(self, name: str, args: Sequence[BaseExpression]) -> None:
|
|
650
640
|
self.name = name
|
|
651
641
|
self.args = args
|
|
652
642
|
super().__init__()
|
|
@@ -666,7 +656,9 @@ class FunctionExtension(FilterExpression):
|
|
|
666
656
|
try:
|
|
667
657
|
func = context.env.function_extensions[self.name]
|
|
668
658
|
except KeyError:
|
|
669
|
-
|
|
659
|
+
# This can only happen if the environment's function register has been
|
|
660
|
+
# changed since the query was parsed.
|
|
661
|
+
return UNDEFINED
|
|
670
662
|
args = [arg.evaluate(context) for arg in self.args]
|
|
671
663
|
return func(*self._unpack_node_lists(func, args))
|
|
672
664
|
|
|
@@ -674,7 +666,9 @@ class FunctionExtension(FilterExpression):
|
|
|
674
666
|
try:
|
|
675
667
|
func = context.env.function_extensions[self.name]
|
|
676
668
|
except KeyError:
|
|
677
|
-
|
|
669
|
+
# This can only happen if the environment's function register has been
|
|
670
|
+
# changed since the query was parsed.
|
|
671
|
+
return UNDEFINED
|
|
678
672
|
args = [await arg.evaluate_async(context) for arg in self.args]
|
|
679
673
|
return func(*self._unpack_node_lists(func, args))
|
|
680
674
|
|
|
@@ -713,15 +707,15 @@ class FunctionExtension(FilterExpression):
|
|
|
713
707
|
for obj in args
|
|
714
708
|
]
|
|
715
709
|
|
|
716
|
-
def children(self) -> List[
|
|
710
|
+
def children(self) -> List[BaseExpression]:
|
|
717
711
|
return list(self.args)
|
|
718
712
|
|
|
719
|
-
def set_children(self, children: List[
|
|
713
|
+
def set_children(self, children: List[BaseExpression]) -> None:
|
|
720
714
|
assert len(children) == len(self.args)
|
|
721
715
|
self.args = children
|
|
722
716
|
|
|
723
717
|
|
|
724
|
-
class CurrentKey(
|
|
718
|
+
class CurrentKey(BaseExpression):
|
|
725
719
|
"""The key/property or index associated with the current object."""
|
|
726
720
|
|
|
727
721
|
__slots__ = ()
|
|
@@ -744,17 +738,17 @@ class CurrentKey(FilterExpression):
|
|
|
744
738
|
async def evaluate_async(self, context: FilterContext) -> object:
|
|
745
739
|
return self.evaluate(context)
|
|
746
740
|
|
|
747
|
-
def children(self) -> List[
|
|
741
|
+
def children(self) -> List[BaseExpression]:
|
|
748
742
|
return []
|
|
749
743
|
|
|
750
|
-
def set_children(self, children: List[
|
|
744
|
+
def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
|
|
751
745
|
return
|
|
752
746
|
|
|
753
747
|
|
|
754
748
|
CURRENT_KEY = CurrentKey()
|
|
755
749
|
|
|
756
750
|
|
|
757
|
-
def walk(expr:
|
|
751
|
+
def walk(expr: BaseExpression) -> Iterable[BaseExpression]:
|
|
758
752
|
"""Walk the filter expression tree starting at _expr_."""
|
|
759
753
|
yield expr
|
|
760
754
|
for child in expr.children():
|
|
@@ -764,7 +758,7 @@ def walk(expr: FilterExpression) -> Iterable[FilterExpression]:
|
|
|
764
758
|
VALUE_TYPE_EXPRESSIONS = (
|
|
765
759
|
Nil,
|
|
766
760
|
Undefined,
|
|
767
|
-
|
|
761
|
+
FilterExpressionLiteral,
|
|
768
762
|
ListLiteral,
|
|
769
763
|
CurrentKey,
|
|
770
764
|
)
|
|
@@ -4,10 +4,11 @@ from .filter_function import ExpressionType
|
|
|
4
4
|
from .filter_function import FilterFunction
|
|
5
5
|
from .count import Count
|
|
6
6
|
from .is_instance import IsInstance
|
|
7
|
-
from .keys import
|
|
7
|
+
from .keys import Keys
|
|
8
8
|
from .length import Length
|
|
9
9
|
from .match import Match
|
|
10
10
|
from .search import Search
|
|
11
|
+
from .starts_with import StartsWith
|
|
11
12
|
from .typeof import TypeOf
|
|
12
13
|
from .value import Value
|
|
13
14
|
|
|
@@ -16,10 +17,11 @@ __all__ = (
|
|
|
16
17
|
"ExpressionType",
|
|
17
18
|
"FilterFunction",
|
|
18
19
|
"IsInstance",
|
|
19
|
-
"
|
|
20
|
+
"Keys",
|
|
20
21
|
"Length",
|
|
21
22
|
"Match",
|
|
22
23
|
"Search",
|
|
24
|
+
"StartsWith",
|
|
23
25
|
"TypeOf",
|
|
24
26
|
"validate",
|
|
25
27
|
"Value",
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
import regex as re
|
|
6
|
+
|
|
7
|
+
REGEX_AVAILABLE = True
|
|
8
|
+
except ImportError:
|
|
9
|
+
import re # type: ignore
|
|
10
|
+
|
|
11
|
+
REGEX_AVAILABLE = False
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from iregexp_check import check
|
|
15
|
+
|
|
16
|
+
IREGEXP_AVAILABLE = True
|
|
17
|
+
except ImportError:
|
|
18
|
+
IREGEXP_AVAILABLE = False
|
|
19
|
+
|
|
20
|
+
from jsonpath.exceptions import JSONPathError
|
|
21
|
+
from jsonpath.function_extensions import ExpressionType
|
|
22
|
+
from jsonpath.function_extensions import FilterFunction
|
|
23
|
+
from jsonpath.lru_cache import LRUCache
|
|
24
|
+
from jsonpath.lru_cache import ThreadSafeLRUCache
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AbstractRegexFilterFunction(FilterFunction):
|
|
28
|
+
"""Base class for filter function that accept regular expression arguments.
|
|
29
|
+
|
|
30
|
+
Arguments:
|
|
31
|
+
cache_capacity: The size of the regular expression cache.
|
|
32
|
+
debug: When `True`, raise an exception when regex pattern compilation
|
|
33
|
+
fails. The default - as required by RFC 9535 - is `False`, which
|
|
34
|
+
silently ignores bad patterns.
|
|
35
|
+
thread_safe: When `True`, use a `ThreadSafeLRUCache` instead of an
|
|
36
|
+
instance of `LRUCache`.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
arg_types = [ExpressionType.VALUE, ExpressionType.VALUE]
|
|
40
|
+
return_type = ExpressionType.LOGICAL
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
*,
|
|
45
|
+
cache_capacity: int = 300,
|
|
46
|
+
debug: bool = False,
|
|
47
|
+
thread_safe: bool = False,
|
|
48
|
+
):
|
|
49
|
+
self.cache: LRUCache[str, Optional[re.Pattern]] = ( # type: ignore
|
|
50
|
+
ThreadSafeLRUCache(capacity=cache_capacity)
|
|
51
|
+
if thread_safe
|
|
52
|
+
else LRUCache(capacity=cache_capacity)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
self.debug = debug
|
|
56
|
+
|
|
57
|
+
def check_cache(self, pattern: str) -> Optional[re.Pattern]: # type: ignore
|
|
58
|
+
"""Return a compiled re pattern if `pattern` is valid, or `None` otherwise."""
|
|
59
|
+
try:
|
|
60
|
+
_pattern = self.cache[pattern]
|
|
61
|
+
except KeyError:
|
|
62
|
+
if IREGEXP_AVAILABLE and not check(pattern):
|
|
63
|
+
if self.debug:
|
|
64
|
+
raise JSONPathError(
|
|
65
|
+
"search pattern is not a valid I-Regexp", token=None
|
|
66
|
+
) from None
|
|
67
|
+
_pattern = None
|
|
68
|
+
else:
|
|
69
|
+
if REGEX_AVAILABLE:
|
|
70
|
+
pattern = map_re(pattern)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
_pattern = re.compile(pattern)
|
|
74
|
+
except re.error:
|
|
75
|
+
if self.debug:
|
|
76
|
+
raise
|
|
77
|
+
_pattern = None
|
|
78
|
+
|
|
79
|
+
self.cache[pattern] = _pattern
|
|
80
|
+
|
|
81
|
+
return _pattern
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def map_re(pattern: str) -> str:
|
|
85
|
+
"""Convert an I-Regexp pattern into a Python re pattern."""
|
|
86
|
+
escaped = False
|
|
87
|
+
char_class = False
|
|
88
|
+
parts: List[str] = []
|
|
89
|
+
for ch in pattern:
|
|
90
|
+
if escaped:
|
|
91
|
+
parts.append(ch)
|
|
92
|
+
escaped = False
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
if ch == ".":
|
|
96
|
+
if not char_class:
|
|
97
|
+
parts.append(r"(?:(?![\r\n])\P{Cs}|\p{Cs}\p{Cs})")
|
|
98
|
+
else:
|
|
99
|
+
parts.append(ch)
|
|
100
|
+
elif ch == "\\":
|
|
101
|
+
escaped = True
|
|
102
|
+
parts.append(ch)
|
|
103
|
+
elif ch == "[":
|
|
104
|
+
char_class = True
|
|
105
|
+
parts.append(ch)
|
|
106
|
+
elif ch == "]":
|
|
107
|
+
char_class = False
|
|
108
|
+
parts.append(ch)
|
|
109
|
+
else:
|
|
110
|
+
parts.append(ch)
|
|
111
|
+
|
|
112
|
+
return "".join(parts)
|
|
@@ -1,12 +1,31 @@
|
|
|
1
|
-
"""The
|
|
1
|
+
"""The `keys` JSONPath filter function."""
|
|
2
|
+
|
|
2
3
|
from typing import Mapping
|
|
3
|
-
from typing import Optional
|
|
4
4
|
from typing import Tuple
|
|
5
|
+
from typing import Union
|
|
6
|
+
|
|
7
|
+
from jsonpath.filter import UNDEFINED
|
|
8
|
+
from jsonpath.filter import _Undefined
|
|
9
|
+
|
|
10
|
+
from .filter_function import ExpressionType
|
|
11
|
+
from .filter_function import FilterFunction
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Keys(FilterFunction):
|
|
15
|
+
"""The `keys` JSONPath filter function."""
|
|
16
|
+
|
|
17
|
+
arg_types = [ExpressionType.VALUE]
|
|
18
|
+
return_type = ExpressionType.VALUE
|
|
5
19
|
|
|
20
|
+
def __call__(
|
|
21
|
+
self, value: Mapping[str, object]
|
|
22
|
+
) -> Union[Tuple[str, ...], _Undefined]:
|
|
23
|
+
"""Return a tuple of keys in `value`.
|
|
6
24
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
If `value` does not have a `keys()` method, the special _Nothing_ value
|
|
26
|
+
is returned.
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
return tuple(value.keys())
|
|
30
|
+
except AttributeError:
|
|
31
|
+
return UNDEFINED
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
"""The standard `match` function extension."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from ._pattern import AbstractRegexFilterFunction
|
|
4
4
|
|
|
5
|
-
from jsonpath.function_extensions import ExpressionType
|
|
6
|
-
from jsonpath.function_extensions import FilterFunction
|
|
7
5
|
|
|
6
|
+
class Match(AbstractRegexFilterFunction):
|
|
7
|
+
"""The standard `match` function."""
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
def __call__(self, value: object, pattern: object) -> bool:
|
|
10
|
+
"""Return `True` if _value_ matches _pattern_, or `False` otherwise."""
|
|
11
|
+
if not isinstance(value, str) or not isinstance(pattern, str):
|
|
12
|
+
return False
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
return_type = ExpressionType.LOGICAL
|
|
14
|
+
_pattern = self.check_cache(pattern)
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
"""Return `True` if _string_ matches _pattern_, or `False` otherwise."""
|
|
17
|
-
try:
|
|
18
|
-
# re.fullmatch caches compiled patterns internally
|
|
19
|
-
return bool(re.fullmatch(pattern, string))
|
|
20
|
-
except (TypeError, re.error):
|
|
16
|
+
if _pattern is None:
|
|
21
17
|
return False
|
|
18
|
+
|
|
19
|
+
return bool(_pattern.fullmatch(value))
|