Skip to content

Commit a0b3d7e

Browse files
committed
Consistent naming with RFC 9535
1 parent 5c56575 commit a0b3d7e

32 files changed

+471
-527
lines changed

README.md

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,16 @@ TODO:
2626

2727
## Usage
2828

29-
NOTE: If you're coming from [Python JSONPath](https://github.com/jg-rp/python-jsonpath), this API is similar but different. Neither implementation is guaranteed to be a drop-in replacement for the other.
29+
### `find(query, value)`
3030

31-
### `query(path, data)`
31+
Apply JSONPath expression _query_ to _value_. _value_ should arbitrary, possible nested, Python dictionaries, lists, strings, integers, floats, Booleans or `None`, as you would get from [`json.load()`](https://docs.python.org/3/library/json.html#json.load).
3232

33-
Apply JSONPath expression _path_ to _data_. _data_ should arbitrary, possible nested, Python dictionaries, lists, strings, integers, floats, booleans or `None`, as you would get from [`json.load()`](https://docs.python.org/3/library/json.html#json.load).
34-
35-
A list of `JSONPathNode` instances is returned, one node for each value in _data_ matched by _path_. The returned list will be empty if there were no matches.
33+
A list of `JSONPathNode` instances is returned, one node for each value matched by _path_. The returned list will be empty if there were no matches.
3634

3735
Each `JSONPathNode` has:
3836

3937
- a `value` property, which is the JSON-like value associated with the node.
40-
- a `parts` property, which is a tuple of property names and array/list indices that were required to reach the node's value in the target JSON document.
38+
- a `location` property, which is a tuple of property names and array/list indexes that were required to reach the node's value in the target JSON document.
4139
- a `path()` method, which returns the normalized path to the node in the target JSON document.
4240

4341
The returned list is a subclass of `list` with some helper methods.
@@ -48,9 +46,9 @@ The returned list is a subclass of `list` with some helper methods.
4846
**Example:**
4947

5048
```python
51-
from jsonpath_rfc9535 import query
49+
from jsonpath_rfc9535 import find
5250

53-
data = {
51+
value = {
5452
"users": [
5553
{"name": "Sue", "score": 100},
5654
{"name": "John", "score": 86, "admin": True},
@@ -60,22 +58,16 @@ data = {
6058
"moderator": "John",
6159
}
6260

63-
for node in query("$.users[?@.score > 85]", data):
61+
for node in find("$.users[?@.score > 85]", value):
6462
print(f"{node.value} at '{node.path()}'")
6563

6664
# {'name': 'Sue', 'score': 100} at '$['users'][0]'
6765
# {'name': 'John', 'score': 86, 'admin': True} at '$['users'][1]'
6866
```
6967

70-
### findall(path, data)
71-
72-
`findall()` accepts the same arguments as [`query()`](#querypath-data), but returns a list of value rather than a list of nodes.
73-
74-
`findall()` is equivalent to `query("$.some.thing", data).values()`.
75-
76-
### finditer(path, data)
68+
### finditer(query, value)
7769

78-
`finditer()` accepts the same arguments as [`query()`](#querypath-data), but returns an iterator over `JSONPathNode` instances rather than a list. This could be useful if you're expecting a large number of results that you don't want to load into memory all at once.
70+
`finditer()` accepts the same arguments as [`find()`](#findquery-value), but returns an iterator over `JSONPathNode` instances rather than a list. This could be useful if you're expecting a large number of results that you don't want to load into memory all at once.
7971

8072
## License
8173

jsonpath_rfc9535/__init__.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
from .environment import JSONLikeData
21
from .environment import JSONPathEnvironment
2+
from .environment import JSONValue
33
from .exceptions import JSONPathError
44
from .exceptions import JSONPathIndexError
55
from .exceptions import JSONPathNameError
66
from .exceptions import JSONPathRecursionError
77
from .exceptions import JSONPathSyntaxError
88
from .exceptions import JSONPathTypeError
9-
from .expressions import NOTHING
9+
from .filter_expressions import NOTHING
1010
from .lex import Lexer
1111
from .node import JSONPathNode
1212
from .node import JSONPathNodeList
1313
from .parse import Parser
14-
from .path import JSONPath
14+
from .query import JSONPathQuery
1515

1616
__all__ = (
17-
"JSONLikeData",
17+
"JSONValue",
1818
"JSONPathEnvironment",
1919
"JSONPathError",
2020
"JSONPathIndexError",
@@ -27,12 +27,14 @@
2727
"JSONPathNode",
2828
"JSONPathNodeList",
2929
"Parser",
30-
"JSONPath",
30+
"JSONPathQuery",
31+
"find",
32+
"finditer",
33+
"compile",
3134
)
3235

3336
# For convenience
3437
DEFAULT_ENV = JSONPathEnvironment()
3538
compile = DEFAULT_ENV.compile # noqa: A001
36-
findall = DEFAULT_ENV.findall
3739
finditer = DEFAULT_ENV.finditer
38-
query = DEFAULT_ENV.query
40+
find = DEFAULT_ENV.find

jsonpath_rfc9535/cli.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""JSONPath, JSON Pointer and JSON Patch command line interface."""
1+
"""JSONPath command line interface."""
22

33
import argparse
44
import json
@@ -14,8 +14,8 @@
1414

1515
_EPILOG = """\
1616
Example usage:
17-
Find values in source.json matching a JSONPath query, write them to result.json.
18-
$ jsonpath-rfc9535 path -q "$.foo['bar'][?@.baz > 1]" -f source.json -o result.json
17+
Find values in source.json matching a JSONPath expression, output to result.json.
18+
$ jsonpath-rfc9535 -q "$.foo['bar'][?@.baz > 1]" -f source.json -o result.json
1919
"""
2020

2121

@@ -31,8 +31,7 @@ def setup_parser() -> argparse.ArgumentParser: # noqa: D103
3131
prog="jsonpath-rfc9535",
3232
formatter_class=DescriptionHelpFormatter,
3333
description=(
34-
"Find values in a JSON document given an "
35-
"RFC 9535 JSONPath query expression."
34+
"Find values in a JSON document given an RFC 9535 JSONPath expression."
3635
),
3736
epilog=_EPILOG,
3837
)
@@ -63,14 +62,14 @@ def setup_parser() -> argparse.ArgumentParser: # noqa: D103
6362
group.add_argument(
6463
"-q",
6564
"--query",
66-
help="JSONPath query string.",
65+
help="JSONPath expression as a string.",
6766
)
6867

6968
group.add_argument(
7069
"-r",
71-
"--path-file",
70+
"--query-file",
7271
type=argparse.FileType(mode="r"),
73-
help="Text file containing a JSONPath query.",
72+
help="Text file containing a JSONPath expression.",
7473
)
7574

7675
parser.add_argument(
@@ -98,9 +97,7 @@ def setup_parser() -> argparse.ArgumentParser: # noqa: D103
9897
return parser
9998

10099

101-
def handle_path_command(args: argparse.Namespace) -> None: # noqa: PLR0912
102-
"""Handle the `path` sub command."""
103-
# Empty string is OK.
100+
def handle_path_command(args: argparse.Namespace) -> None: # noqa: PLR0912, D103
104101
if args.query is not None:
105102
query = args.query
106103
else:
@@ -111,22 +108,22 @@ def handle_path_command(args: argparse.Namespace) -> None: # noqa: PLR0912
111108
except JSONPathSyntaxError as err:
112109
if args.debug:
113110
raise
114-
sys.stderr.write(f"json path syntax error: {err}\n")
111+
sys.stderr.write(f"syntax error: {err}\n")
115112
sys.exit(1)
116113
except JSONPathTypeError as err:
117114
if args.debug:
118115
raise
119-
sys.stderr.write(f"json path type error: {err}\n")
116+
sys.stderr.write(f"type error: {err}\n")
120117
sys.exit(1)
121118
except JSONPathIndexError as err:
122119
if args.debug:
123120
raise
124-
sys.stderr.write(f"json path index error: {err}\n")
121+
sys.stderr.write(f"index error: {err}\n")
125122
sys.exit(1)
126123

127124
try:
128125
data = json.load(args.file)
129-
matches = path.findall(data)
126+
values = path.find(data).values()
130127
except json.JSONDecodeError as err:
131128
if args.debug:
132129
raise
@@ -136,11 +133,11 @@ def handle_path_command(args: argparse.Namespace) -> None: # noqa: PLR0912
136133
# Type errors are currently only occurring are compile-time.
137134
if args.debug:
138135
raise
139-
sys.stderr.write(f"json path type error: {err}\n")
136+
sys.stderr.write(f"type error: {err}\n")
140137
sys.exit(1)
141138

142139
indent = INDENT if args.pretty else None
143-
json.dump(matches, args.output, indent=indent)
140+
json.dump(values, args.output, indent=indent)
144141

145142

146143
def main() -> None:

jsonpath_rfc9535/environment.py

Lines changed: 34 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,26 @@
1818
from . import function_extensions
1919
from .exceptions import JSONPathNameError
2020
from .exceptions import JSONPathTypeError
21-
from .expressions import ComparisonExpression
22-
from .expressions import FunctionExtension
23-
from .expressions import JSONPathLiteral
24-
from .expressions import LogicalExpression
25-
from .expressions import Path
21+
from .filter_expressions import ComparisonExpression
22+
from .filter_expressions import FilterQuery
23+
from .filter_expressions import FunctionExtension
24+
from .filter_expressions import JSONPathLiteral
25+
from .filter_expressions import LogicalExpression
2626
from .function_extensions import ExpressionType
2727
from .function_extensions import FilterFunction
2828
from .lex import tokenize
2929
from .parse import Parser
30-
from .path import JSONPath
30+
from .query import JSONPathQuery
3131
from .tokens import TokenStream
3232

3333
if TYPE_CHECKING:
34-
from .expressions import Expression
34+
from .filter_expressions import Expression
3535
from .node import JSONPathNode
3636
from .node import JSONPathNodeList
3737
from .tokens import Token
3838

3939

40-
JSONLikeData = Union[
40+
JSONValue = Union[
4141
List[Any],
4242
Dict[str, Any],
4343
str,
@@ -84,86 +84,65 @@ def __init__(self) -> None:
8484

8585
self.setup_function_extensions()
8686

87-
def compile(self, path: str) -> JSONPath: # noqa: A003
88-
"""Prepare a path string ready for repeated matching against different data.
87+
def compile(self, query: str) -> JSONPathQuery: # noqa: A003
88+
"""Prepare a JSONPath expression ready for repeated application.
8989
9090
Arguments:
91-
path: A JSONPath query string.
91+
query: A JSONPath expression.
9292
9393
Returns:
94-
A `JSONPath` ready to match against some data.
94+
A `JSONPathQuery` ready to match against a JSON-like value.
9595
9696
Raises:
97-
JSONPathSyntaxError: If _path_ is invalid.
97+
JSONPathSyntaxError: If _query_ is invalid.
9898
JSONPathTypeError: If filter functions are given arguments of an
9999
unacceptable type.
100100
"""
101-
tokens = tokenize(path)
101+
tokens = tokenize(query)
102102
stream = TokenStream(tokens)
103-
return JSONPath(env=self, segments=tuple(self.parser.parse(stream)))
103+
return JSONPathQuery(env=self, segments=tuple(self.parser.parse(stream)))
104104

105105
def finditer(
106106
self,
107-
path: str,
108-
data: JSONLikeData,
107+
query: str,
108+
value: JSONValue,
109109
) -> Iterable[JSONPathNode]:
110-
"""Generate `JSONPathNode` instances for each match of _path_ in _data_.
110+
"""Generate `JSONPathNode` instances for each match of _query_ in _value_.
111111
112112
Arguments:
113-
path: A JSONPath query string.
114-
data: JSON-like data to query, as you'd get from `json.load`.
113+
query: A JSONPath expression.
114+
value: JSON-like data to query, as you'd get from `json.load`.
115115
116116
Returns:
117117
An iterator yielding `JSONPathNode` objects for each match.
118118
119119
Raises:
120-
JSONPathSyntaxError: If the path is invalid.
120+
JSONPathSyntaxError: If the query is invalid.
121121
JSONPathTypeError: If a filter expression attempts to use types in
122122
an incompatible way.
123123
"""
124-
return self.compile(path).finditer(data)
124+
return self.compile(query).finditer(value)
125125

126-
def findall(
126+
def find(
127127
self,
128-
path: str,
129-
data: JSONLikeData,
130-
) -> List[object]:
131-
"""Find all values in _data_ matching JSONPath query _path_.
132-
133-
Arguments:
134-
path: A JSONPath query string.
135-
data: JSON-like data to query, as you'd get from `json.load`.
136-
137-
Returns:
138-
A list of matched values. If there are no matches, the list will be empty.
139-
140-
Raises:
141-
JSONPathSyntaxError: If the path is invalid.
142-
JSONPathTypeError: If a filter expression attempts to use types in
143-
an incompatible way.
144-
"""
145-
return self.compile(path).findall(data)
146-
147-
def query(
148-
self,
149-
path: str,
150-
data: JSONLikeData,
128+
query: str,
129+
value: JSONValue,
151130
) -> JSONPathNodeList:
152-
"""Apply the JSONPath query _path_ to JSON-like _data_.
131+
"""Apply the JSONPath expression _query_ to JSON-like data _value_.
153132
154133
Arguments:
155-
path: A JSONPath query string.
156-
data: JSON-like data to query, as you'd get from `json.load`.
134+
query: A JSONPath expression.
135+
value: JSON-like data to query, as you'd get from `json.load`.
157136
158137
Returns:
159138
A list of `JSONPathNode` instance.
160139
161140
Raises:
162-
JSONPathSyntaxError: If the path is invalid.
141+
JSONPathSyntaxError: If the query is invalid.
163142
JSONPathTypeError: If a filter expression attempts to use types in
164143
an incompatible way.
165144
"""
166-
return self.compile(path).query(data)
145+
return self.compile(query).find(value)
167146

168147
def setup_function_extensions(self) -> None:
169148
"""Initialize function extensions."""
@@ -176,11 +155,7 @@ def setup_function_extensions(self) -> None:
176155
def validate_function_extension_signature(
177156
self, token: Token, args: List[Any]
178157
) -> List[Any]:
179-
"""Compile-time validation of function extension arguments.
180-
181-
RFC 9535 requires us to reject paths that use filter functions with
182-
too many or too few arguments.
183-
"""
158+
"""Compile-time validation of function extension arguments."""
184159
try:
185160
func = self.function_extensions[token.value]
186161
except KeyError as err:
@@ -211,7 +186,7 @@ def check_well_typedness(
211186
if typ == ExpressionType.VALUE:
212187
if not (
213188
isinstance(arg, JSONPathLiteral)
214-
or (isinstance(arg, Path) and arg.path.singular_query())
189+
or (isinstance(arg, FilterQuery) and arg.query.singular_query())
215190
or (self._function_return_type(arg) == ExpressionType.VALUE)
216191
):
217192
raise JSONPathTypeError(
@@ -220,14 +195,14 @@ def check_well_typedness(
220195
)
221196
elif typ == ExpressionType.LOGICAL:
222197
if not isinstance(
223-
arg, (Path, (LogicalExpression, ComparisonExpression))
198+
arg, (FilterQuery, (LogicalExpression, ComparisonExpression))
224199
):
225200
raise JSONPathTypeError(
226201
f"{token.value}() argument {idx} must be of LogicalType",
227202
token=token,
228203
)
229204
elif typ == ExpressionType.NODES and not (
230-
isinstance(arg, Path)
205+
isinstance(arg, FilterQuery)
231206
or self._function_return_type(arg) == ExpressionType.NODES
232207
):
233208
raise JSONPathTypeError(

jsonpath_rfc9535/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __str__(self) -> str:
3232

3333

3434
class JSONPathSyntaxError(JSONPathError):
35-
"""An exception raised when a error occurs during JSONPath query parsing.
35+
"""An exception raised when a error occurs during JSONPath expression parsing.
3636
3737
Arguments:
3838
args: Arguments passed to `Exception`.

0 commit comments

Comments
 (0)