Skip to content

Commit e96b56d

Browse files
committed
feat: __eq__ and __hash__ for paths and selectors
1 parent ebbb995 commit e96b56d

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

jsonpath/path.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ def __str__(self) -> str:
5353
str(selector) for selector in self.selectors
5454
)
5555

56+
def __eq__(self, __value: object) -> bool:
57+
return isinstance(__value, JSONPath) and self.selectors == __value.selectors
58+
59+
def __hash__(self) -> int:
60+
return hash(self.selectors)
61+
5662
def findall(
5763
self,
5864
data: Union[str, IOBase, Sequence[Any], Mapping[str, Any]],
@@ -236,6 +242,16 @@ def __str__(self) -> str:
236242
buf.append(str(path))
237243
return "".join(buf)
238244

245+
def __eq__(self, __value: object) -> bool:
246+
return (
247+
isinstance(__value, CompoundJSONPath)
248+
and self.path == __value.path
249+
and self.paths == __value.paths
250+
)
251+
252+
def __hash__(self) -> int:
253+
return hash((self.path, self.paths))
254+
239255
def findall(
240256
self,
241257
data: Union[str, IOBase, Sequence[Any], Mapping[str, Any]],

jsonpath/selectors.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ def __init__(
6565
def __str__(self) -> str:
6666
return f"['{self.name}']"
6767

68+
def __eq__(self, __value: object) -> bool:
69+
return (
70+
isinstance(__value, PropertySelector)
71+
and self.name == __value.name
72+
and self.token == __value.token
73+
)
74+
75+
def __hash__(self) -> int:
76+
return hash((self.name, self.token))
77+
6878
def resolve(self, matches: Iterable[JSONPathMatch]) -> Iterable[JSONPathMatch]:
6979
for match in matches:
7080
if not isinstance(match.obj, Mapping):
@@ -124,6 +134,16 @@ def __init__(
124134
def __str__(self) -> str:
125135
return f"[{self.index}]"
126136

137+
def __eq__(self, __value: object) -> bool:
138+
return (
139+
isinstance(__value, IndexSelector)
140+
and self.index == __value.index
141+
and self.token == __value.token
142+
)
143+
144+
def __hash__(self) -> int:
145+
return hash((self.index, self.token))
146+
127147
def _normalized_index(self, obj: Sequence[object]) -> int:
128148
if self.index < 0 and len(obj) >= abs(self.index):
129149
return len(obj) + self.index
@@ -198,6 +218,12 @@ class KeysSelector(JSONPathSelector):
198218
def __str__(self) -> str:
199219
return f"[{self.env.keys_selector_token}]"
200220

221+
def __eq__(self, __value: object) -> bool:
222+
return isinstance(__value, KeysSelector) and self.token == __value.token
223+
224+
def __hash__(self) -> int:
225+
return hash(self.token)
226+
201227
def _keys(self, match: JSONPathMatch) -> Iterable[JSONPathMatch]:
202228
if isinstance(match.obj, Mapping):
203229
for i, key in enumerate(match.obj.keys()):
@@ -248,6 +274,16 @@ def __str__(self) -> str:
248274
step = self.slice.step if self.slice.step is not None else "1"
249275
return f"[{start}:{stop}:{step}]"
250276

277+
def __eq__(self, __value: object) -> bool:
278+
return (
279+
isinstance(__value, SliceSelector)
280+
and self.slice == __value.slice
281+
and self.token == __value.token
282+
)
283+
284+
def __hash__(self) -> int:
285+
return hash((str(self), self.token))
286+
251287
def _check_range(self, *indices: Optional[int]) -> None:
252288
for i in indices:
253289
if i is not None and (
@@ -311,6 +347,12 @@ class WildSelector(JSONPathSelector):
311347
def __str__(self) -> str:
312348
return "[*]"
313349

350+
def __eq__(self, __value: object) -> bool:
351+
return isinstance(__value, WildSelector) and self.token == __value.token
352+
353+
def __hash__(self) -> int:
354+
return hash(self.token)
355+
314356
def resolve(self, matches: Iterable[JSONPathMatch]) -> Iterable[JSONPathMatch]:
315357
for match in matches:
316358
if isinstance(match.obj, str):
@@ -376,6 +418,15 @@ class RecursiveDescentSelector(JSONPathSelector):
376418
def __str__(self) -> str:
377419
return ".."
378420

421+
def __eq__(self, __value: object) -> bool:
422+
return (
423+
isinstance(__value, RecursiveDescentSelector)
424+
and self.token == __value.token
425+
)
426+
427+
def __hash__(self) -> int:
428+
return hash(self.token)
429+
379430
def _expand(self, match: JSONPathMatch) -> Iterable[JSONPathMatch]:
380431
if isinstance(match.obj, Mapping):
381432
for key, val in match.obj.items():
@@ -453,7 +504,7 @@ def __init__(
453504
],
454505
) -> None:
455506
super().__init__(env=env, token=token)
456-
self.items = items
507+
self.items = tuple(items)
457508

458509
def __str__(self) -> str:
459510
buf: List[str] = []
@@ -473,6 +524,16 @@ def __str__(self) -> str:
473524
buf.append(str(item.index))
474525
return f"[{', '.join(buf)}]"
475526

527+
def __eq__(self, __value: object) -> bool:
528+
return (
529+
isinstance(__value, ListSelector)
530+
and self.items == __value.items
531+
and self.token == __value.token
532+
)
533+
534+
def __hash__(self) -> int:
535+
return hash((self.items, self.token))
536+
476537
def resolve(self, matches: Iterable[JSONPathMatch]) -> Iterable[JSONPathMatch]:
477538
_matches = list(matches)
478539
for item in self.items:
@@ -507,6 +568,16 @@ def __init__(
507568
def __str__(self) -> str:
508569
return f"[?({self.expression})]"
509570

571+
def __eq__(self, __value: object) -> bool:
572+
return (
573+
isinstance(__value, Filter)
574+
and self.expression == __value.expression
575+
and self.token == __value.token
576+
)
577+
578+
def __hash__(self) -> int:
579+
return hash((self.expression, self.token))
580+
510581
def resolve( # noqa: PLR0912
511582
self, matches: Iterable[JSONPathMatch]
512583
) -> Iterable[JSONPathMatch]:

jsonpath/token.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ def __eq__(self, other: object) -> bool:
118118
and self.path == other.path
119119
)
120120

121+
def __hash__(self) -> int:
122+
return hash((self.kind, self.value, self.index, self.path))
123+
121124
def position(self) -> Tuple[int, int]:
122125
"""Return the line and column number for the start of this token."""
123126
line_number = self.value.count("\n", 0, self.index) + 1

0 commit comments

Comments
 (0)