cognite-neat 0.123.38__py3-none-any.whl → 0.123.40__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.

@@ -0,0 +1,67 @@
1
+ from collections import Counter
2
+
3
+ from pydantic import Field, ValidationInfo, field_validator
4
+ from pyparsing import cast
5
+
6
+ from cognite.neat._data_model.models.entities import ConceptEntity
7
+ from cognite.neat._data_model.models.entities._constants import PREFIX_PATTERN, SUFFIX_PATTERN, VERSION_PATTERN
8
+ from cognite.neat._utils.text import humanize_collection
9
+
10
+ from ._base import ResourceMetadata
11
+ from ._property import Property
12
+
13
+
14
+ class Concept(ResourceMetadata):
15
+ space: str = Field(
16
+ description="Id of the space that the concept belongs to.",
17
+ min_length=1,
18
+ max_length=43,
19
+ pattern=PREFIX_PATTERN,
20
+ alias="prefix",
21
+ )
22
+ external_id: str = Field(
23
+ description="External-id of the concept.",
24
+ min_length=1,
25
+ max_length=255,
26
+ pattern=SUFFIX_PATTERN,
27
+ alias="suffix",
28
+ )
29
+ version: str | None = Field(
30
+ default=None,
31
+ description="Version of the concept.",
32
+ max_length=43,
33
+ pattern=VERSION_PATTERN,
34
+ )
35
+
36
+ implements: list[ConceptEntity] | None = Field(
37
+ default=None,
38
+ description="References to the concepts from where this concept will inherit properties.",
39
+ )
40
+
41
+ properties: dict[str, Property] | None = Field(default=None, description="Properties associated with the concept.")
42
+
43
+ @field_validator("implements", mode="after")
44
+ def cannot_implement_itself(cls, value: list[ConceptEntity], info: ValidationInfo) -> list[ConceptEntity]:
45
+ if not value:
46
+ return value
47
+
48
+ this_concept = ConceptEntity(
49
+ prefix=info.data["space"],
50
+ suffix=info.data["external_id"],
51
+ version=cast(str, info.data.get("version")),
52
+ )
53
+
54
+ if this_concept in value:
55
+ raise ValueError("A concept cannot implement itself.")
56
+
57
+ return value
58
+
59
+ @field_validator("implements", mode="after")
60
+ def cannot_have_duplicates(cls, value: list[ConceptEntity], info: ValidationInfo) -> list[ConceptEntity]:
61
+ counts = Counter(value)
62
+ duplicates = {concept for concept, count in counts.items() if count > 1}
63
+
64
+ if duplicates:
65
+ raise ValueError(f"Duplicate concepts found: {humanize_collection(duplicates)}")
66
+
67
+ return value
@@ -0,0 +1,104 @@
1
+ from typing import Any
2
+
3
+ from pydantic import Field, ValidationInfo, field_validator, model_validator
4
+
5
+ from cognite.neat._data_model.models.entities import URI, ConceptEntity, DataType, UnknownEntity
6
+
7
+ from ._base import ResourceMetadata
8
+
9
+
10
+ class Property(ResourceMetadata):
11
+ value_type: DataType | ConceptEntity | UnknownEntity = Field(
12
+ union_mode="left_to_right",
13
+ description="Value type that the property can hold. It takes either subset of XSD type or a class defined.",
14
+ )
15
+ min_count: int | None = Field(
16
+ default=None,
17
+ ge=0,
18
+ description="Minimum number of values that the property can hold. "
19
+ "If no value is provided, the default value is None meaning `0`, "
20
+ "which means that the property is optional.",
21
+ )
22
+ max_count: int | None = Field(
23
+ default=None,
24
+ ge=0,
25
+ description="Maximum number of values that the property can hold. "
26
+ "If no value is provided, the default value is None meaning `inf`, "
27
+ "which means that the property can hold any number of values (listable).",
28
+ )
29
+ default: Any | None = Field(alias="Default", default=None, description="Default value of the property.")
30
+ instance_reference: list[URI] | None = Field(
31
+ default=None,
32
+ description="The URI(s) in the graph to get the value of the property.",
33
+ )
34
+
35
+ @model_validator(mode="after")
36
+ def check_min_max_count(self) -> "Property":
37
+ if self.min_count is not None and self.max_count is not None:
38
+ if self.min_count > self.max_count:
39
+ raise ValueError("min_count must be less than or equal to max_count")
40
+ return self
41
+
42
+ @field_validator("default", mode="after")
43
+ def check_default_value_primitive_type(cls, value: Any, info: ValidationInfo) -> Any:
44
+ if not value:
45
+ return value
46
+
47
+ value_type = info.data.get("value_type")
48
+ if not isinstance(value_type, DataType):
49
+ raise ValueError("Setting default value is only supported for primitive value types.")
50
+ return value
51
+
52
+ @field_validator("default", mode="after")
53
+ def check_default_value_python_type_exists(cls, value: Any, info: ValidationInfo) -> Any:
54
+ if not value:
55
+ return value
56
+
57
+ value_type = info.data.get("value_type")
58
+
59
+ if isinstance(value_type, DataType) and not hasattr(value_type, "python"):
60
+ raise ValueError(
61
+ f"DataType {value_type} does not have a python type defined."
62
+ " Setting default value for property is not possible."
63
+ )
64
+ return value
65
+
66
+ @field_validator("default", mode="after")
67
+ def check_default_value_not_list(cls, value: Any, info: ValidationInfo) -> Any:
68
+ if not value:
69
+ return value
70
+
71
+ if isinstance(value, list):
72
+ raise ValueError("Setting list as default value is not supported.")
73
+ return value
74
+
75
+ @field_validator("default", mode="after")
76
+ def check_default_value_single_valued(cls, value: Any, info: ValidationInfo) -> Any:
77
+ if not value:
78
+ return value
79
+
80
+ max_count = info.data.get("max_count")
81
+
82
+ if max_count is None or max_count > 1:
83
+ raise ValueError(
84
+ "Setting default value is only supported for single-valued properties."
85
+ f" Property has max_count={max_count or 'Inf'}."
86
+ )
87
+ return value
88
+
89
+ @field_validator("default", mode="after")
90
+ def check_default_value_type_match(cls, value: Any, info: ValidationInfo) -> Any:
91
+ if not value:
92
+ return value
93
+
94
+ value_type = info.data.get("value_type")
95
+
96
+ if (
97
+ isinstance(value_type, DataType)
98
+ and hasattr(value_type, "python")
99
+ and not isinstance(value, value_type.python)
100
+ ):
101
+ raise ValueError(
102
+ f"Default value type is {type(value)}, which does not match expected value type {value_type.python}."
103
+ )
104
+ return value
@@ -32,15 +32,50 @@ from cognite.neat._data_model.models.dms._data_types import (
32
32
  from cognite.neat._data_model.models.dms._indexes import BtreeIndex, Index, IndexDefinition, InvertedIndex
33
33
  from cognite.neat._data_model.models.dms._space import Space, SpaceRequest, SpaceResponse
34
34
 
35
+ from ._data_model import DataModelRequest, DataModelResponse
36
+ from ._references import (
37
+ ContainerDirectReference,
38
+ ContainerReference,
39
+ NodeReference,
40
+ ViewDirectReference,
41
+ ViewReference,
42
+ )
43
+ from ._view_property import (
44
+ ConnectionPropertyDefinition,
45
+ ConstraintOrIndexState,
46
+ MultiEdgeProperty,
47
+ MultiReverseDirectRelationPropertyRequest,
48
+ MultiReverseDirectRelationPropertyResponse,
49
+ SingleEdgeProperty,
50
+ SingleReverseDirectRelationPropertyRequest,
51
+ SingleReverseDirectRelationPropertyResponse,
52
+ ViewCorePropertyRequest,
53
+ ViewCorePropertyResponse,
54
+ ViewPropertyDefinition,
55
+ ViewRequestProperty,
56
+ ViewResponseProperty,
57
+ )
58
+ from ._views import (
59
+ View,
60
+ ViewRequest,
61
+ ViewResponse,
62
+ )
63
+
35
64
  __all__ = [
36
65
  "BooleanProperty",
37
66
  "BtreeIndex",
67
+ "ConnectionPropertyDefinition",
38
68
  "Constraint",
39
69
  "ConstraintDefinition",
70
+ "ConstraintOrIndexState",
40
71
  "Container",
72
+ "ContainerDirectReference",
41
73
  "ContainerPropertyDefinition",
74
+ "ContainerReference",
42
75
  "ContainerRequest",
43
76
  "ContainerResponse",
77
+ "DataModelRequest",
78
+ "DataModelResponse",
44
79
  "DataType",
45
80
  "DateProperty",
46
81
  "DirectNodeRelation",
@@ -54,16 +89,38 @@ __all__ = [
54
89
  "InvertedIndex",
55
90
  "JSONProperty",
56
91
  "ListablePropertyTypeDefinition",
92
+ "MultiEdgeProperty",
93
+ "MultiReverseDirectRelationPropertyRequest",
94
+ "MultiReverseDirectRelationPropertyResponse",
95
+ "NodeReference",
57
96
  "PropertyTypeDefinition",
58
97
  "RequiresConstraintDefinition",
59
98
  "Resource",
60
99
  "SequenceCDFExternalIdReference",
100
+ "SequenceCDFExternalIdReference",
101
+ "SingleEdgeProperty",
102
+ "SingleReverseDirectRelationPropertyRequest",
103
+ "SingleReverseDirectRelationPropertyResponse",
61
104
  "Space",
62
105
  "SpaceRequest",
63
106
  "SpaceResponse",
64
107
  "TextProperty",
108
+ "TextProperty",
109
+ "TimeseriesCDFExternalIdReference",
65
110
  "TimeseriesCDFExternalIdReference",
66
111
  "TimestampProperty",
112
+ "TimestampProperty",
113
+ "UniquenessConstraintDefinition",
67
114
  "UniquenessConstraintDefinition",
115
+ "View",
116
+ "ViewCorePropertyRequest",
117
+ "ViewCorePropertyResponse",
118
+ "ViewDirectReference",
119
+ "ViewPropertyDefinition",
120
+ "ViewReference",
121
+ "ViewRequest",
122
+ "ViewRequestProperty",
123
+ "ViewResponse",
124
+ "ViewResponseProperty",
68
125
  "WriteableResource",
69
126
  ]
@@ -3,6 +3,7 @@ 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
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
+ DM_VERSION_PATTERN = r"^[a-zA-Z0-9]([.a-zA-Z0-9_-]{0,41}[a-zA-Z0-9])?$"
6
7
  FORBIDDEN_ENUM_VALUES = frozenset({"true", "false", "null"})
7
8
  FORBIDDEN_SPACES = frozenset(["space", "cdf", "dms", "pg3", "shared", "system", "node", "edge"])
8
9
  FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS = frozenset(
@@ -18,6 +18,7 @@ from ._constants import (
18
18
  from ._constraints import Constraint
19
19
  from ._data_types import DataType
20
20
  from ._indexes import Index
21
+ from ._references import ContainerReference
21
22
 
22
23
  KEY_PATTERN = re.compile(CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN)
23
24
 
@@ -132,6 +133,12 @@ class Container(Resource, ABC):
132
133
  )
133
134
  return val
134
135
 
136
+ def as_reference(self) -> ContainerReference:
137
+ return ContainerReference(
138
+ space=self.space,
139
+ externalId=self.external_id,
140
+ )
141
+
135
142
 
136
143
  class ContainerRequest(Container): ...
137
144
 
@@ -0,0 +1,71 @@
1
+ from abc import ABC
2
+
3
+ from pydantic import Field
4
+
5
+ from ._base import Resource, WriteableResource
6
+ from ._constants import (
7
+ DM_EXTERNAL_ID_PATTERN,
8
+ DM_VERSION_PATTERN,
9
+ SPACE_FORMAT_PATTERN,
10
+ )
11
+ from ._references import ViewReference
12
+
13
+
14
+ class DataModel(Resource, ABC):
15
+ """Cognite Data Model resource.
16
+
17
+ Data models group and structure views into reusable collections.
18
+ A data model contains a set of views where the node types can
19
+ refer to each other with direct relations and edges.
20
+ """
21
+
22
+ space: str = Field(
23
+ description="The workspace for the data model, a unique identifier for the space.",
24
+ min_length=1,
25
+ max_length=43,
26
+ pattern=SPACE_FORMAT_PATTERN,
27
+ )
28
+ external_id: str = Field(
29
+ description="External-id of the data model.",
30
+ min_length=1,
31
+ max_length=255,
32
+ pattern=DM_EXTERNAL_ID_PATTERN,
33
+ )
34
+ version: str = Field(
35
+ description="Version of the data model.",
36
+ max_length=43,
37
+ pattern=DM_VERSION_PATTERN,
38
+ )
39
+ name: str | None = Field(
40
+ default=None,
41
+ description="Human readable name for the data model.",
42
+ max_length=255,
43
+ )
44
+ description: str | None = Field(
45
+ default=None,
46
+ description="Description of the data model.",
47
+ max_length=1024,
48
+ )
49
+ # The API supports View here, but in Neat we will only use ViewReference
50
+ views: list[ViewReference] | None = Field(
51
+ description="List of views included in this data model.",
52
+ default=None,
53
+ )
54
+
55
+
56
+ class DataModelRequest(DataModel): ...
57
+
58
+
59
+ class DataModelResponse(DataModel, WriteableResource[DataModelRequest]):
60
+ created_time: int = Field(
61
+ description="When the data model was created. The number of milliseconds since 00:00:00 Thursday, "
62
+ "1 January 1970, Coordinated Universal Time (UTC), minus leap seconds."
63
+ )
64
+ last_updated_time: int = Field(
65
+ description="When the data model was last updated. The number of milliseconds since 00:00:00 Thursday, "
66
+ "1 January 1970, Coordinated Universal Time (UTC), minus leap seconds."
67
+ )
68
+ is_global: bool = Field(description="Is this a global data model.")
69
+
70
+ def as_request(self) -> DataModelRequest:
71
+ return DataModelRequest.model_validate(self.model_dump(by_alias=True))
@@ -3,10 +3,19 @@ from typing import Literal
3
3
  from pydantic import Field
4
4
 
5
5
  from ._base import BaseModelObject
6
- from ._constants import DM_EXTERNAL_ID_PATTERN, SPACE_FORMAT_PATTERN
6
+ from ._constants import (
7
+ CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
8
+ DM_EXTERNAL_ID_PATTERN,
9
+ DM_VERSION_PATTERN,
10
+ INSTANCE_ID_PATTERN,
11
+ SPACE_FORMAT_PATTERN,
12
+ )
7
13
 
8
14
 
9
- class ContainerReference(BaseModelObject):
15
+ class ReferenceObject(BaseModelObject, frozen=True): ...
16
+
17
+
18
+ class ContainerReference(ReferenceObject):
10
19
  type: Literal["container"] = "container"
11
20
  space: str = Field(
12
21
  description="Id of the space hosting (containing) the container.",
@@ -20,3 +29,63 @@ class ContainerReference(BaseModelObject):
20
29
  max_length=255,
21
30
  pattern=DM_EXTERNAL_ID_PATTERN,
22
31
  )
32
+
33
+
34
+ class ViewReference(BaseModelObject):
35
+ type: Literal["view"] = "view"
36
+ space: str = Field(
37
+ description="Id of the space that the view belongs to.",
38
+ min_length=1,
39
+ max_length=43,
40
+ pattern=SPACE_FORMAT_PATTERN,
41
+ )
42
+ external_id: str = Field(
43
+ description="External-id of the view.",
44
+ min_length=1,
45
+ max_length=255,
46
+ pattern=DM_EXTERNAL_ID_PATTERN,
47
+ )
48
+ version: str = Field(
49
+ description="Version of the view.",
50
+ max_length=43,
51
+ pattern=DM_VERSION_PATTERN,
52
+ )
53
+
54
+
55
+ class NodeReference(BaseModelObject):
56
+ space: str = Field(
57
+ description="Id of the space hosting (containing) the node.",
58
+ min_length=1,
59
+ max_length=43,
60
+ pattern=SPACE_FORMAT_PATTERN,
61
+ )
62
+ external_id: str = Field(
63
+ description="External-id of the node.",
64
+ min_length=1,
65
+ max_length=255,
66
+ pattern=INSTANCE_ID_PATTERN,
67
+ )
68
+
69
+
70
+ class ContainerDirectReference(BaseModelObject):
71
+ source: ContainerReference = Field(
72
+ description="Reference to the container from where this relation is inherited.",
73
+ )
74
+ identifier: str = Field(
75
+ description="Identifier of the relation in the source container.",
76
+ min_length=1,
77
+ max_length=255,
78
+ pattern=CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
79
+ )
80
+
81
+
82
+ class ViewDirectReference(BaseModelObject):
83
+ source: ViewReference = Field(
84
+ description="Reference to the view from where this relation is inherited.",
85
+ )
86
+ identifier: str = Field(
87
+ description="Identifier of the relation in the source view.",
88
+ min_length=1,
89
+ max_length=255,
90
+ pattern=CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
91
+ )
@@ -0,0 +1,198 @@
1
+ from abc import ABC
2
+ from typing import Annotated, Literal
3
+
4
+ from pydantic import Field, Json
5
+
6
+ from ._base import BaseModelObject, Resource, WriteableResource
7
+ from ._constants import CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN
8
+ from ._data_types import DataType
9
+ from ._references import ContainerDirectReference, ContainerReference, NodeReference, ViewDirectReference, ViewReference
10
+
11
+
12
+ class ViewPropertyDefinition(Resource, ABC):
13
+ connection_type: str
14
+
15
+
16
+ class ViewCoreProperty(ViewPropertyDefinition, ABC):
17
+ # Core properties do not have connection type in the API, but we add it here such that
18
+ # we can use it as a discriminator in unions. The exclude=True ensures that it is not
19
+ # sent to the API.
20
+ connection_type: Literal["primary_property"] = Field(default="primary_property", exclude=True)
21
+ name: str | None = Field(
22
+ default=None,
23
+ description="Readable property name.",
24
+ max_length=255,
25
+ )
26
+ description: str | None = Field(
27
+ default=None,
28
+ description="Description of the content and suggested use for this property..",
29
+ max_length=1024,
30
+ )
31
+ container: ContainerReference = Field(
32
+ description="Reference to the container where this property is defined.",
33
+ )
34
+ container_property_identifier: str = Field(
35
+ description="Identifier of the property in the container.",
36
+ min_length=1,
37
+ max_length=255,
38
+ pattern=CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
39
+ )
40
+ source: ViewReference | None = Field(
41
+ default=None,
42
+ description="Indicates on what type a referenced direct relation is expected to be. "
43
+ "Only applicable for direct relation properties.",
44
+ )
45
+
46
+
47
+ class ViewCorePropertyRequest(ViewCoreProperty): ...
48
+
49
+
50
+ class ConstraintOrIndexState(BaseModelObject):
51
+ nullability: Literal["current", "pending", "failed"] | None = Field(
52
+ description="""For properties that have isNullable set to false, this field describes the validity of the
53
+ not-null constraint. It is not specified for nullable properties.
54
+
55
+ Possible values are:
56
+
57
+ "failed": The property contains null values, violating the constraint. This can occur if a property with
58
+ existing nulls was made non-nullable. New null values will still be rejected.
59
+ "current": The constraint is satisfied; all values in the property are not null.
60
+ "pending": The constraint validity has not yet been computed.
61
+ """
62
+ )
63
+
64
+
65
+ class ViewCorePropertyResponse(ViewCoreProperty, WriteableResource[ViewCorePropertyRequest]):
66
+ immutable: bool | None = Field(
67
+ default=None,
68
+ description="Should updates to this property be rejected after the initial population?",
69
+ )
70
+ nullable: bool | None = Field(
71
+ default=None,
72
+ description="Does this property need to be set to a value, or not?",
73
+ )
74
+ auto_increment: bool | None = Field(
75
+ default=None,
76
+ description="Increment the property based on its highest current value (max value).",
77
+ )
78
+ default_value: str | int | bool | dict[str, Json] | None = Field(
79
+ default=None,
80
+ description="Default value to use when you do not specify a value for the property.",
81
+ )
82
+ constraint_state: ConstraintOrIndexState = Field(
83
+ description="Describes the validity of constraints defined on this property"
84
+ )
85
+ type: DataType = Field(description="The type of data you can store in this property.")
86
+
87
+ def as_request(self) -> ViewCorePropertyRequest:
88
+ return ViewCorePropertyRequest.model_validate(self.model_dump(by_alias=True))
89
+
90
+
91
+ class ConnectionPropertyDefinition(ViewPropertyDefinition, ABC):
92
+ name: str | None = Field(
93
+ default=None,
94
+ description="Readable property name.",
95
+ max_length=255,
96
+ )
97
+ description: str | None = Field(
98
+ default=None,
99
+ description="Description of the content and suggested use for this property..",
100
+ max_length=1024,
101
+ )
102
+
103
+
104
+ class EdgeProperty(ConnectionPropertyDefinition, ABC):
105
+ source: ViewReference = Field(
106
+ description="The target node(s) of this connection can be read through the view specified in 'source'."
107
+ )
108
+ type: NodeReference = Field(
109
+ description="Reference to the node pointed to by the direct relation. The reference consists of a "
110
+ "space and an external-id."
111
+ )
112
+ edge_source: ViewReference | None = Field(
113
+ None, description="The edge(s) of this connection can be read through the view specified in 'edgeSource'."
114
+ )
115
+ direction: Literal["outwards", "inwards"] = Field(
116
+ "outwards", description="The direction of the edge(s) of this connection."
117
+ )
118
+
119
+
120
+ class SingleEdgeProperty(EdgeProperty):
121
+ connection_type: Literal["single_edge_connection"] = "single_edge_connection"
122
+
123
+
124
+ class MultiEdgeProperty(EdgeProperty):
125
+ connection_type: Literal["multi_edge_connection"] = "multi_edge_connection"
126
+
127
+
128
+ class ReverseDirectRelationProperty(ConnectionPropertyDefinition, ABC):
129
+ source: ViewReference = Field(
130
+ description="The node(s) containing the direct relation property can be read "
131
+ "through the view specified in 'source'."
132
+ )
133
+
134
+
135
+ class SingleReverseDirectRelationPropertyRequest(ReverseDirectRelationProperty):
136
+ connection_type: Literal["single_reverse_direct_relation"] = "single_reverse_direct_relation"
137
+ # The API support through as either ViewDirectReference or ContainerDirectReference. However, in Neat
138
+ # we only use ContainerDirectReference. This is for simplicity and it improves performance as the server
139
+ # does not have to resolve the view to a container first.
140
+ through: ContainerDirectReference = Field(
141
+ description="The view of the node containing the direct relation property."
142
+ )
143
+
144
+
145
+ class SingleReverseDirectRelationPropertyResponse(
146
+ ReverseDirectRelationProperty, WriteableResource[SingleReverseDirectRelationPropertyRequest]
147
+ ):
148
+ connection_type: Literal["single_reverse_direct_relation"] = "single_reverse_direct_relation"
149
+ through: ContainerDirectReference | ViewDirectReference = Field(
150
+ description="The view of the node containing the direct relation property."
151
+ )
152
+
153
+ def as_request(self) -> SingleReverseDirectRelationPropertyRequest:
154
+ if isinstance(self.through, ViewDirectReference):
155
+ raise TypeError("Cannot convert to request when 'through' is a ViewDirectReference.")
156
+ return SingleReverseDirectRelationPropertyRequest.model_validate(self.model_dump(by_alias=True))
157
+
158
+
159
+ class MultiReverseDirectRelationPropertyRequest(ReverseDirectRelationProperty):
160
+ connection_type: Literal["multi_reverse_direct_relation"] = "multi_reverse_direct_relation"
161
+ # The API support through as either ViewDirectReference or ContainerDirectReference. However, in Neat
162
+ # we only use ContainerDirectReference. This is for simplicity and it improves performance as the server
163
+ # does not have to resolve the view to a container first.
164
+ through: ContainerDirectReference = Field(
165
+ description="The view of the node containing the direct relation property."
166
+ )
167
+
168
+
169
+ class MultiReverseDirectRelationPropertyResponse(
170
+ ReverseDirectRelationProperty, WriteableResource[MultiReverseDirectRelationPropertyRequest]
171
+ ):
172
+ connection_type: Literal["multi_reverse_direct_relation"] = "multi_reverse_direct_relation"
173
+ through: ContainerDirectReference | ViewDirectReference = Field(
174
+ description="The view of the node containing the direct relation property."
175
+ )
176
+
177
+ def as_request(self) -> MultiReverseDirectRelationPropertyRequest:
178
+ if isinstance(self.through, ViewDirectReference):
179
+ raise TypeError("Cannot convert to request when 'through' is a ViewDirectReference.")
180
+ return MultiReverseDirectRelationPropertyRequest.model_validate(self.model_dump(by_alias=True))
181
+
182
+
183
+ ViewRequestProperty = Annotated[
184
+ SingleEdgeProperty
185
+ | MultiEdgeProperty
186
+ | SingleReverseDirectRelationPropertyRequest
187
+ | MultiReverseDirectRelationPropertyRequest
188
+ | ViewCorePropertyRequest,
189
+ Field(discriminator="connection_type"),
190
+ ]
191
+ ViewResponseProperty = Annotated[
192
+ SingleEdgeProperty
193
+ | MultiEdgeProperty
194
+ | SingleReverseDirectRelationPropertyResponse
195
+ | MultiReverseDirectRelationPropertyResponse
196
+ | ViewCorePropertyResponse,
197
+ Field(discriminator="connection_type"),
198
+ ]
@@ -0,0 +1,161 @@
1
+ import re
2
+ from abc import ABC
3
+ from typing import Literal, TypeVar
4
+
5
+ from pydantic import Field, Json, field_validator, model_validator
6
+
7
+ from cognite.neat._utils.text import humanize_collection
8
+
9
+ from ._base import Resource, WriteableResource
10
+ from ._constants import (
11
+ CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
12
+ DM_EXTERNAL_ID_PATTERN,
13
+ DM_VERSION_PATTERN,
14
+ FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS,
15
+ FORBIDDEN_CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER,
16
+ SPACE_FORMAT_PATTERN,
17
+ )
18
+ from ._references import ContainerReference, ViewReference
19
+ from ._view_property import (
20
+ ViewRequestProperty,
21
+ ViewResponseProperty,
22
+ )
23
+
24
+ KEY_PATTERN = re.compile(CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN)
25
+
26
+
27
+ class View(Resource, ABC):
28
+ space: str = Field(
29
+ description="Id of the space that the view belongs to.",
30
+ min_length=1,
31
+ max_length=43,
32
+ pattern=SPACE_FORMAT_PATTERN,
33
+ )
34
+ external_id: str = Field(
35
+ description="External-id of the view.",
36
+ min_length=1,
37
+ max_length=255,
38
+ pattern=DM_EXTERNAL_ID_PATTERN,
39
+ )
40
+ version: str = Field(
41
+ description="Version of the view.",
42
+ max_length=43,
43
+ pattern=DM_VERSION_PATTERN,
44
+ )
45
+ name: str | None = Field(
46
+ default=None,
47
+ description="name for the view.",
48
+ max_length=255,
49
+ )
50
+ description: str | None = Field(
51
+ default=None,
52
+ description="Description of the view.",
53
+ max_length=1024,
54
+ )
55
+ filter: dict[str, Json] | None = Field(
56
+ default=None,
57
+ description="A filter Domain Specific Language (DSL) used to create advanced filter queries.",
58
+ )
59
+ implements: list[ViewReference] | None = Field(
60
+ default=None,
61
+ description="References to the views from where this view will inherit properties.",
62
+ )
63
+
64
+ @model_validator(mode="before")
65
+ def set_connection_type_on_primary_properties(cls, data: dict) -> dict:
66
+ if "properties" not in data:
67
+ return data
68
+ properties = data["properties"]
69
+ if not isinstance(properties, dict):
70
+ return data
71
+ # We assume all properties without connectionType are core properties.
72
+ # The reason we set connectionType it easy for pydantic to discriminate the union.
73
+ # This also leads to better error messages, as if there is a union and pydantic do not know which
74
+ # type to pick it will give errors from all type in the union.
75
+ for prop in properties.values():
76
+ if isinstance(prop, dict) and "connectionType" not in prop:
77
+ prop["connectionType"] = "primary_property"
78
+ return data
79
+
80
+ @field_validator("external_id", mode="after")
81
+ def check_forbidden_external_id_value(cls, val: str) -> str:
82
+ """Check the external_id not present in forbidden set"""
83
+ if val in FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS:
84
+ raise ValueError(
85
+ f"'{val}' is a reserved view External ID. Reserved External IDs are: "
86
+ f"{humanize_collection(FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS)}"
87
+ )
88
+ return val
89
+
90
+
91
+ class ViewRequest(View):
92
+ properties: dict[str, ViewRequestProperty] = Field(
93
+ description="View with included properties and expected edges, indexed by a unique space-local identifier."
94
+ )
95
+
96
+ @field_validator("properties", mode="after")
97
+ def validate_properties_identifier(cls, val: dict[str, ViewRequestProperty]) -> dict[str, ViewRequestProperty]:
98
+ """Validate properties Identifier"""
99
+ return _validate_properties_keys(val)
100
+
101
+
102
+ class ViewResponse(View, WriteableResource[ViewRequest]):
103
+ properties: dict[str, ViewResponseProperty] = Field(
104
+ description="List of properties and connections included in this view."
105
+ )
106
+
107
+ created_time: int = Field(
108
+ description="When the view was created. The number of milliseconds since 00:00:00 Thursday, 1 January 1970, "
109
+ "Coordinated Universal Time (UTC), minus leap seconds."
110
+ )
111
+ last_updated_time: int = Field(
112
+ description="When the view was last updated. The number of milliseconds since 00:00:00 Thursday, "
113
+ "1 January 1970, Coordinated Universal Time (UTC), minus leap seconds."
114
+ )
115
+ writable: bool = Field(
116
+ description="oes the view support write operations, i.e. is it writable? "
117
+ "You can write to a view if the view maps all non-nullable properties."
118
+ )
119
+ queryable: bool = Field(
120
+ description="Does the view support queries, i.e. is it queryable? You can query a view if "
121
+ "it either has a filter or at least one property mapped to a container."
122
+ )
123
+ used_for: Literal["node", "edge", "all"] = Field(description="Should this operation apply to nodes, edges or both.")
124
+ is_global: bool = Field(description="Is this a global view.")
125
+ mapped_containers: list[ContainerReference] = Field(
126
+ description="List of containers with properties mapped by this view."
127
+ )
128
+
129
+ @field_validator("properties", mode="after")
130
+ def validate_properties_identifier(cls, val: dict[str, ViewResponseProperty]) -> dict[str, ViewResponseProperty]:
131
+ """Validate properties Identifier"""
132
+ return _validate_properties_keys(val)
133
+
134
+ def as_request(self) -> ViewRequest:
135
+ dumped = self.model_dump(by_alias=True, exclude={"properties"})
136
+ dumped["properties"] = {
137
+ key: value.as_request().model_dump(by_alias=True)
138
+ if isinstance(value, WriteableResource)
139
+ else value.model_dump(by_alias=True)
140
+ for key, value in self.properties.items()
141
+ }
142
+ return ViewRequest.model_validate(dumped)
143
+
144
+
145
+ T_Property = TypeVar("T_Property")
146
+
147
+
148
+ def _validate_properties_keys(properties: dict[str, T_Property]) -> dict[str, T_Property]:
149
+ """Validate keys of a properties dictionary."""
150
+ errors: list[str] = []
151
+ for key in properties:
152
+ if not KEY_PATTERN.match(key):
153
+ errors.append(f"Property '{key}' does not match the required pattern: {KEY_PATTERN.pattern}")
154
+ if key in FORBIDDEN_CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER:
155
+ errors.append(
156
+ f"'{key}' is a reserved property identifier. Reserved identifiers are: "
157
+ f"{humanize_collection(FORBIDDEN_CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER)}"
158
+ )
159
+ if errors:
160
+ raise ValueError("; ".join(errors))
161
+ return properties
@@ -4,6 +4,9 @@ from typing import Any
4
4
  from pydantic import BaseModel, Field, field_validator, model_serializer
5
5
 
6
6
  from ._constants import (
7
+ PREFIX_PATTERN,
8
+ SUFFIX_PATTERN,
9
+ VERSION_PATTERN,
7
10
  Undefined,
8
11
  Unknown,
9
12
  _UndefinedType,
@@ -15,10 +18,8 @@ from ._constants import (
15
18
  class Entity(BaseModel, extra="ignore", populate_by_name=True):
16
19
  """Entity is a concept, class, property, datatype in semantics sense."""
17
20
 
18
- prefix: str | _UndefinedType = Field(
19
- default=Undefined, pattern=r"^[a-zA-Z][a-zA-Z0-9_-]{0,41}[a-zA-Z0-9]?$", min_length=1, max_length=43
20
- )
21
- suffix: str = Field(min_length=1, max_length=255, pattern=r"^[a-zA-Z0-9._~?@!$&'*+,;=%-]+$")
21
+ prefix: str | _UndefinedType = Field(default=Undefined, pattern=PREFIX_PATTERN, min_length=1, max_length=43)
22
+ suffix: str = Field(min_length=1, max_length=255, pattern=SUFFIX_PATTERN)
22
23
 
23
24
  @model_serializer(when_used="unless-none", return_type=str)
24
25
  def as_str(self) -> str:
@@ -88,7 +89,7 @@ class Entity(BaseModel, extra="ignore", populate_by_name=True):
88
89
 
89
90
 
90
91
  class ConceptEntity(Entity):
91
- version: str | None = None
92
+ version: str | None = Field(default=None, pattern=VERSION_PATTERN, max_length=43)
92
93
 
93
94
 
94
95
  class UnknownEntity(ConceptEntity):
@@ -15,3 +15,8 @@ class _UnknownType(BaseModel):
15
15
  # This is a trick to make Undefined and Unknown singletons
16
16
  Undefined = _UndefinedType()
17
17
  Unknown = _UnknownType()
18
+
19
+
20
+ PREFIX_PATTERN = r"^[a-zA-Z][a-zA-Z0-9_-]{0,41}[a-zA-Z0-9]?$"
21
+ SUFFIX_PATTERN = r"^[a-zA-Z0-9._~?@!$&'*+,;=%-]+$"
22
+ VERSION_PATTERN = r"^[a-zA-Z0-9]([.a-zA-Z0-9_-]{0,41}[a-zA-Z0-9])?$"
cognite/neat/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.123.38"
1
+ __version__ = "0.123.40"
2
2
  __engine__ = "^2.0.4"
@@ -302,7 +302,7 @@ class DateTime(DataType):
302
302
  python = datetime
303
303
  dms = dms.Timestamp
304
304
  graphql = "Timestamp"
305
- xsd = "dateTimeStamp"
305
+ xsd = "dateTime"
306
306
  sql = "TIMESTAMP"
307
307
 
308
308
  name: typing.Literal["dateTime"] = "dateTime"
@@ -43,9 +43,12 @@ class DictExtractor(BaseExtractor):
43
43
  self, key: str, value: Any, unpack_json: bool
44
44
  ) -> Iterable[tuple[str, Literal | URIRef]]:
45
45
  if key in self.uri_ref_keys and not isinstance(value, dict | list):
46
+ # exist if key is meant to form a URIRef
46
47
  yield key, URIRef(self.namespace[urllib.parse.quote(value)])
47
- if isinstance(value, str | float | bool | int):
48
+ elif isinstance(value, float | bool | int):
48
49
  yield key, Literal(value)
50
+ elif isinstance(value, str):
51
+ yield key, Literal(string_to_ideal_type(value)) if self.str_to_ideal_type else Literal(value)
49
52
  elif isinstance(value, dict) and unpack_json:
50
53
  yield from self._unpack_json(value)
51
54
  elif isinstance(value, dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognite-neat
3
- Version: 0.123.38
3
+ Version: 0.123.40
4
4
  Summary: Knowledge graph transformation
5
5
  Project-URL: Documentation, https://cognite-neat.readthedocs-hosted.com/
6
6
  Project-URL: Homepage, https://cognite-neat.readthedocs-hosted.com/
@@ -1,6 +1,6 @@
1
1
  cognite/neat/__init__.py,sha256=Lo4DbjDOwnhCYUoAgPp5RG1fDdF7OlnomalTe7n1ydw,211
2
2
  cognite/neat/_issues.py,sha256=uv0fkkWwTKqNmTmHqyoBB3L6yMCh42EZpEkLGmIJYOY,812
3
- cognite/neat/_version.py,sha256=AcC3bBw58APEu-16ayi6h9wHbjFkLKdwAnhBj7XMeqk,47
3
+ cognite/neat/_version.py,sha256=EIGI3787UrfBLzGRh7i7dj6FiXl8fYEyp70inEeAqk8,47
4
4
  cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  cognite/neat/_data_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  cognite/neat/_data_model/_constants.py,sha256=NGGvWHlQqhkkSBP_AqoofGYjNph3SiZX6QPINlMsy04,107
@@ -8,19 +8,24 @@ cognite/neat/_data_model/_identifiers.py,sha256=a0LcQ_h0NffxSKTCrzCDpYkrlaUTk-D_
8
8
  cognite/neat/_data_model/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  cognite/neat/_data_model/models/conceptual/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  cognite/neat/_data_model/models/conceptual/_base.py,sha256=SFkoBJDM51pqew_isHFJoB20OgfofpwVRnTrg-rKkNY,710
11
+ cognite/neat/_data_model/models/conceptual/_concept.py,sha256=0Pk4W2TJ_Y0Z7oPHpzely1kPXrAkmkyqw6a0n3il6LY,2248
11
12
  cognite/neat/_data_model/models/conceptual/_properties.py,sha256=CpF37vJYBTLT4DH4ZOu2U-JyWtkb_27V8fw52qiaE_k,4007
12
- cognite/neat/_data_model/models/dms/__init__.py,sha256=0YalizBE9PK_5JtzG1_fhrRhrRuqCUsWwWBkY4X4PHo,1876
13
+ cognite/neat/_data_model/models/conceptual/_property.py,sha256=CpF37vJYBTLT4DH4ZOu2U-JyWtkb_27V8fw52qiaE_k,4007
14
+ cognite/neat/_data_model/models/dms/__init__.py,sha256=WDF6VOPdVbH5QX1Z_k5fLqxY1Joqy94IEEpPs69UnrQ,3477
13
15
  cognite/neat/_data_model/models/dms/_base.py,sha256=R8SP3Zi9daTBqewYKGjuNEkrWc-j91f-6t34CN-9YJ0,719
14
- cognite/neat/_data_model/models/dms/_constants.py,sha256=tLyle1N_ekNBhANleG7GvZB85P4uGaoWl2mqeR-vfmY,1272
16
+ cognite/neat/_data_model/models/dms/_constants.py,sha256=wBkLjAPwufPc2naxOfPA1XC0CM2RbDbo6Dpiv9dPrew,1344
15
17
  cognite/neat/_data_model/models/dms/_constraints.py,sha256=7Nv-EoNl6lSHUQh6r15Ei0LzR3gWV006K3RlAi1Ic68,956
16
- cognite/neat/_data_model/models/dms/_container.py,sha256=ovyAwsh2ACt0j9-j6ooDOdUl0uKlGdkjYA2UUvJfHrw,5756
18
+ cognite/neat/_data_model/models/dms/_container.py,sha256=SgrMCRo04A1gqayJsCia6oMhYT7q3NaeMBZMOQNI-lA,5967
19
+ cognite/neat/_data_model/models/dms/_data_model.py,sha256=vm_MPKdNAh9Ig4J4yz8gHv-qam_1rnH4SGwWXzPFPtw,2316
17
20
  cognite/neat/_data_model/models/dms/_data_types.py,sha256=XSGQWVzYFRYAzKsDln20nC2kqiHQC6JAvDeTzCBPT-0,5202
18
21
  cognite/neat/_data_model/models/dms/_indexes.py,sha256=MsiHbGCH50QaF1mw_pIY-AZ5dY37vS3fQECOd2rBXWo,780
19
- cognite/neat/_data_model/models/dms/_references.py,sha256=yrmMo2JNfupG3tppdjLOaEhUAsRSIjZlBOI6hCol1z4,613
22
+ cognite/neat/_data_model/models/dms/_references.py,sha256=OLqp3EFMJDOl4Zaw2IdtM2vPWeDZf6VMYPgjNX6R0Jc,2532
20
23
  cognite/neat/_data_model/models/dms/_space.py,sha256=-1LkRmQaAdIj2EYDcVv5Mcejl5uswgAEVv7DztpIUk4,1680
24
+ cognite/neat/_data_model/models/dms/_view_property.py,sha256=wiRxb5qtzGnBooBUzN7xtwMBZiZ9aRFcN5ML4lCTXdQ,8331
25
+ cognite/neat/_data_model/models/dms/_views.py,sha256=mpIKOgMs5WkUZ8fupcHGOrcJtDH9o4f0fhuB3Pua5OE,6435
21
26
  cognite/neat/_data_model/models/entities/__init__.py,sha256=qdtIpyy2hjfdMwEHya-efOCvWassaalQla15RnqK00E,798
22
- cognite/neat/_data_model/models/entities/_base.py,sha256=PREgBqc6DM9pLn3VXFWxPBMVK0sexl6s3twSHn9wsf0,3874
23
- cognite/neat/_data_model/models/entities/_constants.py,sha256=P56zgsL2xqfegWOxEAyPm9qrZcxrjb1ZXqMG7cDmQxc,333
27
+ cognite/neat/_data_model/models/entities/_base.py,sha256=PaNrD29iwxuqTpRWbmESMTxRhhKXmRyDF_cLZEC69dg,3927
28
+ cognite/neat/_data_model/models/entities/_constants.py,sha256=EK9Bus8UgFgxK5cVFMTAqWSl6aWkDe7d59hpUmlHlBs,517
24
29
  cognite/neat/_data_model/models/entities/_data_types.py,sha256=DfdEWGek7gODro-_0SiiInhPGwul4zn-ASACQfn8HUY,2838
25
30
  cognite/neat/_data_model/models/entities/_identifiers.py,sha256=uBiK4ot3V0b_LGXuJ7bfha6AEcFI3p2letr1z2iSvig,1923
26
31
  cognite/neat/_data_model/models/entities/_parser.py,sha256=ZLGw0cFV-B7JuaAUJ65Jbjf6o-vidz9_BZvilS6lAZw,6455
@@ -85,7 +90,7 @@ cognite/neat/v0/core/_data_model/models/_base_unverified.py,sha256=1Wfbp-tJaEF6h
85
90
  cognite/neat/v0/core/_data_model/models/_base_verified.py,sha256=9EhRUEJ7ma-MnEFvXeQg6ew_gAcfLEapkckPX6mrlYQ,15142
86
91
  cognite/neat/v0/core/_data_model/models/_import_contexts.py,sha256=_agCsWvq_ky_Np83UO1qRSlOodlBSLDFp29rB5Ty5hw,3439
87
92
  cognite/neat/v0/core/_data_model/models/_types.py,sha256=A7lKYqGlEGRvXpsCdx-XGcFony0VeNCk9tRxeQpF6Po,5503
88
- cognite/neat/v0/core/_data_model/models/data_types.py,sha256=Px1PeZmq23iaVWhOgJNA5VZAFrAym6dipbd3Fw4SnFU,10547
93
+ cognite/neat/v0/core/_data_model/models/data_types.py,sha256=qZIlRWaOGqfq97iRAP0lVNNXUAKxwy0zJoQex4uoqcA,10542
89
94
  cognite/neat/v0/core/_data_model/models/conceptual/__init__.py,sha256=9A6myEV8s0-LqdXejaljqPj8S0pIpUL75rNdRDZzyR8,585
90
95
  cognite/neat/v0/core/_data_model/models/conceptual/_unverified.py,sha256=UvqjGxVQsf_cNCYf726TXgnPplUy3iBuI-nUp0TclD0,6539
91
96
  cognite/neat/v0/core/_data_model/models/conceptual/_validation.py,sha256=oaXRe1RfwsCQ_D5-IdXJf6yZ4Eug_PLu-bDklADYGV4,13895
@@ -123,7 +128,7 @@ cognite/neat/v0/core/_instances/examples/__init__.py,sha256=yAjHVY3b5jOjmbW-iLbh
123
128
  cognite/neat/v0/core/_instances/examples/skos-capturing-sheet-wind-topics.xlsx,sha256=CV_yK5ZSbYS_ktfIZUPD8Sevs47zpswLXQUDFkGE4Gw,45798
124
129
  cognite/neat/v0/core/_instances/extractors/__init__.py,sha256=x8nnTw8io_9INZuiHzWe__yB6G9GUdjEeaWGAx_MzHk,2212
125
130
  cognite/neat/v0/core/_instances/extractors/_base.py,sha256=dz0xAHDA0EUofRRxja9IKWoAXl3_vY4mnxO5We0LLDw,1974
126
- cognite/neat/v0/core/_instances/extractors/_dict.py,sha256=7Qa0oLdKvUTBSshUK3g4T6n_6UE3ncaGBUXXzwveS_Q,4337
131
+ cognite/neat/v0/core/_instances/extractors/_dict.py,sha256=wsHDEa-VcoLxayWm5BONw3LyeT-jF8n4sBSInUXRZ0k,4529
127
132
  cognite/neat/v0/core/_instances/extractors/_dms.py,sha256=DFYSNd3wU-MCCDddojH6KX1wsx1gLz9fv34-WfnjwBE,13858
128
133
  cognite/neat/v0/core/_instances/extractors/_dms_graph.py,sha256=VS8--8BH8cy9f1JUek_g3vIC4c6VkzBXhvPKqHydKjc,10228
129
134
  cognite/neat/v0/core/_instances/extractors/_mock_graph_generator.py,sha256=3WxMWXXxQdth7Na6XMhNeAfqUKxK-8S8CEKAEPKBMDk,15954
@@ -223,7 +228,7 @@ cognite/neat/v0/session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4
223
228
  cognite/neat/v0/session/engine/_import.py,sha256=1QxA2_EK613lXYAHKQbZyw2yjo5P9XuiX4Z6_6-WMNQ,169
224
229
  cognite/neat/v0/session/engine/_interface.py,sha256=3W-cYr493c_mW3P5O6MKN1xEQg3cA7NHR_ev3zdF9Vk,533
225
230
  cognite/neat/v0/session/engine/_load.py,sha256=u0x7vuQCRoNcPt25KJBJRn8sJabonYK4vtSZpiTdP4k,5201
226
- cognite_neat-0.123.38.dist-info/METADATA,sha256=iDFSXJ075Uh7qGQoJ-CP6VsKePjEuOdiKjf45BFYKWQ,9148
227
- cognite_neat-0.123.38.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
228
- cognite_neat-0.123.38.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
229
- cognite_neat-0.123.38.dist-info/RECORD,,
231
+ cognite_neat-0.123.40.dist-info/METADATA,sha256=922IsoPiMWLcfFWeR64XxhshmVXVHh8iI8HSqODPWrY,9148
232
+ cognite_neat-0.123.40.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
233
+ cognite_neat-0.123.40.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
234
+ cognite_neat-0.123.40.dist-info/RECORD,,