cognite-neat 0.78.3__py3-none-any.whl → 0.78.5__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/_version.py +1 -1
- cognite/neat/graph/_tracking/__init__.py +4 -0
- cognite/neat/graph/_tracking/base.py +30 -0
- cognite/neat/graph/_tracking/log.py +27 -0
- cognite/neat/graph/extractors/__init__.py +19 -0
- cognite/neat/graph/extractors/_classic_cdf/__init__.py +0 -0
- cognite/neat/graph/extractors/_classic_cdf/_assets.py +107 -0
- cognite/neat/graph/extractors/_classic_cdf/_events.py +117 -0
- cognite/neat/graph/extractors/_classic_cdf/_files.py +131 -0
- cognite/neat/graph/extractors/_classic_cdf/_labels.py +72 -0
- cognite/neat/graph/extractors/_classic_cdf/_relationships.py +153 -0
- cognite/neat/graph/extractors/_classic_cdf/_sequences.py +92 -0
- cognite/neat/graph/extractors/_classic_cdf/_timeseries.py +118 -0
- cognite/neat/graph/issues/__init__.py +0 -0
- cognite/neat/graph/issues/loader.py +104 -0
- cognite/neat/graph/loaders/__init__.py +4 -0
- cognite/neat/graph/loaders/_base.py +109 -0
- cognite/neat/graph/loaders/_rdf2dms.py +280 -0
- cognite/neat/graph/stores/_base.py +19 -4
- cognite/neat/issues.py +150 -0
- cognite/neat/rules/exporters/_base.py +2 -3
- cognite/neat/rules/exporters/_rules2dms.py +5 -5
- cognite/neat/rules/importers/_base.py +1 -1
- cognite/neat/rules/issues/__init__.py +2 -3
- cognite/neat/rules/issues/base.py +9 -133
- cognite/neat/rules/issues/spreadsheet.py +3 -2
- cognite/neat/rules/models/_base.py +6 -0
- cognite/neat/rules/models/dms/_rules.py +3 -0
- cognite/neat/rules/models/dms/_schema.py +133 -3
- cognite/neat/rules/models/domain.py +3 -0
- cognite/neat/rules/models/information/_rules.py +4 -1
- cognite/neat/{rules/exporters/_models.py → utils/upload.py} +26 -6
- cognite/neat/utils/utils.py +24 -0
- {cognite_neat-0.78.3.dist-info → cognite_neat-0.78.5.dist-info}/METADATA +2 -2
- {cognite_neat-0.78.3.dist-info → cognite_neat-0.78.5.dist-info}/RECORD +38 -21
- {cognite_neat-0.78.3.dist-info → cognite_neat-0.78.5.dist-info}/LICENSE +0 -0
- {cognite_neat-0.78.3.dist-info → cognite_neat-0.78.5.dist-info}/WHEEL +0 -0
- {cognite_neat-0.78.3.dist-info → cognite_neat-0.78.5.dist-info}/entry_points.txt +0 -0
|
@@ -1,87 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
import warnings
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from collections import UserList
|
|
5
|
-
from collections.abc import Sequence
|
|
1
|
+
from abc import ABC
|
|
6
2
|
from dataclasses import dataclass
|
|
7
|
-
from
|
|
8
|
-
from typing import Any, ClassVar
|
|
9
|
-
from warnings import WarningMessage
|
|
3
|
+
from typing import Any
|
|
10
4
|
|
|
11
|
-
import pandas as pd
|
|
12
5
|
from pydantic_core import ErrorDetails
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
from exceptiongroup import ExceptionGroup
|
|
16
|
-
else:
|
|
17
|
-
pass
|
|
7
|
+
from cognite.neat.issues import MultiValueError, NeatError, NeatIssue, NeatIssueList, NeatWarning
|
|
18
8
|
|
|
19
9
|
__all__ = [
|
|
20
10
|
"ValidationIssue",
|
|
21
11
|
"NeatValidationError",
|
|
22
12
|
"DefaultPydanticError",
|
|
23
13
|
"ValidationWarning",
|
|
24
|
-
"DefaultWarning",
|
|
25
14
|
"IssueList",
|
|
26
15
|
"MultiValueError",
|
|
27
16
|
]
|
|
28
17
|
|
|
29
18
|
|
|
30
|
-
@total_ordering
|
|
31
19
|
@dataclass(frozen=True)
|
|
32
|
-
class ValidationIssue(ABC):
|
|
33
|
-
description: ClassVar[str]
|
|
34
|
-
fix: ClassVar[str]
|
|
35
|
-
|
|
36
|
-
def message(self) -> str:
|
|
37
|
-
"""Return a human-readable message for the issue.
|
|
38
|
-
|
|
39
|
-
This is the default implementation, which returns the description.
|
|
40
|
-
It is recommended to override this method in subclasses with a more
|
|
41
|
-
specific message.
|
|
42
|
-
"""
|
|
43
|
-
return self.description
|
|
44
|
-
|
|
45
|
-
@abstractmethod
|
|
46
|
-
def dump(self) -> dict[str, Any]:
|
|
47
|
-
"""Return a dictionary representation of the issue."""
|
|
48
|
-
raise NotImplementedError()
|
|
49
|
-
|
|
50
|
-
def __lt__(self, other: "ValidationIssue") -> bool:
|
|
51
|
-
if not isinstance(other, ValidationIssue):
|
|
52
|
-
return NotImplemented
|
|
53
|
-
return (type(self).__name__, self.message()) < (type(other).__name__, other.message())
|
|
54
|
-
|
|
55
|
-
def __eq__(self, other: object) -> bool:
|
|
56
|
-
if not isinstance(other, ValidationIssue):
|
|
57
|
-
return NotImplemented
|
|
58
|
-
return (type(self).__name__, self.message()) == (type(other).__name__, other.message())
|
|
20
|
+
class ValidationIssue(NeatIssue, ABC): ...
|
|
59
21
|
|
|
60
22
|
|
|
61
23
|
@dataclass(frozen=True)
|
|
62
|
-
class NeatValidationError(ValidationIssue, ABC):
|
|
63
|
-
def dump(self) -> dict[str, Any]:
|
|
64
|
-
return {"error": type(self).__name__}
|
|
65
|
-
|
|
24
|
+
class NeatValidationError(NeatError, ValidationIssue, ABC):
|
|
66
25
|
@classmethod
|
|
67
26
|
def from_pydantic_errors(cls, errors: list[ErrorDetails], **kwargs) -> "list[NeatValidationError]":
|
|
68
27
|
"""Convert a list of pydantic errors to a list of Error instances.
|
|
69
28
|
|
|
70
29
|
This is intended to be overridden in subclasses to handle specific error types.
|
|
71
30
|
"""
|
|
72
|
-
all_errors = []
|
|
31
|
+
all_errors: list[NeatValidationError] = []
|
|
73
32
|
for error in errors:
|
|
74
33
|
if isinstance(ctx := error.get("ctx"), dict) and isinstance(
|
|
75
34
|
multi_error := ctx.get("error"), MultiValueError
|
|
76
35
|
):
|
|
77
|
-
all_errors.extend(multi_error.errors)
|
|
36
|
+
all_errors.extend(multi_error.errors) # type: ignore[arg-type]
|
|
78
37
|
else:
|
|
79
38
|
all_errors.append(DefaultPydanticError.from_pydantic_error(error))
|
|
80
39
|
return all_errors
|
|
81
40
|
|
|
82
|
-
def as_exception(self) -> Exception:
|
|
83
|
-
return ValueError(self.message())
|
|
84
|
-
|
|
85
41
|
|
|
86
42
|
@dataclass(frozen=True)
|
|
87
43
|
class DefaultPydanticError(NeatValidationError):
|
|
@@ -120,87 +76,7 @@ class DefaultPydanticError(NeatValidationError):
|
|
|
120
76
|
|
|
121
77
|
|
|
122
78
|
@dataclass(frozen=True)
|
|
123
|
-
class ValidationWarning(ValidationIssue, ABC
|
|
124
|
-
def dump(self) -> dict[str, Any]:
|
|
125
|
-
return {"warning": type(self).__name__}
|
|
126
|
-
|
|
127
|
-
@classmethod
|
|
128
|
-
def from_warning(cls, warning: WarningMessage) -> "ValidationWarning":
|
|
129
|
-
return DefaultWarning.from_warning_message(warning)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
@dataclass(frozen=True)
|
|
133
|
-
class DefaultWarning(ValidationWarning):
|
|
134
|
-
description = "A warning was raised during validation."
|
|
135
|
-
fix = "No fix is available."
|
|
136
|
-
|
|
137
|
-
warning: str | Warning
|
|
138
|
-
category: type[Warning]
|
|
139
|
-
source: str | None = None
|
|
140
|
-
|
|
141
|
-
def dump(self) -> dict[str, Any]:
|
|
142
|
-
output = super().dump()
|
|
143
|
-
output["msg"] = str(self.warning)
|
|
144
|
-
output["category"] = self.category.__name__
|
|
145
|
-
output["source"] = self.source
|
|
146
|
-
return output
|
|
147
|
-
|
|
148
|
-
@classmethod
|
|
149
|
-
def from_warning_message(cls, warning: WarningMessage) -> "ValidationWarning":
|
|
150
|
-
if isinstance(warning.message, ValidationWarning):
|
|
151
|
-
return warning.message
|
|
152
|
-
|
|
153
|
-
return cls(
|
|
154
|
-
warning=warning.message,
|
|
155
|
-
category=warning.category,
|
|
156
|
-
source=warning.source,
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
def message(self) -> str:
|
|
160
|
-
return str(self.warning)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class IssueList(UserList[ValidationIssue]):
|
|
164
|
-
def __init__(self, issues: Sequence[ValidationIssue] | None = None, title: str | None = None):
|
|
165
|
-
super().__init__(issues or [])
|
|
166
|
-
self.title = title
|
|
167
|
-
|
|
168
|
-
@property
|
|
169
|
-
def errors(self) -> "IssueList":
|
|
170
|
-
return IssueList([issue for issue in self if isinstance(issue, NeatValidationError)])
|
|
171
|
-
|
|
172
|
-
@property
|
|
173
|
-
def has_errors(self) -> bool:
|
|
174
|
-
return any(isinstance(issue, NeatValidationError) for issue in self)
|
|
175
|
-
|
|
176
|
-
@property
|
|
177
|
-
def warnings(self) -> "IssueList":
|
|
178
|
-
return IssueList([issue for issue in self if isinstance(issue, ValidationWarning)])
|
|
179
|
-
|
|
180
|
-
def as_errors(self) -> ExceptionGroup:
|
|
181
|
-
return ExceptionGroup(
|
|
182
|
-
"Validation failed",
|
|
183
|
-
[ValueError(issue.message()) for issue in self if isinstance(issue, NeatValidationError)],
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
def trigger_warnings(self) -> None:
|
|
187
|
-
for warning in [issue for issue in self if isinstance(issue, ValidationWarning)]:
|
|
188
|
-
warnings.warn(warning, stacklevel=2)
|
|
189
|
-
|
|
190
|
-
def to_pandas(self) -> pd.DataFrame:
|
|
191
|
-
return pd.DataFrame([issue.dump() for issue in self])
|
|
192
|
-
|
|
193
|
-
def _repr_html_(self) -> str | None:
|
|
194
|
-
return self.to_pandas()._repr_html_() # type: ignore[operator]
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
class MultiValueError(ValueError):
|
|
198
|
-
"""This is a container for multiple errors.
|
|
199
|
-
|
|
200
|
-
It is used in the pydantic field_validator/model_validator to collect multiple errors, which
|
|
201
|
-
can then be caught in a try-except block and returned as an IssueList.
|
|
79
|
+
class ValidationWarning(NeatWarning, ValidationIssue, ABC): ...
|
|
202
80
|
|
|
203
|
-
"""
|
|
204
81
|
|
|
205
|
-
|
|
206
|
-
self.errors = list(errors)
|
|
82
|
+
class IssueList(NeatIssueList[ValidationIssue]): ...
|
|
@@ -8,9 +8,10 @@ from cognite.client.data_classes import data_modeling as dm
|
|
|
8
8
|
from cognite.client.data_classes.data_modeling import ContainerId, ViewId
|
|
9
9
|
from pydantic_core import ErrorDetails
|
|
10
10
|
|
|
11
|
+
from cognite.neat.issues import MultiValueError
|
|
11
12
|
from cognite.neat.utils.spreadsheet import SpreadsheetRead
|
|
12
13
|
|
|
13
|
-
from .base import DefaultPydanticError,
|
|
14
|
+
from .base import DefaultPydanticError, NeatValidationError
|
|
14
15
|
|
|
15
16
|
if sys.version_info >= (3, 11):
|
|
16
17
|
from typing import Self
|
|
@@ -69,7 +70,7 @@ class InvalidSheetError(NeatValidationError, ABC):
|
|
|
69
70
|
new_row = reader.adjusted_row_number(caught_error.row)
|
|
70
71
|
# The error is frozen, so we have to use __setattr__ to change the row number
|
|
71
72
|
object.__setattr__(caught_error, "row", new_row)
|
|
72
|
-
output.append(caught_error)
|
|
73
|
+
output.append(caught_error) # type: ignore[arg-type]
|
|
73
74
|
continue
|
|
74
75
|
|
|
75
76
|
if len(error["loc"]) >= 4:
|
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
import math
|
|
8
8
|
import sys
|
|
9
9
|
import types
|
|
10
|
+
from abc import abstractmethod
|
|
10
11
|
from collections.abc import Callable, Iterator
|
|
11
12
|
from functools import wraps
|
|
12
13
|
from typing import Annotated, Any, ClassVar, Generic, Literal, TypeAlias, TypeVar
|
|
@@ -245,6 +246,11 @@ class BaseMetadata(RuleModel):
|
|
|
245
246
|
def include_role(self, serializer: Callable) -> dict:
|
|
246
247
|
return {"role": self.role.value, **serializer(self)}
|
|
247
248
|
|
|
249
|
+
@abstractmethod
|
|
250
|
+
def as_identifier(self) -> str:
|
|
251
|
+
"""Returns a unique identifier for the metadata."""
|
|
252
|
+
raise NotImplementedError()
|
|
253
|
+
|
|
248
254
|
|
|
249
255
|
class BaseRules(RuleModel):
|
|
250
256
|
"""
|
|
@@ -132,6 +132,9 @@ class DMSMetadata(BaseMetadata):
|
|
|
132
132
|
views=[],
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
+
def as_identifier(self) -> str:
|
|
136
|
+
return repr(self.as_data_model_id())
|
|
137
|
+
|
|
135
138
|
@classmethod
|
|
136
139
|
def _get_description_and_creator(cls, description_raw: str | None) -> tuple[str | None, list[str]]:
|
|
137
140
|
if description_raw and (description_match := re.search(r"Creator: (.+)", description_raw)):
|
|
@@ -2,17 +2,27 @@ import json
|
|
|
2
2
|
import sys
|
|
3
3
|
import warnings
|
|
4
4
|
import zipfile
|
|
5
|
-
from collections import Counter, defaultdict
|
|
5
|
+
from collections import ChainMap, Counter, defaultdict
|
|
6
|
+
from collections.abc import Iterable, MutableMapping
|
|
6
7
|
from dataclasses import Field, dataclass, field, fields
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Any, ClassVar, cast
|
|
9
|
+
from typing import Any, ClassVar, Literal, cast
|
|
9
10
|
|
|
10
11
|
import yaml
|
|
11
12
|
from cognite.client import CogniteClient
|
|
12
13
|
from cognite.client import data_modeling as dm
|
|
13
14
|
from cognite.client.data_classes import DatabaseWrite, DatabaseWriteList, TransformationWrite, TransformationWriteList
|
|
14
15
|
from cognite.client.data_classes.data_modeling import ViewApply
|
|
15
|
-
from cognite.client.data_classes.data_modeling.views import
|
|
16
|
+
from cognite.client.data_classes.data_modeling.views import (
|
|
17
|
+
ReverseDirectRelation,
|
|
18
|
+
ReverseDirectRelationApply,
|
|
19
|
+
SingleEdgeConnection,
|
|
20
|
+
SingleEdgeConnectionApply,
|
|
21
|
+
SingleReverseDirectRelation,
|
|
22
|
+
SingleReverseDirectRelationApply,
|
|
23
|
+
ViewProperty,
|
|
24
|
+
ViewPropertyApply,
|
|
25
|
+
)
|
|
16
26
|
from cognite.client.data_classes.transformations.common import Edges, EdgeType, Nodes, ViewInfo
|
|
17
27
|
|
|
18
28
|
from cognite.neat.rules import issues
|
|
@@ -668,6 +678,126 @@ class DMSSchema:
|
|
|
668
678
|
referenced_spaces |= {s.space for s in self.spaces.values()}
|
|
669
679
|
return referenced_spaces
|
|
670
680
|
|
|
681
|
+
def as_read_model(self) -> dm.DataModel[dm.View]:
|
|
682
|
+
if self.data_model is None:
|
|
683
|
+
raise ValueError("Data model is not defined")
|
|
684
|
+
all_containers = self.containers.copy()
|
|
685
|
+
all_views = self.views.copy()
|
|
686
|
+
for other_schema in [self.reference, self.last]:
|
|
687
|
+
if other_schema:
|
|
688
|
+
all_containers |= other_schema.containers
|
|
689
|
+
all_views |= other_schema.views
|
|
690
|
+
|
|
691
|
+
views: list[dm.View] = []
|
|
692
|
+
for view in self.views.values():
|
|
693
|
+
referenced_containers = ContainerApplyDict()
|
|
694
|
+
properties: dict[str, ViewProperty] = {}
|
|
695
|
+
# ChainMap is used to merge properties from the view and its parents
|
|
696
|
+
# Note that the order of the ChainMap is important, as the first dictionary has the highest priority
|
|
697
|
+
# So if a child and parent have the same property, the child property will be used.
|
|
698
|
+
write_properties = ChainMap(view.properties, *(all_views[v].properties for v in view.implements or [])) # type: ignore[arg-type]
|
|
699
|
+
for prop_name, prop in write_properties.items():
|
|
700
|
+
read_prop = self._as_read_properties(prop, all_containers)
|
|
701
|
+
if isinstance(read_prop, dm.MappedProperty) and read_prop.container not in referenced_containers:
|
|
702
|
+
referenced_containers[read_prop.container] = all_containers[read_prop.container]
|
|
703
|
+
properties[prop_name] = read_prop
|
|
704
|
+
|
|
705
|
+
read_view = dm.View(
|
|
706
|
+
space=view.space,
|
|
707
|
+
external_id=view.external_id,
|
|
708
|
+
version=view.version,
|
|
709
|
+
description=view.description,
|
|
710
|
+
name=view.name,
|
|
711
|
+
filter=view.filter,
|
|
712
|
+
implements=view.implements.copy(),
|
|
713
|
+
used_for=self._used_for(referenced_containers.values()),
|
|
714
|
+
writable=self._writable(properties.values(), referenced_containers.values()),
|
|
715
|
+
properties=properties,
|
|
716
|
+
is_global=False,
|
|
717
|
+
last_updated_time=0,
|
|
718
|
+
created_time=0,
|
|
719
|
+
)
|
|
720
|
+
views.append(read_view)
|
|
721
|
+
|
|
722
|
+
return dm.DataModel(
|
|
723
|
+
space=self.data_model.space,
|
|
724
|
+
external_id=self.data_model.external_id,
|
|
725
|
+
version=self.data_model.version,
|
|
726
|
+
name=self.data_model.name,
|
|
727
|
+
description=self.data_model.description,
|
|
728
|
+
views=views,
|
|
729
|
+
is_global=False,
|
|
730
|
+
last_updated_time=0,
|
|
731
|
+
created_time=0,
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
@staticmethod
|
|
735
|
+
def _as_read_properties(
|
|
736
|
+
write: ViewPropertyApply, all_containers: MutableMapping[dm.ContainerId, dm.ContainerApply]
|
|
737
|
+
) -> ViewProperty:
|
|
738
|
+
if isinstance(write, dm.MappedPropertyApply):
|
|
739
|
+
container_prop = all_containers[write.container].properties[write.container_property_identifier]
|
|
740
|
+
return dm.MappedProperty(
|
|
741
|
+
container=write.container,
|
|
742
|
+
container_property_identifier=write.container_property_identifier,
|
|
743
|
+
name=write.name,
|
|
744
|
+
description=write.description,
|
|
745
|
+
source=write.source,
|
|
746
|
+
type=container_prop.type,
|
|
747
|
+
nullable=container_prop.nullable,
|
|
748
|
+
auto_increment=container_prop.auto_increment,
|
|
749
|
+
# Likely bug in SDK.
|
|
750
|
+
default_value=container_prop.default_value, # type: ignore[arg-type]
|
|
751
|
+
)
|
|
752
|
+
if isinstance(write, dm.EdgeConnectionApply):
|
|
753
|
+
edge_cls = SingleEdgeConnection if isinstance(write, SingleEdgeConnectionApply) else dm.MultiEdgeConnection
|
|
754
|
+
return edge_cls(
|
|
755
|
+
type=write.type,
|
|
756
|
+
source=write.source,
|
|
757
|
+
name=write.name,
|
|
758
|
+
description=write.description,
|
|
759
|
+
edge_source=write.edge_source,
|
|
760
|
+
direction=write.direction,
|
|
761
|
+
)
|
|
762
|
+
if isinstance(write, ReverseDirectRelationApply):
|
|
763
|
+
relation_cls = (
|
|
764
|
+
SingleReverseDirectRelation
|
|
765
|
+
if isinstance(write, SingleReverseDirectRelationApply)
|
|
766
|
+
else dm.MultiReverseDirectRelation
|
|
767
|
+
)
|
|
768
|
+
return relation_cls(
|
|
769
|
+
source=write.source,
|
|
770
|
+
through=write.through,
|
|
771
|
+
name=write.name,
|
|
772
|
+
description=write.description,
|
|
773
|
+
)
|
|
774
|
+
raise ValueError(f"Cannot convert {write} to read format")
|
|
775
|
+
|
|
776
|
+
@staticmethod
|
|
777
|
+
def _used_for(containers: Iterable[dm.ContainerApply]) -> Literal["node", "edge", "all"]:
|
|
778
|
+
used_for = {container.used_for for container in containers}
|
|
779
|
+
if used_for == {"node"}:
|
|
780
|
+
return "node"
|
|
781
|
+
if used_for == {"edge"}:
|
|
782
|
+
return "edge"
|
|
783
|
+
return "all"
|
|
784
|
+
|
|
785
|
+
@staticmethod
|
|
786
|
+
def _writable(properties: Iterable[ViewProperty], containers: Iterable[dm.ContainerApply]) -> bool:
|
|
787
|
+
used_properties = {
|
|
788
|
+
(prop.container, prop.container_property_identifier)
|
|
789
|
+
for prop in properties
|
|
790
|
+
if isinstance(prop, dm.MappedProperty)
|
|
791
|
+
}
|
|
792
|
+
required_properties = {
|
|
793
|
+
(container.as_id(), prop_id)
|
|
794
|
+
for container in containers
|
|
795
|
+
for prop_id, prop in container.properties.items()
|
|
796
|
+
if not prop.nullable
|
|
797
|
+
}
|
|
798
|
+
# If a container has a required property that is not used by the view, the view is not writable
|
|
799
|
+
return not bool(required_properties - used_properties)
|
|
800
|
+
|
|
671
801
|
|
|
672
802
|
@dataclass
|
|
673
803
|
class PipelineSchema(DMSSchema):
|
|
@@ -21,6 +21,9 @@ class DomainMetadata(BaseMetadata):
|
|
|
21
21
|
role: ClassVar[RoleTypes] = RoleTypes.domain_expert
|
|
22
22
|
creator: StrOrListType
|
|
23
23
|
|
|
24
|
+
def as_identifier(self) -> str:
|
|
25
|
+
return "DomainRules"
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
class DomainProperty(SheetEntity):
|
|
26
29
|
class_: ClassEntity = Field(alias="Class")
|
|
@@ -8,8 +8,8 @@ from pydantic.main import IncEx
|
|
|
8
8
|
from rdflib import Namespace
|
|
9
9
|
|
|
10
10
|
from cognite.neat.constants import PREFIXES
|
|
11
|
+
from cognite.neat.issues import MultiValueError
|
|
11
12
|
from cognite.neat.rules import exceptions, issues
|
|
12
|
-
from cognite.neat.rules.issues.base import MultiValueError
|
|
13
13
|
from cognite.neat.rules.models._base import (
|
|
14
14
|
BaseMetadata,
|
|
15
15
|
BaseRules,
|
|
@@ -116,6 +116,9 @@ class InformationMetadata(BaseMetadata):
|
|
|
116
116
|
def as_enum_model_type(cls, value: str) -> DataModelType:
|
|
117
117
|
return DataModelType(value)
|
|
118
118
|
|
|
119
|
+
def as_identifier(self) -> str:
|
|
120
|
+
return f"{self.prefix}:{self.name}"
|
|
121
|
+
|
|
119
122
|
|
|
120
123
|
class InformationClass(SheetEntity):
|
|
121
124
|
"""
|
|
@@ -2,29 +2,31 @@ from abc import ABC
|
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
3
|
from functools import total_ordering
|
|
4
4
|
|
|
5
|
-
from cognite.neat.
|
|
5
|
+
from cognite.neat.issues import NeatIssueList
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@total_ordering
|
|
9
9
|
@dataclass
|
|
10
10
|
class UploadResultCore(ABC):
|
|
11
11
|
name: str
|
|
12
|
+
error_messages: list[str] = field(default_factory=list)
|
|
13
|
+
issues: NeatIssueList = field(default_factory=NeatIssueList)
|
|
12
14
|
|
|
13
15
|
def __lt__(self, other: object) -> bool:
|
|
14
|
-
if isinstance(other,
|
|
16
|
+
if isinstance(other, UploadDiffsCount):
|
|
15
17
|
return self.name < other.name
|
|
16
18
|
else:
|
|
17
19
|
return NotImplemented
|
|
18
20
|
|
|
19
21
|
def __eq__(self, other: object) -> bool:
|
|
20
|
-
if isinstance(other,
|
|
22
|
+
if isinstance(other, UploadDiffsCount):
|
|
21
23
|
return self.name == other.name
|
|
22
24
|
else:
|
|
23
25
|
return NotImplemented
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
@dataclass
|
|
27
|
-
class
|
|
29
|
+
class UploadDiffsCount(UploadResultCore):
|
|
28
30
|
created: int = 0
|
|
29
31
|
deleted: int = 0
|
|
30
32
|
changed: int = 0
|
|
@@ -33,8 +35,6 @@ class UploadResult(UploadResultCore):
|
|
|
33
35
|
failed_created: int = 0
|
|
34
36
|
failed_changed: int = 0
|
|
35
37
|
failed_deleted: int = 0
|
|
36
|
-
error_messages: list[str] = field(default_factory=list)
|
|
37
|
-
issues: IssueList = field(default_factory=IssueList)
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
40
|
def total(self) -> int:
|
|
@@ -64,3 +64,23 @@ class UploadResult(UploadResultCore):
|
|
|
64
64
|
line.append(f"failed to delete {self.failed_deleted}")
|
|
65
65
|
|
|
66
66
|
return f"{self.name.title()}: {', '.join(line)}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class UploadResultIDs(UploadResultCore):
|
|
71
|
+
success: list[str] = field(default_factory=list)
|
|
72
|
+
failed: list[str] = field(default_factory=list)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class UploadDiffsID(UploadResultCore):
|
|
77
|
+
created: list[str] = field(default_factory=list)
|
|
78
|
+
changed: list[str] = field(default_factory=list)
|
|
79
|
+
unchanged: list[str] = field(default_factory=list)
|
|
80
|
+
failed: list[str] = field(default_factory=list)
|
|
81
|
+
|
|
82
|
+
def as_upload_result_ids(self) -> UploadResultIDs:
|
|
83
|
+
result = UploadResultIDs(name=self.name, error_messages=self.error_messages, issues=self.issues)
|
|
84
|
+
result.success = self.created + self.changed + self.unchanged
|
|
85
|
+
result.failed = self.failed
|
|
86
|
+
return result
|
cognite/neat/utils/utils.py
CHANGED
|
@@ -355,3 +355,27 @@ def get_inheritance_path(child: Any, child_parent: dict[Any, list[Any]]) -> list
|
|
|
355
355
|
|
|
356
356
|
def replace_non_alphanumeric_with_underscore(text):
|
|
357
357
|
return re.sub(r"\W+", "_", text)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def string_to_ideal_type(input_string: str) -> int | bool | float | datetime | str:
|
|
361
|
+
try:
|
|
362
|
+
# Try converting to int
|
|
363
|
+
return int(input_string)
|
|
364
|
+
except ValueError:
|
|
365
|
+
try:
|
|
366
|
+
# Try converting to float
|
|
367
|
+
return float(input_string) # type: ignore
|
|
368
|
+
except ValueError:
|
|
369
|
+
if input_string.lower() == "true":
|
|
370
|
+
# Return True if input is 'true'
|
|
371
|
+
return True
|
|
372
|
+
elif input_string.lower() == "false":
|
|
373
|
+
# Return False if input is 'false'
|
|
374
|
+
return False
|
|
375
|
+
else:
|
|
376
|
+
try:
|
|
377
|
+
# Try converting to datetime
|
|
378
|
+
return datetime.fromisoformat(input_string) # type: ignore
|
|
379
|
+
except ValueError:
|
|
380
|
+
# Return the input string if no conversion is possible
|
|
381
|
+
return input_string
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cognite-neat
|
|
3
|
-
Version: 0.78.
|
|
3
|
+
Version: 0.78.5
|
|
4
4
|
Summary: Knowledge graph transformation
|
|
5
5
|
Home-page: https://cognite-neat.readthedocs-hosted.com/
|
|
6
6
|
License: Apache-2.0
|
|
@@ -22,7 +22,7 @@ Requires-Dist: backports.strenum (>=1.2,<2.0) ; python_version < "3.11"
|
|
|
22
22
|
Requires-Dist: cognite-sdk (>=7.37.0,<8.0.0)
|
|
23
23
|
Requires-Dist: deepdiff
|
|
24
24
|
Requires-Dist: exceptiongroup (>=1.1.3,<2.0.0) ; python_version < "3.11"
|
|
25
|
-
Requires-Dist: fastapi (>=0
|
|
25
|
+
Requires-Dist: fastapi (>=0,<1)
|
|
26
26
|
Requires-Dist: google-api-python-client ; extra == "google" or extra == "all"
|
|
27
27
|
Requires-Dist: google-auth-oauthlib ; extra == "google" or extra == "all"
|
|
28
28
|
Requires-Dist: gspread ; extra == "google" or extra == "all"
|