cognite-neat 0.123.36__py3-none-any.whl → 0.123.37__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.
- cognite/neat/_data_model/models/conceptual/__init__.py +0 -0
- cognite/neat/_data_model/models/conceptual/_base.py +18 -0
- cognite/neat/_data_model/models/conceptual/_properties.py +104 -0
- cognite/neat/_data_model/models/dms/__init__.py +61 -2
- cognite/neat/_data_model/models/dms/_constants.py +45 -1
- cognite/neat/_data_model/models/dms/_constraints.py +30 -0
- cognite/neat/_data_model/models/dms/_container.py +152 -0
- cognite/neat/_data_model/models/dms/_data_types.py +167 -0
- cognite/neat/_data_model/models/dms/_indexes.py +26 -0
- cognite/neat/_data_model/models/dms/_references.py +22 -0
- cognite/neat/_data_model/models/entities/__init__.py +3 -0
- cognite/neat/_issues.py +33 -0
- cognite/neat/_utils/text.py +40 -0
- cognite/neat/_utils/validation.py +81 -0
- cognite/neat/_version.py +1 -1
- cognite/neat/v0/core/_data_model/exporters/__init__.py +1 -2
- cognite/neat/v0/core/_data_model/exporters/{_data_model2ontology.py → _data_model2semantic_model.py} +81 -88
- {cognite_neat-0.123.36.dist-info → cognite_neat-0.123.37.dist-info}/METADATA +1 -1
- {cognite_neat-0.123.36.dist-info → cognite_neat-0.123.37.dist-info}/RECORD +21 -10
- {cognite_neat-0.123.36.dist-info → cognite_neat-0.123.37.dist-info}/WHEEL +0 -0
- {cognite_neat-0.123.36.dist-info → cognite_neat-0.123.37.dist-info}/licenses/LICENSE +0 -0
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
from pydantic.alias_generators import to_camel
|
|
3
|
+
|
|
4
|
+
from cognite.neat._data_model._identifiers import URI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseModelObject(BaseModel, alias_generator=to_camel, extra="ignore", populate_by_name=True):
|
|
8
|
+
"""Base class for all object. This includes resources and nested objects."""
|
|
9
|
+
|
|
10
|
+
...
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ResourceMetadata(BaseModelObject):
|
|
14
|
+
name: str | None = Field(
|
|
15
|
+
None, description="Human readable / display name of resource being described.", max_length=1024
|
|
16
|
+
)
|
|
17
|
+
description: str | None = Field(None, description="The description of the resource.", max_length=255)
|
|
18
|
+
uri: URI | None = Field(None, description="The URI of the resource being described.")
|
|
@@ -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
|
|
@@ -1,10 +1,69 @@
|
|
|
1
|
-
from ._base import Resource, WriteableResource
|
|
2
|
-
from .
|
|
1
|
+
from cognite.neat._data_model.models.dms._base import Resource, WriteableResource
|
|
2
|
+
from cognite.neat._data_model.models.dms._constraints import (
|
|
3
|
+
Constraint,
|
|
4
|
+
ConstraintDefinition,
|
|
5
|
+
RequiresConstraintDefinition,
|
|
6
|
+
UniquenessConstraintDefinition,
|
|
7
|
+
)
|
|
8
|
+
from cognite.neat._data_model.models.dms._container import (
|
|
9
|
+
Container,
|
|
10
|
+
ContainerPropertyDefinition,
|
|
11
|
+
ContainerRequest,
|
|
12
|
+
ContainerResponse,
|
|
13
|
+
)
|
|
14
|
+
from cognite.neat._data_model.models.dms._data_types import (
|
|
15
|
+
BooleanProperty,
|
|
16
|
+
DataType,
|
|
17
|
+
DateProperty,
|
|
18
|
+
DirectNodeRelation,
|
|
19
|
+
EnumProperty,
|
|
20
|
+
FileCDFExternalIdReference,
|
|
21
|
+
FloatProperty,
|
|
22
|
+
Int32Property,
|
|
23
|
+
Int64Property,
|
|
24
|
+
JSONProperty,
|
|
25
|
+
ListablePropertyTypeDefinition,
|
|
26
|
+
PropertyTypeDefinition,
|
|
27
|
+
SequenceCDFExternalIdReference,
|
|
28
|
+
TextProperty,
|
|
29
|
+
TimeseriesCDFExternalIdReference,
|
|
30
|
+
TimestampProperty,
|
|
31
|
+
)
|
|
32
|
+
from cognite.neat._data_model.models.dms._indexes import BtreeIndex, Index, IndexDefinition, InvertedIndex
|
|
33
|
+
from cognite.neat._data_model.models.dms._space import Space, SpaceRequest, SpaceResponse
|
|
3
34
|
|
|
4
35
|
__all__ = [
|
|
36
|
+
"BooleanProperty",
|
|
37
|
+
"BtreeIndex",
|
|
38
|
+
"Constraint",
|
|
39
|
+
"ConstraintDefinition",
|
|
40
|
+
"Container",
|
|
41
|
+
"ContainerPropertyDefinition",
|
|
42
|
+
"ContainerRequest",
|
|
43
|
+
"ContainerResponse",
|
|
44
|
+
"DataType",
|
|
45
|
+
"DateProperty",
|
|
46
|
+
"DirectNodeRelation",
|
|
47
|
+
"EnumProperty",
|
|
48
|
+
"FileCDFExternalIdReference",
|
|
49
|
+
"FloatProperty",
|
|
50
|
+
"Index",
|
|
51
|
+
"IndexDefinition",
|
|
52
|
+
"Int32Property",
|
|
53
|
+
"Int64Property",
|
|
54
|
+
"InvertedIndex",
|
|
55
|
+
"JSONProperty",
|
|
56
|
+
"ListablePropertyTypeDefinition",
|
|
57
|
+
"PropertyTypeDefinition",
|
|
58
|
+
"RequiresConstraintDefinition",
|
|
5
59
|
"Resource",
|
|
60
|
+
"SequenceCDFExternalIdReference",
|
|
6
61
|
"Space",
|
|
7
62
|
"SpaceRequest",
|
|
8
63
|
"SpaceResponse",
|
|
64
|
+
"TextProperty",
|
|
65
|
+
"TimeseriesCDFExternalIdReference",
|
|
66
|
+
"TimestampProperty",
|
|
67
|
+
"UniquenessConstraintDefinition",
|
|
9
68
|
"WriteableResource",
|
|
10
69
|
]
|
|
@@ -1,2 +1,46 @@
|
|
|
1
|
-
SPACE_FORMAT_PATTERN = r"[a-zA-Z][a-zA-Z0-9_-]{0,41}[a-zA-Z0-9]?$"
|
|
1
|
+
SPACE_FORMAT_PATTERN = r"^[a-zA-Z][a-zA-Z0-9_-]{0,41}[a-zA-Z0-9]?$"
|
|
2
|
+
DM_EXTERNAL_ID_PATTERN = r"^[a-zA-Z]([a-zA-Z0-9_]{0,253}[a-zA-Z0-9])?$"
|
|
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}$"
|
|
5
|
+
ENUM_VALUE_IDENTIFIER_PATTERN = r"^[_A-Za-z][_0-9A-Za-z]{0,127}$"
|
|
6
|
+
FORBIDDEN_ENUM_VALUES = frozenset({"true", "false", "null"})
|
|
2
7
|
FORBIDDEN_SPACES = frozenset(["space", "cdf", "dms", "pg3", "shared", "system", "node", "edge"])
|
|
8
|
+
FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS = frozenset(
|
|
9
|
+
[
|
|
10
|
+
"Query",
|
|
11
|
+
"Mutation",
|
|
12
|
+
"Subscription",
|
|
13
|
+
"String",
|
|
14
|
+
"Int32",
|
|
15
|
+
"Int64",
|
|
16
|
+
"Int",
|
|
17
|
+
"Float32",
|
|
18
|
+
"Float64",
|
|
19
|
+
"Float",
|
|
20
|
+
"Timestamp",
|
|
21
|
+
"JSONObject",
|
|
22
|
+
"Date",
|
|
23
|
+
"Numeric",
|
|
24
|
+
"Boolean",
|
|
25
|
+
"PageInfo",
|
|
26
|
+
"File",
|
|
27
|
+
"Sequence",
|
|
28
|
+
"TimeSeries",
|
|
29
|
+
]
|
|
30
|
+
)
|
|
31
|
+
FORBIDDEN_CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER = frozenset(
|
|
32
|
+
[
|
|
33
|
+
"space",
|
|
34
|
+
"externalId",
|
|
35
|
+
"createdTime",
|
|
36
|
+
"lastUpdatedTime",
|
|
37
|
+
"deletedTime",
|
|
38
|
+
"edge_id",
|
|
39
|
+
"node_id",
|
|
40
|
+
"project_id",
|
|
41
|
+
"property_group",
|
|
42
|
+
"seq",
|
|
43
|
+
"tg_table_name",
|
|
44
|
+
"extensions",
|
|
45
|
+
]
|
|
46
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Annotated, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from ._base import BaseModelObject
|
|
7
|
+
from ._references import ContainerReference
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConstraintDefinition(BaseModelObject, ABC):
|
|
11
|
+
constraint_type: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UniquenessConstraintDefinition(ConstraintDefinition):
|
|
15
|
+
constraint_type: Literal["uniqueness"] = "uniqueness"
|
|
16
|
+
properties: list[str] = Field(
|
|
17
|
+
description="List of properties included in the constraint.", min_length=1, max_length=10
|
|
18
|
+
)
|
|
19
|
+
by_space: bool | None = Field(default=None, description="Whether to make the constraint space-specific.")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RequiresConstraintDefinition(ConstraintDefinition):
|
|
23
|
+
constraint_type: Literal["requires"] = "requires"
|
|
24
|
+
require: ContainerReference = Field(description="Reference to an existing container.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Constraint = Annotated[
|
|
28
|
+
UniquenessConstraintDefinition | RequiresConstraintDefinition,
|
|
29
|
+
Field(discriminator="constraint_type"),
|
|
30
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
from pydantic import Field, field_validator
|
|
6
|
+
from pydantic_core.core_schema import ValidationInfo
|
|
7
|
+
|
|
8
|
+
from cognite.neat._utils.text import humanize_collection
|
|
9
|
+
|
|
10
|
+
from ._base import BaseModelObject, Resource, WriteableResource
|
|
11
|
+
from ._constants import (
|
|
12
|
+
CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN,
|
|
13
|
+
DM_EXTERNAL_ID_PATTERN,
|
|
14
|
+
FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS,
|
|
15
|
+
FORBIDDEN_CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER,
|
|
16
|
+
SPACE_FORMAT_PATTERN,
|
|
17
|
+
)
|
|
18
|
+
from ._constraints import Constraint
|
|
19
|
+
from ._data_types import DataType
|
|
20
|
+
from ._indexes import Index
|
|
21
|
+
|
|
22
|
+
KEY_PATTERN = re.compile(CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ContainerPropertyDefinition(BaseModelObject):
|
|
26
|
+
immutable: bool | None = Field(
|
|
27
|
+
default=None,
|
|
28
|
+
description="Should updates to this property be rejected after the initial population?",
|
|
29
|
+
)
|
|
30
|
+
nullable: bool | None = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Does this property need to be set to a value, or not?",
|
|
33
|
+
)
|
|
34
|
+
auto_increment: bool | None = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
description="Increment the property based on its highest current value (max value).",
|
|
37
|
+
)
|
|
38
|
+
default_value: str | int | bool | dict[str, Any] | None = Field(
|
|
39
|
+
default=None,
|
|
40
|
+
description="Default value to use when you do not specify a value for the property.",
|
|
41
|
+
)
|
|
42
|
+
description: str | None = Field(
|
|
43
|
+
default=None,
|
|
44
|
+
description="Description of the content and suggested use for this property.",
|
|
45
|
+
max_length=1024,
|
|
46
|
+
)
|
|
47
|
+
name: str | None = Field(
|
|
48
|
+
default=None,
|
|
49
|
+
description="Readable property name.",
|
|
50
|
+
max_length=255,
|
|
51
|
+
)
|
|
52
|
+
type: DataType = Field(description="The type of data you can store in this property.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Container(Resource, ABC):
|
|
56
|
+
space: str = Field(
|
|
57
|
+
description="The workspace for the container, a unique identifier for the space.",
|
|
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 container.",
|
|
64
|
+
min_length=1,
|
|
65
|
+
max_length=255,
|
|
66
|
+
pattern=DM_EXTERNAL_ID_PATTERN,
|
|
67
|
+
)
|
|
68
|
+
name: str | None = Field(
|
|
69
|
+
default=None,
|
|
70
|
+
description="name for the container.",
|
|
71
|
+
max_length=255,
|
|
72
|
+
)
|
|
73
|
+
description: str | None = Field(
|
|
74
|
+
default=None,
|
|
75
|
+
description="Description of the container.",
|
|
76
|
+
max_length=1024,
|
|
77
|
+
)
|
|
78
|
+
used_for: Literal["node", "edge", "all"] | None = Field(
|
|
79
|
+
default=None,
|
|
80
|
+
description="Should this operation apply to nodes, edges or both.",
|
|
81
|
+
)
|
|
82
|
+
properties: dict[str, ContainerPropertyDefinition] = Field(
|
|
83
|
+
description="Set of properties to apply to the container.",
|
|
84
|
+
min_length=1,
|
|
85
|
+
)
|
|
86
|
+
constraints: dict[str, Constraint] | None = Field(
|
|
87
|
+
default=None,
|
|
88
|
+
description="Set of constraints to apply to the container.",
|
|
89
|
+
max_length=10,
|
|
90
|
+
)
|
|
91
|
+
indexes: dict[str, Index] | None = Field(
|
|
92
|
+
default=None,
|
|
93
|
+
description="Set of indexes to apply to the container.",
|
|
94
|
+
max_length=10,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@field_validator("indexes", "constraints", mode="after")
|
|
98
|
+
def validate_key_length(cls, val: dict[str, Any] | None, info: ValidationInfo) -> dict[str, Any] | None:
|
|
99
|
+
"""Validate keys"""
|
|
100
|
+
if not isinstance(val, dict):
|
|
101
|
+
return val
|
|
102
|
+
invalid_keys = {key for key in val.keys() if not (1 <= len(key) <= 43)}
|
|
103
|
+
if invalid_keys:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"{info.field_name} keys must be between 1 and 43 characters long. Invalid keys: "
|
|
106
|
+
f"{humanize_collection(invalid_keys)}"
|
|
107
|
+
)
|
|
108
|
+
return val
|
|
109
|
+
|
|
110
|
+
@field_validator("properties", mode="after")
|
|
111
|
+
def validate_property_keys(cls, val: dict[str, Any]) -> dict[str, Any]:
|
|
112
|
+
"""Validate property keys"""
|
|
113
|
+
if invalid_keys := {key for key in val if not KEY_PATTERN.match(key)}:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Property keys must match pattern '{CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN}'. "
|
|
116
|
+
f"Invalid keys: {humanize_collection(invalid_keys)}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if forbidden_keys := set(val.keys()).intersection(FORBIDDEN_CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER):
|
|
120
|
+
raise ValueError(
|
|
121
|
+
f"Property keys cannot be any of the following reserved values: {humanize_collection(forbidden_keys)}"
|
|
122
|
+
)
|
|
123
|
+
return val
|
|
124
|
+
|
|
125
|
+
@field_validator("external_id")
|
|
126
|
+
def check_forbidden_external_id_value(cls, val: str) -> str:
|
|
127
|
+
"""Check the external_id not present in forbidden set"""
|
|
128
|
+
if val in FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS:
|
|
129
|
+
raise ValueError(
|
|
130
|
+
f"{val!r} is a reserved container External ID. Reserved External IDs are: "
|
|
131
|
+
f"{humanize_collection(FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS)}"
|
|
132
|
+
)
|
|
133
|
+
return val
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ContainerRequest(Container): ...
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ContainerResponse(Container, WriteableResource[ContainerRequest]):
|
|
140
|
+
created_time: int = Field(
|
|
141
|
+
description="When the container was created. The number of milliseconds since 00:00:00 "
|
|
142
|
+
"Thursday, 1 January 1970, "
|
|
143
|
+
"Coordinated Universal Time (UTC), minus leap seconds."
|
|
144
|
+
)
|
|
145
|
+
last_updated_time: int = Field(
|
|
146
|
+
description="When the container was last updated. The number of milliseconds since 00:00:00 Thursday, "
|
|
147
|
+
"1 January 1970, Coordinated Universal Time (UTC), minus leap seconds."
|
|
148
|
+
)
|
|
149
|
+
is_global: bool = Field(description="Whether the container is a global container.")
|
|
150
|
+
|
|
151
|
+
def as_request(self) -> "ContainerRequest":
|
|
152
|
+
return ContainerRequest.model_validate(self.model_dump(by_alias=True))
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Annotated, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import Field, field_validator
|
|
5
|
+
|
|
6
|
+
from cognite.neat._utils.text import humanize_collection
|
|
7
|
+
|
|
8
|
+
from ._base import BaseModelObject
|
|
9
|
+
from ._constants import ENUM_VALUE_IDENTIFIER_PATTERN, FORBIDDEN_ENUM_VALUES, INSTANCE_ID_PATTERN
|
|
10
|
+
from ._references import ContainerReference
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PropertyTypeDefinition(BaseModelObject, ABC):
|
|
14
|
+
type: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ListablePropertyTypeDefinition(PropertyTypeDefinition, ABC):
|
|
18
|
+
list: bool | None = Field(
|
|
19
|
+
default=None,
|
|
20
|
+
description="Specifies that the data type is a list of values.",
|
|
21
|
+
)
|
|
22
|
+
max_list_size: int | None = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description="Specifies the maximum number of values in the list",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TextProperty(ListablePropertyTypeDefinition):
|
|
29
|
+
type: Literal["text"] = "text"
|
|
30
|
+
max_text_size: int | None = Field(
|
|
31
|
+
default=None,
|
|
32
|
+
description="Specifies the maximum size in bytes of the text property, when encoded with utf-8.",
|
|
33
|
+
)
|
|
34
|
+
collation: str | None = Field(
|
|
35
|
+
default=None,
|
|
36
|
+
description="he set of language specific rules - used when sorting text fields.",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Unit(BaseModelObject):
|
|
41
|
+
external_id: str = Field(
|
|
42
|
+
description="The external ID of the unit. Must match the unit in the Cognite Unit catalog.",
|
|
43
|
+
min_length=1,
|
|
44
|
+
max_length=256,
|
|
45
|
+
pattern=INSTANCE_ID_PATTERN,
|
|
46
|
+
)
|
|
47
|
+
source_unit: str | None = Field(
|
|
48
|
+
default=None,
|
|
49
|
+
description="The unit in the source system.",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class FloatProperty(ListablePropertyTypeDefinition, ABC):
|
|
54
|
+
unit: Unit | None = Field(default=None, description="The unit of the data stored in this property")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Float32Property(FloatProperty):
|
|
58
|
+
type: Literal["float32"] = "float32"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Float64Property(FloatProperty):
|
|
62
|
+
type: Literal["float64"] = "float64"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class BooleanProperty(ListablePropertyTypeDefinition):
|
|
66
|
+
type: Literal["boolean"] = "boolean"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Int32Property(ListablePropertyTypeDefinition):
|
|
70
|
+
type: Literal["int32"] = "int32"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Int64Property(ListablePropertyTypeDefinition):
|
|
74
|
+
type: Literal["int64"] = "int64"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TimestampProperty(ListablePropertyTypeDefinition):
|
|
78
|
+
type: Literal["timestamp"] = "timestamp"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class DateProperty(ListablePropertyTypeDefinition):
|
|
82
|
+
type: Literal["date"] = "date"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class JSONProperty(ListablePropertyTypeDefinition):
|
|
86
|
+
type: Literal["json"] = "json"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TimeseriesCDFExternalIdReference(ListablePropertyTypeDefinition):
|
|
90
|
+
type: Literal["timeseries"] = "timeseries"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class FileCDFExternalIdReference(ListablePropertyTypeDefinition):
|
|
94
|
+
type: Literal["file"] = "file"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class SequenceCDFExternalIdReference(ListablePropertyTypeDefinition):
|
|
98
|
+
type: Literal["sequence"] = "sequence"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class DirectNodeRelation(ListablePropertyTypeDefinition):
|
|
102
|
+
type: Literal["direct"] = "direct"
|
|
103
|
+
container: ContainerReference | None = Field(
|
|
104
|
+
default=None,
|
|
105
|
+
description="The (optional) required type for the node the direct relation points to. If specified, "
|
|
106
|
+
"the node must exist before the direct relation is referenced and of the specified type. "
|
|
107
|
+
"If no container specification is used, the node will be auto created with the built-in node "
|
|
108
|
+
"container type, and it does not explicitly have to be created before the node that references it.",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class EnumValue(BaseModelObject):
|
|
113
|
+
name: str | None = Field(
|
|
114
|
+
max_length=255,
|
|
115
|
+
description="The name of the enum value.",
|
|
116
|
+
)
|
|
117
|
+
description: str | None = Field(
|
|
118
|
+
default=None,
|
|
119
|
+
max_length=1024,
|
|
120
|
+
description="Description of the enum value.",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class EnumProperty(PropertyTypeDefinition):
|
|
125
|
+
type: Literal["enum"] = "enum"
|
|
126
|
+
unknown_value: str | None = Field(
|
|
127
|
+
default=None,
|
|
128
|
+
description="TThe value to use when the enum value is unknown. This can optionally be used to "
|
|
129
|
+
"provide forward-compatibility, Specifying what value to use if the client does not "
|
|
130
|
+
"recognize the returned value. It is not possible to ingest the unknown value, "
|
|
131
|
+
"but it must be part of the allowed values.",
|
|
132
|
+
)
|
|
133
|
+
values: dict[str, EnumValue] = Field(
|
|
134
|
+
description="A set of all possible values for the enum property.",
|
|
135
|
+
min_length=1,
|
|
136
|
+
max_length=32,
|
|
137
|
+
pattern=ENUM_VALUE_IDENTIFIER_PATTERN,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
@field_validator("values", mode="after")
|
|
141
|
+
def _valid_enum_value(cls, val: dict[str, EnumValue]) -> dict[str, EnumValue]:
|
|
142
|
+
invalid_enum_values = set(val.keys()).intersection(FORBIDDEN_ENUM_VALUES)
|
|
143
|
+
if invalid_enum_values:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
"Enum values cannot be any of the following reserved values: "
|
|
146
|
+
f"{humanize_collection(invalid_enum_values)}"
|
|
147
|
+
)
|
|
148
|
+
return val
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
DataType = Annotated[
|
|
152
|
+
TextProperty
|
|
153
|
+
| Float32Property
|
|
154
|
+
| Float64Property
|
|
155
|
+
| BooleanProperty
|
|
156
|
+
| Int32Property
|
|
157
|
+
| Int64Property
|
|
158
|
+
| TimestampProperty
|
|
159
|
+
| DateProperty
|
|
160
|
+
| JSONProperty
|
|
161
|
+
| TimeseriesCDFExternalIdReference
|
|
162
|
+
| FileCDFExternalIdReference
|
|
163
|
+
| SequenceCDFExternalIdReference
|
|
164
|
+
| DirectNodeRelation
|
|
165
|
+
| EnumProperty,
|
|
166
|
+
Field(discriminator="type"),
|
|
167
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Annotated, Literal
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from ._base import BaseModelObject
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IndexDefinition(BaseModelObject, ABC):
|
|
10
|
+
index_type: str
|
|
11
|
+
properties: list[str] = Field(description="List of properties to define the index across.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BtreeIndex(IndexDefinition):
|
|
15
|
+
index_type: Literal["btree"] = "btree"
|
|
16
|
+
by_space: bool | None = Field(default=None, description="Whether to make the index space-specific.")
|
|
17
|
+
cursorable: bool | None = Field(
|
|
18
|
+
default=None, description="Whether the index can be used for cursor-based pagination."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InvertedIndex(IndexDefinition):
|
|
23
|
+
index_type: Literal["inverted"] = "inverted"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Index = Annotated[BtreeIndex | InvertedIndex, Field(discriminator="index_type")]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from ._base import BaseModelObject
|
|
6
|
+
from ._constants import DM_EXTERNAL_ID_PATTERN, SPACE_FORMAT_PATTERN
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ContainerReference(BaseModelObject):
|
|
10
|
+
type: Literal["container"] = "container"
|
|
11
|
+
space: str = Field(
|
|
12
|
+
description="Id of the space hosting (containing) the container.",
|
|
13
|
+
min_length=1,
|
|
14
|
+
max_length=43,
|
|
15
|
+
pattern=SPACE_FORMAT_PATTERN,
|
|
16
|
+
)
|
|
17
|
+
external_id: str = Field(
|
|
18
|
+
description="External-id of the container.",
|
|
19
|
+
min_length=1,
|
|
20
|
+
max_length=255,
|
|
21
|
+
pattern=DM_EXTERNAL_ID_PATTERN,
|
|
22
|
+
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from ._base import ConceptEntity, UnknownEntity
|
|
1
2
|
from ._constants import Undefined, Unknown
|
|
2
3
|
from ._data_types import (
|
|
3
4
|
AnyURI,
|
|
@@ -24,6 +25,7 @@ __all__ = [
|
|
|
24
25
|
"URI",
|
|
25
26
|
"AnyURI",
|
|
26
27
|
"Boolean",
|
|
28
|
+
"ConceptEntity",
|
|
27
29
|
"DataType",
|
|
28
30
|
"Date",
|
|
29
31
|
"DateTime",
|
|
@@ -42,5 +44,6 @@ __all__ = [
|
|
|
42
44
|
"Timeseries",
|
|
43
45
|
"Undefined",
|
|
44
46
|
"Unknown",
|
|
47
|
+
"UnknownEntity",
|
|
45
48
|
"parse_entity",
|
|
46
49
|
]
|
cognite/neat/_issues.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ModelSyntaxError(BaseModel):
|
|
5
|
+
"""If any syntax error is found. Stop validation
|
|
6
|
+
and ask user to fix the syntax error first."""
|
|
7
|
+
|
|
8
|
+
message: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ImplementationWarning(BaseModel):
|
|
12
|
+
"""This is only for conceptual data model. It means that conversion to DMS
|
|
13
|
+
will fail unless user implements the missing part."""
|
|
14
|
+
|
|
15
|
+
message: str
|
|
16
|
+
fix: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ConsistencyError(BaseModel):
|
|
20
|
+
"""If any consistency error is found, the deployment of the data model will fail. For example,
|
|
21
|
+
if a reverse direct relations points to a non-existing direct relation. This is only relevant for
|
|
22
|
+
DMS model.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
message: str
|
|
26
|
+
fix: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Recommendation(BaseModel):
|
|
30
|
+
"""Best practice recommendation."""
|
|
31
|
+
|
|
32
|
+
message: str
|
|
33
|
+
fix: str | None = None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from collections.abc import Collection
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def humanize_collection(collection: Collection[Any], /, *, sort: bool = True, bind_word: str = "and") -> str:
|
|
6
|
+
"""Convert a collection of items to a human-readable string.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
collection: The collection of items to convert.
|
|
10
|
+
sort: Whether to sort the collection before converting. Default is True.
|
|
11
|
+
bind_word: The word to use to bind the last two items. Default is "and".
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
A human-readable string of the collection.
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
>>> humanize_collection(["b", "c", "a"])
|
|
18
|
+
'a, b and c'
|
|
19
|
+
>>> humanize_collection(["b", "c", "a"], sort=False)
|
|
20
|
+
'b, c and a'
|
|
21
|
+
>>> humanize_collection(["a", "b"])
|
|
22
|
+
'a and b'
|
|
23
|
+
>>> humanize_collection(["a"])
|
|
24
|
+
'a'
|
|
25
|
+
>>> humanize_collection([])
|
|
26
|
+
''
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
if not collection:
|
|
30
|
+
return ""
|
|
31
|
+
elif len(collection) == 1:
|
|
32
|
+
return str(next(iter(collection)))
|
|
33
|
+
|
|
34
|
+
strings = (str(item) for item in collection)
|
|
35
|
+
if sort:
|
|
36
|
+
sequence = sorted(strings)
|
|
37
|
+
else:
|
|
38
|
+
sequence = list(strings)
|
|
39
|
+
|
|
40
|
+
return f"{', '.join(sequence[:-1])} {bind_word} {sequence[-1]}"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from pydantic import ValidationError
|
|
2
|
+
from pydantic_core import ErrorDetails
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def humanize_validation_error(error: ValidationError) -> list[str]:
|
|
6
|
+
"""Converts a ValidationError to a human-readable format.
|
|
7
|
+
|
|
8
|
+
This overwrites the default error messages from Pydantic to be better suited for Toolkit users.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
error: The ValidationError to convert.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
A list of human-readable error messages.
|
|
15
|
+
"""
|
|
16
|
+
errors: list[str] = []
|
|
17
|
+
item: ErrorDetails
|
|
18
|
+
|
|
19
|
+
for item in error.errors(include_input=True, include_url=False):
|
|
20
|
+
loc = item["loc"]
|
|
21
|
+
error_type = item["type"]
|
|
22
|
+
if error_type == "missing":
|
|
23
|
+
msg = f"Missing required field: {loc[-1]!r}"
|
|
24
|
+
elif error_type == "extra_forbidden":
|
|
25
|
+
msg = f"Unused field: {loc[-1]!r}"
|
|
26
|
+
elif error_type == "value_error":
|
|
27
|
+
msg = str(item["ctx"]["error"])
|
|
28
|
+
elif error_type == "literal_error":
|
|
29
|
+
msg = f"{item['msg']}. Got {item['input']!r}."
|
|
30
|
+
elif error_type == "string_type":
|
|
31
|
+
msg = (
|
|
32
|
+
f"{item['msg']}. Got {item['input']!r} of type {type(item['input']).__name__}. "
|
|
33
|
+
f"Hint: Use double quotes to force string."
|
|
34
|
+
)
|
|
35
|
+
elif error_type == "model_type":
|
|
36
|
+
model_name = item["ctx"].get("class_name", "unknown")
|
|
37
|
+
msg = (
|
|
38
|
+
f"Input must be an object of type {model_name}. Got {item['input']!r} of "
|
|
39
|
+
f"type {type(item['input']).__name__}."
|
|
40
|
+
)
|
|
41
|
+
elif error_type.endswith("_type"):
|
|
42
|
+
msg = f"{item['msg']}. Got {item['input']!r} of type {type(item['input']).__name__}."
|
|
43
|
+
else:
|
|
44
|
+
# Default to the Pydantic error message
|
|
45
|
+
msg = item["msg"]
|
|
46
|
+
|
|
47
|
+
if error_type.endswith("dict_type") and len(loc) > 1:
|
|
48
|
+
# If this is a dict_type error for a JSON field, the location will be:
|
|
49
|
+
# dict[str,json-or-python[json=any,python=tagged-union[list[...],dict[str,...],str,bool,int,float,none]]]
|
|
50
|
+
# This is hard to read, so we simplify it to just the field name.
|
|
51
|
+
loc = tuple(["dict" if isinstance(x, str) and "json-or-python" in x else x for x in loc])
|
|
52
|
+
|
|
53
|
+
if len(loc) > 1 and error_type in {"extra_forbidden", "missing"}:
|
|
54
|
+
# We skip the last element as this is in the message already
|
|
55
|
+
msg = f"In {as_json_path(loc[:-1])} {msg[:1].casefold()}{msg[1:]}"
|
|
56
|
+
elif len(loc) > 1:
|
|
57
|
+
msg = f"In {as_json_path(loc)} {msg[:1].casefold()}{msg[1:]}"
|
|
58
|
+
elif len(loc) == 1 and isinstance(loc[0], str) and error_type not in {"extra_forbidden", "missing"}:
|
|
59
|
+
msg = f"In field {loc[0]} {msg[:1].casefold()}{msg[1:]}"
|
|
60
|
+
errors.append(msg)
|
|
61
|
+
return errors
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def as_json_path(loc: tuple[str | int, ...]) -> str:
|
|
65
|
+
"""Converts a location tuple to a JSON path.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
loc: The location tuple to convert.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
A JSON path string.
|
|
72
|
+
"""
|
|
73
|
+
if not loc:
|
|
74
|
+
return ""
|
|
75
|
+
# +1 to convert from 0-based to 1-based indexing
|
|
76
|
+
prefix = ""
|
|
77
|
+
if isinstance(loc[0], int):
|
|
78
|
+
prefix = "item "
|
|
79
|
+
|
|
80
|
+
suffix = ".".join([str(x) if isinstance(x, str) else f"[{x + 1}]" for x in loc]).replace(".[", "[")
|
|
81
|
+
return f"{prefix}{suffix}"
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.123.
|
|
1
|
+
__version__ = "0.123.37"
|
|
2
2
|
__engine__ = "^2.0.4"
|
|
@@ -2,7 +2,7 @@ from ._base import BaseExporter, CDFExporter
|
|
|
2
2
|
from ._data_model2dms import DMSExporter
|
|
3
3
|
from ._data_model2excel import ExcelExporter
|
|
4
4
|
from ._data_model2instance_template import InstanceTemplateExporter
|
|
5
|
-
from .
|
|
5
|
+
from ._data_model2semantic_model import GraphExporter, OWLExporter, SHACLExporter
|
|
6
6
|
from ._data_model2yaml import YAMLExporter
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
@@ -14,7 +14,6 @@ __all__ = [
|
|
|
14
14
|
"InstanceTemplateExporter",
|
|
15
15
|
"OWLExporter",
|
|
16
16
|
"SHACLExporter",
|
|
17
|
-
"SemanticDataModelExporter",
|
|
18
17
|
"YAMLExporter",
|
|
19
18
|
]
|
|
20
19
|
|
cognite/neat/v0/core/_data_model/exporters/{_data_model2ontology.py → _data_model2semantic_model.py}
RENAMED
|
@@ -32,6 +32,8 @@ if sys.version_info >= (3, 11):
|
|
|
32
32
|
else:
|
|
33
33
|
from typing_extensions import Self
|
|
34
34
|
|
|
35
|
+
SHACL = Namespace("http://www.w3.org/ns/shacl#")
|
|
36
|
+
|
|
35
37
|
|
|
36
38
|
class GraphExporter(BaseExporter[ConceptualDataModel, Graph], ABC):
|
|
37
39
|
def export_to_file(self, data_model: ConceptualDataModel, filepath: Path) -> None:
|
|
@@ -42,7 +44,7 @@ class OWLExporter(GraphExporter):
|
|
|
42
44
|
"""Exports verified conceptual data model to an OWL ontology."""
|
|
43
45
|
|
|
44
46
|
def export(self, data_model: ConceptualDataModel) -> Graph:
|
|
45
|
-
return Ontology.from_data_model(data_model).
|
|
47
|
+
return Ontology.from_data_model(data_model).graph
|
|
46
48
|
|
|
47
49
|
@property
|
|
48
50
|
def description(self) -> str:
|
|
@@ -53,44 +55,31 @@ class SHACLExporter(GraphExporter):
|
|
|
53
55
|
"""Exports data_model to a SHACL graph."""
|
|
54
56
|
|
|
55
57
|
def export(self, data_model: ConceptualDataModel) -> Graph:
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
@property
|
|
59
|
-
def description(self) -> str:
|
|
60
|
-
return "Export verified information model to SHACL."
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class SemanticDataModelExporter(GraphExporter):
|
|
64
|
-
"""Exports verified information model to a semantic data model."""
|
|
65
|
-
|
|
66
|
-
def export(self, data_model: ConceptualDataModel) -> Graph:
|
|
67
|
-
return Ontology.from_data_model(data_model).as_semantic_data_model()
|
|
58
|
+
return ShaclShapes.from_data_model(data_model).graph
|
|
68
59
|
|
|
69
60
|
@property
|
|
70
61
|
def description(self) -> str:
|
|
71
|
-
return "Export verified
|
|
62
|
+
return "Export verified conceptual data model to SHACL."
|
|
72
63
|
|
|
73
64
|
|
|
74
|
-
class
|
|
65
|
+
class _ModelConfig(BaseModel):
|
|
75
66
|
model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True, strict=False, extra="allow")
|
|
76
67
|
|
|
77
68
|
|
|
78
|
-
class Ontology(
|
|
69
|
+
class Ontology(_ModelConfig):
|
|
79
70
|
"""
|
|
80
71
|
Represents an ontology. This class is used to generate an OWL ontology from conceptual data model.
|
|
81
72
|
|
|
82
73
|
Args:
|
|
83
74
|
properties: A list of OWL properties.
|
|
84
75
|
classes: A list of OWL classes.
|
|
85
|
-
shapes: A list of SHACL node shapes.
|
|
86
76
|
metadata: Metadata about the ontology.
|
|
87
77
|
prefixes: A dictionary of prefixes and namespaces.
|
|
88
78
|
"""
|
|
89
79
|
|
|
80
|
+
metadata: "OWLMetadata"
|
|
90
81
|
properties: list["OWLProperty"]
|
|
91
82
|
classes: list["OWLClass"]
|
|
92
|
-
shapes: list["SHACLNodeShape"]
|
|
93
|
-
metadata: "OWLMetadata"
|
|
94
83
|
prefixes: dict[str, Namespace]
|
|
95
84
|
|
|
96
85
|
@classmethod
|
|
@@ -105,7 +94,6 @@ class Ontology(OntologyModel):
|
|
|
105
94
|
An instance of Ontology.
|
|
106
95
|
"""
|
|
107
96
|
analysis = DataModelAnalysis(data_model)
|
|
108
|
-
concept_by_suffix = analysis.concept_by_suffix()
|
|
109
97
|
return cls(
|
|
110
98
|
properties=[
|
|
111
99
|
OWLProperty.from_list_of_properties(definition, data_model.metadata.namespace)
|
|
@@ -115,46 +103,12 @@ class Ontology(OntologyModel):
|
|
|
115
103
|
OWLClass.from_concept(definition, data_model.metadata.namespace, data_model.prefixes)
|
|
116
104
|
for definition in data_model.concepts
|
|
117
105
|
],
|
|
118
|
-
shapes=[
|
|
119
|
-
SHACLNodeShape.from_data_model(
|
|
120
|
-
concept_by_suffix[str(concept.suffix)],
|
|
121
|
-
list(properties.values()),
|
|
122
|
-
data_model.metadata.namespace,
|
|
123
|
-
)
|
|
124
|
-
for concept, properties in analysis.properties_by_id_by_concept().items()
|
|
125
|
-
]
|
|
126
|
-
+ [
|
|
127
|
-
SHACLNodeShape.from_data_model(
|
|
128
|
-
concept,
|
|
129
|
-
[],
|
|
130
|
-
data_model.metadata.namespace,
|
|
131
|
-
)
|
|
132
|
-
for concept in concept_by_suffix.values()
|
|
133
|
-
],
|
|
134
106
|
metadata=OWLMetadata(**data_model.metadata.model_dump()),
|
|
135
107
|
prefixes=data_model.prefixes,
|
|
136
108
|
)
|
|
137
109
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Generates a SHACL graph from the ontology.
|
|
141
|
-
|
|
142
|
-
Returns:
|
|
143
|
-
A SHACL graph.
|
|
144
|
-
"""
|
|
145
|
-
|
|
146
|
-
shacl = Graph()
|
|
147
|
-
shacl.bind(self.metadata.prefix, self.metadata.namespace)
|
|
148
|
-
for prefix, namespace in self.prefixes.items():
|
|
149
|
-
shacl.bind(prefix, namespace)
|
|
150
|
-
|
|
151
|
-
for shape in self.shapes:
|
|
152
|
-
for triple in shape.triples:
|
|
153
|
-
shacl.add(triple) # type: ignore[arg-type]
|
|
154
|
-
|
|
155
|
-
return shacl
|
|
156
|
-
|
|
157
|
-
def as_owl(self) -> Graph:
|
|
110
|
+
@property
|
|
111
|
+
def graph(self) -> Graph:
|
|
158
112
|
"""
|
|
159
113
|
Generates an OWL graph from the ontology.
|
|
160
114
|
|
|
@@ -180,33 +134,6 @@ class Ontology(OntologyModel):
|
|
|
180
134
|
|
|
181
135
|
return owl
|
|
182
136
|
|
|
183
|
-
def as_semantic_data_model(self) -> Graph:
|
|
184
|
-
return self.as_owl() + self.as_shacl()
|
|
185
|
-
|
|
186
|
-
@property
|
|
187
|
-
def owl_triples(self) -> list[tuple]:
|
|
188
|
-
return list(self.as_owl())
|
|
189
|
-
|
|
190
|
-
@property
|
|
191
|
-
def shacl_triples(self) -> list[tuple]:
|
|
192
|
-
return list(self.as_shacl())
|
|
193
|
-
|
|
194
|
-
@property
|
|
195
|
-
def triples(self) -> list[tuple]:
|
|
196
|
-
return self.owl_triples + self.shacl_triples
|
|
197
|
-
|
|
198
|
-
@property
|
|
199
|
-
def ontology(self) -> str:
|
|
200
|
-
return self.as_owl().serialize()
|
|
201
|
-
|
|
202
|
-
@property
|
|
203
|
-
def constraints(self) -> str:
|
|
204
|
-
return self.as_shacl().serialize()
|
|
205
|
-
|
|
206
|
-
@property
|
|
207
|
-
def semantic_data_model(self) -> str:
|
|
208
|
-
return (self.as_owl() + self.as_shacl()).serialize()
|
|
209
|
-
|
|
210
137
|
|
|
211
138
|
class OWLMetadata(ConceptualMetadata):
|
|
212
139
|
@property
|
|
@@ -233,7 +160,7 @@ class OWLMetadata(ConceptualMetadata):
|
|
|
233
160
|
return triples
|
|
234
161
|
|
|
235
162
|
|
|
236
|
-
class OWLClass(
|
|
163
|
+
class OWLClass(_ModelConfig):
|
|
237
164
|
id_: URIRef
|
|
238
165
|
type_: URIRef = OWL.Class
|
|
239
166
|
label: str | None
|
|
@@ -306,7 +233,7 @@ class OWLClass(OntologyModel):
|
|
|
306
233
|
)
|
|
307
234
|
|
|
308
235
|
|
|
309
|
-
class OWLProperty(
|
|
236
|
+
class OWLProperty(_ModelConfig):
|
|
310
237
|
id_: URIRef
|
|
311
238
|
type_: set[URIRef]
|
|
312
239
|
label: set[str]
|
|
@@ -510,10 +437,76 @@ class OWLProperty(OntologyModel):
|
|
|
510
437
|
)
|
|
511
438
|
|
|
512
439
|
|
|
513
|
-
|
|
440
|
+
class ShaclShapes(_ModelConfig):
|
|
441
|
+
"""
|
|
442
|
+
Represents a SHACL shapes. This class is used to generate a SHACL graph from conceptual data model.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
shapes: A list of SHACL node shapes.
|
|
446
|
+
prefixes: A dictionary of prefixes and namespaces.
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
shapes: list["SHACLNodeShape"]
|
|
450
|
+
prefixes: dict[str, Namespace]
|
|
451
|
+
|
|
452
|
+
@classmethod
|
|
453
|
+
def from_data_model(cls, data_model: ConceptualDataModel) -> Self:
|
|
454
|
+
"""
|
|
455
|
+
Generates shacl shapes from a conceptual data model.
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
data_model: The data_model to generate the shacl shapes from.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
An instance of ShaclShapes.
|
|
462
|
+
"""
|
|
463
|
+
analysis = DataModelAnalysis(data_model)
|
|
464
|
+
concepts_by_concept_entity = analysis.concept_by_concept_entity
|
|
465
|
+
properties_by_concept_entity = analysis.properties_by_id_by_concept()
|
|
466
|
+
return cls(
|
|
467
|
+
shapes=[
|
|
468
|
+
# shapes that have property shapes as well
|
|
469
|
+
SHACLNodeShape.from_data_model(
|
|
470
|
+
concepts_by_concept_entity[concept_entity],
|
|
471
|
+
list(properties.values()),
|
|
472
|
+
data_model.metadata.namespace,
|
|
473
|
+
)
|
|
474
|
+
for concept_entity, properties in properties_by_concept_entity.items()
|
|
475
|
+
]
|
|
476
|
+
+ [
|
|
477
|
+
# shapes without any property shapes
|
|
478
|
+
SHACLNodeShape.from_data_model(
|
|
479
|
+
concept,
|
|
480
|
+
[],
|
|
481
|
+
data_model.metadata.namespace,
|
|
482
|
+
)
|
|
483
|
+
for concept_entity, concept in concepts_by_concept_entity.items()
|
|
484
|
+
if concept_entity not in properties_by_concept_entity
|
|
485
|
+
],
|
|
486
|
+
prefixes=data_model.prefixes,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
@property
|
|
490
|
+
def graph(self) -> Graph:
|
|
491
|
+
"""
|
|
492
|
+
Generates a SHACL graph from the class instance.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
A SHACL graph.
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
shacl = Graph()
|
|
499
|
+
for prefix, namespace in self.prefixes.items():
|
|
500
|
+
shacl.bind(prefix, namespace)
|
|
501
|
+
|
|
502
|
+
for shape in self.shapes:
|
|
503
|
+
for triple in shape.triples:
|
|
504
|
+
shacl.add(triple) # type: ignore[arg-type]
|
|
505
|
+
|
|
506
|
+
return shacl
|
|
514
507
|
|
|
515
508
|
|
|
516
|
-
class SHACLNodeShape(
|
|
509
|
+
class SHACLNodeShape(_ModelConfig):
|
|
517
510
|
id_: URIRef
|
|
518
511
|
type_: URIRef = SHACL.NodeShape
|
|
519
512
|
target_class: URIRef
|
|
@@ -574,7 +567,7 @@ class SHACLNodeShape(OntologyModel):
|
|
|
574
567
|
)
|
|
575
568
|
|
|
576
569
|
|
|
577
|
-
class SHACLPropertyShape(
|
|
570
|
+
class SHACLPropertyShape(_ModelConfig):
|
|
578
571
|
id_: BNode
|
|
579
572
|
type_: URIRef = SHACL.property
|
|
580
573
|
path: URIRef # URIRef to property in OWL
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognite-neat
|
|
3
|
-
Version: 0.123.
|
|
3
|
+
Version: 0.123.37
|
|
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,15 +1,24 @@
|
|
|
1
1
|
cognite/neat/__init__.py,sha256=Lo4DbjDOwnhCYUoAgPp5RG1fDdF7OlnomalTe7n1ydw,211
|
|
2
|
-
cognite/neat/
|
|
2
|
+
cognite/neat/_issues.py,sha256=uv0fkkWwTKqNmTmHqyoBB3L6yMCh42EZpEkLGmIJYOY,812
|
|
3
|
+
cognite/neat/_version.py,sha256=wYvxxyaT-q6-Euap5rnXOulJ4TQmy1n58UOZ5jyEr1A,47
|
|
3
4
|
cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
5
|
cognite/neat/_data_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
6
|
cognite/neat/_data_model/_constants.py,sha256=NGGvWHlQqhkkSBP_AqoofGYjNph3SiZX6QPINlMsy04,107
|
|
6
7
|
cognite/neat/_data_model/_identifiers.py,sha256=a0LcQ_h0NffxSKTCrzCDpYkrlaUTk-D_rfaQUh-BhWc,1921
|
|
7
8
|
cognite/neat/_data_model/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
cognite/neat/_data_model/models/
|
|
9
|
+
cognite/neat/_data_model/models/conceptual/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
cognite/neat/_data_model/models/conceptual/_base.py,sha256=SFkoBJDM51pqew_isHFJoB20OgfofpwVRnTrg-rKkNY,710
|
|
11
|
+
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
|
|
9
13
|
cognite/neat/_data_model/models/dms/_base.py,sha256=R8SP3Zi9daTBqewYKGjuNEkrWc-j91f-6t34CN-9YJ0,719
|
|
10
|
-
cognite/neat/_data_model/models/dms/_constants.py,sha256=
|
|
14
|
+
cognite/neat/_data_model/models/dms/_constants.py,sha256=tLyle1N_ekNBhANleG7GvZB85P4uGaoWl2mqeR-vfmY,1272
|
|
15
|
+
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
|
|
17
|
+
cognite/neat/_data_model/models/dms/_data_types.py,sha256=XSGQWVzYFRYAzKsDln20nC2kqiHQC6JAvDeTzCBPT-0,5202
|
|
18
|
+
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
|
|
11
20
|
cognite/neat/_data_model/models/dms/_space.py,sha256=bLSTY5tTBnvS19kUacxM0p_c22lYY5FY9etofH1NNMc,1680
|
|
12
|
-
cognite/neat/_data_model/models/entities/__init__.py,sha256=
|
|
21
|
+
cognite/neat/_data_model/models/entities/__init__.py,sha256=qdtIpyy2hjfdMwEHya-efOCvWassaalQla15RnqK00E,798
|
|
13
22
|
cognite/neat/_data_model/models/entities/_base.py,sha256=PREgBqc6DM9pLn3VXFWxPBMVK0sexl6s3twSHn9wsf0,3874
|
|
14
23
|
cognite/neat/_data_model/models/entities/_constants.py,sha256=P56zgsL2xqfegWOxEAyPm9qrZcxrjb1ZXqMG7cDmQxc,333
|
|
15
24
|
cognite/neat/_data_model/models/entities/_data_types.py,sha256=DfdEWGek7gODro-_0SiiInhPGwul4zn-ASACQfn8HUY,2838
|
|
@@ -17,7 +26,9 @@ cognite/neat/_data_model/models/entities/_identifiers.py,sha256=uBiK4ot3V0b_LGXu
|
|
|
17
26
|
cognite/neat/_data_model/models/entities/_parser.py,sha256=ZLGw0cFV-B7JuaAUJ65Jbjf6o-vidz9_BZvilS6lAZw,6455
|
|
18
27
|
cognite/neat/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
28
|
cognite/neat/_utils/auxiliary.py,sha256=Cx-LP8dfN782R3iUcm--q26zdzQ0k_RFnVbJ0bwVZMI,1345
|
|
29
|
+
cognite/neat/_utils/text.py,sha256=1el5Ty1T0tdhTF0gp6L8AAzUxaFmAUAIFy_VvfG_kkM,1194
|
|
20
30
|
cognite/neat/_utils/useful_types.py,sha256=6Fpw_HlWFj8ZYjGPd0KzguBczuI8GFhj5wBceGsaeak,211
|
|
31
|
+
cognite/neat/_utils/validation.py,sha256=qBC3V3BpkZSrsDG4xfINaB_U0e05e2lCt85oIW3on4I,3253
|
|
21
32
|
cognite/neat/_utils/http_client/__init__.py,sha256=gJBrOH1tIzEzLforHbeakYimTn4RlelyANps-jtpREI,894
|
|
22
33
|
cognite/neat/_utils/http_client/_client.py,sha256=2RVwTbbPFlQ8eJVLKNUXwnc4Yq_783PkY44zwr6LlT8,11509
|
|
23
34
|
cognite/neat/_utils/http_client/_config.py,sha256=C8IF1JoijmVMjA_FEMgAkiD1buEV1cY5Og3t-Ecyfmk,756
|
|
@@ -50,12 +61,12 @@ cognite/neat/v0/core/_data_model/catalog/__init__.py,sha256=zWG1-GONe8m05lV3gLAK
|
|
|
50
61
|
cognite/neat/v0/core/_data_model/catalog/classic_model.xlsx,sha256=YkocpkKypizjsWYwOdn5yzIz_BSl8T8SQLxgm4GIjLQ,15014
|
|
51
62
|
cognite/neat/v0/core/_data_model/catalog/conceptual-imf-data-model.xlsx,sha256=vrE5g8vBtsGpwJqygxG3t9I3x4SUAyQsi1vtWfZ8QW4,53682
|
|
52
63
|
cognite/neat/v0/core/_data_model/catalog/hello_world_pump.xlsx,sha256=E63t5U1PQLIoUfXp1mEuhuq8I2TGKovZlEfIhO5bevw,23322
|
|
53
|
-
cognite/neat/v0/core/_data_model/exporters/__init__.py,sha256=
|
|
64
|
+
cognite/neat/v0/core/_data_model/exporters/__init__.py,sha256=NSTeVtfgfkfrhf_Ruz5rHnlF0wIj2dIhAm3io1tEIkQ,1153
|
|
54
65
|
cognite/neat/v0/core/_data_model/exporters/_base.py,sha256=pdtQDi5G89JVbHtGnvLHJDoujT1UVaCBw939Re1Gqv4,2412
|
|
55
66
|
cognite/neat/v0/core/_data_model/exporters/_data_model2dms.py,sha256=LdLGgR73n9k5hHiWUSdvDc0lmaEO8UVQYFTiaVWHQ-c,19918
|
|
56
67
|
cognite/neat/v0/core/_data_model/exporters/_data_model2excel.py,sha256=dT7ZHt57y3mca4TtCBnqMER0xlB7AyvivG0EZ2bGPzg,25484
|
|
57
68
|
cognite/neat/v0/core/_data_model/exporters/_data_model2instance_template.py,sha256=cmyQBlc1UM5l42g1N_F2K1sVu39ontsmA2bfXJO5muQ,6115
|
|
58
|
-
cognite/neat/v0/core/_data_model/exporters/
|
|
69
|
+
cognite/neat/v0/core/_data_model/exporters/_data_model2semantic_model.py,sha256=UcCecmuaeq7rjbpV8l1zK-mrLVtMyw20i58wokfkNks,22526
|
|
59
70
|
cognite/neat/v0/core/_data_model/exporters/_data_model2yaml.py,sha256=v7CgNVtVv4t0FXHPJM-HGkID0AqVDlwsQqUNjWqr_1U,3287
|
|
60
71
|
cognite/neat/v0/core/_data_model/importers/__init__.py,sha256=jkDKSGv5VdJgl44lmn7VQtD6-rqo09VFOr-tTB5Lfyc,1290
|
|
61
72
|
cognite/neat/v0/core/_data_model/importers/_base.py,sha256=SAC3nhJUBBl5PwnIs-bQvVam_0zEGjlOlNMvVOuJZzI,2028
|
|
@@ -212,7 +223,7 @@ cognite/neat/v0/session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4
|
|
|
212
223
|
cognite/neat/v0/session/engine/_import.py,sha256=1QxA2_EK613lXYAHKQbZyw2yjo5P9XuiX4Z6_6-WMNQ,169
|
|
213
224
|
cognite/neat/v0/session/engine/_interface.py,sha256=3W-cYr493c_mW3P5O6MKN1xEQg3cA7NHR_ev3zdF9Vk,533
|
|
214
225
|
cognite/neat/v0/session/engine/_load.py,sha256=u0x7vuQCRoNcPt25KJBJRn8sJabonYK4vtSZpiTdP4k,5201
|
|
215
|
-
cognite_neat-0.123.
|
|
216
|
-
cognite_neat-0.123.
|
|
217
|
-
cognite_neat-0.123.
|
|
218
|
-
cognite_neat-0.123.
|
|
226
|
+
cognite_neat-0.123.37.dist-info/METADATA,sha256=CYYCNFu1dQZDNPfGM0AYm0ggzlvko1oFJPgS1LLOXR0,9148
|
|
227
|
+
cognite_neat-0.123.37.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
228
|
+
cognite_neat-0.123.37.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
|
|
229
|
+
cognite_neat-0.123.37.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|