-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathquery.py
More file actions
141 lines (109 loc) · 4.07 KB
/
query.py
File metadata and controls
141 lines (109 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
"""A compiled JSONPath expression ready to be applied to JSON-like data."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Iterable
from typing import Optional
from typing import Tuple
from .node import JSONPathNode
from .node import JSONPathNodeList
from .segments import JSONPathRecursiveDescentSegment
from .selectors import IndexSelector
from .selectors import NameSelector
if TYPE_CHECKING:
from .environment import JSONPathEnvironment
from .environment import JSONValue
from .segments import JSONPathSegment
class JSONPathQuery:
"""A compiled JSONPath expression ready to be applied to JSON-like data.
Arguments:
env: The `JSONPathEnvironment` this query is bound to.
segments: An iterable of `JSONPathSelector` objects, as generated by
a `Parser`.
Attributes:
env: The `JSONPathEnvironment` this query is bound to.
segments: The `JSONPathSegment` instances that make up this query.
"""
__slots__ = ("env", "segments")
def __init__(
self,
*,
env: JSONPathEnvironment,
segments: Tuple[JSONPathSegment, ...],
) -> None:
self.env = env
self.segments = segments
def __str__(self) -> str:
return "$" + "".join(str(segment) for segment in self.segments)
def __hash__(self) -> int:
return hash(self.segments)
def finditer(
self,
value: JSONValue,
) -> Iterable[JSONPathNode]:
"""Generate `JSONPathNode` instances for each match of this query in value.
Arguments:
value: JSON-like data to query, as you'd get from `json.load`.
Returns:
An iterator yielding `JSONPathNode` objects for each match.
Raises:
JSONPathSyntaxError: If the query is invalid.
JSONPathTypeError: If a filter expression attempts to use types in
an incompatible way.
"""
nodes: Iterable[JSONPathNode] = [
JSONPathNode(
value=value,
location=(),
parent=None,
root=value,
)
]
for segment in self.segments:
nodes = segment.resolve(nodes)
return nodes
def find(
self,
value: JSONValue,
) -> JSONPathNodeList:
"""Apply this JSONPath expression to JSON-like _value_ and return a node list.
Arguments:
value: JSON-like data to query, as you'd get from `json.load`.
Returns:
A list of `JSONPathNode` instance.
Raises:
JSONPathSyntaxError: If the query is invalid.
JSONPathTypeError: If a filter expression attempts to use types in
an incompatible way.
"""
return JSONPathNodeList(self.finditer(value))
apply = find
def find_one(self, value: JSONValue) -> Optional[JSONPathNode]:
"""Return the first node from applying this query to _value_.
Arguments:
value: JSON-like data to query, as you'd get from `json.load`.
Returns:
The first available `JSONPathNode` instance, or `None` if there
are no matches.
Raises:
JSONPathSyntaxError: If the query is invalid.
JSONPathTypeError: If a filter expression attempts to use types in
an incompatible way.
"""
try:
return next(iter(self.finditer(value)))
except StopIteration:
return None
def singular_query(self) -> bool:
"""Return `True` if this JSONPath expression is a singular query."""
for segment in self.segments:
if isinstance(segment, JSONPathRecursiveDescentSegment):
return False
if len(segment.selectors) == 1 and isinstance(
segment.selectors[0], (NameSelector, IndexSelector)
):
continue
return False
return True
def empty(self) -> bool:
"""Return `True` if this query has no segments."""
return not bool(self.segments)