cognite-neat 0.125.1__py3-none-any.whl → 0.126.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cognite-neat might be problematic. Click here for more details.

Files changed (80) hide show
  1. cognite/neat/_client/__init__.py +4 -0
  2. cognite/neat/_client/api.py +8 -0
  3. cognite/neat/_client/client.py +19 -0
  4. cognite/neat/_client/config.py +40 -0
  5. cognite/neat/_client/containers_api.py +73 -0
  6. cognite/neat/_client/data_classes.py +10 -0
  7. cognite/neat/_client/data_model_api.py +63 -0
  8. cognite/neat/_client/spaces_api.py +67 -0
  9. cognite/neat/_client/views_api.py +82 -0
  10. cognite/neat/_data_model/_analysis.py +127 -0
  11. cognite/neat/_data_model/_constants.py +59 -0
  12. cognite/neat/_data_model/_shared.py +46 -0
  13. cognite/neat/_data_model/deployer/__init__.py +0 -0
  14. cognite/neat/_data_model/deployer/_differ.py +113 -0
  15. cognite/neat/_data_model/deployer/_differ_container.py +354 -0
  16. cognite/neat/_data_model/deployer/_differ_data_model.py +29 -0
  17. cognite/neat/_data_model/deployer/_differ_space.py +9 -0
  18. cognite/neat/_data_model/deployer/_differ_view.py +194 -0
  19. cognite/neat/_data_model/deployer/data_classes.py +176 -0
  20. cognite/neat/_data_model/exporters/__init__.py +4 -0
  21. cognite/neat/_data_model/exporters/_base.py +22 -0
  22. cognite/neat/_data_model/exporters/_table_exporter/__init__.py +0 -0
  23. cognite/neat/_data_model/exporters/_table_exporter/exporter.py +106 -0
  24. cognite/neat/_data_model/exporters/_table_exporter/workbook.py +414 -0
  25. cognite/neat/_data_model/exporters/_table_exporter/writer.py +391 -0
  26. cognite/neat/_data_model/importers/__init__.py +2 -1
  27. cognite/neat/_data_model/importers/_api_importer.py +88 -0
  28. cognite/neat/_data_model/importers/_table_importer/data_classes.py +48 -8
  29. cognite/neat/_data_model/importers/_table_importer/importer.py +102 -6
  30. cognite/neat/_data_model/importers/_table_importer/reader.py +860 -0
  31. cognite/neat/_data_model/models/dms/__init__.py +19 -1
  32. cognite/neat/_data_model/models/dms/_base.py +12 -8
  33. cognite/neat/_data_model/models/dms/_constants.py +1 -1
  34. cognite/neat/_data_model/models/dms/_constraints.py +2 -1
  35. cognite/neat/_data_model/models/dms/_container.py +5 -5
  36. cognite/neat/_data_model/models/dms/_data_model.py +3 -3
  37. cognite/neat/_data_model/models/dms/_data_types.py +8 -1
  38. cognite/neat/_data_model/models/dms/_http.py +18 -0
  39. cognite/neat/_data_model/models/dms/_indexes.py +2 -1
  40. cognite/neat/_data_model/models/dms/_references.py +17 -4
  41. cognite/neat/_data_model/models/dms/_space.py +11 -7
  42. cognite/neat/_data_model/models/dms/_view_property.py +7 -4
  43. cognite/neat/_data_model/models/dms/_views.py +16 -6
  44. cognite/neat/_data_model/validation/__init__.py +0 -0
  45. cognite/neat/_data_model/validation/_base.py +16 -0
  46. cognite/neat/_data_model/validation/dms/__init__.py +9 -0
  47. cognite/neat/_data_model/validation/dms/_orchestrator.py +68 -0
  48. cognite/neat/_data_model/validation/dms/_validators.py +139 -0
  49. cognite/neat/_exceptions.py +15 -3
  50. cognite/neat/_issues.py +39 -6
  51. cognite/neat/_session/__init__.py +3 -0
  52. cognite/neat/_session/_physical.py +88 -0
  53. cognite/neat/_session/_session.py +34 -25
  54. cognite/neat/_session/_wrappers.py +61 -0
  55. cognite/neat/_state_machine/__init__.py +10 -0
  56. cognite/neat/{_session/_state_machine → _state_machine}/_base.py +11 -1
  57. cognite/neat/_state_machine/_states.py +53 -0
  58. cognite/neat/_store/__init__.py +3 -0
  59. cognite/neat/_store/_provenance.py +55 -0
  60. cognite/neat/_store/_store.py +124 -0
  61. cognite/neat/_utils/_reader.py +194 -0
  62. cognite/neat/_utils/http_client/__init__.py +14 -20
  63. cognite/neat/_utils/http_client/_client.py +22 -61
  64. cognite/neat/_utils/http_client/_data_classes.py +167 -268
  65. cognite/neat/_utils/text.py +6 -0
  66. cognite/neat/_utils/useful_types.py +26 -2
  67. cognite/neat/_version.py +1 -1
  68. cognite/neat/v0/core/_data_model/importers/_rdf/_shared.py +2 -2
  69. cognite/neat/v0/core/_data_model/importers/_spreadsheet2data_model.py +2 -2
  70. cognite/neat/v0/core/_data_model/models/entities/_single_value.py +1 -1
  71. cognite/neat/v0/core/_data_model/models/physical/_unverified.py +1 -1
  72. cognite/neat/v0/core/_data_model/models/physical/_validation.py +2 -2
  73. cognite/neat/v0/core/_data_model/models/physical/_verified.py +3 -3
  74. cognite/neat/v0/core/_data_model/transformers/_converters.py +1 -1
  75. {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/METADATA +1 -1
  76. {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/RECORD +78 -40
  77. cognite/neat/_session/_state_machine/__init__.py +0 -23
  78. cognite/neat/_session/_state_machine/_states.py +0 -150
  79. {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/WHEEL +0 -0
  80. {cognite_neat-0.125.1.dist-info → cognite_neat-0.126.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,10 @@
1
- from cognite.neat._data_model.models.dms._base import Resource, WriteableResource
1
+ from cognite.neat._data_model.models.dms._base import (
2
+ APIResource,
3
+ BaseModelObject,
4
+ Resource,
5
+ T_Resource,
6
+ WriteableResource,
7
+ )
2
8
  from cognite.neat._data_model.models.dms._constraints import (
3
9
  Constraint,
4
10
  ConstraintAdapter,
@@ -13,6 +19,7 @@ from cognite.neat._data_model.models.dms._container import (
13
19
  ContainerResponse,
14
20
  )
15
21
  from cognite.neat._data_model.models.dms._data_types import (
22
+ DMS_DATA_TYPES,
16
23
  BooleanProperty,
17
24
  DataType,
18
25
  DataTypeAdapter,
@@ -38,11 +45,13 @@ from cognite.neat._data_model.models.dms._indexes import BtreeIndex, Index, Inde
38
45
  from cognite.neat._data_model.models.dms._space import Space, SpaceRequest, SpaceResponse
39
46
 
40
47
  from ._data_model import DataModelRequest, DataModelResponse
48
+ from ._http import DataModelBody, DataModelResource
41
49
  from ._references import (
42
50
  ContainerDirectReference,
43
51
  ContainerReference,
44
52
  DataModelReference,
45
53
  NodeReference,
54
+ SpaceReference,
46
55
  ViewDirectReference,
47
56
  ViewReference,
48
57
  )
@@ -60,6 +69,7 @@ from ._view_property import (
60
69
  ViewCorePropertyResponse,
61
70
  ViewPropertyDefinition,
62
71
  ViewRequestProperty,
72
+ ViewRequestPropertyAdapter,
63
73
  ViewResponseProperty,
64
74
  )
65
75
  from ._views import (
@@ -69,6 +79,9 @@ from ._views import (
69
79
  )
70
80
 
71
81
  __all__ = [
82
+ "DMS_DATA_TYPES",
83
+ "APIResource",
84
+ "BaseModelObject",
72
85
  "BooleanProperty",
73
86
  "BtreeIndex",
74
87
  "ConnectionPropertyDefinition",
@@ -82,8 +95,10 @@ __all__ = [
82
95
  "ContainerReference",
83
96
  "ContainerRequest",
84
97
  "ContainerResponse",
98
+ "DataModelBody",
85
99
  "DataModelReference",
86
100
  "DataModelRequest",
101
+ "DataModelResource",
87
102
  "DataModelResponse",
88
103
  "DataType",
89
104
  "DataTypeAdapter",
@@ -117,8 +132,10 @@ __all__ = [
117
132
  "SingleReverseDirectRelationPropertyRequest",
118
133
  "SingleReverseDirectRelationPropertyResponse",
119
134
  "Space",
135
+ "SpaceReference",
120
136
  "SpaceRequest",
121
137
  "SpaceResponse",
138
+ "T_Resource",
122
139
  "TextProperty",
123
140
  "TextProperty",
124
141
  "TimeseriesCDFExternalIdReference",
@@ -135,6 +152,7 @@ __all__ = [
135
152
  "ViewReference",
136
153
  "ViewRequest",
137
154
  "ViewRequestProperty",
155
+ "ViewRequestPropertyAdapter",
138
156
  "ViewResponse",
139
157
  "ViewResponseProperty",
140
158
  "WriteableResource",
@@ -1,14 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Generic, TypeVar
3
3
 
4
- from pydantic import BaseModel
5
- from pydantic.alias_generators import to_camel
6
-
7
-
8
- class BaseModelObject(BaseModel, alias_generator=to_camel, extra="ignore"):
9
- """Base class for all object. This includes resources and nested objects."""
10
-
11
- ...
4
+ from cognite.neat._utils.useful_types import BaseModelObject, T_Reference
12
5
 
13
6
 
14
7
  class Resource(BaseModelObject):
@@ -21,7 +14,18 @@ T_Resource = TypeVar("T_Resource", bound=Resource)
21
14
 
22
15
 
23
16
  class WriteableResource(Resource, Generic[T_Resource], ABC):
17
+ """Base class for all writeable data modeling resources."""
18
+
24
19
  @abstractmethod
25
20
  def as_request(self) -> T_Resource:
26
21
  """Convert the response model to a request model by removing read-only fields."""
27
22
  raise NotImplementedError()
23
+
24
+
25
+ class APIResource(Generic[T_Reference], ABC):
26
+ """Base class for all API data modeling resources."""
27
+
28
+ @abstractmethod
29
+ def as_reference(self) -> T_Reference:
30
+ """Convert the resource to a reference object (identifier)."""
31
+ raise NotImplementedError()
@@ -1,7 +1,7 @@
1
1
  SPACE_FORMAT_PATTERN = r"^[a-zA-Z][a-zA-Z0-9_-]{0,41}[a-zA-Z0-9]?$"
2
2
  DM_EXTERNAL_ID_PATTERN = r"^[a-zA-Z]([a-zA-Z0-9_]{0,253}[a-zA-Z0-9])?$"
3
3
  CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN = r"^[a-zA-Z0-9][a-zA-Z0-9_-]{0,253}[a-zA-Z0-9]?$"
4
- INSTANCE_ID_PATTERN = r"^[^\\x00]{1,256}$"
4
+ INSTANCE_ID_PATTERN = r"^[^\x00]{1,256}$"
5
5
  ENUM_VALUE_IDENTIFIER_PATTERN = r"^[_A-Za-z][_0-9A-Za-z]{0,127}$"
6
6
  DM_VERSION_PATTERN = r"^[a-zA-Z0-9]([.a-zA-Z0-9_-]{0,41}[a-zA-Z0-9])?$"
7
7
  FORBIDDEN_ENUM_VALUES = frozenset({"true", "false", "null"})
@@ -3,7 +3,8 @@ from typing import Annotated, Literal
3
3
 
4
4
  from pydantic import Field, TypeAdapter
5
5
 
6
- from ._base import BaseModelObject
6
+ from cognite.neat._utils.useful_types import BaseModelObject
7
+
7
8
  from ._references import ContainerReference
8
9
  from ._types import Bool
9
10
 
@@ -6,8 +6,9 @@ from pydantic import Field, field_validator
6
6
  from pydantic_core.core_schema import ValidationInfo
7
7
 
8
8
  from cognite.neat._utils.text import humanize_collection
9
+ from cognite.neat._utils.useful_types import BaseModelObject
9
10
 
10
- from ._base import BaseModelObject, Resource, WriteableResource
11
+ from ._base import APIResource, Resource, WriteableResource
11
12
  from ._constants import (
12
13
  CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
13
14
  DM_EXTERNAL_ID_PATTERN,
@@ -36,7 +37,7 @@ class ContainerPropertyDefinition(BaseModelObject):
36
37
  default=None,
37
38
  description="Increment the property based on its highest current value (max value).",
38
39
  )
39
- default_value: str | int | bool | dict[str, Any] | None = Field(
40
+ default_value: str | int | float | bool | dict[str, Any] | None = Field(
40
41
  default=None,
41
42
  description="Default value to use when you do not specify a value for the property.",
42
43
  )
@@ -53,7 +54,7 @@ class ContainerPropertyDefinition(BaseModelObject):
53
54
  type: DataType = Field(description="The type of data you can store in this property.")
54
55
 
55
56
 
56
- class Container(Resource, ABC):
57
+ class Container(Resource, APIResource[ContainerReference], ABC):
57
58
  space: str = Field(
58
59
  description="The workspace for the container, a unique identifier for the space.",
59
60
  min_length=1,
@@ -82,7 +83,6 @@ class Container(Resource, ABC):
82
83
  )
83
84
  properties: dict[str, ContainerPropertyDefinition] = Field(
84
85
  description="Set of properties to apply to the container.",
85
- min_length=1,
86
86
  )
87
87
  constraints: dict[str, Constraint] | None = Field(
88
88
  default=None,
@@ -136,7 +136,7 @@ class Container(Resource, ABC):
136
136
  def as_reference(self) -> ContainerReference:
137
137
  return ContainerReference(
138
138
  space=self.space,
139
- externalId=self.external_id,
139
+ external_id=self.external_id,
140
140
  )
141
141
 
142
142
 
@@ -2,7 +2,7 @@ from abc import ABC
2
2
 
3
3
  from pydantic import Field
4
4
 
5
- from ._base import Resource, WriteableResource
5
+ from ._base import APIResource, Resource, WriteableResource
6
6
  from ._constants import (
7
7
  DM_EXTERNAL_ID_PATTERN,
8
8
  DM_VERSION_PATTERN,
@@ -11,7 +11,7 @@ from ._constants import (
11
11
  from ._references import DataModelReference, ViewReference
12
12
 
13
13
 
14
- class DataModel(Resource, ABC):
14
+ class DataModel(Resource, APIResource[DataModelReference], ABC):
15
15
  """Cognite Data Model resource.
16
16
 
17
17
  Data models group and structure views into reusable collections.
@@ -55,7 +55,7 @@ class DataModel(Resource, ABC):
55
55
  def as_reference(self) -> DataModelReference:
56
56
  return DataModelReference(
57
57
  space=self.space,
58
- externalId=self.external_id,
58
+ external_id=self.external_id,
59
59
  version=self.version,
60
60
  )
61
61
 
@@ -1,10 +1,13 @@
1
1
  import re
2
2
  from abc import ABC
3
+ from collections.abc import Mapping
3
4
  from typing import Annotated, Literal
4
5
 
5
6
  from pydantic import Field, TypeAdapter, field_validator
6
7
 
7
- from ._base import BaseModelObject
8
+ from cognite.neat._utils.auxiliary import get_concrete_subclasses
9
+ from cognite.neat._utils.useful_types import BaseModelObject
10
+
8
11
  from ._constants import ENUM_VALUE_IDENTIFIER_PATTERN, FORBIDDEN_ENUM_VALUES, INSTANCE_ID_PATTERN
9
12
  from ._references import ContainerReference
10
13
 
@@ -181,3 +184,7 @@ DataType = Annotated[
181
184
  ]
182
185
 
183
186
  DataTypeAdapter: TypeAdapter[DataType] = TypeAdapter(DataType)
187
+
188
+ DMS_DATA_TYPES: Mapping[str, type[PropertyTypeDefinition]] = {
189
+ cls_.model_fields["type"].default: cls_ for cls_ in get_concrete_subclasses(PropertyTypeDefinition)
190
+ }
@@ -0,0 +1,18 @@
1
+ from typing import TypeAlias, TypeVar
2
+
3
+ from cognite.neat._utils.http_client import ItemBody
4
+ from cognite.neat._utils.useful_types import ReferenceObject
5
+
6
+ from ._container import ContainerRequest
7
+ from ._data_model import DataModelRequest
8
+ from ._space import SpaceRequest
9
+ from ._views import ViewRequest
10
+
11
+ DataModelResource: TypeAlias = SpaceRequest | DataModelRequest | ViewRequest | ContainerRequest
12
+
13
+ T_DataModelResource = TypeVar("T_DataModelResource", bound=DataModelResource)
14
+
15
+
16
+ class DataModelBody(ItemBody[ReferenceObject, DataModelResource]):
17
+ def as_ids(self) -> list[ReferenceObject]:
18
+ return [item.as_reference() for item in self.items]
@@ -3,7 +3,8 @@ from typing import Annotated, Literal
3
3
 
4
4
  from pydantic import Field, TypeAdapter
5
5
 
6
- from ._base import BaseModelObject
6
+ from cognite.neat._utils.useful_types import BaseModelObject
7
+
7
8
  from ._types import Bool
8
9
 
9
10
 
@@ -2,7 +2,8 @@ from typing import Literal
2
2
 
3
3
  from pydantic import Field
4
4
 
5
- from ._base import BaseModelObject
5
+ from cognite.neat._utils.useful_types import ReferenceObject
6
+
6
7
  from ._constants import (
7
8
  CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
8
9
  DM_EXTERNAL_ID_PATTERN,
@@ -12,11 +13,17 @@ from ._constants import (
12
13
  )
13
14
 
14
15
 
15
- class ReferenceObject(BaseModelObject, frozen=True): ...
16
+ class SpaceReference(ReferenceObject):
17
+ space: str = Field(
18
+ description="Id of the space.",
19
+ min_length=1,
20
+ max_length=43,
21
+ pattern=SPACE_FORMAT_PATTERN,
22
+ )
16
23
 
17
24
 
18
25
  class ContainerReference(ReferenceObject):
19
- type: Literal["container"] = "container"
26
+ type: Literal["container"] = Field("container", exclude=True)
20
27
  space: str = Field(
21
28
  description="Id of the space hosting (containing) the container.",
22
29
  min_length=1,
@@ -30,9 +37,12 @@ class ContainerReference(ReferenceObject):
30
37
  pattern=DM_EXTERNAL_ID_PATTERN,
31
38
  )
32
39
 
40
+ def __str__(self) -> str:
41
+ return f"{self.space}:{self.external_id}"
42
+
33
43
 
34
44
  class ViewReference(ReferenceObject):
35
- type: Literal["view"] = "view"
45
+ type: Literal["view"] = Field("view", exclude=True)
36
46
  space: str = Field(
37
47
  description="Id of the space that the view belongs to.",
38
48
  min_length=1,
@@ -51,6 +61,9 @@ class ViewReference(ReferenceObject):
51
61
  pattern=DM_VERSION_PATTERN,
52
62
  )
53
63
 
64
+ def __str__(self) -> str:
65
+ return f"{self.space}:{self.external_id}(version={self.version})"
66
+
54
67
 
55
68
  class DataModelReference(ReferenceObject):
56
69
  space: str = Field(
@@ -2,13 +2,14 @@ from abc import ABC
2
2
 
3
3
  from pydantic import Field, field_validator
4
4
 
5
+ from cognite.neat._data_model.models.dms._references import SpaceReference
5
6
  from cognite.neat.v0.core._utils.text import humanize_collection
6
7
 
7
- from ._base import WriteableResource
8
+ from ._base import APIResource, Resource, WriteableResource
8
9
  from ._constants import FORBIDDEN_SPACES, SPACE_FORMAT_PATTERN
9
10
 
10
11
 
11
- class Space(WriteableResource["SpaceRequest"], ABC):
12
+ class Space(Resource, APIResource[SpaceReference], ABC):
12
13
  space: str = Field(
13
14
  description="The Space identifier (id).",
14
15
  min_length=1,
@@ -25,11 +26,14 @@ class Space(WriteableResource["SpaceRequest"], ABC):
25
26
  raise ValueError(f"{val!r} is a reserved space. Reserved Spaces: {humanize_collection(FORBIDDEN_SPACES)}")
26
27
  return val
27
28
 
28
- def as_request(self) -> "SpaceRequest":
29
- return SpaceRequest.model_validate(self.model_dump(by_alias=True))
29
+ def as_reference(self) -> SpaceReference:
30
+ return SpaceReference(space=self.space)
31
+
32
+
33
+ class SpaceRequest(Space): ...
30
34
 
31
35
 
32
- class SpaceResponse(Space):
36
+ class SpaceResponse(Space, WriteableResource[Space]):
33
37
  created_time: int = Field(
34
38
  description="When the space was created. The number of milliseconds since 00:00:00 Thursday, 1 January 1970, "
35
39
  "Coordinated Universal Time (UTC), minus leap seconds."
@@ -40,5 +44,5 @@ class SpaceResponse(Space):
40
44
  )
41
45
  is_global: bool = Field(description="Whether the space is a global space.")
42
46
 
43
-
44
- class SpaceRequest(Space): ...
47
+ def as_request(self) -> "SpaceRequest":
48
+ return SpaceRequest.model_validate(self.model_dump(by_alias=True))
@@ -3,7 +3,9 @@ from typing import Annotated, Literal
3
3
 
4
4
  from pydantic import Field, Json, TypeAdapter
5
5
 
6
- from ._base import BaseModelObject, Resource, WriteableResource
6
+ from cognite.neat._utils.useful_types import BaseModelObject
7
+
8
+ from ._base import Resource, WriteableResource
7
9
  from ._constants import CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN
8
10
  from ._data_types import DataType
9
11
  from ._references import ContainerDirectReference, ContainerReference, NodeReference, ViewDirectReference, ViewReference
@@ -49,6 +51,7 @@ class ViewCorePropertyRequest(ViewCoreProperty): ...
49
51
 
50
52
  class ConstraintOrIndexState(BaseModelObject):
51
53
  nullability: Literal["current", "pending", "failed"] | None = Field(
54
+ None,
52
55
  description="""For properties that have isNullable set to false, this field describes the validity of the
53
56
  not-null constraint. It is not specified for nullable properties.
54
57
 
@@ -58,7 +61,7 @@ Possible values are:
58
61
  existing nulls was made non-nullable. New null values will still be rejected.
59
62
  "current": The constraint is satisfied; all values in the property are not null.
60
63
  "pending": The constraint validity has not yet been computed.
61
- """
64
+ """,
62
65
  )
63
66
 
64
67
 
@@ -147,7 +150,7 @@ class SingleReverseDirectRelationPropertyResponse(
147
150
  ReverseDirectRelationProperty, WriteableResource[SingleReverseDirectRelationPropertyRequest]
148
151
  ):
149
152
  connection_type: Literal["single_reverse_direct_relation"] = "single_reverse_direct_relation"
150
- target_list: bool = Field(
153
+ targets_list: bool = Field(
151
154
  description="Whether or not this reverse direct relation targets a list of direct relations.",
152
155
  )
153
156
 
@@ -159,7 +162,7 @@ class MultiReverseDirectRelationPropertyResponse(
159
162
  ReverseDirectRelationProperty, WriteableResource[MultiReverseDirectRelationPropertyRequest]
160
163
  ):
161
164
  connection_type: Literal["multi_reverse_direct_relation"] = "multi_reverse_direct_relation"
162
- target_list: bool = Field(
165
+ targets_list: bool = Field(
163
166
  description="Whether or not this reverse direct relation targets a list of direct relations.",
164
167
  )
165
168
 
@@ -2,11 +2,11 @@ import re
2
2
  from abc import ABC
3
3
  from typing import Literal, TypeVar
4
4
 
5
- from pydantic import Field, Json, field_validator, model_validator
5
+ from pydantic import Field, JsonValue, field_validator, model_validator
6
6
 
7
7
  from cognite.neat._utils.text import humanize_collection
8
8
 
9
- from ._base import Resource, WriteableResource
9
+ from ._base import APIResource, Resource, WriteableResource
10
10
  from ._constants import (
11
11
  CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
12
12
  DM_EXTERNAL_ID_PATTERN,
@@ -15,8 +15,9 @@ from ._constants import (
15
15
  FORBIDDEN_CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER,
16
16
  SPACE_FORMAT_PATTERN,
17
17
  )
18
- from ._references import ContainerReference, ViewReference
18
+ from ._references import ContainerReference, NodeReference, ViewReference
19
19
  from ._view_property import (
20
+ EdgeProperty,
20
21
  ViewRequestProperty,
21
22
  ViewResponseProperty,
22
23
  )
@@ -24,7 +25,7 @@ from ._view_property import (
24
25
  KEY_PATTERN = re.compile(CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN)
25
26
 
26
27
 
27
- class View(Resource, ABC):
28
+ class View(Resource, APIResource[ViewReference], ABC):
28
29
  space: str = Field(
29
30
  description="Id of the space that the view belongs to.",
30
31
  min_length=1,
@@ -52,7 +53,7 @@ class View(Resource, ABC):
52
53
  description="Description of the view.",
53
54
  max_length=1024,
54
55
  )
55
- filter: dict[str, Json] | None = Field(
56
+ filter: dict[str, JsonValue] | None = Field(
56
57
  default=None,
57
58
  description="A filter Domain Specific Language (DSL) used to create advanced filter queries.",
58
59
  )
@@ -62,7 +63,7 @@ class View(Resource, ABC):
62
63
  )
63
64
 
64
65
  def as_reference(self) -> ViewReference:
65
- return ViewReference(space=self.space, externalId=self.external_id, version=self.version)
66
+ return ViewReference(space=self.space, external_id=self.external_id, version=self.version)
66
67
 
67
68
  @model_validator(mode="before")
68
69
  def set_connection_type_on_primary_properties(cls, data: dict) -> dict:
@@ -134,6 +135,15 @@ class ViewResponse(View, WriteableResource[ViewRequest]):
134
135
  """Validate properties Identifier"""
135
136
  return _validate_properties_keys(val)
136
137
 
138
+ @property
139
+ def node_types(self) -> list[NodeReference]:
140
+ """Get all node types referenced by this view."""
141
+ nodes_refs: set[NodeReference] = set()
142
+ for prop in self.properties.values():
143
+ if isinstance(prop, EdgeProperty):
144
+ nodes_refs.add(prop.type)
145
+ return list(nodes_refs)
146
+
137
147
  def as_request(self) -> ViewRequest:
138
148
  dumped = self.model_dump(by_alias=True, exclude={"properties"})
139
149
  dumped["properties"] = {
File without changes
@@ -0,0 +1,16 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import ClassVar
3
+
4
+ from cognite.neat._issues import ConsistencyError, Recommendation
5
+
6
+
7
+ class DataModelValidator(ABC):
8
+ """Assessors for fundamental data model principles."""
9
+
10
+ code: ClassVar[str]
11
+
12
+ @abstractmethod
13
+ def run(self) -> list[ConsistencyError] | list[Recommendation]:
14
+ """Execute the success handler on the data model."""
15
+ # do something with data model
16
+ pass
@@ -0,0 +1,9 @@
1
+ from ._orchestrator import DmsDataModelValidation
2
+ from ._validators import UndefinedConnectionEndNodeTypes, VersionSpaceInconsistency, ViewsWithoutProperties
3
+
4
+ __all__ = [
5
+ "DmsDataModelValidation",
6
+ "UndefinedConnectionEndNodeTypes",
7
+ "VersionSpaceInconsistency",
8
+ "ViewsWithoutProperties",
9
+ ]
@@ -0,0 +1,68 @@
1
+ from cognite.neat._client import NeatClient
2
+ from cognite.neat._data_model._analysis import DataModelAnalysis
3
+ from cognite.neat._data_model._shared import OnSuccessIssuesChecker
4
+ from cognite.neat._data_model.models.dms._references import ViewReference
5
+ from cognite.neat._data_model.models.dms._schema import RequestSchema
6
+ from cognite.neat._data_model.models.dms._views import ViewRequest
7
+ from cognite.neat._data_model.validation._base import DataModelValidator
8
+
9
+ from ._validators import UndefinedConnectionEndNodeTypes, VersionSpaceInconsistency, ViewsWithoutProperties
10
+
11
+
12
+ class DmsDataModelValidation(OnSuccessIssuesChecker):
13
+ """Placeholder for DMS Quality Assessment functionality."""
14
+
15
+ def __init__(
16
+ self, client: NeatClient | None = None, codes: list[str] | None = None, modus_operandi: str | None = None
17
+ ) -> None:
18
+ super().__init__(client)
19
+ self._codes = codes or ["all"]
20
+ self._modus_operandi = modus_operandi # will be used later to trigger how validators will behave
21
+
22
+ def run(self, data_model: RequestSchema) -> None:
23
+ """Run quality assessment on the DMS data model."""
24
+
25
+ # Helper wrangled data model components
26
+ analysis = DataModelAnalysis(data_model)
27
+ local_views_by_reference = analysis.view_by_reference(include_inherited_properties=True)
28
+ local_connection_end_node_types = analysis.connection_end_node_types
29
+ cdf_views_by_reference = self._cdf_view_by_reference(
30
+ list(analysis.referenced_views(include_connection_end_node_types=True)),
31
+ include_inherited_properties=True,
32
+ )
33
+
34
+ validators: list[DataModelValidator] = [
35
+ ViewsWithoutProperties(
36
+ local_views_by_reference=local_views_by_reference,
37
+ cdf_views_by_reference=cdf_views_by_reference,
38
+ ),
39
+ UndefinedConnectionEndNodeTypes(
40
+ local_connection_end_node_types=local_connection_end_node_types,
41
+ local_views_by_reference=local_views_by_reference,
42
+ cdf_views_by_reference=cdf_views_by_reference,
43
+ ),
44
+ VersionSpaceInconsistency(
45
+ data_model_reference=data_model.data_model.as_reference(),
46
+ view_references=list(local_views_by_reference.keys()),
47
+ ),
48
+ ]
49
+
50
+ for validator in validators:
51
+ if "all" in self._codes or validator.code in self._codes:
52
+ self._issues.extend(validator.run())
53
+
54
+ self._has_run = True
55
+
56
+ def _cdf_view_by_reference(
57
+ self, views: list[ViewReference], include_inherited_properties: bool = True
58
+ ) -> dict[ViewReference, ViewRequest]:
59
+ """Fetch view definition from CDF."""
60
+
61
+ if not self._client:
62
+ return {}
63
+ return {
64
+ response.as_reference(): response.as_request()
65
+ for response in self._client.views.retrieve(
66
+ views, include_inherited_properties=include_inherited_properties
67
+ )
68
+ }