Skip to content

Commit 492960c

Browse files
erlendvollsethaakonvt
authored andcommitted
Add test to ensure CogniteObject subclasses handle unknown args when deserializing (#1504)
1 parent e535a4b commit 492960c

20 files changed

+332
-213
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
---
22
repos:
33
- repo: https://github.com/astral-sh/ruff-pre-commit
4+
rev: v0.1.5
45
hooks:
56
- id: ruff
6-
rev: v0.1.5
77
args:
88
- --fix
99
- --exit-non-zero-on-fix

cognite/client/_api/geospatial.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -943,10 +943,12 @@ def put_raster(
943943
self._raster_resource_path(feature_type_external_id, feature_external_id, raster_property_name)
944944
+ f"?{query_params}"
945945
)
946+
with open(file, "rb") as fh:
947+
data = fh.read()
946948
res = self._do_request(
947949
"PUT",
948950
url_path,
949-
data=open(file, "rb").read(),
951+
data=data,
950952
headers={"Content-Type": "application/binary"},
951953
timeout=self._config.timeout,
952954
)

cognite/client/data_classes/_base.py

-3
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,6 @@ class CogniteObject:
119119
It is used both by the CogniteResources and the nested classes used by the CogniteResources.
120120
"""
121121

122-
def __init__(self, cognite_client: CogniteClient | None = None) -> None:
123-
raise NotImplementedError
124-
125122
def __eq__(self, other: Any) -> bool:
126123
return type(self) is type(other) and self.dump() == other.dump()
127124

cognite/client/data_classes/capabilities.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1167,7 +1167,7 @@ class Scope:
11671167
type[Capability], tuple[tuple[type[Capability.Scope], ...], tuple[str, ...]]
11681168
] = MappingProxyType(
11691169
{
1170-
acl: tuple(zip(*[(v, k) for k, v in vars(acl.Scope).items() if inspect.isclass(v)])) # type: ignore [misc]
1170+
acl: tuple(zip(*[(v, k) for k, v in vars(acl.Scope).items() if inspect.isclass(v)]))
11711171
for acl in _CAPABILITY_CLASS_BY_NAME.values()
11721172
}
11731173
)

cognite/client/data_classes/data_modeling/containers.py

+53-35
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22

33
from abc import ABC, abstractmethod
44
from dataclasses import asdict, dataclass
5-
from typing import TYPE_CHECKING, Any, Literal
5+
from typing import TYPE_CHECKING, Any, Literal, cast
66

77
from typing_extensions import Self
88

99
from cognite.client.data_classes._base import (
1010
CogniteFilter,
11+
CogniteObject,
1112
CogniteResourceList,
1213
)
13-
from cognite.client.data_classes.data_modeling._core import DataModelingResource
1414
from cognite.client.data_classes.data_modeling._validation import validate_data_modeling_identifier
15+
from cognite.client.data_classes.data_modeling.core import DataModelingResource
1516
from cognite.client.data_classes.data_modeling.data_types import (
1617
DirectRelation,
1718
PropertyType,
@@ -112,6 +113,23 @@ def __init__(
112113
validate_data_modeling_identifier(space, external_id)
113114
super().__init__(space, external_id, properties, description, name, used_for, constraints, indexes)
114115

116+
@classmethod
117+
def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> ContainerApply:
118+
return ContainerApply(
119+
space=resource["space"],
120+
external_id=resource["externalId"],
121+
properties={k: ContainerProperty.load(v) for k, v in resource["properties"].items()},
122+
description=resource.get("description"),
123+
name=resource.get("name"),
124+
used_for=resource.get("usedFor"),
125+
constraints={k: Constraint.load(v) for k, v in resource["constraints"].items()}
126+
if "constraints" in resource
127+
else None,
128+
indexes={k: Index.load(v) for k, v in resource["indexes"].items()} or None
129+
if "indexes" in resource
130+
else None,
131+
)
132+
115133

116134
class Container(ContainerCore):
117135
"""Represent the physical storage of data. This is the read format of the container
@@ -210,7 +228,7 @@ def __init__(self, space: str | None = None, include_global: bool = False) -> No
210228

211229

212230
@dataclass(frozen=True)
213-
class ContainerProperty:
231+
class ContainerProperty(CogniteObject):
214232
type: PropertyType
215233
nullable: bool = True
216234
auto_increment: bool = False
@@ -219,21 +237,21 @@ class ContainerProperty:
219237
description: str | None = None
220238

221239
@classmethod
222-
def load(cls, data: dict[str, Any]) -> ContainerProperty:
223-
if "type" not in data:
240+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
241+
if "type" not in resource:
224242
raise ValueError("Type not specified")
225-
if data["type"].get("type") == "direct":
226-
type_: PropertyType = DirectRelation.load(data["type"])
243+
if resource["type"].get("type") == "direct":
244+
type_: PropertyType = DirectRelation.load(resource["type"])
227245
else:
228-
type_ = PropertyType.load(data["type"])
246+
type_ = PropertyType.load(resource["type"])
229247
return cls(
230248
type=type_,
231249
# If nothing is specified, we will pass through null values
232-
nullable=data.get("nullable"), # type: ignore[arg-type]
233-
auto_increment=data.get("autoIncrement"), # type: ignore[arg-type]
234-
name=data.get("name"),
235-
default_value=data.get("defaultValue"),
236-
description=data.get("description"),
250+
nullable=resource.get("nullable"), # type: ignore[arg-type]
251+
auto_increment=resource.get("autoIncrement"), # type: ignore[arg-type]
252+
name=resource.get("name"),
253+
default_value=resource.get("defaultValue"),
254+
description=resource.get("description"),
237255
)
238256

239257
def dump(self, camel_case: bool = True) -> dict[str, str | dict]:
@@ -247,14 +265,14 @@ def dump(self, camel_case: bool = True) -> dict[str, str | dict]:
247265

248266

249267
@dataclass(frozen=True)
250-
class Constraint(ABC):
268+
class Constraint(CogniteObject, ABC):
251269
@classmethod
252-
def load(cls, data: dict) -> RequiresConstraint | UniquenessConstraint:
253-
if data["constraintType"] == "requires":
254-
return RequiresConstraint.load(data)
255-
elif data["constraintType"] == "uniqueness":
256-
return UniquenessConstraint.load(data)
257-
raise ValueError(f"Invalid constraint type {data['constraintType']}")
270+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
271+
if resource["constraintType"] == "requires":
272+
return cast(Self, RequiresConstraint.load(resource))
273+
elif resource["constraintType"] == "uniqueness":
274+
return cast(Self, UniquenessConstraint.load(resource))
275+
raise ValueError(f"Invalid constraint type {resource['constraintType']}")
258276

259277
@abstractmethod
260278
def dump(self, camel_case: bool = True) -> dict[str, str | dict]:
@@ -266,8 +284,8 @@ class RequiresConstraint(Constraint):
266284
require: ContainerId
267285

268286
@classmethod
269-
def load(cls, data: dict) -> RequiresConstraint:
270-
return cls(require=ContainerId.load(data["require"]))
287+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
288+
return cls(require=ContainerId.load(resource["require"]))
271289

272290
def dump(self, camel_case: bool = True) -> dict[str, str | dict]:
273291
as_dict = asdict(self)
@@ -284,8 +302,8 @@ class UniquenessConstraint(Constraint):
284302
properties: list[str]
285303

286304
@classmethod
287-
def load(cls, data: dict) -> UniquenessConstraint:
288-
return cls(properties=data["properties"])
305+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
306+
return cls(properties=resource["properties"])
289307

290308
def dump(self, camel_case: bool = True) -> dict[str, str | dict]:
291309
as_dict = asdict(self)
@@ -296,14 +314,14 @@ def dump(self, camel_case: bool = True) -> dict[str, str | dict]:
296314

297315

298316
@dataclass(frozen=True)
299-
class Index(ABC):
317+
class Index(CogniteObject, ABC):
300318
@classmethod
301-
def load(cls, data: dict) -> Index:
302-
if data["indexType"] == "btree":
303-
return BTreeIndex.load(data)
304-
if data["indexType"] == "inverted":
305-
return InvertedIndex.load(data)
306-
raise ValueError(f"Invalid index type {data['indexType']}")
319+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
320+
if resource["indexType"] == "btree":
321+
return cast(Self, BTreeIndex.load(resource))
322+
if resource["indexType"] == "inverted":
323+
return cast(Self, InvertedIndex.load(resource))
324+
raise ValueError(f"Invalid index type {resource['indexType']}")
307325

308326
@abstractmethod
309327
def dump(self, camel_case: bool = True) -> dict[str, str | dict]:
@@ -316,8 +334,8 @@ class BTreeIndex(Index):
316334
cursorable: bool = False
317335

318336
@classmethod
319-
def load(cls, data: dict[str, Any]) -> BTreeIndex:
320-
return cls(properties=data["properties"], cursorable=data.get("cursorable")) # type: ignore[arg-type]
337+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
338+
return cls(properties=resource["properties"], cursorable=resource.get("cursorable")) # type: ignore[arg-type]
321339

322340
def dump(self, camel_case: bool = True) -> dict[str, Any]:
323341
dumped: dict[str, Any] = {"properties": self.properties}
@@ -332,8 +350,8 @@ class InvertedIndex(Index):
332350
properties: list[str]
333351

334352
@classmethod
335-
def load(cls, data: dict[str, Any]) -> InvertedIndex:
336-
return cls(properties=data["properties"])
353+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
354+
return cls(properties=resource["properties"])
337355

338356
def dump(self, camel_case: bool = True) -> dict[str, Any]:
339357
dumped: dict[str, Any] = {"properties": self.properties}

cognite/client/data_classes/data_modeling/_core.py renamed to cognite/client/data_classes/data_modeling/core.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from typing_extensions import Self
88

9-
from cognite.client.data_classes._base import CogniteResource, basic_instance_dump
9+
from cognite.client.data_classes._base import CogniteObject, CogniteResource, basic_instance_dump
1010
from cognite.client.utils._auxiliary import json_dump_default
1111
from cognite.client.utils._text import convert_all_keys_to_snake_case
1212

@@ -34,13 +34,14 @@ def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None =
3434
return cls(**convert_all_keys_to_snake_case(resource))
3535

3636

37-
class DataModelingSort:
37+
class DataModelingSort(CogniteObject):
3838
def __init__(
3939
self,
4040
property: str | list[str] | tuple[str, ...],
4141
direction: Literal["ascending", "descending"] = "ascending",
4242
nulls_first: bool = False,
4343
) -> None:
44+
super().__init__()
4445
self.property = property
4546
self.direction = direction
4647
self.nulls_first = nulls_first
@@ -55,7 +56,7 @@ def __repr__(self) -> str:
5556
return str(self)
5657

5758
@classmethod
58-
def load(cls, resource: dict) -> Self:
59+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
5960
if not isinstance(resource, dict):
6061
raise TypeError(f"Resource must be mapping, not {type(resource)}")
6162

cognite/client/data_classes/data_modeling/data_models.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from typing_extensions import Self
77

88
from cognite.client.data_classes._base import CogniteFilter, CogniteResourceList
9-
from cognite.client.data_classes.data_modeling._core import DataModelingResource, DataModelingSort
109
from cognite.client.data_classes.data_modeling._validation import validate_data_modeling_identifier
10+
from cognite.client.data_classes.data_modeling.core import DataModelingResource, DataModelingSort
1111
from cognite.client.data_classes.data_modeling.ids import DataModelId, ViewId
1212
from cognite.client.data_classes.data_modeling.views import View, ViewApply
1313

@@ -79,10 +79,15 @@ def _load_view(cls, view_data: dict) -> ViewId | ViewApply:
7979
return ViewApply._load(view_data)
8080

8181
@classmethod
82-
def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self:
83-
if "views" in resource:
84-
resource["views"] = [cls._load_view(v) for v in resource["views"]] or None
85-
return super()._load(resource)
82+
def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> DataModelApply:
83+
return DataModelApply(
84+
space=resource["space"],
85+
external_id=resource["externalId"],
86+
version=resource["version"],
87+
description=resource.get("description"),
88+
name=resource.get("name"),
89+
views=[cls._load_view(v) for v in resource["views"]] if "views" in resource else None,
90+
)
8691

8792
def dump(self, camel_case: bool = True) -> dict[str, Any]:
8893
output = super().dump(camel_case)
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
from __future__ import annotations
22

33
from dataclasses import dataclass
4-
from typing import Any
4+
from typing import TYPE_CHECKING, Any
55

6+
from typing_extensions import Self
7+
8+
from cognite.client.data_classes._base import CogniteObject
69
from cognite.client.data_classes.data_modeling.ids import DataModelId
710

11+
if TYPE_CHECKING:
12+
from cognite.client import CogniteClient
13+
814

915
@dataclass
10-
class DMLApplyResult:
16+
class DMLApplyResult(CogniteObject):
1117
space: str
1218
external_id: str
1319
version: str
@@ -24,13 +30,13 @@ def as_id(self) -> DataModelId:
2430
)
2531

2632
@classmethod
27-
def load(cls, data: dict[str, Any]) -> DMLApplyResult:
33+
def _load(cls, resource: dict[str, Any], cognite_client: CogniteClient | None = None) -> Self:
2834
return cls(
29-
space=data["space"],
30-
external_id=data["externalId"],
31-
version=data["version"],
32-
name=data["name"],
33-
description=data["description"],
34-
created_time=data["createdTime"],
35-
last_updated_time=data["lastUpdatedTime"],
35+
space=resource["space"],
36+
external_id=resource["externalId"],
37+
version=resource["version"],
38+
name=resource["name"],
39+
description=resource["description"],
40+
created_time=resource["createdTime"],
41+
last_updated_time=resource["lastUpdatedTime"],
3642
)

0 commit comments

Comments
 (0)