jsonpath-rfc9535 2.0.0__tar.gz → 2.0.2__tar.gz

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.
Files changed (96) hide show
  1. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/CHANGELOG.md +20 -0
  2. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/PKG-INFO +1 -1
  3. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/pyproject.toml +1 -0
  4. jsonpath_rfc9535-2.0.2/src/jsonpath_rfc9535/__about__.py +1 -0
  5. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_lexer.py +6 -7
  6. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_parser.py +1 -8
  7. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_parser_standard.py +1 -0
  8. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_resolve_standard.py +11 -13
  9. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_resolver.py +3 -0
  10. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_resolver_basic.py +11 -13
  11. jsonpath_rfc9535-2.0.2/tests/test_issues.py +86 -0
  12. jsonpath_rfc9535-2.0.0/src/jsonpath_rfc9535/__about__.py +0 -1
  13. jsonpath_rfc9535-2.0.0/tests/test_issues.py +0 -26
  14. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/.github/FUNDING.yml +0 -0
  15. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/.github/workflows/lint.yaml +0 -0
  16. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/.github/workflows/tests.yaml +0 -0
  17. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/.github/workflows/typing.yaml +0 -0
  18. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/.gitignore +0 -0
  19. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/.gitmodules +0 -0
  20. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/CONTRIBUTING.md +0 -0
  21. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/LICENSE.txt +0 -0
  22. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/README.md +0 -0
  23. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/_static/jsonpath_rfc9535.xcf +0 -0
  24. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/_static/jsonpath_rfc9535_dark.png +0 -0
  25. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/_static/jsonpath_rfc9535_light.png +0 -0
  26. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/api.rst +0 -0
  27. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/conf.py +0 -0
  28. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/functions.rst +0 -0
  29. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/index.rst +0 -0
  30. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/quick.rst +0 -0
  31. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/docs/syntax.rst +0 -0
  32. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/__init__.py +0 -0
  33. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/bench_citylots.py +0 -0
  34. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/bench_parse.py +0 -0
  35. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/bench_resolve.py +0 -0
  36. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/bench_tokenize.py +0 -0
  37. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/benchmark.py +0 -0
  38. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/micro_bench_types.py +0 -0
  39. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/profile_citylots.py +0 -0
  40. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/scripts/profile_types_memory.py +0 -0
  41. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/__init__.py +0 -0
  42. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/__main__.py +0 -0
  43. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_ast.py +0 -0
  44. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_debug.py +0 -0
  45. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_environment.py +0 -0
  46. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_lru_cache.py +0 -0
  47. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_node.py +0 -0
  48. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_nothing.py +0 -0
  49. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_serialize.py +0 -0
  50. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/_tokens.py +0 -0
  51. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/cli.py +0 -0
  52. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/exceptions.py +0 -0
  53. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/functions.py +0 -0
  54. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/py.typed +0 -0
  55. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/src/jsonpath_rfc9535/query.py +0 -0
  56. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/canonical_paths.json +0 -0
  57. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/.editorconfig +0 -0
  58. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/.github/workflows/build_cts_json.yaml +0 -0
  59. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/.github/workflows/validate_json_schema.yaml +0 -0
  60. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/CODE_OF_CONDUCT.md +0 -0
  61. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/CONTRIBUTING.md +0 -0
  62. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/LICENSE +0 -0
  63. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/NOTICE +0 -0
  64. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/README.md +0 -0
  65. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/build.js +0 -0
  66. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/build.ps1 +0 -0
  67. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/build.sh +0 -0
  68. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/cts.schema.json +0 -0
  69. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/basic.json +0 -0
  70. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/filter.json +0 -0
  71. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/functions/count.json +0 -0
  72. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/functions/length.json +0 -0
  73. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/functions/match.json +0 -0
  74. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/functions/search.json +0 -0
  75. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/functions/value.json +0 -0
  76. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/index_selector.json +0 -0
  77. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/name_selector.json +0 -0
  78. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/slice_selector.json +0 -0
  79. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/whitespace/filter.json +0 -0
  80. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/whitespace/functions.json +0 -0
  81. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/whitespace/operators.json +0 -0
  82. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/whitespace/selectors.json +0 -0
  83. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/cts/tests/whitespace/slice.json +0 -0
  84. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_api.py +0 -0
  85. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_canonical_paths.py +0 -0
  86. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_cli.py +0 -0
  87. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_compliance.py +0 -0
  88. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_env.py +0 -0
  89. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_errors.py +0 -0
  90. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_ietf_examples.py +0 -0
  91. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_ietf_well_typedness.py +0 -0
  92. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_iregexp.py +0 -0
  93. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_lru_cache.py +0 -0
  94. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_node.py +0 -0
  95. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_regex_cache.py +0 -0
  96. {jsonpath_rfc9535-2.0.0 → jsonpath_rfc9535-2.0.2}/tests/test_tokenize.py +0 -0
