cognite-neat 0.124.0__py3-none-any.whl → 0.125.0__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/importers/__init__.py +2 -1
- cognite/neat/_data_model/importers/_table_importer/__init__.py +0 -0
- cognite/neat/_data_model/importers/_table_importer/data_classes.py +141 -0
- cognite/neat/_data_model/importers/_table_importer/importer.py +76 -0
- cognite/neat/_data_model/importers/_table_importer/source.py +89 -0
- cognite/neat/_exceptions.py +17 -0
- cognite/neat/_utils/text.py +22 -0
- cognite/neat/_utils/useful_types.py +4 -0
- cognite/neat/_utils/validation.py +7 -4
- cognite/neat/_version.py +1 -1
- cognite/neat/v0/core/_data_model/_constants.py +1 -0
- cognite/neat/v0/core/_data_model/exporters/_data_model2excel.py +3 -3
- cognite/neat/v0/core/_data_model/importers/_dms2data_model.py +4 -3
- cognite/neat/v0/core/_data_model/importers/_spreadsheet2data_model.py +85 -5
- cognite/neat/v0/core/_data_model/models/entities/__init__.py +2 -0
- cognite/neat/v0/core/_data_model/models/entities/_single_value.py +14 -0
- cognite/neat/v0/core/_data_model/models/entities/_types.py +10 -0
- cognite/neat/v0/core/_data_model/models/physical/_exporter.py +3 -11
- cognite/neat/v0/core/_data_model/models/physical/_unverified.py +61 -12
- cognite/neat/v0/core/_data_model/models/physical/_validation.py +8 -4
- cognite/neat/v0/core/_data_model/models/physical/_verified.py +86 -15
- cognite/neat/v0/core/_data_model/transformers/_converters.py +11 -4
- cognite/neat/v0/core/_utils/spreadsheet.py +17 -3
- {cognite_neat-0.124.0.dist-info → cognite_neat-0.125.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.124.0.dist-info → cognite_neat-0.125.0.dist-info}/RECORD +27 -22
- {cognite_neat-0.124.0.dist-info → cognite_neat-0.125.0.dist-info}/WHEEL +0 -0
- {cognite_neat-0.124.0.dist-info → cognite_neat-0.125.0.dist-info}/licenses/LICENSE +0 -0
|
File without changes
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Annotated, cast
|
|
3
|
+
|
|
4
|
+
from pydantic import AliasGenerator, BaseModel, BeforeValidator, Field, model_validator
|
|
5
|
+
from pydantic.alias_generators import to_camel
|
|
6
|
+
|
|
7
|
+
from cognite.neat._data_model.models.entities import ParsedEntity, parse_entities, parse_entity
|
|
8
|
+
from cognite.neat._utils.text import title_case
|
|
9
|
+
from cognite.neat._utils.useful_types import CellValueType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def parse_entity_str(v: str) -> ParsedEntity:
|
|
13
|
+
try:
|
|
14
|
+
return parse_entity(v)
|
|
15
|
+
except ValueError as e:
|
|
16
|
+
raise ValueError(f"Invalid entity syntax: {e}") from e
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_entities_str(v: str) -> list[ParsedEntity] | None:
|
|
20
|
+
try:
|
|
21
|
+
return parse_entities(v)
|
|
22
|
+
except ValueError as e:
|
|
23
|
+
raise ValueError(f"Invalid entity list syntax: {e}") from e
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Entity = Annotated[ParsedEntity, BeforeValidator(parse_entity_str, str)]
|
|
27
|
+
EntityList = Annotated[list[ParsedEntity], BeforeValidator(parse_entities_str, str)]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TableObj(
|
|
31
|
+
BaseModel,
|
|
32
|
+
extra="ignore",
|
|
33
|
+
alias_generator=AliasGenerator(
|
|
34
|
+
validation_alias=title_case,
|
|
35
|
+
serialization_alias=to_camel,
|
|
36
|
+
),
|
|
37
|
+
): ...
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MetadataValue(TableObj):
|
|
41
|
+
key: str
|
|
42
|
+
value: CellValueType
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DMSProperty(TableObj):
|
|
46
|
+
view: Entity
|
|
47
|
+
view_property: str
|
|
48
|
+
name: str | None = None
|
|
49
|
+
description: str | None = None
|
|
50
|
+
connection: Entity | None
|
|
51
|
+
value_type: Entity
|
|
52
|
+
min_count: int | None
|
|
53
|
+
max_count: int | None
|
|
54
|
+
immutable: bool | None = None
|
|
55
|
+
default: CellValueType | None = None
|
|
56
|
+
auto_increment: bool | None = None
|
|
57
|
+
container: Entity | None = None
|
|
58
|
+
container_property: str | None = None
|
|
59
|
+
container_property_name: str | None = None
|
|
60
|
+
container_property_description: str | None = None
|
|
61
|
+
index: EntityList | None = None
|
|
62
|
+
constraint: EntityList | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class DMSView(TableObj):
|
|
66
|
+
view: Entity
|
|
67
|
+
name: str | None = None
|
|
68
|
+
description: str | None = None
|
|
69
|
+
implements: EntityList | None = None
|
|
70
|
+
filter: str | None = None
|
|
71
|
+
in_model: bool | None = None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class DMSContainer(TableObj):
|
|
75
|
+
container: Entity
|
|
76
|
+
name: str | None = None
|
|
77
|
+
description: str | None = None
|
|
78
|
+
constraint: EntityList | None = None
|
|
79
|
+
used_for: str | None = None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class DMSEnum(TableObj):
|
|
83
|
+
collection: str
|
|
84
|
+
value: str
|
|
85
|
+
name: str | None = None
|
|
86
|
+
description: str | None = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class DMSNode(TableObj):
|
|
90
|
+
node: Entity
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class TableDMS(TableObj):
|
|
94
|
+
metadata: list[MetadataValue]
|
|
95
|
+
properties: list[DMSProperty]
|
|
96
|
+
views: list[DMSView]
|
|
97
|
+
containers: list[DMSContainer] = Field(default_factory=list)
|
|
98
|
+
enum: list[DMSEnum] = Field(default_factory=list)
|
|
99
|
+
nodes: list[DMSNode] = Field(default_factory=list)
|
|
100
|
+
|
|
101
|
+
@model_validator(mode="before")
|
|
102
|
+
def _title_case_keys(
|
|
103
|
+
cls, data: dict[str, list[dict[str, CellValueType]]]
|
|
104
|
+
) -> dict[str, list[dict[str, CellValueType]]]:
|
|
105
|
+
if isinstance(data, dict):
|
|
106
|
+
# We are case-insensitive on the table names.
|
|
107
|
+
return {title_case(k): v for k, v in data.items()}
|
|
108
|
+
return data
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
DMS_API_MAPPING: Mapping[str, Mapping[str, str]] = {
|
|
112
|
+
"Views": {
|
|
113
|
+
"space": "View",
|
|
114
|
+
"externalId": "View",
|
|
115
|
+
"version": "View",
|
|
116
|
+
**{
|
|
117
|
+
cast(str, field_.serialization_alias): cast(str, field_.validation_alias)
|
|
118
|
+
for field_id, field_ in DMSView.model_fields.items()
|
|
119
|
+
if field_id != "View"
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
"Containers": {
|
|
123
|
+
"space": "Container",
|
|
124
|
+
"externalId": "Container",
|
|
125
|
+
**{
|
|
126
|
+
cast(str, field_.serialization_alias): cast(str, field_.validation_alias)
|
|
127
|
+
for field_id, field_ in DMSContainer.model_fields.items()
|
|
128
|
+
if field_id != "Container"
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
"Properties": {
|
|
132
|
+
"space": "View",
|
|
133
|
+
"externalId": "View",
|
|
134
|
+
"property": "ViewProperty",
|
|
135
|
+
**{
|
|
136
|
+
cast(str, field_.serialization_alias): cast(str, field_.validation_alias)
|
|
137
|
+
for field_id, field_ in DMSProperty.model_fields.items()
|
|
138
|
+
if field_id not in ("View", "ViewProperty")
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import ClassVar, cast
|
|
3
|
+
|
|
4
|
+
from pydantic import ValidationError
|
|
5
|
+
|
|
6
|
+
from cognite.neat._data_model.importers._base import DMSImporter
|
|
7
|
+
from cognite.neat._data_model.models.dms import (
|
|
8
|
+
RequestSchema,
|
|
9
|
+
)
|
|
10
|
+
from cognite.neat._exceptions import DataModelImportError
|
|
11
|
+
from cognite.neat._issues import ModelSyntaxError
|
|
12
|
+
from cognite.neat._utils.useful_types import CellValueType
|
|
13
|
+
from cognite.neat._utils.validation import as_json_path, humanize_validation_error
|
|
14
|
+
|
|
15
|
+
from .data_classes import TableDMS
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DMSTableImporter(DMSImporter):
|
|
19
|
+
"""Imports DMS from a table structure.
|
|
20
|
+
|
|
21
|
+
The tables can are expected to be a dictionary where the keys are the table names and the values
|
|
22
|
+
are lists of dictionaries representing the rows in the table.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# We can safely cast as we know the validation_alias is always set to a str.
|
|
26
|
+
REQUIRED_SHEETS = tuple(
|
|
27
|
+
cast(str, field_.validation_alias) for field_ in TableDMS.model_fields.values() if field_.is_required()
|
|
28
|
+
)
|
|
29
|
+
REQUIRED_SHEET_MESSAGES: ClassVar[Mapping[str, str]] = {
|
|
30
|
+
f"Missing required column: {sheet!r}": f"Missing required sheet: {sheet!r}" for sheet in REQUIRED_SHEETS
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def __init__(self, tables: dict[str, list[dict[str, CellValueType]]]) -> None:
|
|
34
|
+
self._table = tables
|
|
35
|
+
|
|
36
|
+
def to_data_model(self) -> RequestSchema:
|
|
37
|
+
raise NotImplementedError()
|
|
38
|
+
|
|
39
|
+
def _read_tables(self) -> TableDMS:
|
|
40
|
+
try:
|
|
41
|
+
# Check tables, columns, data type and entity syntax.
|
|
42
|
+
table = TableDMS.model_validate(self._table)
|
|
43
|
+
except ValidationError as e:
|
|
44
|
+
errors = self._create_error_messages(e)
|
|
45
|
+
raise DataModelImportError(errors) from None
|
|
46
|
+
return table
|
|
47
|
+
|
|
48
|
+
def _create_error_messages(self, error: ValidationError) -> list[ModelSyntaxError]:
|
|
49
|
+
errors: list[ModelSyntaxError] = []
|
|
50
|
+
seen: set[str] = set()
|
|
51
|
+
for message in humanize_validation_error(
|
|
52
|
+
error,
|
|
53
|
+
humanize_location=self._location,
|
|
54
|
+
field_name="column",
|
|
55
|
+
missing_required_descriptor="missing",
|
|
56
|
+
):
|
|
57
|
+
# Replace messages about missing required columns with missing required sheets.
|
|
58
|
+
message = self.REQUIRED_SHEET_MESSAGES.get(message, message)
|
|
59
|
+
if message in seen:
|
|
60
|
+
# We treat all rows as the same, so we get duplicated errors for each row.
|
|
61
|
+
continue
|
|
62
|
+
seen.add(message)
|
|
63
|
+
errors.append(ModelSyntaxError(message=message))
|
|
64
|
+
return errors
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _location(loc: tuple[str | int, ...]) -> str:
|
|
68
|
+
if isinstance(loc[0], str) and len(loc) == 2: # Sheet + row.
|
|
69
|
+
# We skip the row as we treat all rows as the same. For example, if a required column is missing in one
|
|
70
|
+
# row, it is missing in all rows.
|
|
71
|
+
return f"{loc[0]} sheet"
|
|
72
|
+
elif len(loc) == 3 and isinstance(loc[0], str) and isinstance(loc[1], int) and isinstance(loc[2], str):
|
|
73
|
+
# This means there is something wrong in a specific cell.
|
|
74
|
+
return f"{loc[0]} sheet row {loc[1] + 1} column {loc[2]!r}"
|
|
75
|
+
# This should be unreachable as the TableDMS model only has 2 levels.
|
|
76
|
+
return as_json_path(loc)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from .data_classes import DMS_API_MAPPING
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class SpreadsheetReadContext:
|
|
9
|
+
"""This class is used to store information about the source spreadsheet.
|
|
10
|
+
|
|
11
|
+
It is used to adjust row numbers to account for header rows and empty rows
|
|
12
|
+
such that the error/warning messages are accurate.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
header_row: int = 1
|
|
16
|
+
empty_rows: list[int] = field(default_factory=list)
|
|
17
|
+
skipped_rows: list[int] = field(default_factory=list)
|
|
18
|
+
is_one_indexed: bool = True
|
|
19
|
+
|
|
20
|
+
def __post_init__(self) -> None:
|
|
21
|
+
self.empty_rows.sort()
|
|
22
|
+
self.skipped_rows.sort()
|
|
23
|
+
|
|
24
|
+
def adjusted_row_number(self, row_no: int) -> int:
|
|
25
|
+
output = row_no
|
|
26
|
+
for empty_row in self.empty_rows:
|
|
27
|
+
if empty_row <= output:
|
|
28
|
+
output += 1
|
|
29
|
+
else:
|
|
30
|
+
break
|
|
31
|
+
|
|
32
|
+
for skipped_rows in self.skipped_rows:
|
|
33
|
+
if skipped_rows <= output:
|
|
34
|
+
output += 1
|
|
35
|
+
else:
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
return output + self.header_row + (1 if self.is_one_indexed else 0)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class TableSource:
|
|
43
|
+
source: str
|
|
44
|
+
table_read: dict[str, SpreadsheetReadContext] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
def location(self, path: tuple[int | str, ...]) -> str:
|
|
47
|
+
table_id: str | None = None
|
|
48
|
+
row_no: int | None = None
|
|
49
|
+
column: str | None = None
|
|
50
|
+
if len(path) >= 1 and isinstance(path[0], str):
|
|
51
|
+
table_id = path[0]
|
|
52
|
+
if len(path) >= 2 and isinstance(path[1], int):
|
|
53
|
+
row_no = path[1]
|
|
54
|
+
if len(path) >= 3 and isinstance(path[2], str):
|
|
55
|
+
column = path[2]
|
|
56
|
+
column = self.field_to_column(table_id, column)
|
|
57
|
+
if isinstance(row_no, int):
|
|
58
|
+
row_no = self.adjust_row_number(table_id, row_no)
|
|
59
|
+
location_parts = []
|
|
60
|
+
if table_id is not None:
|
|
61
|
+
location_parts.append(f"table {table_id!r}")
|
|
62
|
+
if row_no is not None:
|
|
63
|
+
location_parts.append(f"row {row_no}")
|
|
64
|
+
if column is not None:
|
|
65
|
+
location_parts.append(f"column {column!r}")
|
|
66
|
+
if len(path) > 4:
|
|
67
|
+
location_parts.append("-> " + ".".join(str(p) for p in path[3:]))
|
|
68
|
+
|
|
69
|
+
return " ".join(location_parts)
|
|
70
|
+
|
|
71
|
+
def adjust_row_number(self, table_id: str | None, row_no: int) -> int:
|
|
72
|
+
table_read = table_id and self.table_read.get(table_id)
|
|
73
|
+
if table_read:
|
|
74
|
+
return table_read.adjusted_row_number(row_no)
|
|
75
|
+
return row_no + 1 # Convert to 1-indexed if no table read info is available
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def field_to_column(cls, table_id: str | None, field_: str) -> str:
|
|
79
|
+
"""Maps the field name used in the DMS API to the column named used by Neat."""
|
|
80
|
+
mapping = cls.field_mapping(table_id)
|
|
81
|
+
if mapping is not None:
|
|
82
|
+
return mapping.get(field_, field_)
|
|
83
|
+
return field_
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def field_mapping(cls, table_id: str | int | None) -> Mapping[str, str] | None:
|
|
87
|
+
if not isinstance(table_id, str):
|
|
88
|
+
return None
|
|
89
|
+
return DMS_API_MAPPING.get(table_id)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from ._issues import ModelSyntaxError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NeatException(Exception):
|
|
5
|
+
"""Base class for all exceptions raised by Neat."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DataModelImportError(NeatException):
|
|
11
|
+
"""Raised when there is an error importing a model."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, errors: list[ModelSyntaxError]) -> None:
|
|
14
|
+
self.errors = errors
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
return f"Model import failed with {len(self.errors)} errors: " + "; ".join(map(str, self.errors))
|
cognite/neat/_utils/text.py
CHANGED
|
@@ -38,3 +38,25 @@ def humanize_collection(collection: Collection[Any], /, *, sort: bool = True, bi
|
|
|
38
38
|
sequence = list(strings)
|
|
39
39
|
|
|
40
40
|
return f"{', '.join(sequence[:-1])} {bind_word} {sequence[-1]}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def title_case(s: str) -> str:
|
|
44
|
+
"""Convert a string to title case, handling underscores and hyphens.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
s: The string to convert.
|
|
48
|
+
Returns:
|
|
49
|
+
The title-cased string.
|
|
50
|
+
Examples:
|
|
51
|
+
>>> title_case("hello world")
|
|
52
|
+
'Hello World'
|
|
53
|
+
>>> title_case("hello_world")
|
|
54
|
+
'Hello World'
|
|
55
|
+
>>> title_case("hello-world")
|
|
56
|
+
'Hello World'
|
|
57
|
+
>>> title_case("hello_world-and-universe")
|
|
58
|
+
'Hello World And Universe'
|
|
59
|
+
>>> title_case("HELLO WORLD")
|
|
60
|
+
'Hello World'
|
|
61
|
+
"""
|
|
62
|
+
return " ".join(word.capitalize() for word in s.replace("_", " ").replace("-", " ").split())
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
from collections.abc import Hashable
|
|
2
|
+
from datetime import date, datetime, time, timedelta
|
|
2
3
|
from typing import TypeAlias, TypeVar
|
|
3
4
|
|
|
4
5
|
JsonVal: TypeAlias = None | str | int | float | bool | dict[str, "JsonVal"] | list["JsonVal"]
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
T_ID = TypeVar("T_ID", bound=Hashable)
|
|
9
|
+
|
|
10
|
+
# These are the types that openpyxl supports in cells
|
|
11
|
+
CellValueType: TypeAlias = str | int | float | bool | datetime | date | time | timedelta | None
|
|
@@ -31,6 +31,7 @@ def humanize_validation_error(
|
|
|
31
31
|
humanize_location: Callable[[tuple[int | str, ...]], str] = as_json_path,
|
|
32
32
|
field_name: Literal["field", "column", "value"] = "field",
|
|
33
33
|
field_renaming: Mapping[str, str] | None = None,
|
|
34
|
+
missing_required_descriptor: Literal["empty", "missing"] = "missing",
|
|
34
35
|
) -> list[str]:
|
|
35
36
|
"""Converts a ValidationError to a human-readable format.
|
|
36
37
|
|
|
@@ -50,6 +51,8 @@ def humanize_validation_error(
|
|
|
50
51
|
This is useful when the field names in the model are different from the names in the source.
|
|
51
52
|
For example, if the model field is "asset_id" but the source column is "Asset ID",
|
|
52
53
|
you can provide a mapping {"asset_id": "Asset ID"} to have the error messages use the source names.
|
|
54
|
+
missing_required_descriptor: How to describe missing required fields. Default is "missing".
|
|
55
|
+
Other option is "empty" which can be more suitable for table data.
|
|
53
56
|
Returns:
|
|
54
57
|
A list of human-readable error messages.
|
|
55
58
|
"""
|
|
@@ -60,9 +63,9 @@ def humanize_validation_error(
|
|
|
60
63
|
loc = (*parent_loc, *item["loc"])
|
|
61
64
|
error_type = item["type"]
|
|
62
65
|
if error_type == "missing":
|
|
63
|
-
msg = f"Missing required
|
|
66
|
+
msg = f"Missing required {field_name}: {loc[-1]!r}"
|
|
64
67
|
elif error_type == "extra_forbidden":
|
|
65
|
-
msg = f"Unused
|
|
68
|
+
msg = f"Unused {field_name}: {loc[-1]!r}"
|
|
66
69
|
elif error_type == "value_error":
|
|
67
70
|
msg = str(item["ctx"]["error"])
|
|
68
71
|
elif error_type == "literal_error":
|
|
@@ -92,10 +95,10 @@ def humanize_validation_error(
|
|
|
92
95
|
|
|
93
96
|
error_suffix = f"{msg[:1].casefold()}{msg[1:]}"
|
|
94
97
|
if len(loc) > 1 and error_type in {"extra_forbidden", "missing"}:
|
|
95
|
-
if
|
|
98
|
+
if missing_required_descriptor == "empty" and error_type == "missing":
|
|
96
99
|
# This is a table so we modify the error message.
|
|
97
100
|
msg = (
|
|
98
|
-
f"In {humanize_location(loc[:-1])} the
|
|
101
|
+
f"In {humanize_location(loc[:-1])} the {field_name} {field_renaming.get(str(loc[-1]), loc[-1])!r} "
|
|
99
102
|
"cannot be empty."
|
|
100
103
|
)
|
|
101
104
|
else:
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.125.0"
|
|
2
2
|
__engine__ = "^2.0.4"
|
|
@@ -43,6 +43,7 @@ class EntityTypes(StrEnum):
|
|
|
43
43
|
prefix = "prefix"
|
|
44
44
|
space = "space"
|
|
45
45
|
container_index = "container_index"
|
|
46
|
+
container_constraint = "container_constraint"
|
|
46
47
|
concept_restriction = "conceptRestriction"
|
|
47
48
|
value_constraint = "valueConstraint"
|
|
48
49
|
cardinality_constraint = "cardinalityConstraint"
|
|
@@ -32,7 +32,7 @@ from cognite.neat.v0.core._data_model.models.data_types import (
|
|
|
32
32
|
)
|
|
33
33
|
from cognite.neat.v0.core._data_model.models.physical._verified import PhysicalDataModel
|
|
34
34
|
from cognite.neat.v0.core._utils.spreadsheet import (
|
|
35
|
-
|
|
35
|
+
find_column_and_row_with_value,
|
|
36
36
|
generate_data_validation,
|
|
37
37
|
)
|
|
38
38
|
|
|
@@ -217,7 +217,7 @@ class ExcelExporter(BaseExporter[VerifiedDataModel, Workbook]):
|
|
|
217
217
|
continue
|
|
218
218
|
ws = workbook[sheet]
|
|
219
219
|
for col in get_internal_properties():
|
|
220
|
-
column_letter =
|
|
220
|
+
column_letter = find_column_and_row_with_value(ws, col)[0]
|
|
221
221
|
if column_letter:
|
|
222
222
|
ws.column_dimensions[column_letter].hidden = True
|
|
223
223
|
|
|
@@ -451,7 +451,7 @@ class ExcelExporter(BaseExporter[VerifiedDataModel, Workbook]):
|
|
|
451
451
|
workbook[sheet_name].add_data_validation(data_validators[data_validator_name])
|
|
452
452
|
|
|
453
453
|
# APPLY VALIDATOR TO SPECIFIC COLUMN
|
|
454
|
-
if column_letter :=
|
|
454
|
+
if column_letter := find_column_and_row_with_value(workbook[sheet_name], column_name)[0]:
|
|
455
455
|
data_validators[data_validator_name].add(f"{column_letter}{3}:{column_letter}{3 + total_rows}")
|
|
456
456
|
|
|
457
457
|
def _create_sheet_with_header(
|
|
@@ -54,6 +54,7 @@ from cognite.neat.v0.core._data_model.models.entities import (
|
|
|
54
54
|
ReverseConnectionEntity,
|
|
55
55
|
ViewEntity,
|
|
56
56
|
)
|
|
57
|
+
from cognite.neat.v0.core._data_model.models.entities._single_value import ContainerConstraintEntity
|
|
57
58
|
from cognite.neat.v0.core._data_model.models.physical import (
|
|
58
59
|
UnverifiedPhysicalContainer,
|
|
59
60
|
UnverifiedPhysicalEnum,
|
|
@@ -571,17 +572,17 @@ class DMSImporter(BaseImporter[UnverifiedPhysicalDataModel]):
|
|
|
571
572
|
index.append(ContainerIndexEntity(prefix="inverted", suffix=index_name, order=order))
|
|
572
573
|
return index or None
|
|
573
574
|
|
|
574
|
-
def _get_constraint(self, prop: ViewPropertyApply, prop_id: str) -> list[
|
|
575
|
+
def _get_constraint(self, prop: ViewPropertyApply, prop_id: str) -> list[ContainerConstraintEntity] | None:
|
|
575
576
|
if not isinstance(prop, dm.MappedPropertyApply):
|
|
576
577
|
return None
|
|
577
578
|
container = self._all_containers_by_id[prop.container]
|
|
578
|
-
unique_constraints: list[
|
|
579
|
+
unique_constraints: list[ContainerConstraintEntity] = []
|
|
579
580
|
for constraint_name, constraint_obj in (container.constraints or {}).items():
|
|
580
581
|
if isinstance(constraint_obj, dm.RequiresConstraint):
|
|
581
582
|
# This is handled in the .from_container method of DMSContainer
|
|
582
583
|
continue
|
|
583
584
|
elif isinstance(constraint_obj, dm.UniquenessConstraint) and prop_id in constraint_obj.properties:
|
|
584
|
-
unique_constraints.append(constraint_name)
|
|
585
|
+
unique_constraints.append(ContainerConstraintEntity(prefix="uniqueness", suffix=constraint_name))
|
|
585
586
|
elif isinstance(constraint_obj, dm.UniquenessConstraint):
|
|
586
587
|
# This does not apply to this property
|
|
587
588
|
continue
|
|
@@ -10,8 +10,10 @@ import pandas as pd
|
|
|
10
10
|
from openpyxl import load_workbook
|
|
11
11
|
from openpyxl.worksheet.worksheet import Worksheet
|
|
12
12
|
from pandas import ExcelFile
|
|
13
|
+
from pydantic import ValidationError
|
|
13
14
|
from rdflib import Namespace, URIRef
|
|
14
15
|
|
|
16
|
+
from cognite.neat.v0.core._data_model._constants import SPLIT_ON_COMMA_PATTERN
|
|
15
17
|
from cognite.neat.v0.core._data_model._shared import (
|
|
16
18
|
ImportedDataModel,
|
|
17
19
|
T_UnverifiedDataModel,
|
|
@@ -23,6 +25,7 @@ from cognite.neat.v0.core._data_model.models import (
|
|
|
23
25
|
SchemaCompleteness,
|
|
24
26
|
)
|
|
25
27
|
from cognite.neat.v0.core._data_model.models._import_contexts import SpreadsheetContext
|
|
28
|
+
from cognite.neat.v0.core._data_model.models.entities._single_value import ContainerConstraintEntity, ContainerEntity
|
|
26
29
|
from cognite.neat.v0.core._issues import IssueList, MultiValueError
|
|
27
30
|
from cognite.neat.v0.core._issues.errors import (
|
|
28
31
|
FileMissingRequiredFieldError,
|
|
@@ -30,7 +33,11 @@ from cognite.neat.v0.core._issues.errors import (
|
|
|
30
33
|
FileReadError,
|
|
31
34
|
)
|
|
32
35
|
from cognite.neat.v0.core._issues.warnings import FileMissingRequiredFieldWarning
|
|
33
|
-
from cognite.neat.v0.core._utils.spreadsheet import
|
|
36
|
+
from cognite.neat.v0.core._utils.spreadsheet import (
|
|
37
|
+
SpreadsheetRead,
|
|
38
|
+
find_column_and_row_with_value,
|
|
39
|
+
read_individual_sheet,
|
|
40
|
+
)
|
|
34
41
|
from cognite.neat.v0.core._utils.text import humanize_collection
|
|
35
42
|
|
|
36
43
|
from ._base import BaseImporter
|
|
@@ -306,7 +313,7 @@ class ExcelImporter(BaseImporter[T_UnverifiedDataModel]):
|
|
|
306
313
|
|
|
307
314
|
"""
|
|
308
315
|
|
|
309
|
-
workbook = load_workbook(filepath)
|
|
316
|
+
workbook = load_workbook(filepath, data_only=True)
|
|
310
317
|
|
|
311
318
|
if "Classes" in workbook.sheetnames:
|
|
312
319
|
print(
|
|
@@ -323,13 +330,17 @@ class ExcelImporter(BaseImporter[T_UnverifiedDataModel]):
|
|
|
323
330
|
if "Properties" in workbook.sheetnames:
|
|
324
331
|
_replace_class_with_concept_cell(workbook["Properties"])
|
|
325
332
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
333
|
+
elif "Containers" in workbook.sheetnames:
|
|
334
|
+
_replace_legacy_constraint_form(workbook["Containers"])
|
|
335
|
+
_replace_legacy_constraint_form(workbook["Properties"])
|
|
329
336
|
|
|
330
337
|
else:
|
|
331
338
|
return filepath
|
|
332
339
|
|
|
340
|
+
with tempfile.NamedTemporaryFile(prefix="temp_neat_file", suffix=".xlsx", delete=False) as temp_file:
|
|
341
|
+
workbook.save(temp_file.name)
|
|
342
|
+
return Path(temp_file.name)
|
|
343
|
+
|
|
333
344
|
|
|
334
345
|
def _replace_class_with_concept_cell(sheet: Worksheet) -> None:
|
|
335
346
|
"""
|
|
@@ -342,3 +353,72 @@ def _replace_class_with_concept_cell(sheet: Worksheet) -> None:
|
|
|
342
353
|
for cell in row:
|
|
343
354
|
if cell.value == "Class":
|
|
344
355
|
cell.value = "Concept"
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _replace_legacy_constraint_form(sheet: Worksheet) -> None:
|
|
359
|
+
"""
|
|
360
|
+
Replaces the legacy form of container constraints with the new form in the given sheet.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
sheet (Worksheet): The sheet in which to replace the old form of container constraints.
|
|
364
|
+
"""
|
|
365
|
+
column, row = find_column_and_row_with_value(sheet, "Constraint", False)
|
|
366
|
+
|
|
367
|
+
if not column or not row:
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
# Iterate over values in the constraint column and replace old form with new form
|
|
371
|
+
replaced: bool = False
|
|
372
|
+
for cell_row in sheet.iter_rows(min_row=row + 1, min_col=column, max_col=column):
|
|
373
|
+
cell = cell_row[0]
|
|
374
|
+
if cell.value is not None: # Skip empty cells
|
|
375
|
+
# Container sheet update
|
|
376
|
+
if sheet.title.lower() == "containers":
|
|
377
|
+
constraints = []
|
|
378
|
+
for constraint in SPLIT_ON_COMMA_PATTERN.split(str(cell.value)):
|
|
379
|
+
# latest format, do nothing
|
|
380
|
+
if "container" in constraint.lower():
|
|
381
|
+
constraints.append(constraint)
|
|
382
|
+
continue
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
container = ContainerEntity.load(constraint, space="default")
|
|
386
|
+
container_str = container.external_id if container.space == "default" else str(container)
|
|
387
|
+
constraints.append(
|
|
388
|
+
f"requires:{container.space}_{container.external_id}(container={container_str})"
|
|
389
|
+
)
|
|
390
|
+
replaced = True
|
|
391
|
+
except ValidationError:
|
|
392
|
+
constraints.append(constraint)
|
|
393
|
+
|
|
394
|
+
cell.value = ",".join(constraints)
|
|
395
|
+
|
|
396
|
+
# Properties sheet update
|
|
397
|
+
elif sheet.title.lower() == "properties":
|
|
398
|
+
constraints = []
|
|
399
|
+
for constraint in SPLIT_ON_COMMA_PATTERN.split(str(cell.value)):
|
|
400
|
+
try:
|
|
401
|
+
constraint_entity = ContainerConstraintEntity.load(constraint)
|
|
402
|
+
|
|
403
|
+
if constraint_entity.prefix in ["uniqueness", "requires"]:
|
|
404
|
+
constraints.append(constraint)
|
|
405
|
+
|
|
406
|
+
# Replace old format with new format
|
|
407
|
+
else:
|
|
408
|
+
constraints.append(f"uniqueness:{constraint}")
|
|
409
|
+
replaced = True
|
|
410
|
+
|
|
411
|
+
# If the constraint is not valid, we keep it as is
|
|
412
|
+
# to be caught by validation later
|
|
413
|
+
except ValidationError:
|
|
414
|
+
constraints.append(constraint)
|
|
415
|
+
|
|
416
|
+
cell.value = ",".join(constraints)
|
|
417
|
+
|
|
418
|
+
if replaced:
|
|
419
|
+
print(
|
|
420
|
+
(
|
|
421
|
+
"You are using a legacy container constraints format "
|
|
422
|
+
f"in the {sheet.title} sheet. Please update to the latest format."
|
|
423
|
+
),
|
|
424
|
+
)
|
|
@@ -7,6 +7,7 @@ from ._single_value import (
|
|
|
7
7
|
AssetFields,
|
|
8
8
|
ConceptEntity,
|
|
9
9
|
ConceptualEntity,
|
|
10
|
+
ContainerConstraintEntity,
|
|
10
11
|
ContainerEntity,
|
|
11
12
|
ContainerIndexEntity,
|
|
12
13
|
DataModelEntity,
|
|
@@ -35,6 +36,7 @@ __all__ = [
|
|
|
35
36
|
"ConceptPropertyCardinalityConstraint",
|
|
36
37
|
"ConceptPropertyValueConstraint",
|
|
37
38
|
"ConceptualEntity",
|
|
39
|
+
"ContainerConstraintEntity",
|
|
38
40
|
"ContainerEntity",
|
|
39
41
|
"ContainerEntityList",
|
|
40
42
|
"ContainerIndexEntity",
|
|
@@ -660,3 +660,17 @@ class ContainerIndexEntity(PhysicalEntity[None]):
|
|
|
660
660
|
@classmethod
|
|
661
661
|
def from_id(cls, id: None) -> Self:
|
|
662
662
|
return cls(suffix="dummy")
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
class ContainerConstraintEntity(PhysicalEntity[None]):
|
|
666
|
+
type_: ClassVar[EntityTypes] = EntityTypes.container_constraint
|
|
667
|
+
prefix: _UndefinedType | Literal["uniqueness", "requires"] = Undefined # type: ignore[assignment]
|
|
668
|
+
suffix: str
|
|
669
|
+
container: ContainerEntity | None = None
|
|
670
|
+
|
|
671
|
+
def as_id(self) -> None:
|
|
672
|
+
return None
|
|
673
|
+
|
|
674
|
+
@classmethod
|
|
675
|
+
def from_id(cls, id: None) -> Self:
|
|
676
|
+
return cls(suffix="dummy")
|
|
@@ -9,6 +9,7 @@ from pydantic import (
|
|
|
9
9
|
from ._single_value import (
|
|
10
10
|
AssetEntity,
|
|
11
11
|
ConceptEntity,
|
|
12
|
+
ContainerConstraintEntity,
|
|
12
13
|
ContainerEntity,
|
|
13
14
|
ContainerIndexEntity,
|
|
14
15
|
RelationshipEntity,
|
|
@@ -76,6 +77,15 @@ ContainerIndexListType = Annotated[
|
|
|
76
77
|
when_used="unless-none",
|
|
77
78
|
),
|
|
78
79
|
]
|
|
80
|
+
ContainerConstraintListType = Annotated[
|
|
81
|
+
list[ContainerConstraintEntity],
|
|
82
|
+
BeforeValidator(_split_str),
|
|
83
|
+
PlainSerializer(
|
|
84
|
+
_join_str,
|
|
85
|
+
return_type=str,
|
|
86
|
+
when_used="unless-none",
|
|
87
|
+
),
|
|
88
|
+
]
|
|
79
89
|
|
|
80
90
|
ViewEntityList = Annotated[
|
|
81
91
|
list[ViewEntity],
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import hashlib
|
|
2
1
|
import warnings
|
|
3
2
|
from collections import defaultdict
|
|
4
3
|
from collections.abc import Collection, Hashable, Sequence
|
|
@@ -26,7 +25,6 @@ from cognite.neat.v0.core._constants import (
|
|
|
26
25
|
DMS_DIRECT_RELATION_LIST_DEFAULT_LIMIT,
|
|
27
26
|
DMS_PRIMITIVE_LIST_DEFAULT_LIMIT,
|
|
28
27
|
)
|
|
29
|
-
from cognite.neat.v0.core._data_model._constants import CONSTRAINT_ID_MAX_LENGTH
|
|
30
28
|
from cognite.neat.v0.core._data_model.models.data_types import DataType, Double, Enum, Float, LangString, String
|
|
31
29
|
from cognite.neat.v0.core._data_model.models.entities import (
|
|
32
30
|
ConceptEntity,
|
|
@@ -381,7 +379,8 @@ class _DMSExporter:
|
|
|
381
379
|
for prop in container_properties:
|
|
382
380
|
if prop.container_property is not None:
|
|
383
381
|
for constraint in prop.constraint or []:
|
|
384
|
-
uniqueness_properties[constraint].add(prop.container_property)
|
|
382
|
+
uniqueness_properties[cast(str, constraint.suffix)].add(prop.container_property)
|
|
383
|
+
|
|
385
384
|
for constraint_name, properties in uniqueness_properties.items():
|
|
386
385
|
container.constraints = container.constraints or {}
|
|
387
386
|
container.constraints[constraint_name] = dm.UniquenessConstraint(properties=list(properties))
|
|
@@ -411,19 +410,12 @@ class _DMSExporter:
|
|
|
411
410
|
for container in containers:
|
|
412
411
|
if container.constraints:
|
|
413
412
|
container.constraints = {
|
|
414
|
-
|
|
413
|
+
name: const
|
|
415
414
|
for name, const in container.constraints.items()
|
|
416
415
|
if not (isinstance(const, dm.RequiresConstraint) and const.require in container_to_drop)
|
|
417
416
|
}
|
|
418
417
|
return ContainerApplyDict([container for container in containers if container.as_id() not in container_to_drop])
|
|
419
418
|
|
|
420
|
-
@staticmethod
|
|
421
|
-
def _truncate_constraint_name(name: str) -> str:
|
|
422
|
-
if len(name) <= CONSTRAINT_ID_MAX_LENGTH:
|
|
423
|
-
return name
|
|
424
|
-
half_length = int(CONSTRAINT_ID_MAX_LENGTH / 2)
|
|
425
|
-
return f"{name[: half_length - 1]}{hashlib.md5(name.encode()).hexdigest()[:half_length]}"
|
|
426
|
-
|
|
427
419
|
@staticmethod
|
|
428
420
|
def _gather_properties(
|
|
429
421
|
properties: Sequence[PhysicalProperty],
|
|
@@ -21,6 +21,7 @@ from cognite.neat.v0.core._data_model.models._base_unverified import (
|
|
|
21
21
|
)
|
|
22
22
|
from cognite.neat.v0.core._data_model.models.data_types import DataType
|
|
23
23
|
from cognite.neat.v0.core._data_model.models.entities import (
|
|
24
|
+
ContainerConstraintEntity,
|
|
24
25
|
ContainerEntity,
|
|
25
26
|
ContainerIndexEntity,
|
|
26
27
|
DMSNodeEntity,
|
|
@@ -138,7 +139,7 @@ class UnverifiedPhysicalProperty(UnverifiedComponent[PhysicalProperty]):
|
|
|
138
139
|
container: str | None = None
|
|
139
140
|
container_property: str | None = None
|
|
140
141
|
index: str | list[str | ContainerIndexEntity] | ContainerIndexEntity | None = None
|
|
141
|
-
constraint: str | list[str] | None = None
|
|
142
|
+
constraint: str | list[str] | list[ContainerConstraintEntity] | ContainerConstraintEntity | None = None
|
|
142
143
|
neatId: str | URIRef | None = None
|
|
143
144
|
conceptual: str | URIRef | None = None
|
|
144
145
|
|
|
@@ -197,6 +198,8 @@ class UnverifiedPhysicalProperty(UnverifiedComponent[PhysicalProperty]):
|
|
|
197
198
|
else:
|
|
198
199
|
raise TypeError(f"Unexpected type for index: {type(index)}")
|
|
199
200
|
output["Index"] = index_list
|
|
201
|
+
|
|
202
|
+
output["Constraint"] = _parse_constraints(self.constraint, default_space)
|
|
200
203
|
return output
|
|
201
204
|
|
|
202
205
|
def referenced_view(self, default_space: str, default_version: str) -> ViewEntity:
|
|
@@ -249,7 +252,7 @@ class UnverifiedPhysicalContainer(UnverifiedComponent[PhysicalContainer]):
|
|
|
249
252
|
container: str
|
|
250
253
|
name: str | None = None
|
|
251
254
|
description: str | None = None
|
|
252
|
-
constraint: str | None = None
|
|
255
|
+
constraint: str | list[str] | list[ContainerConstraintEntity] | ContainerConstraintEntity | None = None
|
|
253
256
|
neatId: str | URIRef | None = None
|
|
254
257
|
used_for: Literal["node", "edge", "all"] | None = None
|
|
255
258
|
|
|
@@ -260,14 +263,7 @@ class UnverifiedPhysicalContainer(UnverifiedComponent[PhysicalContainer]):
|
|
|
260
263
|
def dump(self, default_space: str) -> dict[str, Any]: # type: ignore[override]
|
|
261
264
|
output = super().dump()
|
|
262
265
|
output["Container"] = self.as_entity_id(default_space, return_on_failure=True)
|
|
263
|
-
output["Constraint"] = (
|
|
264
|
-
[
|
|
265
|
-
ContainerEntity.load(constraint.strip(), space=default_space, return_on_failure=True)
|
|
266
|
-
for constraint in self.constraint.split(",")
|
|
267
|
-
]
|
|
268
|
-
if self.constraint
|
|
269
|
-
else None
|
|
270
|
-
)
|
|
266
|
+
output["Constraint"] = _parse_constraints(self.constraint, default_space)
|
|
271
267
|
return output
|
|
272
268
|
|
|
273
269
|
@overload
|
|
@@ -286,9 +282,13 @@ class UnverifiedPhysicalContainer(UnverifiedComponent[PhysicalContainer]):
|
|
|
286
282
|
@classmethod
|
|
287
283
|
def from_container(cls, container: dm.ContainerApply) -> "UnverifiedPhysicalContainer":
|
|
288
284
|
constraints: list[str] = []
|
|
289
|
-
for
|
|
285
|
+
for constraint_name, constraint_obj in (container.constraints or {}).items():
|
|
290
286
|
if isinstance(constraint_obj, dm.RequiresConstraint):
|
|
291
|
-
|
|
287
|
+
constraint = ContainerConstraintEntity(
|
|
288
|
+
prefix="requires", suffix=constraint_name, container=ContainerEntity.from_id(constraint_obj.require)
|
|
289
|
+
)
|
|
290
|
+
constraints.append(str(constraint))
|
|
291
|
+
|
|
292
292
|
# UniquenessConstraint it handled in the properties
|
|
293
293
|
container_entity = ContainerEntity.from_id(container.as_id())
|
|
294
294
|
return cls(
|
|
@@ -506,3 +506,52 @@ class UnverifiedPhysicalDataModel(UnverifiedDataModel[PhysicalDataModel]):
|
|
|
506
506
|
def imported_views_and_containers_ids(self) -> tuple[set[ViewId], set[ContainerId]]:
|
|
507
507
|
views, containers = self.imported_views_and_containers()
|
|
508
508
|
return {view.as_id() for view in views}, {container.as_id() for container in containers}
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _parse_constraints(
|
|
512
|
+
constraint: str | list[str] | list[ContainerConstraintEntity] | ContainerConstraintEntity | None,
|
|
513
|
+
default_space: str | None = None,
|
|
514
|
+
) -> list[ContainerConstraintEntity | PhysicalUnknownEntity] | None:
|
|
515
|
+
"""Parse constraint input into a standardized list of ContainerConstraintEntity objects.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
constraint: The constraint input in various formats
|
|
519
|
+
default_space: Default space to use when loading constraint entities
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
List of parsed constraint entities, or None if no constraints
|
|
523
|
+
"""
|
|
524
|
+
if constraint is None:
|
|
525
|
+
return None
|
|
526
|
+
|
|
527
|
+
if isinstance(constraint, ContainerConstraintEntity):
|
|
528
|
+
return [constraint]
|
|
529
|
+
|
|
530
|
+
if isinstance(constraint, str) and "," not in constraint:
|
|
531
|
+
return [ContainerConstraintEntity.load(constraint, return_on_failure=True, space=default_space)]
|
|
532
|
+
|
|
533
|
+
if isinstance(constraint, str):
|
|
534
|
+
return [
|
|
535
|
+
ContainerConstraintEntity.load(constraint_item.strip(), return_on_failure=True, space=default_space)
|
|
536
|
+
for constraint_item in SPLIT_ON_COMMA_PATTERN.split(constraint)
|
|
537
|
+
if constraint_item.strip()
|
|
538
|
+
]
|
|
539
|
+
|
|
540
|
+
if isinstance(constraint, list):
|
|
541
|
+
constraint_list: list[ContainerConstraintEntity | PhysicalUnknownEntity] = []
|
|
542
|
+
for constraint_item in constraint:
|
|
543
|
+
if isinstance(constraint_item, ContainerConstraintEntity):
|
|
544
|
+
constraint_list.append(constraint_item)
|
|
545
|
+
elif isinstance(constraint_item, str):
|
|
546
|
+
constraint_list.extend(
|
|
547
|
+
[
|
|
548
|
+
ContainerConstraintEntity.load(idx.strip(), return_on_failure=True, space=default_space)
|
|
549
|
+
for idx in SPLIT_ON_COMMA_PATTERN.split(constraint_item)
|
|
550
|
+
if idx.strip()
|
|
551
|
+
]
|
|
552
|
+
)
|
|
553
|
+
else:
|
|
554
|
+
raise TypeError(f"Unexpected type for constraint: {type(constraint_item)}")
|
|
555
|
+
return constraint_list
|
|
556
|
+
|
|
557
|
+
raise TypeError(f"Unexpected type for constraint: {type(constraint)}")
|
|
@@ -3,6 +3,7 @@ from collections import Counter, defaultdict
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from functools import lru_cache
|
|
6
|
+
from typing import cast
|
|
6
7
|
|
|
7
8
|
from cognite.client import data_modeling as dm
|
|
8
9
|
from cognite.client.data_classes.data_modeling import ContainerList, ViewId, ViewList
|
|
@@ -121,9 +122,9 @@ class PhysicalValidation:
|
|
|
121
122
|
view_with_properties.add(prop.view)
|
|
122
123
|
|
|
123
124
|
for container in self._containers or []:
|
|
124
|
-
for
|
|
125
|
-
if
|
|
126
|
-
imported_containers.add(
|
|
125
|
+
for constraint in container.constraint or []:
|
|
126
|
+
if constraint.container not in existing_containers:
|
|
127
|
+
imported_containers.add(cast(ContainerEntity, constraint.container))
|
|
127
128
|
|
|
128
129
|
if include_views_with_no_properties:
|
|
129
130
|
extra_views = existing_views - view_with_properties
|
|
@@ -470,8 +471,11 @@ class PhysicalValidation:
|
|
|
470
471
|
)
|
|
471
472
|
)
|
|
472
473
|
constraint_definitions = {
|
|
473
|
-
",".join(
|
|
474
|
+
",".join([str(constraint) for constraint in prop.constraint])
|
|
475
|
+
for _, prop in properties
|
|
476
|
+
if prop.constraint is not None
|
|
474
477
|
}
|
|
478
|
+
|
|
475
479
|
if len(constraint_definitions) > 1:
|
|
476
480
|
errors.append(
|
|
477
481
|
PropertyDefinitionDuplicatedError[dm.ContainerId](
|
|
@@ -10,6 +10,7 @@ from pydantic_core.core_schema import SerializationInfo, ValidationInfo
|
|
|
10
10
|
|
|
11
11
|
from cognite.neat.v0.core._client.data_classes.schema import DMSSchema
|
|
12
12
|
from cognite.neat.v0.core._constants import DMS_CONTAINER_LIST_MAX_LIMIT
|
|
13
|
+
from cognite.neat.v0.core._data_model._constants import CONSTRAINT_ID_MAX_LENGTH
|
|
13
14
|
from cognite.neat.v0.core._data_model.models._base_verified import (
|
|
14
15
|
BaseVerifiedDataModel,
|
|
15
16
|
BaseVerifiedMetadata,
|
|
@@ -25,7 +26,6 @@ from cognite.neat.v0.core._data_model.models._types import (
|
|
|
25
26
|
ConceptEntityType,
|
|
26
27
|
ContainerEntityType,
|
|
27
28
|
PhysicalPropertyType,
|
|
28
|
-
StrListType,
|
|
29
29
|
URIRefType,
|
|
30
30
|
ViewEntityType,
|
|
31
31
|
)
|
|
@@ -45,7 +45,10 @@ from cognite.neat.v0.core._data_model.models.entities import (
|
|
|
45
45
|
ViewEntity,
|
|
46
46
|
ViewEntityList,
|
|
47
47
|
)
|
|
48
|
-
from cognite.neat.v0.core._data_model.models.entities._types import
|
|
48
|
+
from cognite.neat.v0.core._data_model.models.entities._types import (
|
|
49
|
+
ContainerConstraintListType,
|
|
50
|
+
ContainerIndexListType,
|
|
51
|
+
)
|
|
49
52
|
from cognite.neat.v0.core._issues.errors import NeatValueError
|
|
50
53
|
from cognite.neat.v0.core._issues.warnings import NeatValueWarning, PropertyDefinitionWarning
|
|
51
54
|
|
|
@@ -149,7 +152,7 @@ class PhysicalProperty(SheetRow):
|
|
|
149
152
|
alias="Index",
|
|
150
153
|
description="The names of the indexes (comma separated) that should be created for the property.",
|
|
151
154
|
)
|
|
152
|
-
constraint:
|
|
155
|
+
constraint: ContainerConstraintListType | None = Field(
|
|
153
156
|
None,
|
|
154
157
|
alias="Constraint",
|
|
155
158
|
description="The names of the uniquness (comma separated) that should be created for the property.",
|
|
@@ -264,11 +267,12 @@ class PhysicalProperty(SheetRow):
|
|
|
264
267
|
def index_set_correctly(cls, value: list[ContainerIndexEntity] | None, info: ValidationInfo) -> Any:
|
|
265
268
|
if value is None:
|
|
266
269
|
return value
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
270
|
+
|
|
271
|
+
container = info.data["container"]
|
|
272
|
+
container_property = info.data["container_property"]
|
|
273
|
+
|
|
274
|
+
if not container or not container_property:
|
|
275
|
+
raise ValueError("Container and container property must be set to use indexes")
|
|
272
276
|
max_count = info.data.get("max_count")
|
|
273
277
|
is_list = (
|
|
274
278
|
max_count is not None and (isinstance(max_count, int | float) and max_count > 1)
|
|
@@ -277,7 +281,7 @@ class PhysicalProperty(SheetRow):
|
|
|
277
281
|
if index.prefix is Undefined:
|
|
278
282
|
message = f"The type of index is not defined. Please set 'inverted:{index!s}' or 'btree:{index!s}'."
|
|
279
283
|
warnings.warn(
|
|
280
|
-
PropertyDefinitionWarning(container, "container property", container_property, message),
|
|
284
|
+
PropertyDefinitionWarning(str(container), "container property", str(container_property), message),
|
|
281
285
|
stacklevel=2,
|
|
282
286
|
)
|
|
283
287
|
elif index.prefix == "inverted" and not is_list:
|
|
@@ -286,7 +290,7 @@ class PhysicalProperty(SheetRow):
|
|
|
286
290
|
"Please consider using btree index instead."
|
|
287
291
|
)
|
|
288
292
|
warnings.warn(
|
|
289
|
-
PropertyDefinitionWarning(container, "container property", container_property, message),
|
|
293
|
+
PropertyDefinitionWarning(str(container), "container property", str(container_property), message),
|
|
290
294
|
stacklevel=2,
|
|
291
295
|
)
|
|
292
296
|
elif index.prefix == "btree" and is_list:
|
|
@@ -295,17 +299,49 @@ class PhysicalProperty(SheetRow):
|
|
|
295
299
|
"Please consider using inverted index instead."
|
|
296
300
|
)
|
|
297
301
|
warnings.warn(
|
|
298
|
-
PropertyDefinitionWarning(container, "container property", container_property, message),
|
|
302
|
+
PropertyDefinitionWarning(str(container), "container property", str(container_property), message),
|
|
299
303
|
stacklevel=2,
|
|
300
304
|
)
|
|
301
305
|
if index.prefix == "inverted" and (index.cursorable is not None or index.by_space is not None):
|
|
302
306
|
message = "Cursorable and bySpace are not supported for inverted indexes. These will be ignored."
|
|
303
307
|
warnings.warn(
|
|
304
|
-
PropertyDefinitionWarning(container, "container property", container_property, message),
|
|
308
|
+
PropertyDefinitionWarning(str(container), "container property", str(container_property), message),
|
|
305
309
|
stacklevel=2,
|
|
306
310
|
)
|
|
307
311
|
return value
|
|
308
312
|
|
|
313
|
+
@field_validator("constraint", mode="after")
|
|
314
|
+
@classmethod
|
|
315
|
+
def constraint_set_correctly(cls, value: ContainerConstraintListType | None, info: ValidationInfo) -> Any:
|
|
316
|
+
if value is None:
|
|
317
|
+
return value
|
|
318
|
+
|
|
319
|
+
container = info.data["container"]
|
|
320
|
+
container_property = info.data["container_property"]
|
|
321
|
+
|
|
322
|
+
if not container or not container_property:
|
|
323
|
+
raise ValueError("Container and container property must be set to use constraint")
|
|
324
|
+
|
|
325
|
+
for constraint in value:
|
|
326
|
+
if constraint.prefix is Undefined:
|
|
327
|
+
message = f"The type of constraint is not defined. Please set 'uniqueness:{constraint!s}'."
|
|
328
|
+
warnings.warn(
|
|
329
|
+
PropertyDefinitionWarning(str(container), "container property", str(container_property), message),
|
|
330
|
+
stacklevel=2,
|
|
331
|
+
)
|
|
332
|
+
elif constraint.prefix != "uniqueness":
|
|
333
|
+
message = (
|
|
334
|
+
f"Unsupported constraint type on container property"
|
|
335
|
+
f" '{constraint.prefix}'. Currently only 'uniqueness' is supported."
|
|
336
|
+
)
|
|
337
|
+
raise ValueError(message) from None
|
|
338
|
+
|
|
339
|
+
if len(constraint.suffix) > CONSTRAINT_ID_MAX_LENGTH:
|
|
340
|
+
message = f"Constraint id '{constraint.suffix}' exceeds maximum length of {CONSTRAINT_ID_MAX_LENGTH}."
|
|
341
|
+
raise ValueError(message) from None
|
|
342
|
+
|
|
343
|
+
return value
|
|
344
|
+
|
|
309
345
|
@field_serializer("value_type", when_used="always")
|
|
310
346
|
def as_dms_type(self, value_type: DataType | EdgeEntity | ViewEntity, info: SerializationInfo) -> str:
|
|
311
347
|
if isinstance(value_type, DataType):
|
|
@@ -352,13 +388,46 @@ class PhysicalContainer(SheetRow):
|
|
|
352
388
|
description: str | None = Field(
|
|
353
389
|
alias="Description", default=None, description="Short description of the node being defined."
|
|
354
390
|
)
|
|
355
|
-
constraint:
|
|
391
|
+
constraint: ContainerConstraintListType | None = Field(
|
|
356
392
|
None, alias="Constraint", description="List of required (comma separated) constraints for the container"
|
|
357
393
|
)
|
|
358
394
|
used_for: Literal["node", "edge", "all"] | None = Field(
|
|
359
395
|
"all", alias="Used For", description=" Whether the container is used for nodes, edges or all."
|
|
360
396
|
)
|
|
361
397
|
|
|
398
|
+
@field_validator("constraint", mode="after")
|
|
399
|
+
@classmethod
|
|
400
|
+
def constraint_set_correctly(cls, value: ContainerConstraintListType | None) -> Any:
|
|
401
|
+
if value is None:
|
|
402
|
+
return value
|
|
403
|
+
|
|
404
|
+
for constraint in value:
|
|
405
|
+
if constraint.prefix is Undefined:
|
|
406
|
+
message = f"The type of constraint is not defined. Please set 'requires:{constraint!s}'."
|
|
407
|
+
warnings.warn(
|
|
408
|
+
message,
|
|
409
|
+
stacklevel=2,
|
|
410
|
+
)
|
|
411
|
+
elif constraint.prefix != "requires":
|
|
412
|
+
message = (
|
|
413
|
+
f"Unsupported constraint type on container as "
|
|
414
|
+
f"the whole '{constraint.prefix}'. Currently only 'requires' is supported."
|
|
415
|
+
)
|
|
416
|
+
raise ValueError(message) from None
|
|
417
|
+
|
|
418
|
+
if len(constraint.suffix) > CONSTRAINT_ID_MAX_LENGTH:
|
|
419
|
+
message = f"Constraint id '{constraint.suffix}' exceeds maximum length of {CONSTRAINT_ID_MAX_LENGTH}."
|
|
420
|
+
raise ValueError(message) from None
|
|
421
|
+
|
|
422
|
+
if constraint.container is None:
|
|
423
|
+
message = (
|
|
424
|
+
f"Container constraint must have a container set. "
|
|
425
|
+
f"Please set 'requires:{constraint!s}(container=space:external_id)'."
|
|
426
|
+
)
|
|
427
|
+
raise ValueError(message) from None
|
|
428
|
+
|
|
429
|
+
return value
|
|
430
|
+
|
|
362
431
|
def _identifier(self) -> tuple[Hashable, ...]:
|
|
363
432
|
return (self.container,)
|
|
364
433
|
|
|
@@ -366,8 +435,10 @@ class PhysicalContainer(SheetRow):
|
|
|
366
435
|
container_id = self.container.as_id()
|
|
367
436
|
constraints: dict[str, dm.Constraint] = {}
|
|
368
437
|
for constraint in self.constraint or []:
|
|
369
|
-
|
|
370
|
-
|
|
438
|
+
if constraint.container is None:
|
|
439
|
+
continue
|
|
440
|
+
requires = dm.RequiresConstraint(constraint.container.as_id())
|
|
441
|
+
constraints[constraint.suffix] = requires
|
|
371
442
|
|
|
372
443
|
return dm.ContainerApply(
|
|
373
444
|
space=container_id.space,
|
|
@@ -29,7 +29,7 @@ from cognite.neat.v0.core._constants import (
|
|
|
29
29
|
DMS_RESERVED_PROPERTIES,
|
|
30
30
|
get_default_prefixes_and_namespaces,
|
|
31
31
|
)
|
|
32
|
-
from cognite.neat.v0.core._data_model._constants import PATTERNS, get_reserved_words
|
|
32
|
+
from cognite.neat.v0.core._data_model._constants import CONSTRAINT_ID_MAX_LENGTH, PATTERNS, get_reserved_words
|
|
33
33
|
from cognite.neat.v0.core._data_model._shared import (
|
|
34
34
|
ImportContext,
|
|
35
35
|
ImportedDataModel,
|
|
@@ -72,6 +72,7 @@ from cognite.neat.v0.core._data_model.models.entities import (
|
|
|
72
72
|
UnknownEntity,
|
|
73
73
|
ViewEntity,
|
|
74
74
|
)
|
|
75
|
+
from cognite.neat.v0.core._data_model.models.entities._single_value import ContainerConstraintEntity
|
|
75
76
|
from cognite.neat.v0.core._data_model.models.physical import (
|
|
76
77
|
PhysicalMetadata,
|
|
77
78
|
PhysicalProperty,
|
|
@@ -1638,14 +1639,20 @@ class _ConceptualDataModelConverter:
|
|
|
1638
1639
|
default_space: str,
|
|
1639
1640
|
concept_by_concept_entity: dict[ConceptEntity, Concept],
|
|
1640
1641
|
referenced_containers: Collection[ContainerEntity],
|
|
1641
|
-
) -> list[
|
|
1642
|
-
constrains: list[
|
|
1642
|
+
) -> list[ContainerConstraintEntity]:
|
|
1643
|
+
constrains: list[ContainerConstraintEntity] = []
|
|
1643
1644
|
for entity in concept_entities:
|
|
1644
1645
|
concept = concept_by_concept_entity[entity]
|
|
1645
1646
|
for parent in concept.implements or []:
|
|
1646
1647
|
parent_entity = parent.as_container_entity(default_space)
|
|
1647
1648
|
if parent_entity in referenced_containers:
|
|
1648
|
-
constrains.append(
|
|
1649
|
+
constrains.append(
|
|
1650
|
+
ContainerConstraintEntity(
|
|
1651
|
+
prefix="requires",
|
|
1652
|
+
suffix=f"{parent_entity.space}_{parent_entity.external_id}"[:CONSTRAINT_ID_MAX_LENGTH],
|
|
1653
|
+
container=parent_entity,
|
|
1654
|
+
)
|
|
1655
|
+
)
|
|
1649
1656
|
return constrains
|
|
1650
1657
|
|
|
1651
1658
|
@classmethod
|
|
@@ -143,13 +143,27 @@ def _get_row_number(sheet: Worksheet, values_to_find: list[str]) -> int | None:
|
|
|
143
143
|
return None
|
|
144
144
|
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
@overload
|
|
147
|
+
def find_column_and_row_with_value(
|
|
148
|
+
sheet: Worksheet, value: Any, column_letter: Literal[True] = True
|
|
149
|
+
) -> tuple[str, int] | tuple[None, None]: ...
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@overload
|
|
153
|
+
def find_column_and_row_with_value(
|
|
154
|
+
sheet: Worksheet, value: Any, column_letter: Literal[False]
|
|
155
|
+
) -> tuple[int, int] | tuple[None, None]: ...
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def find_column_and_row_with_value(
|
|
159
|
+
sheet: Worksheet, value: Any, column_letter: bool = True
|
|
160
|
+
) -> tuple[int, int] | tuple[str, int] | tuple[None, None]:
|
|
147
161
|
for row in sheet.iter_rows():
|
|
148
162
|
for cell in row:
|
|
149
163
|
if cell.value and isinstance(cell.value, str) and cell.value.lower() == value.lower():
|
|
150
|
-
return cell.column_letter
|
|
164
|
+
return (cell.column_letter, cell.row) if column_letter else (cell.column, cell.row)
|
|
151
165
|
|
|
152
|
-
return None
|
|
166
|
+
return None, None
|
|
153
167
|
|
|
154
168
|
|
|
155
169
|
def generate_data_validation(sheet: str, column: str, total_header_rows: int, validation_range: int) -> DataValidation:
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
cognite/neat/__init__.py,sha256=Lo4DbjDOwnhCYUoAgPp5RG1fDdF7OlnomalTe7n1ydw,211
|
|
2
|
+
cognite/neat/_exceptions.py,sha256=IvDKO8kHk4dCkrmQU6z_svJcPqs2m7QYLosuOuM_iwE,473
|
|
2
3
|
cognite/neat/_issues.py,sha256=uv0fkkWwTKqNmTmHqyoBB3L6yMCh42EZpEkLGmIJYOY,812
|
|
3
|
-
cognite/neat/_version.py,sha256=
|
|
4
|
+
cognite/neat/_version.py,sha256=uGBcX4xuE__guAX0yJVFt4XCMymM1MTnVvYJi0BG7QM,46
|
|
4
5
|
cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
6
|
cognite/neat/_data_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
7
|
cognite/neat/_data_model/_constants.py,sha256=NGGvWHlQqhkkSBP_AqoofGYjNph3SiZX6QPINlMsy04,107
|
|
7
8
|
cognite/neat/_data_model/_identifiers.py,sha256=a0LcQ_h0NffxSKTCrzCDpYkrlaUTk-D_rfaQUh-BhWc,1921
|
|
8
|
-
cognite/neat/_data_model/importers/__init__.py,sha256=
|
|
9
|
+
cognite/neat/_data_model/importers/__init__.py,sha256=Ntk6Z8jpPvr7awV4kgs-uMRB2RrA3ufi0O7sSMgcv0o,133
|
|
9
10
|
cognite/neat/_data_model/importers/_base.py,sha256=NRB0FcEBj4GaethU68nRffBfTedBBA866A3zfJNfmiQ,433
|
|
11
|
+
cognite/neat/_data_model/importers/_table_importer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
cognite/neat/_data_model/importers/_table_importer/data_classes.py,sha256=c_P4j7cHTyltNRiNzJJkJ-TKM5uoiizyLI6cgvLA3nM,4057
|
|
13
|
+
cognite/neat/_data_model/importers/_table_importer/importer.py,sha256=aBO8CBHCAeZJbNa7LPt4JfZMb558nKu5KT9aXhp2vvs,3245
|
|
14
|
+
cognite/neat/_data_model/importers/_table_importer/source.py,sha256=CU6kHJquKxDgYSMwONALbl6yleF_FoAgGU2SXl_uj0Y,3098
|
|
10
15
|
cognite/neat/_data_model/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
16
|
cognite/neat/_data_model/models/conceptual/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
17
|
cognite/neat/_data_model/models/conceptual/_base.py,sha256=SFkoBJDM51pqew_isHFJoB20OgfofpwVRnTrg-rKkNY,710
|
|
@@ -41,9 +46,9 @@ cognite/neat/_session/_state_machine/_base.py,sha256=gdk1uIj--f50v2_X9zxqFRYTXiC
|
|
|
41
46
|
cognite/neat/_session/_state_machine/_states.py,sha256=RX6C0YNNT9mX0C6c-ZTiUVh6pmMT5ZNXiOuSs80vVXU,4853
|
|
42
47
|
cognite/neat/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
48
|
cognite/neat/_utils/auxiliary.py,sha256=Cx-LP8dfN782R3iUcm--q26zdzQ0k_RFnVbJ0bwVZMI,1345
|
|
44
|
-
cognite/neat/_utils/text.py,sha256=
|
|
45
|
-
cognite/neat/_utils/useful_types.py,sha256=
|
|
46
|
-
cognite/neat/_utils/validation.py,sha256=
|
|
49
|
+
cognite/neat/_utils/text.py,sha256=tDspKjU4AkzB6P1kKPNaVpx-yvx3V2uQVC3FAr6nibM,1835
|
|
50
|
+
cognite/neat/_utils/useful_types.py,sha256=3qVfeQXg0xCcSEG7RymYU84LDbk0joKzruFY9EKAX8g,415
|
|
51
|
+
cognite/neat/_utils/validation.py,sha256=vS7GAvMMeco1jUaEs_Rz2pUoz28uee6xU4yt8CrCzE4,5430
|
|
47
52
|
cognite/neat/_utils/http_client/__init__.py,sha256=gJBrOH1tIzEzLforHbeakYimTn4RlelyANps-jtpREI,894
|
|
48
53
|
cognite/neat/_utils/http_client/_client.py,sha256=2RVwTbbPFlQ8eJVLKNUXwnc4Yq_783PkY44zwr6LlT8,11509
|
|
49
54
|
cognite/neat/_utils/http_client/_config.py,sha256=C8IF1JoijmVMjA_FEMgAkiD1buEV1cY5Og3t-Ecyfmk,756
|
|
@@ -68,7 +73,7 @@ cognite/neat/v0/core/_client/data_classes/neat_sequence.py,sha256=QZWSfWnwk6KlYJ
|
|
|
68
73
|
cognite/neat/v0/core/_client/data_classes/schema.py,sha256=YoLm0YQIhbYSFuSgC-GB3jgNy7qZw4WhT3A-jKkIVhk,25079
|
|
69
74
|
cognite/neat/v0/core/_client/data_classes/statistics.py,sha256=GU-u41cOTig0Y5pYhW5KqzCsuAUIX9tOmdizMEveYuw,4487
|
|
70
75
|
cognite/neat/v0/core/_data_model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
71
|
-
cognite/neat/v0/core/_data_model/_constants.py,sha256=
|
|
76
|
+
cognite/neat/v0/core/_data_model/_constants.py,sha256=oNgFTYEQm1yjpUWhWlY9bVjoZ4odXOLQK0r_kGllNOA,6121
|
|
72
77
|
cognite/neat/v0/core/_data_model/_shared.py,sha256=ug6B_Vz7seg464xMMn0v3SKN_FNCNG1QeLqW-Bto-yA,2100
|
|
73
78
|
cognite/neat/v0/core/_data_model/analysis/__init__.py,sha256=v3hSfz7AEEqcmdjL71I09tP8Hl-gPZYOiDYMp_CW4vg,70
|
|
74
79
|
cognite/neat/v0/core/_data_model/analysis/_base.py,sha256=AfaKN-NlrXT5J-Tf3CuVzZHwbJXuJbdXwFRJ9uGI_Qg,24472
|
|
@@ -79,7 +84,7 @@ cognite/neat/v0/core/_data_model/catalog/hello_world_pump.xlsx,sha256=E63t5U1PQL
|
|
|
79
84
|
cognite/neat/v0/core/_data_model/exporters/__init__.py,sha256=NSTeVtfgfkfrhf_Ruz5rHnlF0wIj2dIhAm3io1tEIkQ,1153
|
|
80
85
|
cognite/neat/v0/core/_data_model/exporters/_base.py,sha256=pdtQDi5G89JVbHtGnvLHJDoujT1UVaCBw939Re1Gqv4,2412
|
|
81
86
|
cognite/neat/v0/core/_data_model/exporters/_data_model2dms.py,sha256=LdLGgR73n9k5hHiWUSdvDc0lmaEO8UVQYFTiaVWHQ-c,19918
|
|
82
|
-
cognite/neat/v0/core/_data_model/exporters/_data_model2excel.py,sha256=
|
|
87
|
+
cognite/neat/v0/core/_data_model/exporters/_data_model2excel.py,sha256=Oba2Cj81QNNxnSVX4-xxwyysGeAUPqUaf9oHEnpQjAc,25514
|
|
83
88
|
cognite/neat/v0/core/_data_model/exporters/_data_model2instance_template.py,sha256=cmyQBlc1UM5l42g1N_F2K1sVu39ontsmA2bfXJO5muQ,6115
|
|
84
89
|
cognite/neat/v0/core/_data_model/exporters/_data_model2semantic_model.py,sha256=vxeDEWyvj7uv6geNlTQe6MfGcNZkeQJYz0vLzPI7e14,23154
|
|
85
90
|
cognite/neat/v0/core/_data_model/exporters/_data_model2yaml.py,sha256=v7CgNVtVv4t0FXHPJM-HGkID0AqVDlwsQqUNjWqr_1U,3287
|
|
@@ -87,9 +92,9 @@ cognite/neat/v0/core/_data_model/importers/__init__.py,sha256=jkDKSGv5VdJgl44lmn
|
|
|
87
92
|
cognite/neat/v0/core/_data_model/importers/_base.py,sha256=SAC3nhJUBBl5PwnIs-bQvVam_0zEGjlOlNMvVOuJZzI,2028
|
|
88
93
|
cognite/neat/v0/core/_data_model/importers/_base_file_reader.py,sha256=OjjXb0IOlu69PXrhwH9EH4jbwNFwyKfYOQR0WnuwqO4,1627
|
|
89
94
|
cognite/neat/v0/core/_data_model/importers/_dict2data_model.py,sha256=CeDZNAO4W5JZmrboYtTfHnuU1UoXRI2nOUaasWmxfqM,4720
|
|
90
|
-
cognite/neat/v0/core/_data_model/importers/_dms2data_model.py,sha256=
|
|
95
|
+
cognite/neat/v0/core/_data_model/importers/_dms2data_model.py,sha256=yXQucBtmJLzEHKly2RcOB-g2iiAyM_h4jFbVT-38gHM,29557
|
|
91
96
|
cognite/neat/v0/core/_data_model/importers/_graph2data_model.py,sha256=ILgrhror8PUpwKT9Q9Y8XO_rLSe4qPV4v7jvltjhzeM,13720
|
|
92
|
-
cognite/neat/v0/core/_data_model/importers/_spreadsheet2data_model.py,sha256=
|
|
97
|
+
cognite/neat/v0/core/_data_model/importers/_spreadsheet2data_model.py,sha256=qsvb4cA93VpTfMATaVnZyXs-hpzTfxl8jwGk5cpp3k0,15437
|
|
93
98
|
cognite/neat/v0/core/_data_model/importers/_rdf/__init__.py,sha256=1yOjV2PKCxwH7uCTXVZhSdxtn5etmFX40cksvwtKcZ8,199
|
|
94
99
|
cognite/neat/v0/core/_data_model/importers/_rdf/_base.py,sha256=Rv24TQQDZqAuFD8Qh0yRBiB7-JvoMVj3mGoPBm-xBFs,6052
|
|
95
100
|
cognite/neat/v0/core/_data_model/importers/_rdf/_inference2rdata_model.py,sha256=w8LsdkzgoTXN3LZyjEL_5grWZGlf7PPj8qOu370_K6M,28976
|
|
@@ -105,25 +110,25 @@ cognite/neat/v0/core/_data_model/models/conceptual/__init__.py,sha256=9A6myEV8s0
|
|
|
105
110
|
cognite/neat/v0/core/_data_model/models/conceptual/_unverified.py,sha256=UvqjGxVQsf_cNCYf726TXgnPplUy3iBuI-nUp0TclD0,6539
|
|
106
111
|
cognite/neat/v0/core/_data_model/models/conceptual/_validation.py,sha256=oaXRe1RfwsCQ_D5-IdXJf6yZ4Eug_PLu-bDklADYGV4,13895
|
|
107
112
|
cognite/neat/v0/core/_data_model/models/conceptual/_verified.py,sha256=8-H535sx-V11cNyuVOHImqWga8CsqcjPvXfwp0PclBQ,13743
|
|
108
|
-
cognite/neat/v0/core/_data_model/models/entities/__init__.py,sha256=
|
|
113
|
+
cognite/neat/v0/core/_data_model/models/entities/__init__.py,sha256=pDnSy0k2WmoGuvzyLmXimH3IDA6pjO0bO0aEse2TC3o,1853
|
|
109
114
|
cognite/neat/v0/core/_data_model/models/entities/_constants.py,sha256=GXRzVfArwxF3C67VCkzy0JWTZRkRJUYXBQaaecrqcWc,351
|
|
110
115
|
cognite/neat/v0/core/_data_model/models/entities/_loaders.py,sha256=6UD1ik4juMh-yW9cEEnXuheV1vwopdM6d_BBpyUOUUY,5709
|
|
111
116
|
cognite/neat/v0/core/_data_model/models/entities/_multi_value.py,sha256=lkQ95o3Tvf7QImDq8d6KYfgYyq4_zQCqVBAZDOdK2TY,2798
|
|
112
117
|
cognite/neat/v0/core/_data_model/models/entities/_restrictions.py,sha256=wjA6GpfL1FGIjsf6Mo6Rj9Ht-gRnBvOkhj_waN2w2p4,8943
|
|
113
|
-
cognite/neat/v0/core/_data_model/models/entities/_single_value.py,sha256=
|
|
114
|
-
cognite/neat/v0/core/_data_model/models/entities/_types.py,sha256=
|
|
118
|
+
cognite/neat/v0/core/_data_model/models/entities/_single_value.py,sha256=ou6adX0-h7PVN7_6IrvP8WXoCDpstTkq7C9Yh_scaZU,24404
|
|
119
|
+
cognite/neat/v0/core/_data_model/models/entities/_types.py,sha256=ZmtRvQf4Ghhf4hDnKbZeQq4tZyj5NcQngtw8GeZYa_k,2432
|
|
115
120
|
cognite/neat/v0/core/_data_model/models/entities/_wrapped.py,sha256=hOvdyxCNFgv1UdfaasviKnbEN4yN09Iip0ggQiaXgB4,7993
|
|
116
121
|
cognite/neat/v0/core/_data_model/models/mapping/__init__.py,sha256=T68Hf7rhiXa7b03h4RMwarAmkGnB-Bbhc1H07b2PyC4,100
|
|
117
122
|
cognite/neat/v0/core/_data_model/models/mapping/_classic2core.py,sha256=F0zusTh9pPR4z-RExPw3o4EMBSU2si6FJLuej2a3JzM,1430
|
|
118
123
|
cognite/neat/v0/core/_data_model/models/mapping/_classic2core.yaml,sha256=ei-nuivNWVW9HmvzDBKIPF6ZdgaMq64XHw_rKm0CMxg,22584
|
|
119
124
|
cognite/neat/v0/core/_data_model/models/physical/__init__.py,sha256=pH5ZF8jiW0A2w7VCSoHUsXxe894QFvTtgjxXNGVVaxk,990
|
|
120
|
-
cognite/neat/v0/core/_data_model/models/physical/_exporter.py,sha256=
|
|
121
|
-
cognite/neat/v0/core/_data_model/models/physical/_unverified.py,sha256=
|
|
122
|
-
cognite/neat/v0/core/_data_model/models/physical/_validation.py,sha256=
|
|
123
|
-
cognite/neat/v0/core/_data_model/models/physical/_verified.py,sha256=
|
|
125
|
+
cognite/neat/v0/core/_data_model/models/physical/_exporter.py,sha256=ega1Yyosq-D9XYCypx9wtuMTFwS0EMRo_e3JwW434XU,30335
|
|
126
|
+
cognite/neat/v0/core/_data_model/models/physical/_unverified.py,sha256=ZhxKGVM_wyl8uNFspsoNs9uSD0SVE1HO9HcRy2ffzDQ,22421
|
|
127
|
+
cognite/neat/v0/core/_data_model/models/physical/_validation.py,sha256=wxk65FrF5u_OwT28yB1tQHwRwkjj5Q938P6_b9RMIsA,41330
|
|
128
|
+
cognite/neat/v0/core/_data_model/models/physical/_verified.py,sha256=J0HNOAlAm2WNlNI0EcOaBOI-mJxUHghs2BIgLYevSbM,29830
|
|
124
129
|
cognite/neat/v0/core/_data_model/transformers/__init__.py,sha256=N6yRBplAkrwwxoTAre_1BE_fdSZL5jihr7xTQjW3KnM,1876
|
|
125
130
|
cognite/neat/v0/core/_data_model/transformers/_base.py,sha256=3ZO73A3xIAFUAyH4U682CbPUmZLPUB8HpWGpYvVbRBQ,3145
|
|
126
|
-
cognite/neat/v0/core/_data_model/transformers/_converters.py,sha256=
|
|
131
|
+
cognite/neat/v0/core/_data_model/transformers/_converters.py,sha256=vgypwPCI14DtKDl3WdXzuFxH1bgxH-7yLG-Ytvjo0MM,112042
|
|
127
132
|
cognite/neat/v0/core/_data_model/transformers/_mapping.py,sha256=6R4QVblqivI1NvI0iSG5xC7fGHrdZcxqRg3c2Zx5o2E,19045
|
|
128
133
|
cognite/neat/v0/core/_data_model/transformers/_union_conceptual.py,sha256=Dp8Oe6i2duihwDWnGnfPEXRExyKIMiFM_XEcr0U9IbE,8867
|
|
129
134
|
cognite/neat/v0/core/_data_model/transformers/_verification.py,sha256=rIsmrmHvCgEddpPFIEXMMbRb9x9m3jCBi184g3JcrNA,5214
|
|
@@ -200,7 +205,7 @@ cognite/neat/v0/core/_utils/collection_.py,sha256=hYyvje47AAvY7sCmZy0NouX1OMVKLh
|
|
|
200
205
|
cognite/neat/v0/core/_utils/graph_transformations_report.py,sha256=-V0LM9BI_bHT6PTTG9Qby4PGtFkYU3cxtF0cz8KoKek,1238
|
|
201
206
|
cognite/neat/v0/core/_utils/io_.py,sha256=D2Mg8sOxfBoDg3fC0jBzaxO3vkXmr0QvZSgYIv6xRkM,386
|
|
202
207
|
cognite/neat/v0/core/_utils/rdf_.py,sha256=ccnKqLPyKyZ44KknsN3kpCf-DkjSHSqX2KMreFN4Dlc,11226
|
|
203
|
-
cognite/neat/v0/core/_utils/spreadsheet.py,sha256=
|
|
208
|
+
cognite/neat/v0/core/_utils/spreadsheet.py,sha256=lc8lO1v6PIRZ7s0sFBqUOzDGRD1JhsuW-zSghC-xnqE,6409
|
|
204
209
|
cognite/neat/v0/core/_utils/tarjan.py,sha256=IZvwaIITryGVNbo9Bv5EA9_sW3DyfUNAe7uYyPOCL0g,1357
|
|
205
210
|
cognite/neat/v0/core/_utils/text.py,sha256=3Dkz_lmGOHHJ_LKnd1GM_wrGHUWXsXse0Us2BAW5fUI,8522
|
|
206
211
|
cognite/neat/v0/core/_utils/time_.py,sha256=7ayUm0OWZm1JDmy32E4ip8WRr2o0GLwrHwJA8sJ43Z4,357
|
|
@@ -239,7 +244,7 @@ cognite/neat/v0/session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4
|
|
|
239
244
|
cognite/neat/v0/session/engine/_import.py,sha256=1QxA2_EK613lXYAHKQbZyw2yjo5P9XuiX4Z6_6-WMNQ,169
|
|
240
245
|
cognite/neat/v0/session/engine/_interface.py,sha256=3W-cYr493c_mW3P5O6MKN1xEQg3cA7NHR_ev3zdF9Vk,533
|
|
241
246
|
cognite/neat/v0/session/engine/_load.py,sha256=u0x7vuQCRoNcPt25KJBJRn8sJabonYK4vtSZpiTdP4k,5201
|
|
242
|
-
cognite_neat-0.
|
|
243
|
-
cognite_neat-0.
|
|
244
|
-
cognite_neat-0.
|
|
245
|
-
cognite_neat-0.
|
|
247
|
+
cognite_neat-0.125.0.dist-info/METADATA,sha256=7ivvPpgIWACTNikYCjpnMN88v2kbu9G3JNIhGL3oTgQ,9147
|
|
248
|
+
cognite_neat-0.125.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
249
|
+
cognite_neat-0.125.0.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
|
|
250
|
+
cognite_neat-0.125.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|