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 +1 -1
- jsonpath/__init__.py +290 -10
- jsonpath/_types.py +31 -0
- jsonpath/cli.py +11 -1
- jsonpath/env.py +101 -48
- jsonpath/exceptions.py +77 -9
- jsonpath/filter.py +114 -90
- 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/pointer.py +71 -47
- 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.1.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.1.dist-info/RECORD +0 -36
- {python_jsonpath-1.3.1.dist-info → python_jsonpath-2.0.0.dist-info}/WHEEL +0 -0
- {python_jsonpath-1.3.1.dist-info → python_jsonpath-2.0.0.dist-info}/entry_points.txt +0 -0
- {python_jsonpath-1.3.1.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,33 +529,40 @@ 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()
|
|
546
538
|
|
|
547
|
-
return NodeList(
|
|
539
|
+
return NodeList(
|
|
540
|
+
self.path.finditer(
|
|
541
|
+
context.current,
|
|
542
|
+
filter_context=context.extra_context,
|
|
543
|
+
)
|
|
544
|
+
)
|
|
548
545
|
|
|
549
546
|
async def evaluate_async(self, context: FilterContext) -> object:
|
|
550
|
-
if isinstance(context.current, str)
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
return NodeList()
|
|
554
|
-
if not isinstance(context.current, (Sequence, Mapping)):
|
|
547
|
+
if isinstance(context.current, str) or not isinstance(
|
|
548
|
+
context.current, (Sequence, Mapping)
|
|
549
|
+
):
|
|
555
550
|
if self.path.empty():
|
|
556
551
|
return context.current
|
|
557
552
|
return NodeList()
|
|
558
553
|
|
|
559
554
|
return NodeList(
|
|
560
|
-
[
|
|
555
|
+
[
|
|
556
|
+
match
|
|
557
|
+
async for match in await self.path.finditer_async(
|
|
558
|
+
context.current,
|
|
559
|
+
filter_context=context.extra_context,
|
|
560
|
+
)
|
|
561
|
+
]
|
|
561
562
|
)
|
|
562
563
|
|
|
563
564
|
|
|
564
|
-
class
|
|
565
|
+
class RootFilterQuery(FilterQuery):
|
|
565
566
|
"""A JSONPath starting at the root node."""
|
|
566
567
|
|
|
567
568
|
__slots__ = ()
|
|
@@ -576,15 +577,26 @@ class RootPath(Path):
|
|
|
576
577
|
return str(self.path)
|
|
577
578
|
|
|
578
579
|
def evaluate(self, context: FilterContext) -> object:
|
|
579
|
-
return NodeList(
|
|
580
|
+
return NodeList(
|
|
581
|
+
self.path.finditer(
|
|
582
|
+
context.root,
|
|
583
|
+
filter_context=context.extra_context,
|
|
584
|
+
)
|
|
585
|
+
)
|
|
580
586
|
|
|
581
587
|
async def evaluate_async(self, context: FilterContext) -> object:
|
|
582
588
|
return NodeList(
|
|
583
|
-
[
|
|
589
|
+
[
|
|
590
|
+
match
|
|
591
|
+
async for match in await self.path.finditer_async(
|
|
592
|
+
context.root,
|
|
593
|
+
filter_context=context.extra_context,
|
|
594
|
+
)
|
|
595
|
+
]
|
|
584
596
|
)
|
|
585
597
|
|
|
586
598
|
|
|
587
|
-
class FilterContextPath(
|
|
599
|
+
class FilterContextPath(FilterQuery):
|
|
588
600
|
"""A JSONPath starting at the root of any extra context data."""
|
|
589
601
|
|
|
590
602
|
__slots__ = ()
|
|
@@ -600,23 +612,31 @@ class FilterContextPath(Path):
|
|
|
600
612
|
return "_" + path_repr[1:]
|
|
601
613
|
|
|
602
614
|
def evaluate(self, context: FilterContext) -> object:
|
|
603
|
-
return NodeList(
|
|
615
|
+
return NodeList(
|
|
616
|
+
self.path.finditer(
|
|
617
|
+
context.extra_context,
|
|
618
|
+
filter_context=context.extra_context,
|
|
619
|
+
)
|
|
620
|
+
)
|
|
604
621
|
|
|
605
622
|
async def evaluate_async(self, context: FilterContext) -> object:
|
|
606
623
|
return NodeList(
|
|
607
624
|
[
|
|
608
625
|
match
|
|
609
|
-
async for match in await self.path.finditer_async(
|
|
626
|
+
async for match in await self.path.finditer_async(
|
|
627
|
+
context.extra_context,
|
|
628
|
+
filter_context=context.extra_context,
|
|
629
|
+
)
|
|
610
630
|
]
|
|
611
631
|
)
|
|
612
632
|
|
|
613
633
|
|
|
614
|
-
class FunctionExtension(
|
|
634
|
+
class FunctionExtension(BaseExpression):
|
|
615
635
|
"""A filter function."""
|
|
616
636
|
|
|
617
637
|
__slots__ = ("name", "args")
|
|
618
638
|
|
|
619
|
-
def __init__(self, name: str, args: Sequence[
|
|
639
|
+
def __init__(self, name: str, args: Sequence[BaseExpression]) -> None:
|
|
620
640
|
self.name = name
|
|
621
641
|
self.args = args
|
|
622
642
|
super().__init__()
|
|
@@ -636,7 +656,9 @@ class FunctionExtension(FilterExpression):
|
|
|
636
656
|
try:
|
|
637
657
|
func = context.env.function_extensions[self.name]
|
|
638
658
|
except KeyError:
|
|
639
|
-
|
|
659
|
+
# This can only happen if the environment's function register has been
|
|
660
|
+
# changed since the query was parsed.
|
|
661
|
+
return UNDEFINED
|
|
640
662
|
args = [arg.evaluate(context) for arg in self.args]
|
|
641
663
|
return func(*self._unpack_node_lists(func, args))
|
|
642
664
|
|
|
@@ -644,7 +666,9 @@ class FunctionExtension(FilterExpression):
|
|
|
644
666
|
try:
|
|
645
667
|
func = context.env.function_extensions[self.name]
|
|
646
668
|
except KeyError:
|
|
647
|
-
|
|
669
|
+
# This can only happen if the environment's function register has been
|
|
670
|
+
# changed since the query was parsed.
|
|
671
|
+
return UNDEFINED
|
|
648
672
|
args = [await arg.evaluate_async(context) for arg in self.args]
|
|
649
673
|
return func(*self._unpack_node_lists(func, args))
|
|
650
674
|
|
|
@@ -683,15 +707,15 @@ class FunctionExtension(FilterExpression):
|
|
|
683
707
|
for obj in args
|
|
684
708
|
]
|
|
685
709
|
|
|
686
|
-
def children(self) -> List[
|
|
710
|
+
def children(self) -> List[BaseExpression]:
|
|
687
711
|
return list(self.args)
|
|
688
712
|
|
|
689
|
-
def set_children(self, children: List[
|
|
713
|
+
def set_children(self, children: List[BaseExpression]) -> None:
|
|
690
714
|
assert len(children) == len(self.args)
|
|
691
715
|
self.args = children
|
|
692
716
|
|
|
693
717
|
|
|
694
|
-
class CurrentKey(
|
|
718
|
+
class CurrentKey(BaseExpression):
|
|
695
719
|
"""The key/property or index associated with the current object."""
|
|
696
720
|
|
|
697
721
|
__slots__ = ()
|
|
@@ -714,17 +738,17 @@ class CurrentKey(FilterExpression):
|
|
|
714
738
|
async def evaluate_async(self, context: FilterContext) -> object:
|
|
715
739
|
return self.evaluate(context)
|
|
716
740
|
|
|
717
|
-
def children(self) -> List[
|
|
741
|
+
def children(self) -> List[BaseExpression]:
|
|
718
742
|
return []
|
|
719
743
|
|
|
720
|
-
def set_children(self, children: List[
|
|
744
|
+
def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
|
|
721
745
|
return
|
|
722
746
|
|
|
723
747
|
|
|
724
748
|
CURRENT_KEY = CurrentKey()
|
|
725
749
|
|
|
726
750
|
|
|
727
|
-
def walk(expr:
|
|
751
|
+
def walk(expr: BaseExpression) -> Iterable[BaseExpression]:
|
|
728
752
|
"""Walk the filter expression tree starting at _expr_."""
|
|
729
753
|
yield expr
|
|
730
754
|
for child in expr.children():
|
|
@@ -734,7 +758,7 @@ def walk(expr: FilterExpression) -> Iterable[FilterExpression]:
|
|
|
734
758
|
VALUE_TYPE_EXPRESSIONS = (
|
|
735
759
|
Nil,
|
|
736
760
|
Undefined,
|
|
737
|
-
|
|
761
|
+
FilterExpressionLiteral,
|
|
738
762
|
ListLiteral,
|
|
739
763
|
CurrentKey,
|
|
740
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)
|