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/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[FilterExpression]:
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[FilterExpression]) -> None: # noqa: ARG002
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(FilterExpression):
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[FilterExpression]:
95
+ def children(self) -> List[BaseExpression]:
99
96
  return []
100
97
 
101
- def set_children(self, children: List[FilterExpression]) -> None: # noqa: ARG002
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(FilterExpression):
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[FilterExpression]:
147
+ def children(self) -> List[BaseExpression]:
151
148
  return []
152
149
 
153
- def set_children(self, children: List[FilterExpression]) -> None: # noqa: ARG002
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 Literal(FilterExpression, Generic[LITERAL_EXPRESSION_T]):
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[FilterExpression]:
183
+ def children(self) -> List[BaseExpression]:
187
184
  return []
188
185
 
189
- def set_children(self, children: List[FilterExpression]) -> None: # noqa: ARG002
186
+ def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
190
187
  return
191
188
 
192
189
 
193
- class BooleanLiteral(Literal[bool]):
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(Literal[str]):
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(Literal[int]):
211
+ class IntegerLiteral(FilterExpressionLiteral[int]):
215
212
  """An integer literal."""
216
213
 
217
214
  __slots__ = ()
218
215
 
219
216
 
220
- class FloatLiteral(Literal[float]):
217
+ class FloatLiteral(FilterExpressionLiteral[float]):
221
218
  """A float literal."""
222
219
 
223
220
  __slots__ = ()
224
221
 
225
222
 
226
- class RegexLiteral(Literal[Pattern[str]]):
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(FilterExpression):
246
+ class ListLiteral(BaseExpression):
250
247
  """A list literal."""
251
248
 
252
249
  __slots__ = ("items",)
253
250
 
254
- def __init__(self, items: List[FilterExpression]) -> None:
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[FilterExpression]:
268
+ def children(self) -> List[BaseExpression]:
272
269
  return self.items
273
270
 
274
- def set_children(self, children: List[FilterExpression]) -> None: # noqa: ARG002
271
+ def set_children(self, children: List[BaseExpression]) -> None: # noqa: ARG002
275
272
  self.items = children
276
273
 
277
274
 
278
- class PrefixExpression(FilterExpression):
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: FilterExpression):
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[FilterExpression]:
306
+ def children(self) -> List[BaseExpression]:
310
307
  return [self.right]
311
308
 
312
- def set_children(self, children: List[FilterExpression]) -> None:
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(FilterExpression):
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: FilterExpression,
321
+ left: BaseExpression,
325
322
  operator: str,
326
- right: FilterExpression,
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[FilterExpression]:
366
+ def children(self) -> List[BaseExpression]:
370
367
  return [self.left, self.right]
371
368
 
372
- def set_children(self, children: List[FilterExpression]) -> None:
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 BooleanExpression(FilterExpression):
385
- """An expression that always evaluates to `True` or `False`."""
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: FilterExpression):
386
+ def __init__(self, expression: BaseExpression):
390
387
  self.expression = expression
391
388
  super().__init__()
392
389
 
393
- def cache_tree(self) -> BooleanExpression:
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: FilterExpression) -> FilterExpression:
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 BooleanExpression(_cache_tree(copy.copy(self.expression)))
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, BooleanExpression) and self.expression == other.expression
418
+ isinstance(other, FilterExpression) and self.expression == other.expression
422
419
  )
423
420
 
424
421
  def _canonical_string(
425
- self, expression: FilterExpression, parent_precedence: int
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[FilterExpression]:
454
+ def children(self) -> List[BaseExpression]:
458
455
  return [self.expression]
459
456
 
460
- def set_children(self, children: List[FilterExpression]) -> None:
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(FilterExpression):
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: FilterExpression):
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[FilterExpression]:
487
+ def children(self) -> List[BaseExpression]:
491
488
  return self._expr.children()
492
489
 
493
- def set_children(self, children: List[FilterExpression]) -> None:
490
+ def set_children(self, children: List[BaseExpression]) -> None:
494
491
  self._expr.set_children(children)
495
492
 
496
493
 
497
- class Path(FilterExpression, ABC):
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, Path) and str(self) == str(other)
508
-
509
- def children(self) -> List[FilterExpression]:
510
- _children: List[FilterExpression] = []
511
- for segment in self.path.selectors:
512
- if isinstance(segment, ListSelector):
513
- _children.extend(
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[FilterExpression]) -> None: # noqa: ARG002
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 SelfPath(Path):
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): # TODO: refactor
539
- if self.path.empty():
540
- return context.current
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): # TODO: refactor
556
- if self.path.empty():
557
- return context.current
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 RootPath(Path):
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(Path):
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(FilterExpression):
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[FilterExpression]) -> None:
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
- return UNDEFINED # TODO: should probably raise an exception
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
- return UNDEFINED # TODO: should probably raise an exception
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[FilterExpression]:
710
+ def children(self) -> List[BaseExpression]:
717
711
  return list(self.args)
718
712
 
719
- def set_children(self, children: List[FilterExpression]) -> None:
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(FilterExpression):
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[FilterExpression]:
741
+ def children(self) -> List[BaseExpression]:
748
742
  return []
749
743
 
750
- def set_children(self, children: List[FilterExpression]) -> None: # noqa: ARG002
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: FilterExpression) -> Iterable[FilterExpression]:
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
- Literal,
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 keys
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
- "keys",
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 built-in `keys` function extension."""
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
- def keys(obj: Mapping[str, object]) -> Optional[Tuple[str, ...]]:
8
- """Return an object's keys, or `None` if the object has no _keys_ method."""
9
- try:
10
- return tuple(obj.keys())
11
- except AttributeError:
12
- return None
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 re
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
- class Match(FilterFunction):
10
- """A type-aware implementation of the standard `match` function."""
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
- arg_types = [ExpressionType.VALUE, ExpressionType.VALUE]
13
- return_type = ExpressionType.LOGICAL
14
+ _pattern = self.check_cache(pattern)
14
15
 
15
- def __call__(self, string: str, pattern: str) -> bool:
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))