@@ -1,5 +1,25 @@
1
1
  # Python JSONPath RFC 9535 Change Log
2
2
 
3
+ ## Version 2.0.2
4
+
5
+ **Fixes**
6
+
7
+ - Restored support for traversing `dict` and `list` subclasses when resolving a JSONPath expression. See [#32](https://github.com/jg-rp/python-jsonpath-rfc9535/issues/32).
8
+
9
+ ## Version 2.0.1
10
+
11
+ **Fixes**
12
+
13
+ - Fixed trailing comma detection in bracketed segments. Previously we failed to consume whitespace after a comma and before checking for `]`. See [#26](https://github.com/jg-rp/python-jsonpath-rfc9535/issues/26).
14
+
15
+ - Fixed comparison operators `<`, `>`, `<=` and `>=` when comparing `true` and/or `false` with `1` and `0`. Previously we were leaking Python behavior. See [#27](https://github.com/jg-rp/python-jsonpath-rfc9535/issues/27).
16
+
17
+ - Fixed `\u` escaped sequence decoding. Previously we would not recognize lower case `e` and `f` hex digits due to a typo in a regular expression. See [#24](https://github.com/jg-rp/python-jsonpath-rfc9535/issues/24).
18
+
19
+ - Fixed `\u` escape sequence rejection of code points less than or equal to 0x1F. The spec requires us to reject literals of 0x1F or lower, not escape sequences. See [#24](https://github.com/jg-rp/python-jsonpath-rfc9535/issues/24).
20
+
21
+ - Fixed the shorthand name selector to accept the full range of allow Unicode code points and reject names containing `-`. See [#25](https://github.com/jg-rp/python-jsonpath-rfc9535/issues/25).
22
+
3
23
  ## Version 2.0.0
4
24
 
5
25
  This release includes performance improvements and some breaking API changes. JSONPath syntax and semantics are unchanged.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: jsonpath-rfc9535
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: RFC 9535 - JSONPath: Query Expressions for JSON in Python
5
5
  Project-URL: Documentation, https://jg-rp.github.io/python-jsonpath-rfc9535/
6
6
  Project-URL: Issues, https://github.com/jg-rp/python-jsonpath-rfc9535/issues
@@ -49,6 +49,7 @@ dependencies = [
49
49
  "sphinx_copybutton",
50
50
  "types-regex>=2026.8.31.20260901",
51
51
  "ghp-import",
52
+ "twine",
52
53
  ]
53
54
 
54
55
  [tool.hatch.envs.benchmark]
@@ -0,0 +1 @@
1
+ __version__ = "2.0.2"
@@ -6,16 +6,14 @@ from .exceptions import JSONPathSyntaxError
6
6
  RE_FLOAT = re.compile(r"(:?-?[0-9]+\.[0-9]+(?:[eE][+-]?[0-9]+)?)|(-?[0-9]+[eE]-[0-9]+)")
7
7
  RE_INT = re.compile(r"-?[0-9]+")
8
8
  RE_INT_EXP = re.compile(r"-?[0-9]+[eE]\+?[0-9]+")
9
- RE_NAME = re.compile(r"[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*")
9
+ RE_NAME = re.compile(
10
+ r"[\u0080-\uD7FF\uE000-\U0010FFFFa-zA-Z_][\u0080-\u10FFFF\uE000-\U0010FFFFa-zA-Z0-9_]*"
11
+ )
10
12
  RE_TRIVIA = re.compile(r"[ \n\r\t]+")
11
13
 
12
14
 
13
15
  def tokenize(source: str) -> list[Token]:
14
- """Transform a JSONPath query source string into a list of tokens.
15
-
16
- This tokenizer emits some non-standard tokens. The parser is responsible
17
- for reject non-standard tokens with a syntax error if desired.
18
- """
16
+ """Transform a JSONPath query source string into a list of tokens."""
19
17
  tokens: list[Token] = []
20
18
  length = len(source)
21
19
  pos = 0
@@ -238,7 +236,8 @@ def _is_name_first_ch(ch: int) -> bool:
238
236
  (ch >= 65 and ch <= 90)
239
237
  or (ch >= 97 and ch <= 122)
240
238
  or ch == 95
241
- or (ch >= 0x80 and ch <= 0xFFFF)
239
+ or (ch >= 0x80 and ch <= 0xD7FF)
240
+ or (ch >= 0xE000 and ch <= 0x10FFFF)
242
241
  )
243
242
 
244
243
 
@@ -20,7 +20,7 @@ from .query import JSONPathQuery
20
20
  if TYPE_CHECKING:
21
21
  from ._environment import JSONPathEnvironment
22
22
 
23
- _RE_ESCAPE_U = re.compile(r"(\\u[0-9a-dA-F]{4}|\\.)")
23
+ _RE_ESCAPE_U = re.compile(r"(\\u[0-9a-fA-F]{4}|\\.)")
24
24
 
25
25
 
26
26
  class Parser(Protocol):
@@ -344,13 +344,6 @@ class Parser_(ABC):
344
344
  ((code_point & 0x03FF) << 10) | (low_surrogate & 0x03FF)
345
345
  )
346
346
 
347
- if code_point <= 0x1F:
348
- raise JSONPathSyntaxError(
349
- "invalid escape sequence",
350
- token,
351
- self.source,
352
- )
353
-
354
347
  unescaped.append(chr(code_point))
355
348
  else:
356
349
  raise JSONPathSyntaxError(
@@ -195,6 +195,7 @@ class StandardParser(Parser_):
195
195
  )
196
196
 
197
197
  self.eat(TOKEN_COMMA)
198
+ self.skip(TOKEN_TRIVIA)
198
199
  if self.kind() == TOKEN_RIGHT_BRACKET:
199
200
  raise JSONPathSyntaxError(
200
201
  "unexpected trailing comma",
@@ -66,37 +66,37 @@ class StandardResolver(Resolver_):
66
66
 
67
67
  match selector:
68
68
  case (_ast.NAME_SELECTOR, _, name):
69
- if type(obj) is dict and name in obj:
69
+ if isinstance(obj, dict) and name in obj:
70
70
  yield self.new_child(node, name, obj[name])
71
71
 
72
72
  case (_ast.INDEX_SELECTOR, _, index):
73
- if type(obj) is list and len(obj) >= abs(index) + int(index >= 0):
73
+ if isinstance(obj, list) and len(obj) >= abs(index) + int(index >= 0):
74
74
  if index < 0 and len(obj) >= abs(index):
75
75
  index = len(obj) + index
76
76
 
77
77
  yield self.new_child(node, index, obj[index])
78
78
 
79
79
  case (_ast.SLICE_SELECTOR, _, slice_):
80
- if type(obj) is list and slice_.step != 0:
80
+ if isinstance(obj, list) and slice_.step != 0:
81
81
  for i, elem in zip(range(*slice_.indices(len(obj))), obj[slice_]):
82
82
  yield self.new_child(node, i, elem)
83
83
 
84
84
  case (_ast.WILDCARD_SELECTOR, _):
85
- if type(obj) is dict:
85
+ if isinstance(obj, dict):
86
86
  for k, v in obj.items():
87
87
  yield self.new_child(node, k, v)
88
88
 
89
- elif type(obj) is list:
89
+ elif isinstance(obj, list):
90
90
  for i, elem in enumerate(obj):
91
91
  yield self.new_child(node, i, elem)
92
92
 
93
93
  case (_ast.FILTER_SELECTOR, _, expr):
94
- if type(obj) is dict:
94
+ if isinstance(obj, dict):
95
95
  for k, v in obj.items():
96
96
  if self.truthy(self._evaluate_expression(expr, k, v)):
97
97
  yield self.new_child(node, k, v)
98
98
 
99
- elif type(obj) is list:
99
+ elif isinstance(obj, list):
100
100
  for i, elem in enumerate(obj):
101
101
  if self.truthy(self._evaluate_expression(expr, i, elem)):
102
102
  yield self.new_child(node, i, elem)
@@ -210,16 +210,14 @@ class StandardResolver(Resolver_):
210
210
  yield node
211
211
  obj = node[0]
212
212
 
213
- if type(obj) is dict:
213
+ if isinstance(obj, dict):
214
214
  for k, v in obj.items():
215
- vt = type(v)
216
- if isinstance(k, str) and (vt is list or vt is dict):
215
+ if isinstance(k, str) and isinstance(v, (dict, list)):
217
216
  yield from visit_(self.new_child(node, k, v), depth + 1)
218
217
 
219
- elif type(obj) is list:
218
+ elif isinstance(obj, list):
220
219
  for i, elem in enumerate(obj):
221
- et = type(elem)
222
- if et is list or et is dict:
220
+ if isinstance(elem, (dict, list)):
223
221
  yield from visit_(self.new_child(node, i, elem), depth + 1)
224
222
 
225
223
  return visit_(node, 1)
@@ -105,6 +105,9 @@ class Resolver_:
105
105
  else:
106
106
  return False
107
107
 
108
+ if isinstance(left, bool) or isinstance(right, bool):
109
+ return False
110
+
108
111
  if isinstance(left, str) and isinstance(right, str):
109
112
  return left < right
110
113
 
@@ -64,37 +64,37 @@ class BasicResolver(Resolver_):
64
64
  def _resolve_selector(self, selector: Selector, obj: object) -> Iterable[object]:
65
65
  match selector:
66
66
  case (_ast.NAME_SELECTOR, _, name):
67
- if type(obj) is dict and name in obj:
67
+ if isinstance(obj, dict) and name in obj:
68
68
  yield obj[name]
69
69
 
70
70
  case (_ast.INDEX_SELECTOR, _, index):
71
- if type(obj) is list and len(obj) >= abs(index) + int(index >= 0):
71
+ if isinstance(obj, list) and len(obj) >= abs(index) + int(index >= 0):
72
72
  if index < 0 and len(obj) >= abs(index):
73
73
  index = len(obj) + index
74
74
 
75
75
  yield obj[index]
76
76
 
77
77
  case (_ast.SLICE_SELECTOR, _, slice_):
78
- if type(obj) is list and slice_.step != 0:
78
+ if isinstance(obj, list) and slice_.step != 0:
79
79
  for i, elem in zip(range(*slice_.indices(len(obj))), obj[slice_]):
80
80
  yield elem
81
81
 
82
82
  case (_ast.WILDCARD_SELECTOR, _):
83
- if type(obj) is dict:
83
+ if isinstance(obj, dict):
84
84
  for k, v in obj.items():
85
85
  yield v
86
86
 
87
- elif type(obj) is list:
87
+ elif isinstance(obj, list):
88
88
  for i, elem in enumerate(obj):
89
89
  yield elem
90
90
 
91
91
  case (_ast.FILTER_SELECTOR, _, expr):
92
- if type(obj) is dict:
92
+ if isinstance(obj, dict):
93
93
  for k, v in obj.items():
94
94
  if self.truthy(self._evaluate_expression(expr, k, v)):
95
95
  yield v
96
96
 
97
- elif type(obj) is list:
97
+ elif isinstance(obj, list):
98
98
  for i, elem in enumerate(obj):
99
99
  if self.truthy(self._evaluate_expression(expr, i, elem)):
100
100
  yield elem
@@ -210,16 +210,14 @@ class BasicResolver(Resolver_):
210
210
 
211
211
  yield obj
212
212
 
213
- if type(obj) is dict:
213
+ if isinstance(obj, dict):
214
214
  for k, v in obj.items():
215
- vt = type(v)
216
- if isinstance(k, str) and (vt is list or vt is dict):
215
+ if isinstance(k, str) and isinstance(v, (list, dict)):
217
216
  yield from visit_(v, depth + 1)
218
217
 
219
- elif type(obj) is list:
218
+ elif isinstance(obj, list):
220
219
  for elem in obj:
221
- et = type(elem)
222
- if et is list or et is dict:
220
+ if isinstance(elem, (dict, list)):
223
221
  yield from visit_(elem, depth + 1)
224
222
 
225
223
  return visit_(node, 1)
@@ -0,0 +1,86 @@
1
+ import pytest
2
+
3
+ import jsonpath_rfc9535 as jsonpath
4
+
5
+
6
+ def test_issue_13() -> None:
7
+ # This was failing with "unbalanced parentheses".
8
+ _q = jsonpath.compile("$[? count(@.likes[? @.location]) > 3]")
9
+
10
+
11
+ def test_issue_21() -> None:
12
+ data = {"foo": {"bar": {"baz": 42}}}
13
+ node = jsonpath.find_one("$.foo.bar.baz", data)
14
+
15
+ expected = 42
16
+ assert node is not None
17
+ assert node.value == expected
18
+ assert data["foo"]["bar"]["baz"] == expected
19
+
20
+ new_value = 99
21
+ node.value = new_value
22
+ assert node.value == new_value
23
+ assert data["foo"]["bar"]["baz"] == new_value
24
+
25
+ parent = node.parent()
26
+ assert parent is not None
27
+ assert parent.value == {"baz": new_value}
28
+ assert parent.value["baz"] == new_value # type: ignore
29
+
30
+
31
+ def test_issue_24() -> None:
32
+ data = {"\u001f": 1, "é": 2}
33
+
34
+ assert jsonpath.findall("$['\\u001f']", data) == [1]
35
+ assert jsonpath.findall("$['\\u00e9']", data) == [2]
36
+ assert jsonpath.findall("$['\\u00E9']", data) == [2]
37
+
38
+
39
+ def test_issue_25() -> None:
40
+ with pytest.raises(jsonpath.JSONPathSyntaxError):
41
+ assert jsonpath.compile("$.a-b")
42
+
43
+ assert jsonpath.findall("$.😀", {"😀": 1}) == [1]
44
+ assert jsonpath.findall("$['😀']", {"😀": 1}) == [1]
45
+
46
+
47
+ def test_issue_26() -> None:
48
+ with pytest.raises(jsonpath.JSONPathSyntaxError):
49
+ jsonpath.compile("$[1,]")
50
+
51
+ with pytest.raises(jsonpath.JSONPathSyntaxError):
52
+ jsonpath.compile("$[1, ]")
53
+
54
+ with pytest.raises(jsonpath.JSONPathSyntaxError):
55
+ jsonpath.compile("$[ 1, ]")
56
+
57
+
58
+ def test_issue_27() -> None:
59
+ assert jsonpath.findall("$[?@ > false]", [0, 1, True, False]) == []
60
+ assert jsonpath.findall("$[?@ < true]", [0, 1, True]) == []
61
+ assert jsonpath.findall("$[?@ >= 0]", [True, False, 0]) == [0]
62
+
63
+
64
+ def test_issue_32() -> None:
65
+ class MyDict(dict[str, object]):
66
+ pass
67
+
68
+ class MyList(list[object]):
69
+ pass
70
+
71
+ data = MyDict(
72
+ {
73
+ "users": MyList(
74
+ [
75
+ {"id": "usr_101", "name": "Alice", "status": "pending"},
76
+ {"id": "usr_102", "name": "Bob", "status": "active"},
77
+ {"id": "usr_103", "name": "Charlie", "status": "pending"},
78
+ ]
79
+ ),
80
+ }
81
+ )
82
+
83
+ expr = "$.users[?@.status == 'pending'].name"
84
+
85
+ assert jsonpath.findall(expr, data) == ["Alice", "Charlie"]
86
+ assert jsonpath.find(expr, data).values() == ["Alice", "Charlie"]
@@ -1 +0,0 @@
1
- __version__ = "2.0.0"
@@ -1,26 +0,0 @@
1
- import jsonpath_rfc9535 as jsonpath
2
-
3
-
4
- def test_issue_13() -> None:
5
- # This was failing with "unbalanced parentheses".
6
- _q = jsonpath.compile("$[? count(@.likes[? @.location]) > 3]")
7
-
8
-
9
- def test_issue_21() -> None:
10
- data = {"foo": {"bar": {"baz": 42}}}
11
- node = jsonpath.find_one("$.foo.bar.baz", data)
12
-
13
- expected = 42
14
- assert node is not None
15
- assert node.value == expected
16
- assert data["foo"]["bar"]["baz"] == expected
17
-
18
- new_value = 99
19
- node.value = new_value
20
- assert node.value == new_value
21
- assert data["foo"]["bar"]["baz"] == new_value
22
-
23
- parent = node.parent()
24
- assert parent is not None
25
- assert parent.value == {"baz": new_value}
26
- assert parent.value["baz"] == new_value # type: ignore