cognite-neat 0.104.0__py3-none-any.whl → 0.105.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cognite-neat might be problematic. Click here for more details.
- cognite/neat/_client/_api/data_modeling_loaders.py +83 -23
- cognite/neat/_client/_api/schema.py +2 -1
- cognite/neat/_client/data_classes/neat_sequence.py +261 -0
- cognite/neat/_client/data_classes/schema.py +5 -1
- cognite/neat/_client/testing.py +33 -0
- cognite/neat/_constants.py +57 -0
- cognite/neat/_graph/extractors/_classic_cdf/_base.py +6 -5
- cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +225 -11
- cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
- cognite/neat/_graph/loaders/_rdf2dms.py +31 -5
- cognite/neat/_graph/transformers/__init__.py +3 -1
- cognite/neat/_graph/transformers/_classic_cdf.py +39 -51
- cognite/neat/_graph/transformers/_rdfpath.py +14 -15
- cognite/neat/_graph/transformers/_value_type.py +72 -0
- cognite/neat/_issues/__init__.py +0 -2
- cognite/neat/_issues/_base.py +19 -35
- cognite/neat/_issues/warnings/__init__.py +6 -1
- cognite/neat/_issues/warnings/_general.py +7 -0
- cognite/neat/_issues/warnings/_properties.py +11 -0
- cognite/neat/_issues/warnings/_resources.py +11 -0
- cognite/neat/_rules/exporters/_rules2dms.py +35 -1
- cognite/neat/_rules/exporters/_rules2excel.py +2 -2
- cognite/neat/_rules/importers/_dms2rules.py +66 -55
- cognite/neat/_rules/models/_base_rules.py +4 -1
- cognite/neat/_rules/models/entities/_wrapped.py +10 -5
- cognite/neat/_rules/models/mapping/_classic2core.yaml +239 -38
- cognite/neat/_rules/transformers/__init__.py +8 -2
- cognite/neat/_rules/transformers/_converters.py +271 -188
- cognite/neat/_rules/transformers/_mapping.py +75 -59
- cognite/neat/_rules/transformers/_verification.py +2 -3
- cognite/neat/_session/_inspect.py +3 -1
- cognite/neat/_session/_prepare.py +112 -24
- cognite/neat/_session/_read.py +33 -70
- cognite/neat/_session/_state.py +2 -2
- cognite/neat/_session/_to.py +2 -2
- cognite/neat/_store/_rules_store.py +4 -8
- cognite/neat/_utils/reader/_base.py +27 -0
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.1.dist-info}/METADATA +4 -3
- cognite_neat-0.105.1.dist-info/RECORD +179 -0
- {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.1.dist-info}/WHEEL +1 -1
- cognite/neat/_app/api/__init__.py +0 -0
- cognite/neat/_app/api/asgi/metrics.py +0 -4
- cognite/neat/_app/api/configuration.py +0 -98
- cognite/neat/_app/api/context_manager/__init__.py +0 -3
- cognite/neat/_app/api/context_manager/manager.py +0 -16
- cognite/neat/_app/api/data_classes/__init__.py +0 -0
- cognite/neat/_app/api/data_classes/rest.py +0 -59
- cognite/neat/_app/api/explorer.py +0 -66
- cognite/neat/_app/api/routers/configuration.py +0 -25
- cognite/neat/_app/api/routers/crud.py +0 -102
- cognite/neat/_app/api/routers/metrics.py +0 -10
- cognite/neat/_app/api/routers/workflows.py +0 -224
- cognite/neat/_app/api/utils/__init__.py +0 -0
- cognite/neat/_app/api/utils/data_mapping.py +0 -17
- cognite/neat/_app/api/utils/logging.py +0 -26
- cognite/neat/_app/api/utils/query_templates.py +0 -92
- cognite/neat/_app/main.py +0 -17
- cognite/neat/_app/monitoring/__init__.py +0 -0
- cognite/neat/_app/monitoring/metrics.py +0 -69
- cognite/neat/_app/ui/index.html +0 -1
- cognite/neat/_app/ui/neat-app/.gitignore +0 -23
- cognite/neat/_app/ui/neat-app/README.md +0 -70
- cognite/neat/_app/ui/neat-app/build/asset-manifest.json +0 -14
- cognite/neat/_app/ui/neat-app/build/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/build/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/build/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/build/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/build/index.html +0 -1
- cognite/neat/_app/ui/neat-app/build/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/build/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/build/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css +0 -2
- cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js +0 -3
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.LICENSE.txt +0 -88
- cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.map +0 -1
- cognite/neat/_app/ui/neat-app/build/static/media/logo.8093b84df9ed36a174c629d6fe0b730d.svg +0 -1
- cognite/neat/_app/ui/neat-app/package-lock.json +0 -18306
- cognite/neat/_app/ui/neat-app/package.json +0 -62
- cognite/neat/_app/ui/neat-app/public/favicon.ico +0 -0
- cognite/neat/_app/ui/neat-app/public/img/architect-icon.svg +0 -116
- cognite/neat/_app/ui/neat-app/public/img/developer-icon.svg +0 -112
- cognite/neat/_app/ui/neat-app/public/img/sme-icon.svg +0 -34
- cognite/neat/_app/ui/neat-app/public/index.html +0 -43
- cognite/neat/_app/ui/neat-app/public/logo192.png +0 -0
- cognite/neat/_app/ui/neat-app/public/manifest.json +0 -25
- cognite/neat/_app/ui/neat-app/public/robots.txt +0 -3
- cognite/neat/_app/ui/neat-app/src/App.css +0 -38
- cognite/neat/_app/ui/neat-app/src/App.js +0 -17
- cognite/neat/_app/ui/neat-app/src/App.test.js +0 -8
- cognite/neat/_app/ui/neat-app/src/MainContainer.tsx +0 -70
- cognite/neat/_app/ui/neat-app/src/components/JsonViewer.tsx +0 -43
- cognite/neat/_app/ui/neat-app/src/components/LocalUploader.tsx +0 -124
- cognite/neat/_app/ui/neat-app/src/components/OverviewComponentEditorDialog.tsx +0 -63
- cognite/neat/_app/ui/neat-app/src/components/StepEditorDialog.tsx +0 -511
- cognite/neat/_app/ui/neat-app/src/components/TabPanel.tsx +0 -36
- cognite/neat/_app/ui/neat-app/src/components/Utils.tsx +0 -56
- cognite/neat/_app/ui/neat-app/src/components/WorkflowDeleteDialog.tsx +0 -60
- cognite/neat/_app/ui/neat-app/src/components/WorkflowExecutionReport.tsx +0 -112
- cognite/neat/_app/ui/neat-app/src/components/WorkflowImportExportDialog.tsx +0 -67
- cognite/neat/_app/ui/neat-app/src/components/WorkflowMetadataDialog.tsx +0 -79
- cognite/neat/_app/ui/neat-app/src/index.css +0 -13
- cognite/neat/_app/ui/neat-app/src/index.js +0 -13
- cognite/neat/_app/ui/neat-app/src/logo.svg +0 -1
- cognite/neat/_app/ui/neat-app/src/reportWebVitals.js +0 -13
- cognite/neat/_app/ui/neat-app/src/setupTests.js +0 -5
- cognite/neat/_app/ui/neat-app/src/types/WorkflowTypes.ts +0 -388
- cognite/neat/_app/ui/neat-app/src/views/AboutView.tsx +0 -61
- cognite/neat/_app/ui/neat-app/src/views/ConfigView.tsx +0 -184
- cognite/neat/_app/ui/neat-app/src/views/GlobalConfigView.tsx +0 -180
- cognite/neat/_app/ui/neat-app/src/views/WorkflowView.tsx +0 -570
- cognite/neat/_app/ui/neat-app/tsconfig.json +0 -27
- cognite/neat/_workflows/__init__.py +0 -17
- cognite/neat/_workflows/base.py +0 -590
- cognite/neat/_workflows/cdf_store.py +0 -393
- cognite/neat/_workflows/examples/Export_DMS/workflow.yaml +0 -89
- cognite/neat/_workflows/examples/Export_Semantic_Data_Model/workflow.yaml +0 -66
- cognite/neat/_workflows/examples/Import_DMS/workflow.yaml +0 -65
- cognite/neat/_workflows/examples/Validate_Rules/workflow.yaml +0 -67
- cognite/neat/_workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
- cognite/neat/_workflows/manager.py +0 -292
- cognite/neat/_workflows/model.py +0 -203
- cognite/neat/_workflows/steps/__init__.py +0 -0
- cognite/neat/_workflows/steps/data_contracts.py +0 -109
- cognite/neat/_workflows/steps/lib/__init__.py +0 -0
- cognite/neat/_workflows/steps/lib/current/__init__.py +0 -6
- cognite/neat/_workflows/steps/lib/current/graph_extractor.py +0 -100
- cognite/neat/_workflows/steps/lib/current/graph_loader.py +0 -51
- cognite/neat/_workflows/steps/lib/current/graph_store.py +0 -48
- cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -537
- cognite/neat/_workflows/steps/lib/current/rules_importer.py +0 -323
- cognite/neat/_workflows/steps/lib/current/rules_validator.py +0 -106
- cognite/neat/_workflows/steps/lib/io/__init__.py +0 -1
- cognite/neat/_workflows/steps/lib/io/io_steps.py +0 -393
- cognite/neat/_workflows/steps/step_model.py +0 -79
- cognite/neat/_workflows/steps_registry.py +0 -218
- cognite/neat/_workflows/tasks.py +0 -18
- cognite/neat/_workflows/triggers.py +0 -169
- cognite/neat/_workflows/utils.py +0 -19
- cognite_neat-0.104.0.dist-info/RECORD +0 -276
- {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.1.dist-info}/LICENSE +0 -0
- {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.1.dist-info}/entry_points.txt +0 -0
cognite/neat/_issues/_base.py
CHANGED
|
@@ -425,6 +425,10 @@ class NeatIssueList(list, Sequence[T_NeatIssue], ABC):
|
|
|
425
425
|
"""Return True if this list contains any errors of the given type."""
|
|
426
426
|
return any(isinstance(issue, error_type) for issue in self)
|
|
427
427
|
|
|
428
|
+
def has_warning_type(self, warning_type: type[NeatWarning]) -> bool:
|
|
429
|
+
"""Return True if this list contains any warnings of the given type."""
|
|
430
|
+
return any(isinstance(issue, warning_type) for issue in self)
|
|
431
|
+
|
|
428
432
|
def as_errors(self, operation: str = "Operation failed") -> ExceptionGroup:
|
|
429
433
|
"""Return an ExceptionGroup with all the errors in this list."""
|
|
430
434
|
return ExceptionGroup(
|
|
@@ -505,55 +509,35 @@ def _get_subclasses(cls_: type[T_Cls], include_base: bool = False) -> Iterable[t
|
|
|
505
509
|
|
|
506
510
|
|
|
507
511
|
@contextmanager
|
|
508
|
-
def catch_warnings(
|
|
509
|
-
issues: IssueList | None = None,
|
|
510
|
-
warning_cls: type[NeatWarning] = DefaultWarning,
|
|
511
|
-
) -> Iterator[None]:
|
|
512
|
+
def catch_warnings() -> Iterator[IssueList]:
|
|
512
513
|
"""Catch warnings and append them to the issues list."""
|
|
514
|
+
issues = IssueList()
|
|
513
515
|
with warnings.catch_warnings(record=True) as warning_logger:
|
|
514
516
|
warnings.simplefilter("always")
|
|
515
517
|
try:
|
|
516
|
-
yield
|
|
518
|
+
yield issues
|
|
517
519
|
finally:
|
|
518
|
-
if warning_logger
|
|
519
|
-
issues.extend([
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
class FutureResult:
|
|
523
|
-
def __init__(self) -> None:
|
|
524
|
-
self._result: Literal["success", "failure", "pending"] = "pending"
|
|
525
|
-
|
|
526
|
-
@property
|
|
527
|
-
def result(self) -> Literal["success", "failure", "pending"]:
|
|
528
|
-
return self._result
|
|
520
|
+
if warning_logger:
|
|
521
|
+
issues.extend([NeatWarning.from_warning(warning) for warning in warning_logger]) # type: ignore[misc]
|
|
529
522
|
|
|
530
523
|
|
|
531
524
|
@contextmanager
|
|
532
|
-
def catch_issues(
|
|
533
|
-
issues: IssueList,
|
|
534
|
-
error_cls: type[NeatError] = NeatError,
|
|
535
|
-
warning_cls: type[NeatWarning] = NeatWarning,
|
|
536
|
-
error_args: dict[str, Any] | None = None,
|
|
537
|
-
) -> Iterator[FutureResult]:
|
|
525
|
+
def catch_issues(error_args: dict[str, Any] | None = None) -> Iterator[IssueList]:
|
|
538
526
|
"""This is an internal help function to handle issues and warnings.
|
|
539
527
|
|
|
540
528
|
Args:
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
529
|
+
error_args: Additional arguments to pass to the error class. The only use case as of (2025-01-03) is to pass
|
|
530
|
+
the read_info_by_sheet to the error class such that the row numbers can be adjusted to match the source
|
|
531
|
+
spreadsheet.
|
|
544
532
|
|
|
545
533
|
Returns:
|
|
546
|
-
|
|
534
|
+
IssueList: The list of issues.
|
|
535
|
+
|
|
547
536
|
"""
|
|
548
|
-
with catch_warnings(issues
|
|
549
|
-
future_result = FutureResult()
|
|
537
|
+
with catch_warnings() as issues:
|
|
550
538
|
try:
|
|
551
|
-
yield
|
|
539
|
+
yield issues
|
|
552
540
|
except ValidationError as e:
|
|
553
|
-
issues.extend(
|
|
554
|
-
future_result._result = "failure"
|
|
541
|
+
issues.extend(NeatError.from_errors(e.errors(), **(error_args or {}))) # type: ignore[arg-type]
|
|
555
542
|
except (NeatError, MultiValueError) as e:
|
|
556
|
-
issues.extend(
|
|
557
|
-
future_result._result = "failure"
|
|
558
|
-
else:
|
|
559
|
-
future_result._result = "success"
|
|
543
|
+
issues.extend(NeatError.from_errors([e], **(error_args or {}))) # type: ignore[arg-type, list-item]
|
|
@@ -13,7 +13,7 @@ from ._external import (
|
|
|
13
13
|
FileReadWarning,
|
|
14
14
|
FileTypeUnexpectedWarning,
|
|
15
15
|
)
|
|
16
|
-
from ._general import NeatValueWarning, NotSupportedWarning, RegexViolationWarning
|
|
16
|
+
from ._general import MissingCogniteClientWarning, NeatValueWarning, NotSupportedWarning, RegexViolationWarning
|
|
17
17
|
from ._models import (
|
|
18
18
|
BreakingModelingPrincipleWarning,
|
|
19
19
|
CDFNotSupportedWarning,
|
|
@@ -28,6 +28,7 @@ from ._models import (
|
|
|
28
28
|
from ._properties import (
|
|
29
29
|
PropertyDataTypeConversionWarning,
|
|
30
30
|
PropertyDefinitionDuplicatedWarning,
|
|
31
|
+
PropertyDirectRelationLimitWarning,
|
|
31
32
|
PropertyNotFoundWarning,
|
|
32
33
|
PropertyOverwritingWarning,
|
|
33
34
|
PropertySkippedWarning,
|
|
@@ -41,6 +42,7 @@ from ._resources import (
|
|
|
41
42
|
ResourceRetrievalWarning,
|
|
42
43
|
ResourcesDuplicatedWarning,
|
|
43
44
|
ResourceTypeNotSupportedWarning,
|
|
45
|
+
ResourceUnknownWarning,
|
|
44
46
|
)
|
|
45
47
|
|
|
46
48
|
__all__ = [
|
|
@@ -53,6 +55,7 @@ __all__ = [
|
|
|
53
55
|
"FileMissingRequiredFieldWarning",
|
|
54
56
|
"FileReadWarning",
|
|
55
57
|
"FileTypeUnexpectedWarning",
|
|
58
|
+
"MissingCogniteClientWarning",
|
|
56
59
|
"NeatValueWarning",
|
|
57
60
|
"NotSupportedHasDataFilterLimitWarning",
|
|
58
61
|
"NotSupportedViewContainerLimitWarning",
|
|
@@ -62,6 +65,7 @@ __all__ = [
|
|
|
62
65
|
"PrincipleSolutionBuildsOnEnterpriseWarning",
|
|
63
66
|
"PropertyDataTypeConversionWarning",
|
|
64
67
|
"PropertyDefinitionDuplicatedWarning",
|
|
68
|
+
"PropertyDirectRelationLimitWarning",
|
|
65
69
|
"PropertyNotFoundWarning",
|
|
66
70
|
"PropertyOverwritingWarning",
|
|
67
71
|
"PropertySkippedWarning",
|
|
@@ -73,6 +77,7 @@ __all__ = [
|
|
|
73
77
|
"ResourceRegexViolationWarning",
|
|
74
78
|
"ResourceRetrievalWarning",
|
|
75
79
|
"ResourceTypeNotSupportedWarning",
|
|
80
|
+
"ResourceUnknownWarning",
|
|
76
81
|
"ResourcesDuplicatedWarning",
|
|
77
82
|
"UndefinedViewWarning",
|
|
78
83
|
"UserModelingWarning",
|
|
@@ -27,3 +27,10 @@ class RegexViolationWarning(NeatWarning):
|
|
|
27
27
|
identifier: str
|
|
28
28
|
pattern_name: str
|
|
29
29
|
motivation: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(unsafe_hash=True)
|
|
33
|
+
class MissingCogniteClientWarning(NeatWarning):
|
|
34
|
+
"""Missing Cognite Client required for {functionality}"""
|
|
35
|
+
|
|
36
|
+
functionality: str
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from typing import Generic
|
|
3
3
|
|
|
4
|
+
from cognite.neat._constants import DMS_DIRECT_RELATION_LIST_LIMIT
|
|
4
5
|
from cognite.neat._issues._base import ResourceType
|
|
5
6
|
|
|
6
7
|
from ._resources import ResourceNeatWarning, T_Identifier, T_ReferenceIdentifier
|
|
@@ -77,3 +78,13 @@ class PropertyDataTypeConversionWarning(PropertyWarning[T_Identifier]):
|
|
|
77
78
|
"""The {resource_type} with identifier {identifier} failed to convert the property {property_name}: {error}"""
|
|
78
79
|
|
|
79
80
|
error: str
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass(unsafe_hash=True)
|
|
84
|
+
class PropertyDirectRelationLimitWarning(PropertyWarning[T_Identifier]):
|
|
85
|
+
"""The listable direct relation property {property_name} in the {resource_type} with identifier {identifier}
|
|
86
|
+
has more than {limit} relations. The relations will be sorted by (space, externalId) and truncated."""
|
|
87
|
+
|
|
88
|
+
resource_type = "view"
|
|
89
|
+
|
|
90
|
+
limit: int = DMS_DIRECT_RELATION_LIST_LIMIT
|
|
@@ -39,6 +39,17 @@ class ResourceNotFoundWarning(ResourceNeatWarning, Generic[T_Identifier, T_Refer
|
|
|
39
39
|
referred_type: str
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
@dataclass(unsafe_hash=True)
|
|
43
|
+
class ResourceUnknownWarning(ResourceNeatWarning, Generic[T_Identifier, T_ReferenceIdentifier]):
|
|
44
|
+
"""The {resource_type} with identifier {identifier} referred by {referred_type} {referred_by} is unknown.
|
|
45
|
+
Will continue, but the model is incomplete."""
|
|
46
|
+
|
|
47
|
+
referred_by: T_ReferenceIdentifier
|
|
48
|
+
referred_type: str
|
|
49
|
+
|
|
50
|
+
fix = "You can maybe retrieve the resource from the CDF."
|
|
51
|
+
|
|
52
|
+
|
|
42
53
|
@dataclass(unsafe_hash=True)
|
|
43
54
|
class ResourceNotDefinedWarning(ResourceNeatWarning, Generic[T_Identifier, T_ReferenceIdentifier]):
|
|
44
55
|
"""The {resource_type} {identifier} is not defined in the {location}"""
|
|
@@ -4,6 +4,7 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Generic, Literal
|
|
6
6
|
|
|
7
|
+
from cognite.client import data_modeling as dm
|
|
7
8
|
from cognite.client.data_classes._base import (
|
|
8
9
|
T_CogniteResourceList,
|
|
9
10
|
T_WritableCogniteResource,
|
|
@@ -19,7 +20,7 @@ from cognite.client.exceptions import CogniteAPIError
|
|
|
19
20
|
|
|
20
21
|
from cognite.neat._client import DataModelingLoader, NeatClient
|
|
21
22
|
from cognite.neat._client._api.data_modeling_loaders import MultiCogniteAPIError, T_WritableCogniteResourceList
|
|
22
|
-
from cognite.neat._client.data_classes.data_modeling import Component
|
|
23
|
+
from cognite.neat._client.data_classes.data_modeling import Component, ViewApplyDict
|
|
23
24
|
from cognite.neat._client.data_classes.schema import DMSSchema
|
|
24
25
|
from cognite.neat._issues import IssueList
|
|
25
26
|
from cognite.neat._issues.warnings import (
|
|
@@ -199,6 +200,10 @@ class DMSExporter(CDFExporter[DMSRules, DMSSchema]):
|
|
|
199
200
|
) -> Iterable[UploadResult]:
|
|
200
201
|
schema = self.export(rules)
|
|
201
202
|
|
|
203
|
+
# The CDF UI does not deal well with a child view overwriting a parent property with the same name
|
|
204
|
+
# This is a workaround to remove the duplicated properties
|
|
205
|
+
self._remove_duplicated_properties(schema.views, client)
|
|
206
|
+
|
|
202
207
|
categorized_items_by_loader = self._categorize_by_loader(client, schema)
|
|
203
208
|
|
|
204
209
|
is_failing = self.existing == "fail" and any(
|
|
@@ -376,3 +381,32 @@ class DMSExporter(CDFExporter[DMSRules, DMSSchema]):
|
|
|
376
381
|
for data_model in data_models
|
|
377
382
|
if (data_model.space, data_model.external_id) != (space, external_id)
|
|
378
383
|
]
|
|
384
|
+
|
|
385
|
+
@staticmethod
|
|
386
|
+
def _remove_duplicated_properties(views: ViewApplyDict, client: NeatClient) -> None:
|
|
387
|
+
parent_view_ids = {parent for view in views.values() for parent in view.implements}
|
|
388
|
+
parent_view_list = client.data_modeling.views.retrieve(
|
|
389
|
+
list(parent_view_ids), include_inherited_properties=False
|
|
390
|
+
)
|
|
391
|
+
parent_view_by_id = {view.as_id(): view.as_write() for view in parent_view_list}
|
|
392
|
+
for view in views.values():
|
|
393
|
+
if view.implements is None:
|
|
394
|
+
continue
|
|
395
|
+
for parent_id in view.implements:
|
|
396
|
+
if not (parent_view := parent_view_by_id.get(parent_id)):
|
|
397
|
+
continue
|
|
398
|
+
for shared_prop_id in set(view.properties or {}) & set(parent_view.properties or {}):
|
|
399
|
+
if view.properties is None or parent_view.properties is None:
|
|
400
|
+
continue
|
|
401
|
+
prop = view.properties[shared_prop_id]
|
|
402
|
+
parent_prop = parent_view.properties[shared_prop_id]
|
|
403
|
+
if (
|
|
404
|
+
isinstance(prop, dm.MappedPropertyApply)
|
|
405
|
+
and isinstance(parent_prop, dm.MappedPropertyApply)
|
|
406
|
+
and (
|
|
407
|
+
prop.container_property_identifier == parent_prop.container_property_identifier
|
|
408
|
+
and prop.container == parent_prop.container
|
|
409
|
+
and prop.source == parent_prop.source
|
|
410
|
+
)
|
|
411
|
+
):
|
|
412
|
+
view.properties.pop(shared_prop_id)
|
|
@@ -229,8 +229,8 @@ class ExcelExporter(BaseExporter[VerifiedRules, Workbook]):
|
|
|
229
229
|
if isinstance(selected_column, MergedCell):
|
|
230
230
|
selected_column = column_cells[1]
|
|
231
231
|
|
|
232
|
-
current = sheet.column_dimensions[selected_column.column_letter].width or (max_length + 0.5)
|
|
233
|
-
sheet.column_dimensions[selected_column.column_letter].width = min(
|
|
232
|
+
current = sheet.column_dimensions[selected_column.column_letter].width or (max_length + 0.5) # type: ignore[union-attr]
|
|
233
|
+
sheet.column_dimensions[selected_column.column_letter].width = min( # type: ignore[union-attr]
|
|
234
234
|
max(current, max_length + 0.5), MAX_COLUMN_WIDTH
|
|
235
235
|
)
|
|
236
236
|
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from collections import
|
|
1
|
+
from collections import defaultdict
|
|
2
2
|
from collections.abc import Collection, Iterable, Sequence
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Literal, cast
|
|
6
6
|
|
|
@@ -20,12 +20,19 @@ from cognite.client.utils import ms_to_datetime
|
|
|
20
20
|
|
|
21
21
|
from cognite.neat._client import NeatClient
|
|
22
22
|
from cognite.neat._issues import IssueList, MultiValueError, NeatIssue
|
|
23
|
-
from cognite.neat._issues.errors import
|
|
23
|
+
from cognite.neat._issues.errors import (
|
|
24
|
+
FileTypeUnexpectedError,
|
|
25
|
+
NeatValueError,
|
|
26
|
+
ResourceMissingIdentifierError,
|
|
27
|
+
ResourceRetrievalError,
|
|
28
|
+
)
|
|
24
29
|
from cognite.neat._issues.warnings import (
|
|
30
|
+
MissingCogniteClientWarning,
|
|
25
31
|
PropertyNotFoundWarning,
|
|
26
32
|
PropertyTypeNotSupportedWarning,
|
|
27
33
|
ResourceNotFoundWarning,
|
|
28
34
|
ResourcesDuplicatedWarning,
|
|
35
|
+
ResourceUnknownWarning,
|
|
29
36
|
)
|
|
30
37
|
from cognite.neat._rules._shared import ReadRules
|
|
31
38
|
from cognite.neat._rules.importers._base import BaseImporter, _handle_issues
|
|
@@ -60,7 +67,6 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
60
67
|
schema: The schema containing the data model.
|
|
61
68
|
read_issues: A list of issues that occurred during the import.
|
|
62
69
|
metadata: Metadata for the data model.
|
|
63
|
-
ref_metadata: Metadata for the reference data model.
|
|
64
70
|
|
|
65
71
|
"""
|
|
66
72
|
|
|
@@ -69,30 +75,23 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
69
75
|
schema: DMSSchema,
|
|
70
76
|
read_issues: Sequence[NeatIssue] | None = None,
|
|
71
77
|
metadata: DMSInputMetadata | None = None,
|
|
72
|
-
|
|
78
|
+
referenced_containers: Iterable[dm.ContainerApply] | None = None,
|
|
73
79
|
):
|
|
74
|
-
|
|
75
|
-
# * User Schema
|
|
76
|
-
# * Reference Schema
|
|
77
|
-
self.root_schema = schema
|
|
80
|
+
self.schema = schema
|
|
78
81
|
self.metadata = metadata
|
|
79
|
-
self.ref_metadata = ref_metadata
|
|
80
82
|
self.issue_list = IssueList(read_issues)
|
|
81
83
|
self._all_containers_by_id = schema.containers.copy()
|
|
82
84
|
self._all_views_by_id = schema.views.copy()
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if container.as_id() in self._all_containers_by_id:
|
|
89
|
-
continue
|
|
90
|
-
self._all_containers_by_id[container.as_id()] = container
|
|
85
|
+
if referenced_containers is not None:
|
|
86
|
+
for container in referenced_containers:
|
|
87
|
+
if container.as_id() in self._all_containers_by_id:
|
|
88
|
+
continue
|
|
89
|
+
self._all_containers_by_id[container.as_id()] = container
|
|
91
90
|
|
|
92
91
|
@property
|
|
93
92
|
def description(self) -> str:
|
|
94
|
-
if self.
|
|
95
|
-
identifier = f"{self.
|
|
93
|
+
if self.schema.data_model is not None:
|
|
94
|
+
identifier = f"{self.schema.data_model.as_id().as_tuple()!s}"
|
|
96
95
|
else:
|
|
97
96
|
identifier = "Unknown"
|
|
98
97
|
return f"DMS Data model {identifier} read as unverified data model"
|
|
@@ -107,8 +106,6 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
107
106
|
|
|
108
107
|
Args:
|
|
109
108
|
client: Instantiated CogniteClient to retrieve data model.
|
|
110
|
-
reference_model_id: The reference data model to retrieve. This is the data model that
|
|
111
|
-
the given data model is built on top of, typically, an enterprise data model.
|
|
112
109
|
data_model_id: Data Model to retrieve.
|
|
113
110
|
|
|
114
111
|
Returns:
|
|
@@ -141,7 +138,12 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
141
138
|
|
|
142
139
|
metadata = cls._create_metadata_from_model(user_model)
|
|
143
140
|
|
|
144
|
-
return cls(
|
|
141
|
+
return cls(
|
|
142
|
+
schema,
|
|
143
|
+
issue_list,
|
|
144
|
+
metadata,
|
|
145
|
+
referenced_containers=cls._lookup_referenced_containers(schema, issue_list, client),
|
|
146
|
+
)
|
|
145
147
|
|
|
146
148
|
@classmethod
|
|
147
149
|
def _find_model_in_list(
|
|
@@ -182,15 +184,17 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
182
184
|
)
|
|
183
185
|
|
|
184
186
|
@classmethod
|
|
185
|
-
def from_directory(cls, directory: str | Path) -> "DMSImporter":
|
|
187
|
+
def from_directory(cls, directory: str | Path, client: NeatClient | None = None) -> "DMSImporter":
|
|
186
188
|
issue_list = IssueList()
|
|
187
189
|
with _handle_issues(issue_list) as _:
|
|
188
190
|
schema = DMSSchema.from_directory(directory)
|
|
189
191
|
# If there were errors during the import, the to_rules
|
|
190
|
-
return cls(
|
|
192
|
+
return cls(
|
|
193
|
+
schema, issue_list, referenced_containers=cls._lookup_referenced_containers(schema, issue_list, client)
|
|
194
|
+
)
|
|
191
195
|
|
|
192
196
|
@classmethod
|
|
193
|
-
def from_zip_file(cls, zip_file: str | Path) -> "DMSImporter":
|
|
197
|
+
def from_zip_file(cls, zip_file: str | Path, client: NeatClient | None = None) -> "DMSImporter":
|
|
194
198
|
if Path(zip_file).suffix != ".zip":
|
|
195
199
|
return cls(
|
|
196
200
|
DMSSchema(),
|
|
@@ -199,30 +203,49 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
199
203
|
issue_list = IssueList()
|
|
200
204
|
with _handle_issues(issue_list) as _:
|
|
201
205
|
schema = DMSSchema.from_zip(zip_file)
|
|
202
|
-
return cls(
|
|
206
|
+
return cls(
|
|
207
|
+
schema, issue_list, referenced_containers=cls._lookup_referenced_containers(schema, issue_list, client)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def _lookup_referenced_containers(
|
|
212
|
+
cls, schema: DMSSchema, issue_list: IssueList, client: NeatClient | None = None
|
|
213
|
+
) -> Iterable[dm.ContainerApply]:
|
|
214
|
+
ref_containers = schema.externally_referenced_containers()
|
|
215
|
+
if not ref_containers:
|
|
216
|
+
return []
|
|
217
|
+
elif client is None:
|
|
218
|
+
id_ = ""
|
|
219
|
+
if schema.data_model:
|
|
220
|
+
id_ = f" {schema.data_model.as_id()!r}"
|
|
221
|
+
issue_list.append(MissingCogniteClientWarning(f"importing full DMS model{id_}"))
|
|
222
|
+
return []
|
|
223
|
+
return client.loaders.containers.retrieve(list(ref_containers), format="write")
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def from_path(cls, path: Path, client: NeatClient | None = None) -> "DMSImporter":
|
|
227
|
+
if path.is_file():
|
|
228
|
+
return cls.from_zip_file(path, client)
|
|
229
|
+
elif path.is_dir():
|
|
230
|
+
return cls.from_directory(path, client)
|
|
231
|
+
else:
|
|
232
|
+
raise NeatValueError(f"Unsupported YAML format: {format}")
|
|
203
233
|
|
|
204
234
|
def to_rules(self) -> ReadRules[DMSInputRules]:
|
|
205
235
|
if self.issue_list.has_errors:
|
|
206
236
|
# In case there were errors during the import, the to_rules method will return None
|
|
207
|
-
self._end = datetime.now(timezone.utc)
|
|
208
237
|
self.issue_list.trigger_warnings()
|
|
209
238
|
raise MultiValueError(self.issue_list.errors)
|
|
210
239
|
|
|
211
|
-
if not self.
|
|
212
|
-
self.issue_list.append(ResourceMissingIdentifierError("data model", type(self.
|
|
213
|
-
self._end = datetime.now(timezone.utc)
|
|
240
|
+
if not self.schema.data_model:
|
|
241
|
+
self.issue_list.append(ResourceMissingIdentifierError("data model", type(self.schema).__name__))
|
|
214
242
|
self.issue_list.trigger_warnings()
|
|
215
243
|
raise MultiValueError(self.issue_list.errors)
|
|
216
244
|
|
|
217
|
-
model = self.
|
|
245
|
+
model = self.schema.data_model
|
|
218
246
|
|
|
219
|
-
user_rules = self._create_rule_components(
|
|
220
|
-
model,
|
|
221
|
-
self.root_schema,
|
|
222
|
-
self.metadata,
|
|
223
|
-
)
|
|
247
|
+
user_rules = self._create_rule_components(model, self.schema, self.metadata)
|
|
224
248
|
|
|
225
|
-
self._end = datetime.now(timezone.utc)
|
|
226
249
|
self.issue_list.trigger_warnings()
|
|
227
250
|
if self.issue_list.has_errors:
|
|
228
251
|
raise MultiValueError(self.issue_list.errors)
|
|
@@ -234,7 +257,7 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
234
257
|
schema: DMSSchema,
|
|
235
258
|
metadata: DMSInputMetadata | None = None,
|
|
236
259
|
) -> DMSInputRules:
|
|
237
|
-
enum_by_container_property = self._create_enum_collections(
|
|
260
|
+
enum_by_container_property = self._create_enum_collections(self._all_containers_by_id.values())
|
|
238
261
|
enum_collection_by_container_property = {
|
|
239
262
|
key: enum_list[0].collection for key, enum_list in enum_by_container_property.items() if enum_list
|
|
240
263
|
}
|
|
@@ -267,21 +290,6 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
267
290
|
enum=[enum for enum_list in enum_by_container_property.values() for enum in enum_list] or None,
|
|
268
291
|
)
|
|
269
292
|
|
|
270
|
-
@classmethod
|
|
271
|
-
def _create_default_metadata(
|
|
272
|
-
cls, views: Sequence[dm.View | dm.ViewApply], is_ref: bool = False
|
|
273
|
-
) -> DMSInputMetadata:
|
|
274
|
-
now = datetime.now().replace(microsecond=0)
|
|
275
|
-
space = Counter(view.space for view in views).most_common(1)[0][0]
|
|
276
|
-
return DMSInputMetadata(
|
|
277
|
-
space=space,
|
|
278
|
-
external_id="Unknown",
|
|
279
|
-
version="0.1.0",
|
|
280
|
-
creator="Unknown",
|
|
281
|
-
created=now,
|
|
282
|
-
updated=now,
|
|
283
|
-
)
|
|
284
|
-
|
|
285
293
|
def _create_dms_property(
|
|
286
294
|
self,
|
|
287
295
|
prop_id: str,
|
|
@@ -384,8 +392,11 @@ class DMSImporter(BaseImporter[DMSInputRules]):
|
|
|
384
392
|
elif isinstance(prop, dm.MappedPropertyApply):
|
|
385
393
|
container_prop = self._container_prop_unsafe(cast(dm.MappedPropertyApply, prop))
|
|
386
394
|
if isinstance(container_prop.type, dm.DirectRelation):
|
|
387
|
-
if prop.source is None
|
|
395
|
+
if prop.source is None:
|
|
388
396
|
return DMSUnknownEntity()
|
|
397
|
+
elif prop.source not in self._all_views_by_id:
|
|
398
|
+
self.issue_list.append(ResourceUnknownWarning(prop.source, "view", view_entity.as_id(), "view"))
|
|
399
|
+
return ViewEntity.from_id(prop.source)
|
|
389
400
|
else:
|
|
390
401
|
return ViewEntity.from_id(prop.source)
|
|
391
402
|
elif isinstance(container_prop.type, PropertyTypeWithUnit) and container_prop.type.unit:
|
|
@@ -398,7 +398,10 @@ class SheetList(list, MutableSequence[T_SheetRow]):
|
|
|
398
398
|
|
|
399
399
|
def _repr_html_(self) -> str:
|
|
400
400
|
"""Returns HTML representation of ResourceDict."""
|
|
401
|
-
|
|
401
|
+
df = self.to_pandas(drop_na_columns=True)
|
|
402
|
+
if "neatId" in df.columns:
|
|
403
|
+
df = df.drop(columns=["neatId"])
|
|
404
|
+
return df._repr_html_() # type: ignore[operator]
|
|
402
405
|
|
|
403
406
|
# Implemented to get correct type hints
|
|
404
407
|
def __iter__(self) -> Iterator[T_SheetRow]:
|
|
@@ -9,7 +9,7 @@ from cognite.client import data_modeling as dm
|
|
|
9
9
|
from cognite.client.data_classes.data_modeling import ContainerId, NodeId
|
|
10
10
|
from pydantic import BaseModel, model_serializer, model_validator
|
|
11
11
|
|
|
12
|
-
from ._single_value import ContainerEntity, DMSNodeEntity, Entity
|
|
12
|
+
from ._single_value import ContainerEntity, DMSNodeEntity, Entity, ViewEntity
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@total_ordering
|
|
@@ -121,6 +121,8 @@ class DMSFilter(WrappedEntity):
|
|
|
121
121
|
return HasDataFilter(
|
|
122
122
|
inner=[
|
|
123
123
|
ContainerEntity(space=entry["space"], externalId=entry["externalId"])
|
|
124
|
+
if entry["type"] == "container"
|
|
125
|
+
else ViewEntity(space=entry["space"], externalId=entry["externalId"], version=entry["version"])
|
|
124
126
|
for entry in body
|
|
125
127
|
if isinstance(entry, dict) and "space" in entry and "externalId" in entry
|
|
126
128
|
]
|
|
@@ -161,21 +163,24 @@ class NodeTypeFilter(DMSFilter):
|
|
|
161
163
|
|
|
162
164
|
class HasDataFilter(DMSFilter):
|
|
163
165
|
name: ClassVar[str] = "hasData"
|
|
164
|
-
_inner_cls: ClassVar[type[ContainerEntity]] = ContainerEntity
|
|
165
|
-
inner: list[ContainerEntity] | None = None # type: ignore[assignment]
|
|
166
|
+
_inner_cls: ClassVar[type[ContainerEntity | ViewEntity]] = ContainerEntity
|
|
167
|
+
inner: list[ContainerEntity | ViewEntity] | None = None # type: ignore[assignment]
|
|
166
168
|
|
|
167
169
|
def as_dms_filter(self, default: Collection[ContainerId] | None = None) -> dm.Filter:
|
|
168
170
|
containers: list[ContainerId]
|
|
169
171
|
if self.inner:
|
|
170
|
-
containers = [
|
|
172
|
+
containers = [item.as_id() for item in self.inner if isinstance(item, ContainerEntity)]
|
|
173
|
+
views = [item.as_id() for item in self.inner if isinstance(item, ViewEntity)]
|
|
171
174
|
elif default:
|
|
172
175
|
containers = list(default)
|
|
176
|
+
views = []
|
|
173
177
|
else:
|
|
174
178
|
raise ValueError("Empty hasData filter, please provide a default containers.")
|
|
175
179
|
|
|
176
180
|
return dm.filters.HasData(
|
|
177
181
|
# Sorting to ensure deterministic order
|
|
178
|
-
containers=sorted(containers, key=lambda container: container.as_tuple()) # type: ignore[union-attr]
|
|
182
|
+
containers=sorted(containers, key=lambda container: container.as_tuple()), # type: ignore[union-attr]
|
|
183
|
+
views=sorted(views, key=lambda view: view.as_tuple()), # type: ignore[union-attr]
|
|
179
184
|
)
|
|
180
185
|
|
|
181
186
|
|