python-jsonpath 0.10.1__py3-none-any.whl → 0.10.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jsonpath/__about__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2023-present James Prior <jamesgr.prior@gmail.com>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.10.1"
4
+ __version__ = "0.10.3"
jsonpath/filter.py CHANGED
@@ -623,7 +623,19 @@ class FunctionExtension(FilterExpression):
623
623
  if func.arg_types[idx] != ExpressionType.NODES and isinstance(
624
624
  arg, NodeList
625
625
  ):
626
- _args.append(arg.values_or_singular())
626
+ if len(arg) == 0:
627
+ # If the query results in an empty nodelist, the
628
+ # argument is the special result Nothing.
629
+ _args.append(UNDEFINED)
630
+ elif len(arg) == 1:
631
+ # If the query results in a nodelist consisting of a
632
+ # single node, the argument is the value of the node
633
+ _args.append(arg[0].obj)
634
+ else:
635
+ # This should not be possible as a non-singular query
636
+ # would have been rejected when checking function
637
+ # well-typedness.
638
+ _args.append(arg)
627
639
  else:
628
640
  _args.append(arg)
629
641
  return _args
@@ -1,7 +1,9 @@
1
1
  """The standard `length` function extension."""
2
2
  from collections.abc import Sized
3
- from typing import Optional
3
+ from typing import Union
4
4
 
5
+ from jsonpath.filter import UNDEFINED
6
+ from jsonpath.filter import _Undefined
5
7
  from jsonpath.function_extensions import ExpressionType
6
8
  from jsonpath.function_extensions import FilterFunction
7
9
 
@@ -12,9 +14,13 @@ class Length(FilterFunction):
12
14
  arg_types = [ExpressionType.VALUE]
13
15
  return_type = ExpressionType.VALUE
14
16
 
15
- def __call__(self, obj: Sized) -> Optional[int]:
16
- """Return an object's length, or `None` if the object does not have a length."""
17
+ def __call__(self, obj: Sized) -> Union[int, _Undefined]:
18
+ """Return an object's length.
19
+
20
+ If the object does not have a length, the special _Nothing_ value is
21
+ returned.
22
+ """
17
23
  try:
18
24
  return len(obj)
19
25
  except TypeError:
20
- return None
26
+ return UNDEFINED
jsonpath/lex.py CHANGED
@@ -66,10 +66,34 @@ if TYPE_CHECKING:
66
66
 
67
67
 
68
68
  class Lexer:
69
- """Tokenize a JSONPath string."""
69
+ """Tokenize a JSONPath string.
70
+
71
+ Some customization can be achieved by subclassing _Lexer_ and setting
72
+ class attributes. Then setting `lexer_class` on a `JSONPathEnvironment`.
73
+
74
+ Attributes:
75
+ key_pattern: The regular expression pattern used to match mapping
76
+ keys/properties.
77
+ logical_not_pattern: The regular expression pattern used to match
78
+ logical negation tokens. By default, `not` and `!` are
79
+ equivalent.
80
+ logical_and_pattern: The regular expression pattern used to match
81
+ logical _and_ tokens. By default, `and` and `&&` are equivalent.
82
+ logical_or_pattern: The regular expression pattern used to match
83
+ logical _or_ tokens. By default, `or` and `||` are equivalent.
84
+ """
70
85
 
71
86
  key_pattern = r"[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*"
72
87
 
88
+ # `not` or !
89
+ logical_not_pattern = r"(?:not|!)"
90
+
91
+ # && or `and`
92
+ logical_and_pattern = r"(?:&&|and)"
93
+
94
+ # || or `or`
95
+ logical_or_pattern = r"(?:\|\||or)"
96
+
73
97
  def __init__(self, *, env: JSONPathEnvironment) -> None:
74
98
  self.env = env
75
99
 
@@ -85,15 +109,6 @@ class Lexer:
85
109
  r"(?::\s*(?P<G_LSLICE_STEP>\-?\d*))?"
86
110
  )
87
111
 
