-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathids.py
226 lines (171 loc) · 8 KB
/
ids.py
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
from __future__ import annotations
from abc import ABC
from collections.abc import Sequence
from dataclasses import asdict, dataclass, field
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Protocol, TypeVar, cast
from typing_extensions import Self
from cognite.client.data_classes._base import CogniteObject
from cognite.client.utils._identifier import (
DataModelingIdentifier,
DataModelingIdentifierSequence,
InstanceId,
)
from cognite.client.utils._text import convert_all_keys_recursive
from cognite.client.utils.useful_types import SequenceNotStr
if TYPE_CHECKING:
from cognite.client import CogniteClient
@dataclass(frozen=True)
class AbstractDataclass(ABC):
def __new__(cls, *args: Any, **kwargs: Any) -> Any:
if cls is AbstractDataclass or cls.__bases__[0] is AbstractDataclass:
raise TypeError("Cannot instantiate abstract class.")
return super().__new__(cls)
@dataclass(frozen=True)
class DataModelingId(AbstractDataclass):
_type: ClassVar[str] = field(init=False)
space: str
external_id: str
def as_tuple(self) -> tuple[str, str]:
return self.space, self.external_id
def dump(self, camel_case: bool = True, include_type: bool = True) -> dict[str, str]:
output = asdict(self)
if include_type:
output["type"] = self._type
return convert_all_keys_recursive(output, camel_case)
@classmethod
def load(cls: type[T_DataModelingId], data: dict | T_DataModelingId | tuple[str, str]) -> T_DataModelingId:
if isinstance(data, cls):
return data
elif isinstance(data, tuple):
return cls(space=data[0], external_id=data[1])
elif isinstance(data, dict):
return cls(space=data["space"], external_id=data["externalId"])
raise TypeError(f"Cannot load {data} into {cls}, invalid type={type(data)}")
T_DataModelingId = TypeVar("T_DataModelingId", bound=DataModelingId)
@dataclass(frozen=True)
class VersionedDataModelingId(AbstractDataclass):
_type: ClassVar[str] = field(init=False)
space: str
external_id: str
version: str | None = None
def as_tuple(self) -> tuple[str, str, str | None]:
return self.space, self.external_id, self.version
def dump(self, camel_case: bool = True, include_type: bool = True) -> dict[str, str]:
output = asdict(self)
if include_type:
output["type"] = self._type
return convert_all_keys_recursive(output, camel_case)
@classmethod
def load(
cls: type[T_Versioned_DataModeling_Id],
data: dict | T_Versioned_DataModeling_Id | tuple[str, str] | tuple[str, str, str],
) -> T_Versioned_DataModeling_Id:
if isinstance(data, cls):
return data
elif isinstance(data, tuple):
return cls(space=data[0], external_id=data[1], version=data[2] if len(data) == 3 else None)
elif isinstance(data, dict):
return cls(space=data["space"], external_id=data["externalId"], version=data.get("version"))
raise TypeError(f"Cannot load {data} into {cls}, invalid type={type(data)}")
T_Versioned_DataModeling_Id = TypeVar("T_Versioned_DataModeling_Id", bound=VersionedDataModelingId)
@dataclass(frozen=True)
class NodeId(InstanceId):
_instance_type: ClassVar[Literal["node", "edge"]] = "node"
def dump(self, camel_case: bool = True, include_instance_type: bool = True) -> dict[str, str]:
output = super().dump(camel_case=camel_case)
if include_instance_type:
output["type"] = self._instance_type
return output
@dataclass(frozen=True)
class EdgeId(InstanceId):
_instance_type: ClassVar[Literal["node", "edge"]] = "edge"
def dump(self, camel_case: bool = True, include_instance_type: bool = True) -> dict[str, str]:
output = super().dump(camel_case=camel_case)
if include_instance_type:
output["type"] = self._instance_type
return output
@dataclass(frozen=True)
class ContainerId(DataModelingId):
_type = "container"
def as_source_identifier(self) -> str:
return self.external_id
def as_property_ref(self, property: str) -> tuple[str, str, str]:
return (self.space, self.as_source_identifier(), property)
@dataclass(frozen=True)
class ViewId(VersionedDataModelingId):
_type = "view"
def as_source_identifier(self) -> str:
return f"{self.external_id}/{self.version}"
def as_property_ref(self, property: str) -> tuple[str, str, str]:
return (self.space, self.as_source_identifier(), property)
@dataclass(frozen=True)
class PropertyId(CogniteObject):
source: ViewId | ContainerId
property: str
@classmethod
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
return cls(
source=cls.__load_view_or_container_id(resource["source"]),
property=resource["identifier"],
)
@staticmethod
def __load_view_or_container_id(view_or_container_id: dict[str, Any]) -> ViewId | ContainerId:
if "type" in view_or_container_id and view_or_container_id["type"] in {"view", "container"}:
if view_or_container_id["type"] == "view":
return ViewId.load(view_or_container_id)
return ContainerId.load(view_or_container_id)
raise ValueError(f"Invalid type {view_or_container_id}")
def dump(self, camel_case: bool = True) -> dict[str, Any]:
return {
"source": self.source.dump(camel_case=camel_case, include_type=True),
"identifier": self.property,
}
@dataclass(frozen=True)
class DataModelId(VersionedDataModelingId):
_type = "datamodel"
class IdLike(Protocol):
@property
def space(self) -> str: ...
@property
def external_id(self) -> str: ...
class VersionedIdLike(IdLike, Protocol):
@property
def version(self) -> str | None: ...
ContainerIdentifier = ContainerId | tuple[str, str]
ConstraintIdentifier = tuple[ContainerId, str]
IndexIdentifier = tuple[ContainerId, str]
ViewIdentifier = ViewId | tuple[str, str] | tuple[str, str, str]
DataModelIdentifier = DataModelId | tuple[str, str] | tuple[str, str, str]
NodeIdentifier = NodeId | tuple[str, str, str]
EdgeIdentifier = EdgeId | tuple[str, str, str]
Id = tuple[str, str] | tuple[str, str, str] | IdLike | VersionedIdLike
def _load_space_identifier(ids: str | Sequence[str] | SequenceNotStr[str]) -> DataModelingIdentifierSequence:
is_sequence = isinstance(ids, Sequence) and not isinstance(ids, str)
spaces = [ids] if isinstance(ids, str) else ids
return DataModelingIdentifierSequence(
identifiers=[DataModelingIdentifier(space) for space in spaces], is_singleton=not is_sequence
)
def _load_identifier(
ids: Id | Sequence[Id], id_type: Literal["container", "view", "data_model", "space", "node", "edge"]
) -> DataModelingIdentifierSequence:
is_sequence = isinstance(ids, Sequence) and not (isinstance(ids, tuple) and isinstance(ids[0], str))
is_view_or_data_model = id_type in {"view", "data_model"}
is_instance = id_type in {"node", "edge"}
id_list = cast(Sequence, ids)
if not is_sequence:
id_list = [ids]
def create_args(id_: Id) -> tuple[str, str, str | None, Literal["node", "edge"] | None]:
if isinstance(id_, tuple) and is_instance:
if len(id_) == 2:
return id_[0], id_[1], None, id_type # type: ignore[return-value]
raise ValueError("Instance given as a tuple must have two elements (space, externalId)")
if isinstance(id_, tuple):
return id_[0], id_[1], (id_[2] if len(id_) == 3 else None), None
instance_type = None
if is_instance:
instance_type = "node" if isinstance(id_, NodeId) else "edge"
return id_.space, id_.external_id, getattr(id_, "version", None), instance_type # type: ignore[return-value]
return DataModelingIdentifierSequence(
identifiers=[DataModelingIdentifier(*create_args(id_)) for id_ in id_list],
is_singleton=not is_sequence and not is_view_or_data_model,
)