-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnode.py
More file actions
112 lines (89 loc) · 3.2 KB
/
node.py
File metadata and controls
112 lines (89 loc) · 3.2 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
"""JSONPath node and node list definitions."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
from .serialize import canonical_string
if TYPE_CHECKING:
from .environment import JSONValue
class JSONPathNode:
"""A JSON-like value and its location in a JSON document.
Assigning to `JSONPathNode.value` will update and mutate source data too.
Updating data after evaluating a query can invalidate existing child
nodes. Use at your own risk.
Attributes:
value: The JSON-like value at this node.
location: The names indices that make up the normalized path to _value_.
parent: The parent node, or None if this is the root node.
"""
__slots__ = (
"_value",
"location",
"parent",
"root",
)
def __init__(
self,
*,
value: object,
location: Tuple[Union[int, str], ...],
parent: Optional[JSONPathNode],
root: JSONValue,
) -> None:
self._value: object = value
self.location: Tuple[Union[int, str], ...] = location
self.parent = parent
self.root = root
@property
def value(self) -> object:
"""The JSON-like value at this node."""
return self._value
@value.setter
def value(self, val: object) -> None:
parent = self.parent
if parent is not None and self.location:
# If data has changed since this node was created, this could fail.
# Letting the exception raise is probably the most useful thing we can do.
parent._value[self.location[-1]] = val # type: ignore # noqa: SLF001
self._value = val
def path(self) -> str:
"""Return the normalized path to this node."""
return "$" + "".join(
f"[{canonical_string(p)}]" if isinstance(p, str) else f"[{p}]"
for p in self.location
)
def new_child(
self,
value: object,
key: Union[int, str],
parent: Optional[JSONPathNode],
) -> JSONPathNode:
"""Return a new node using this node's location."""
return JSONPathNode(
value=value,
location=self.location + (key,),
parent=parent,
root=self.root,
)
def __str__(self) -> str:
return f"JSONPathNode({self.path()!r})"
class JSONPathNodeList(List[JSONPathNode]):
"""A list JSONPathNode instances.
This is a `list` subclass with some helper methods.
"""
def values(self) -> List[object]:
"""Return the values from this node list."""
return [node.value for node in self]
def paths(self) -> List[str]:
"""Return normalized paths from this node list."""
return [node.path() for node in self]
def items(self) -> List[Tuple[str, object]]:
"""Return a list of (path, value) pairs, one for each node in the list."""
return [(node.path(), node.value) for node in self]
def empty(self) -> bool:
"""Return `True` if this node list is empty."""
return not self
def __str__(self) -> str:
return f"NodeList{super().__str__()}"