88
- # `not` or !
89
- self.logical_not_pattern = r"(?:not|!)"
90
-
91
- # && or `and`
92
- self.bool_and_pattern = r"(?:&&|and)"
93
-
94
- # || or `or`
95
- self.bool_or_pattern = r"(?:\|\||or)"
96
-
97
112
  # /pattern/ or /pattern/flags
98
113
  self.re_pattern = r"/(?P<G_RE>.+?)/(?P<G_RE_FLAGS>[aims]*)"
99
114
 
@@ -114,8 +129,8 @@ class Lexer:
114
129
  (TOKEN_FLOAT, r"-?\d+\.\d*(?:e[+-]?\d+)?"),
115
130
  (TOKEN_INT, r"-?\d+(?P<G_EXP>e[+\-]?\d+)?\b"),
116
131
  (TOKEN_DDOT, r"\.\."),
117
- (TOKEN_AND, self.bool_and_pattern),
118
- (TOKEN_OR, self.bool_or_pattern),
132
+ (TOKEN_AND, self.logical_and_pattern),
133
+ (TOKEN_OR, self.logical_or_pattern),
119
134
  (TOKEN_ROOT, re.escape(self.env.root_token)),
120
135
  (TOKEN_SELF, re.escape(self.env.self_token)),
121
136
  (TOKEN_KEY, re.escape(self.env.key_token)),
jsonpath/parse.py CHANGED
@@ -158,7 +158,7 @@ class Parser:
158
158
  TOKEN_LG: PRECEDENCE_RELATIONAL,
159
159
  TOKEN_LT: PRECEDENCE_RELATIONAL,
160
160
  TOKEN_NE: PRECEDENCE_RELATIONAL,
161
- TOKEN_NOT: PRECEDENCE_LOGICALRIGHT,
161
+ TOKEN_NOT: PRECEDENCE_PREFIX,
162
162
  TOKEN_OR: PRECEDENCE_LOGICAL,
163
163
  TOKEN_RE: PRECEDENCE_RELATIONAL,
164
164
  TOKEN_RPAREN: PRECEDENCE_LOWEST,
@@ -180,7 +180,7 @@ class Parser:
180
180
  TOKEN_RE: "=~",
181
181
  }
182
182
 
183
- SINGULAR_QUERY_COMPARISON_OPERATORS = frozenset(
183
+ COMPARISON_OPERATORS = frozenset(
184
184
  [
185
185
  "==",
186
186
  ">=",
@@ -446,6 +446,7 @@ class Parser:
446
446
  )
447
447
 
448
448
  if stream.peek.kind != TOKEN_RBRACKET:
449
+ # TODO: error message .. expected a comma or logical operator
449
450
  stream.expect_peek(TOKEN_COMMA)
450
451
  stream.next_token()
451
452
 
@@ -499,9 +500,7 @@ class Parser:
499
500
  assert tok.kind == TOKEN_NOT
500
501
  return PrefixExpression(
501
502
  operator="!",
502
- right=self.parse_filter_selector(
503
- stream, precedence=self.PRECEDENCE_LOGICALRIGHT
504
- ),
503
+ right=self.parse_filter_selector(stream, precedence=self.PRECEDENCE_PREFIX),
505
504
  )
506
505
 
507
506
  def parse_infix_expression(
@@ -512,10 +511,7 @@ class Parser:
512
511
  right = self.parse_filter_selector(stream, precedence)
513
512
  operator = self.BINARY_OPERATORS[tok.kind]
514
513
 
515
- self._raise_for_non_singular_query(left, tok) # TODO: store tok on expression
516
- self._raise_for_non_singular_query(right, tok)
517
-
518
- if operator in self.SINGULAR_QUERY_COMPARISON_OPERATORS:
514
+ if self.env.well_typed and operator in self.COMPARISON_OPERATORS:
519
515
  self._raise_for_non_comparable_function(left, tok)
520
516
  self._raise_for_non_comparable_function(right, tok)
521
517
 
@@ -667,26 +663,18 @@ class Parser:
667
663
 
668
664
  return token.value
669
665
 
670
- def _raise_for_non_singular_query(
671
- self, expr: FilterExpression, token: Token
672
- ) -> None:
673
- if (
674
- self.env.well_typed
675
- and isinstance(expr, Path)
676
- and not expr.path.singular_query()
677
- ):
678
- raise JSONPathSyntaxError(
679
- "non-singular query is not comparable", token=token
680
- )
681
-
682
666
  def _raise_for_non_comparable_function(
683
667
  self, expr: FilterExpression, token: Token
684
668
  ) -> None:
685
- if not self.env.well_typed or not isinstance(expr, FunctionExtension):
686
- return
687
- func = self.env.function_extensions.get(expr.name)
688
- if (
689
- isinstance(func, FilterFunction)
690
- and func.return_type != ExpressionType.VALUE
691
- ):
692
- raise JSONPathTypeError(f"result of {expr.name}() is not comparable", token)
669
+ if isinstance(expr, Path) and not expr.path.singular_query():
670
+ raise JSONPathTypeError("non-singular query is not comparable", token=token)
671
+
672
+ if isinstance(expr, FunctionExtension):
673
+ func = self.env.function_extensions.get(expr.name)
674
+ if (
675
+ isinstance(func, FilterFunction)
676
+ and func.return_type != ExpressionType.VALUE
677
+ ):
678
+ raise JSONPathTypeError(
679
+ f"result of {expr.name}() is not comparable", token
680
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-jsonpath
3
- Version: 0.10.1
3
+ Version: 0.10.3
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
@@ -36,6 +36,9 @@ A flexible JSONPath engine for Python with JSON Pointer and JSON Patch.
36
36
  <a href="https://github.com/jg-rp/python-jsonpath/actions">
37
37
  <img src="https://img.shields.io/github/actions/workflow/status/jg-rp/python-jsonpath/tests.yaml?branch=main&label=tests&style=flat-square" alt="Tests">
38
38
  </a>
39
+ <a href="https://pypi.org/project/python-jsonpath">
40
+ <img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/python-jsonpath?style=flat-square">
41
+ </a>
39
42
  <br>
40
43
  <a href="https://pypi.org/project/python-jsonpath">
41
44
  <img src="https://img.shields.io/pypi/v/python-jsonpath.svg?style=flat-square" alt="PyPi - Version">
@@ -1,14 +1,14 @@
1
- jsonpath/__about__.py,sha256=Q8tvcfeI8V3W2SG92pUmdmY29ZOPlI8LEgmMT73xNoY,133
1
+ jsonpath/__about__.py,sha256=RPhvY8Ls3nFAxAsmj87zrfksIIJaJ6PYlv7USsbgxvg,133
2
2
  jsonpath/__init__.py,sha256=VyX-rbLzz-g-2khpIKPE4Xg92LidBatcuNllQkCKl-c,2022
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
6
  jsonpath/env.py,sha256=uEgcZHkdzdXjbmk_OsaEjK1nTxgaceORI77DYMleOmA,20675
7
7
  jsonpath/exceptions.py,sha256=5n-9vaKTu5asWllHT0IN-824ewcNCyQIyKvQ58TERVk,4351
8
- jsonpath/filter.py,sha256=WWZiyHi5S2LlTXr4wtCD9tW4YXIsV5UeExAq3ISixG0,19840
9
- jsonpath/lex.py,sha256=yxfAlcARsX7JQIvqghY8WtXWkc2rrI099AIGMLQiur4,9267
8
+ jsonpath/filter.py,sha256=oJ0M2D0UZ_Y5AkcVgaNuvm9P6vB2Gl7JVuyZBjICEG0,20511
9
+ jsonpath/lex.py,sha256=YcNFNiKPEabcFA5A3wmwQj0P5SliSJSZiH9X-xFaQTA,9981
10
10
  jsonpath/match.py,sha256=_5ds2mEM0jBmo8uBuLA39o8ulKPs5_QvBQ373RduwGo,3369
11
- jsonpath/parse.py,sha256=qyYeuL-Ixl0qrZ6THko43OpdjW4sPMCvyEVP87zQxwY,23541
11
+ jsonpath/parse.py,sha256=u3Tw6aWTnhWy19W4RfR_tTi1zEolovGPJHjEi5CUPno,23231
12
12
  jsonpath/patch.py,sha256=V_BiRr20qyByBNppBeA0e2I1nfbPJ_svPspDubMzA20,21350
13
13
  jsonpath/path.py,sha256=qQrmSfrUn19dNJtFVK6xHw-FTABX6BZpStiWWkR711c,14344
14
14
  jsonpath/pointer.py,sha256=YvfNAp5qADiqJGuTE1jvPrtCTVyyDk9rlT2xJB9SjtE,22234
@@ -22,13 +22,13 @@ jsonpath/function_extensions/count.py,sha256=FzTOkOLa01PFFSR1wjkDEOyBr_ZC_6yrNfF
22
22
  jsonpath/function_extensions/filter_function.py,sha256=B6z-02xO6CtNghLN92Acr_UmeiWwJaRDLCOTMcPOrfg,834
23
23
  jsonpath/function_extensions/is_instance.py,sha256=qm9m3UpKJew7qsuSQ5dEem7Ybo4jhCM1uf12wre0SRg,1760
24
24
  jsonpath/function_extensions/keys.py,sha256=vNdIV8xqr1LMG6RLJwb6iA5-VXgYSZu6CqprXEs6rYM,364
25
- jsonpath/function_extensions/length.py,sha256=8lNCX838dqEQVRgfZduRhN_NsLtBM8JvtxPgPFZ8y-Y,641
25
+ jsonpath/function_extensions/length.py,sha256=Z9gtz0zhzT1_8LzzHm1TRhJlMWT5yEsqNtQCuWg14I0,786
26
26
  jsonpath/function_extensions/match.py,sha256=KjsH33fCFGonp2RV__FuaeIOTwLLcvgaaCi0juyEl-U,712
27
27
  jsonpath/function_extensions/search.py,sha256=O11fnkHlbvf0QPrLISYfhlPXBvVPBr-U8V0dGbd614Y,710
28
28
  jsonpath/function_extensions/typeof.py,sha256=yCAj9zOqSnam1mfHCGolNHWDmsBOvU3rAhbZDYycx50,1780
29
29
  jsonpath/function_extensions/value.py,sha256=fQMbPUV87Jn1nOwAlBpTeLmLIG5ejH0XQBOM_SR-Us4,721
30
- python_jsonpath-0.10.1.dist-info/METADATA,sha256=-UIlWZwpkSRtYphX5XkGcDk8UUKJCGNPgZANDhT6dT4,5004
31
- python_jsonpath-0.10.1.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
32
- python_jsonpath-0.10.1.dist-info/entry_points.txt,sha256=xvbWnAebJyOMI_9ugK0xrpFRlwmEsAJD2kNHU0Dvscc,43
33
- python_jsonpath-0.10.1.dist-info/licenses/LICENSE.txt,sha256=u7PksAQGI1QYWcERHeauMseZ4XAzDKUrKW8Z4wbeU1k,1101
34
- python_jsonpath-0.10.1.dist-info/RECORD,,
30
+ python_jsonpath-0.10.3.dist-info/METADATA,sha256=Q9aCV13AWBWjRYXGUX6W8_PUocqCkhsjaDC9LFzvXQA,5169
31
+ python_jsonpath-0.10.3.dist-info/WHEEL,sha256=9QBuHhg6FNW7lppboF2vKVbCGTVzsFykgRQjjlajrhA,87
32
+ python_jsonpath-0.10.3.dist-info/entry_points.txt,sha256=xvbWnAebJyOMI_9ugK0xrpFRlwmEsAJD2kNHU0Dvscc,43
33
+ python_jsonpath-0.10.3.dist-info/licenses/LICENSE.txt,sha256=u7PksAQGI1QYWcERHeauMseZ4XAzDKUrKW8Z4wbeU1k,1101
34
+ python_jsonpath-0.10.3.dist-info/RECORD,,