cognite-neat 0.104.0__py3-none-any.whl → 0.105.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.

Files changed (141) hide show
  1. cognite/neat/_client/_api/data_modeling_loaders.py +83 -23
  2. cognite/neat/_client/_api/schema.py +2 -1
  3. cognite/neat/_client/data_classes/neat_sequence.py +261 -0
  4. cognite/neat/_client/data_classes/schema.py +5 -1
  5. cognite/neat/_client/testing.py +33 -0
  6. cognite/neat/_constants.py +56 -0
  7. cognite/neat/_graph/extractors/_classic_cdf/_base.py +6 -5
  8. cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +225 -11
  9. cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
  10. cognite/neat/_graph/loaders/_rdf2dms.py +13 -2
  11. cognite/neat/_graph/transformers/__init__.py +3 -1
  12. cognite/neat/_graph/transformers/_classic_cdf.py +2 -1
  13. cognite/neat/_graph/transformers/_value_type.py +72 -0
  14. cognite/neat/_issues/__init__.py +0 -2
  15. cognite/neat/_issues/_base.py +19 -35
  16. cognite/neat/_issues/warnings/__init__.py +4 -1
  17. cognite/neat/_issues/warnings/_general.py +7 -0
  18. cognite/neat/_issues/warnings/_resources.py +11 -0
  19. cognite/neat/_rules/exporters/_rules2dms.py +35 -1
  20. cognite/neat/_rules/exporters/_rules2excel.py +2 -2
  21. cognite/neat/_rules/importers/_dms2rules.py +66 -55
  22. cognite/neat/_rules/models/_base_rules.py +4 -1
  23. cognite/neat/_rules/models/entities/_wrapped.py +10 -5
  24. cognite/neat/_rules/models/mapping/_classic2core.yaml +239 -38
  25. cognite/neat/_rules/transformers/__init__.py +8 -2
  26. cognite/neat/_rules/transformers/_converters.py +271 -188
  27. cognite/neat/_rules/transformers/_mapping.py +75 -59
  28. cognite/neat/_rules/transformers/_verification.py +2 -3
  29. cognite/neat/_session/_inspect.py +3 -1
  30. cognite/neat/_session/_prepare.py +112 -24
  31. cognite/neat/_session/_read.py +33 -70
  32. cognite/neat/_session/_state.py +2 -2
  33. cognite/neat/_session/_to.py +2 -2
  34. cognite/neat/_store/_rules_store.py +4 -8
  35. cognite/neat/_utils/reader/_base.py +27 -0
  36. cognite/neat/_version.py +1 -1
  37. {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.0.dist-info}/METADATA +3 -2
  38. cognite_neat-0.105.0.dist-info/RECORD +179 -0
  39. {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.0.dist-info}/WHEEL +1 -1
  40. cognite/neat/_app/api/__init__.py +0 -0
  41. cognite/neat/_app/api/asgi/metrics.py +0 -4
  42. cognite/neat/_app/api/configuration.py +0 -98
  43. cognite/neat/_app/api/context_manager/__init__.py +0 -3
  44. cognite/neat/_app/api/context_manager/manager.py +0 -16
  45. cognite/neat/_app/api/data_classes/__init__.py +0 -0
  46. cognite/neat/_app/api/data_classes/rest.py +0 -59
  47. cognite/neat/_app/api/explorer.py +0 -66
  48. cognite/neat/_app/api/routers/configuration.py +0 -25
  49. cognite/neat/_app/api/routers/crud.py +0 -102
  50. cognite/neat/_app/api/routers/metrics.py +0 -10
  51. cognite/neat/_app/api/routers/workflows.py +0 -224
  52. cognite/neat/_app/api/utils/__init__.py +0 -0
  53. cognite/neat/_app/api/utils/data_mapping.py +0 -17
  54. cognite/neat/_app/api/utils/logging.py +0 -26
  55. cognite/neat/_app/api/utils/query_templates.py +0 -92
  56. cognite/neat/_app/main.py +0 -17
  57. cognite/neat/_app/monitoring/__init__.py +0 -0
  58. cognite/neat/_app/monitoring/metrics.py +0 -69
  59. cognite/neat/_app/ui/index.html +0 -1
  60. cognite/neat/_app/ui/neat-app/.gitignore +0 -23
  61. cognite/neat/_app/ui/neat-app/README.md +0 -70
  62. cognite/neat/_app/ui/neat-app/build/asset-manifest.json +0 -14
  63. cognite/neat/_app/ui/neat-app/build/favicon.ico +0 -0
  64. cognite/neat/_app/ui/neat-app/build/img/architect-icon.svg +0 -116
  65. cognite/neat/_app/ui/neat-app/build/img/developer-icon.svg +0 -112
  66. cognite/neat/_app/ui/neat-app/build/img/sme-icon.svg +0 -34
  67. cognite/neat/_app/ui/neat-app/build/index.html +0 -1
  68. cognite/neat/_app/ui/neat-app/build/logo192.png +0 -0
  69. cognite/neat/_app/ui/neat-app/build/manifest.json +0 -25
  70. cognite/neat/_app/ui/neat-app/build/robots.txt +0 -3
  71. cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css +0 -2
  72. cognite/neat/_app/ui/neat-app/build/static/css/main.72e3d92e.css.map +0 -1
  73. cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js +0 -3
  74. cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.LICENSE.txt +0 -88
  75. cognite/neat/_app/ui/neat-app/build/static/js/main.5a52cf09.js.map +0 -1
  76. cognite/neat/_app/ui/neat-app/build/static/media/logo.8093b84df9ed36a174c629d6fe0b730d.svg +0 -1
  77. cognite/neat/_app/ui/neat-app/package-lock.json +0 -18306
  78. cognite/neat/_app/ui/neat-app/package.json +0 -62
  79. cognite/neat/_app/ui/neat-app/public/favicon.ico +0 -0
  80. cognite/neat/_app/ui/neat-app/public/img/architect-icon.svg +0 -116
  81. cognite/neat/_app/ui/neat-app/public/img/developer-icon.svg +0 -112
  82. cognite/neat/_app/ui/neat-app/public/img/sme-icon.svg +0 -34
  83. cognite/neat/_app/ui/neat-app/public/index.html +0 -43
  84. cognite/neat/_app/ui/neat-app/public/logo192.png +0 -0
  85. cognite/neat/_app/ui/neat-app/public/manifest.json +0 -25
  86. cognite/neat/_app/ui/neat-app/public/robots.txt +0 -3
  87. cognite/neat/_app/ui/neat-app/src/App.css +0 -38
  88. cognite/neat/_app/ui/neat-app/src/App.js +0 -17
  89. cognite/neat/_app/ui/neat-app/src/App.test.js +0 -8
  90. cognite/neat/_app/ui/neat-app/src/MainContainer.tsx +0 -70
  91. cognite/neat/_app/ui/neat-app/src/components/JsonViewer.tsx +0 -43
  92. cognite/neat/_app/ui/neat-app/src/components/LocalUploader.tsx +0 -124
  93. cognite/neat/_app/ui/neat-app/src/components/OverviewComponentEditorDialog.tsx +0 -63
  94. cognite/neat/_app/ui/neat-app/src/components/StepEditorDialog.tsx +0 -511
  95. cognite/neat/_app/ui/neat-app/src/components/TabPanel.tsx +0 -36
  96. cognite/neat/_app/ui/neat-app/src/components/Utils.tsx +0 -56
  97. cognite/neat/_app/ui/neat-app/src/components/WorkflowDeleteDialog.tsx +0 -60
  98. cognite/neat/_app/ui/neat-app/src/components/WorkflowExecutionReport.tsx +0 -112
  99. cognite/neat/_app/ui/neat-app/src/components/WorkflowImportExportDialog.tsx +0 -67
  100. cognite/neat/_app/ui/neat-app/src/components/WorkflowMetadataDialog.tsx +0 -79
  101. cognite/neat/_app/ui/neat-app/src/index.css +0 -13
  102. cognite/neat/_app/ui/neat-app/src/index.js +0 -13
  103. cognite/neat/_app/ui/neat-app/src/logo.svg +0 -1
  104. cognite/neat/_app/ui/neat-app/src/reportWebVitals.js +0 -13
  105. cognite/neat/_app/ui/neat-app/src/setupTests.js +0 -5
  106. cognite/neat/_app/ui/neat-app/src/types/WorkflowTypes.ts +0 -388
  107. cognite/neat/_app/ui/neat-app/src/views/AboutView.tsx +0 -61
  108. cognite/neat/_app/ui/neat-app/src/views/ConfigView.tsx +0 -184
  109. cognite/neat/_app/ui/neat-app/src/views/GlobalConfigView.tsx +0 -180
  110. cognite/neat/_app/ui/neat-app/src/views/WorkflowView.tsx +0 -570
  111. cognite/neat/_app/ui/neat-app/tsconfig.json +0 -27
  112. cognite/neat/_workflows/__init__.py +0 -17
  113. cognite/neat/_workflows/base.py +0 -590
  114. cognite/neat/_workflows/cdf_store.py +0 -393
  115. cognite/neat/_workflows/examples/Export_DMS/workflow.yaml +0 -89
  116. cognite/neat/_workflows/examples/Export_Semantic_Data_Model/workflow.yaml +0 -66
  117. cognite/neat/_workflows/examples/Import_DMS/workflow.yaml +0 -65
  118. cognite/neat/_workflows/examples/Validate_Rules/workflow.yaml +0 -67
  119. cognite/neat/_workflows/examples/Validate_Solution_Model/workflow.yaml +0 -64
  120. cognite/neat/_workflows/manager.py +0 -292
  121. cognite/neat/_workflows/model.py +0 -203
  122. cognite/neat/_workflows/steps/__init__.py +0 -0
  123. cognite/neat/_workflows/steps/data_contracts.py +0 -109
  124. cognite/neat/_workflows/steps/lib/__init__.py +0 -0
  125. cognite/neat/_workflows/steps/lib/current/__init__.py +0 -6
  126. cognite/neat/_workflows/steps/lib/current/graph_extractor.py +0 -100
  127. cognite/neat/_workflows/steps/lib/current/graph_loader.py +0 -51
  128. cognite/neat/_workflows/steps/lib/current/graph_store.py +0 -48
  129. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +0 -537
  130. cognite/neat/_workflows/steps/lib/current/rules_importer.py +0 -323
  131. cognite/neat/_workflows/steps/lib/current/rules_validator.py +0 -106
  132. cognite/neat/_workflows/steps/lib/io/__init__.py +0 -1
  133. cognite/neat/_workflows/steps/lib/io/io_steps.py +0 -393
  134. cognite/neat/_workflows/steps/step_model.py +0 -79
  135. cognite/neat/_workflows/steps_registry.py +0 -218
  136. cognite/neat/_workflows/tasks.py +0 -18
  137. cognite/neat/_workflows/triggers.py +0 -169
  138. cognite/neat/_workflows/utils.py +0 -19
  139. cognite_neat-0.104.0.dist-info/RECORD +0 -276
  140. {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.0.dist-info}/LICENSE +0 -0
  141. {cognite_neat-0.104.0.dist-info → cognite_neat-0.105.0.dist-info}/entry_points.txt +0 -0
@@ -46,6 +46,7 @@ from cognite.neat._rules.models.entities import (
46
46
  DMSUnknownEntity,
47
47
  EdgeEntity,
48
48
  Entity,
49
+ HasDataFilter,
49
50
  MultiValueTypeInfo,
50
51
  ReverseConnectionEntity,
51
52
  T_Entity,
@@ -58,7 +59,6 @@ from cognite.neat._rules.models.information._rules_input import (
58
59
  InformationInputProperty,
59
60
  InformationInputRules,
60
61
  )
61
- from cognite.neat._utils.collection_ import remove_list_elements
62
62
  from cognite.neat._utils.text import to_camel
63
63
 
64
64
  from ._base import RulesTransformer
@@ -302,107 +302,259 @@ class SetIDDMSModel(RulesTransformer[DMSRules, DMSRules]):
302
302
  return DMSRules.model_validate(DMSInputRules.load(dump).dump())
303
303
 
304
304
 
305
- class ToExtension(RulesTransformer[DMSRules, DMSRules]):
305
+ class ToExtensionModel(RulesTransformer[DMSRules, DMSRules], ABC):
306
+ type_: ClassVar[str]
307
+
308
+ def __init__(self, new_model_id: DataModelIdentifier, org_name: str, dummy_property: str | None = None) -> None:
309
+ self.new_model_id = DataModelId.load(new_model_id)
310
+ if not self.new_model_id.version:
311
+ raise NeatValueError("Version is required for the new model.")
312
+ self.org_name = org_name
313
+ self.dummy_property = dummy_property
314
+
315
+ @property
316
+ def description(self) -> str:
317
+ return f"Prepared data model {self.new_model_id} to be {self.type_.replace('_', ' ')} data model."
318
+
319
+ def _create_new_views(
320
+ self, rules: DMSRules
321
+ ) -> tuple[SheetList[DMSView], SheetList[DMSContainer], SheetList[DMSProperty]]:
322
+ """Creates new views for the new model.
323
+
324
+ If the dummy property is provided, it will also create a new container for each view
325
+ with a single property that is the dummy property.
326
+ """
327
+ new_views = SheetList[DMSView]()
328
+ new_containers = SheetList[DMSContainer]()
329
+ new_properties = SheetList[DMSProperty]()
330
+
331
+ for definition in rules.views:
332
+ view_entity = self._remove_cognite_affix(definition.view)
333
+ view_entity.version = cast(str, self.new_model_id.version)
334
+ view_entity.prefix = self.new_model_id.space
335
+
336
+ new_views.append(
337
+ DMSView(
338
+ view=view_entity,
339
+ implements=[definition.view],
340
+ in_model=True,
341
+ name=definition.name,
342
+ )
343
+ )
344
+ if self.dummy_property is None:
345
+ continue
346
+
347
+ container_entity = ContainerEntity(space=view_entity.prefix, externalId=view_entity.external_id)
348
+
349
+ container = DMSContainer(container=container_entity)
350
+
351
+ prefix = to_camel(view_entity.suffix)
352
+ property_ = DMSProperty(
353
+ view=view_entity,
354
+ view_property=f"{prefix}{self.dummy_property}",
355
+ value_type=String(),
356
+ nullable=True,
357
+ immutable=False,
358
+ is_list=False,
359
+ container=container_entity,
360
+ container_property=f"{prefix}{self.dummy_property}",
361
+ )
362
+
363
+ new_properties.append(property_)
364
+ new_containers.append(container)
365
+
366
+ return new_views, new_containers, new_properties
367
+
368
+ def _remove_cognite_affix(self, entity: _T_Entity) -> _T_Entity:
369
+ """This method removes `Cognite` affix from the entity."""
370
+ new_suffix = entity.suffix.replace("Cognite", self.org_name or "")
371
+ if isinstance(entity, ViewEntity):
372
+ return ViewEntity(space=entity.space, externalId=new_suffix, version=entity.version) # type: ignore[return-value]
373
+ elif isinstance(entity, ClassEntity):
374
+ return ClassEntity(prefix=entity.prefix, suffix=new_suffix, version=entity.version) # type: ignore[return-value]
375
+ raise ValueError(f"Unsupported entity type: {type(entity)}")
376
+
377
+
378
+ class ToEnterpriseModel(ToExtensionModel):
379
+ type_: ClassVar[str] = "enterprise"
380
+
306
381
  def __init__(
307
382
  self,
308
383
  new_model_id: DataModelIdentifier,
309
384
  org_name: str = "My",
310
- type_: Literal["enterprise", "solution", "data_product"] = "enterprise",
311
- mode: Literal["read", "write"] = "read",
312
385
  dummy_property: str = "GUID",
313
386
  move_connections: bool = False,
314
- include: Literal["same-space", "all"] = "same-space",
315
387
  ):
316
- self.new_model_id = DataModelId.load(new_model_id)
317
- if not self.new_model_id.version:
318
- raise NeatValueError("Version is required for the new model.")
388
+ super().__init__(new_model_id, org_name, dummy_property)
389
+ self.move_connections = move_connections
319
390
 
320
- self.org_name = org_name
391
+ def transform(self, rules: DMSRules) -> DMSRules:
392
+ reference_model_id = rules.metadata.as_data_model_id()
393
+ if reference_model_id not in COGNITE_MODELS:
394
+ warnings.warn(
395
+ EnterpriseModelNotBuildOnTopOfCDMWarning(reference_model_id=reference_model_id).as_message(),
396
+ stacklevel=2,
397
+ )
398
+
399
+ return self._to_enterprise(rules)
400
+
401
+ def _to_enterprise(self, reference_model: DMSRules) -> DMSRules:
402
+ dump = reference_model.dump()
403
+
404
+ # This will create reference model components in the enterprise model space
405
+ enterprise_model = DMSRules.model_validate(DMSInputRules.load(dump).dump())
406
+
407
+ # Post validation metadata update:
408
+ enterprise_model.metadata.name = self.type_
409
+ enterprise_model.metadata.name = f"{self.org_name} {self.type_} data model"
410
+ enterprise_model.metadata.space = self.new_model_id.space
411
+ enterprise_model.metadata.external_id = self.new_model_id.external_id
412
+ enterprise_model.metadata.version = cast(str, self.new_model_id.version)
413
+
414
+ # Here we are creating enterprise views with a single container with a dummy property
415
+ # for each view
416
+ enterprise_views, enterprise_containers, enterprise_properties = self._create_new_views(enterprise_model)
417
+
418
+ # We keep the reference views, and adding new enterprise views...
419
+ enterprise_model.views.extend(enterprise_views)
420
+
421
+ if self.move_connections:
422
+ # Move connections from reference model to new enterprise model
423
+ enterprise_properties.extend(self._move_connections(enterprise_model))
424
+
425
+ # ... however, we do not want to keep the reference containers and properties
426
+ enterprise_model.containers = enterprise_containers
427
+ enterprise_model.properties = enterprise_properties
428
+
429
+ return enterprise_model
430
+
431
+ @staticmethod
432
+ def _move_connections(rules: DMSRules) -> SheetList[DMSProperty]:
433
+ implements: dict[ViewEntity, list[ViewEntity]] = defaultdict(list)
434
+ new_properties = SheetList[DMSProperty]()
435
+
436
+ for view in rules.views:
437
+ if view.view.space == rules.metadata.space and view.implements:
438
+ for implemented_view in view.implements:
439
+ implements.setdefault(implemented_view, []).append(view.view)
440
+
441
+ # currently only supporting single implementation of reference view in enterprise view
442
+ # connections that do not have properties
443
+ if all(len(v) == 1 for v in implements.values()):
444
+ for prop_ in rules.properties:
445
+ if (
446
+ prop_.view.space != rules.metadata.space
447
+ and prop_.connection
448
+ and isinstance(prop_.value_type, ViewEntity)
449
+ and implements.get(prop_.view)
450
+ and implements.get(prop_.value_type)
451
+ ):
452
+ if isinstance(prop_.connection, EdgeEntity) and prop_.connection.properties:
453
+ continue
454
+ new_property = prop_.model_copy(deep=True)
455
+ new_property.view = implements[prop_.view][0]
456
+ new_property.value_type = implements[prop_.value_type][0]
457
+ new_properties.append(new_property)
458
+
459
+ return new_properties
460
+
461
+
462
+ class ToSolutionModel(ToExtensionModel):
463
+ """Creates a solution data model based on an existing data model.
464
+
465
+ The solution data model will create a new view for each view in the existing model.
466
+
467
+ Args:
468
+ new_model_id: DataData model identifier for the new model.
469
+ org_name: If the existing model is a Cognite Data Model, this will replace the "Cognite" affix.
470
+ mode: The mode of the solution model. Either "read" or "write". A "write" model will create a new
471
+ container for each view with a dummy property. Read mode will only inherit the view filter from the
472
+ original model.
473
+ dummy_property: Only applicable if mode='write'. The identifier of the dummy property in the newly created
474
+ container.
475
+ exclude_views_in_other_spaces: Whether to exclude views that are not in the same space as the existing model,
476
+ when creating the solution model.
477
+ filter_type: If mode="read", this is the type of filter to apply to the new views. The filter is used to
478
+ ensure that the new views will return the same instance as the original views. The view filter is the
479
+ simplest filter, but it has limitation in the fusion UI. The container filter is in essence a more
480
+ verbose version of the view filter, and it has better support in the fusion UI. The default is "container".
481
+
482
+ """
483
+
484
+ type_: ClassVar[str] = "solution"
485
+
486
+ def __init__(
487
+ self,
488
+ new_model_id: DataModelIdentifier,
489
+ org_name: str = "My",
490
+ mode: Literal["read", "write"] = "read",
491
+ dummy_property: str | None = "GUID",
492
+ exclude_views_in_other_spaces: bool = True,
493
+ filter_type: Literal["container", "view"] = "container",
494
+ ):
495
+ super().__init__(new_model_id, org_name, dummy_property if mode == "write" else None)
321
496
  self.mode = mode
322
- self.type_ = type_
323
- self.dummy_property = dummy_property
324
- self.move_connections = move_connections
325
- self.include = include
497
+ self.exclude_views_in_other_spaces = exclude_views_in_other_spaces
498
+ self.filter_type = filter_type
326
499
 
327
500
  def transform(self, rules: DMSRules) -> DMSRules:
328
- # Copy to ensure immutability
329
501
  reference_model = rules
330
502
  reference_model_id = reference_model.metadata.as_data_model_id()
331
503
 
332
504
  # if model is solution then we need to get correct space for views and containers
333
- if self.type_ == "solution":
334
- if self.mode not in ["read", "write"]:
335
- raise NeatValueError(f"Unsupported mode: {self.mode}")
336
-
337
- if reference_model_id in COGNITE_MODELS:
338
- warnings.warn(
339
- SolutionModelBuildOnTopOfCDMWarning(reference_model_id=reference_model_id),
340
- stacklevel=2,
341
- )
342
-
343
- return self._to_solution(reference_model)
344
-
345
- elif self.type_ == "enterprise":
346
- if reference_model_id not in COGNITE_MODELS:
347
- warnings.warn(
348
- EnterpriseModelNotBuildOnTopOfCDMWarning(reference_model_id=reference_model_id).as_message(),
349
- stacklevel=2,
350
- )
505
+ if self.mode not in ["read", "write"]:
506
+ raise NeatValueError(f"Unsupported mode: {self.mode}")
351
507
 
352
- return self._to_enterprise(reference_model)
353
- elif self.type_ == "data_product":
354
- expanded = self._expand_properties(reference_model.model_copy(deep=True))
355
- if self.include == "same-space":
356
- expanded.properties = SheetList[DMSProperty](
357
- [prop for prop in expanded.properties if prop.view.space == expanded.metadata.space]
358
- )
359
- expanded.views = SheetList[DMSView](
360
- [view for view in expanded.views if view.view.space == expanded.metadata.space]
361
- )
362
- return self._to_solution(expanded, remove_views_in_other_space=False)
508
+ if reference_model_id in COGNITE_MODELS:
509
+ warnings.warn(
510
+ SolutionModelBuildOnTopOfCDMWarning(reference_model_id=reference_model_id),
511
+ stacklevel=2,
512
+ )
363
513
 
364
- else:
365
- raise NeatValueError(f"Unsupported data model type: {self.type_}")
514
+ return self._to_solution(reference_model)
366
515
 
367
- def _has_views_in_multiple_space(self, rules: DMSRules) -> bool:
516
+ @staticmethod
517
+ def _has_views_in_multiple_space(rules: DMSRules) -> bool:
368
518
  return any(view.view.space != rules.metadata.space for view in rules.views)
369
519
 
370
- def _to_solution(self, reference_rules: DMSRules, remove_views_in_other_space: bool = True) -> DMSRules:
520
+ def _to_solution(self, reference_rules: DMSRules) -> DMSRules:
371
521
  """For creation of solution data model / rules specifically for mapping over existing containers."""
372
522
 
373
- dump = reference_rules.dump()
523
+ dump = reference_rules.dump(entities_exclude_defaults=True)
374
524
 
375
525
  # Prepare new model metadata prior validation
526
+ # Since we dropped the defaults, all entities will update the space and version
527
+ # to the new model space and version
376
528
  dump["metadata"]["name"] = f"{self.org_name} {self.type_} data model"
377
529
  dump["metadata"]["space"] = self.new_model_id.space
378
530
  dump["metadata"]["external_id"] = self.new_model_id.external_id
379
531
  dump["metadata"]["version"] = self.new_model_id.version
380
532
 
381
- # Set implement to NONE for all views
382
- for view in dump["views"]:
383
- view["implements"] = None
384
-
385
- if remove_views_in_other_space and self._has_views_in_multiple_space(reference_rules):
386
- views_to_remove = []
387
- for view in dump["views"]:
388
- if ":" in view["view"]:
389
- views_to_remove.append(view)
390
-
391
- dump["views"] = remove_list_elements(dump["views"], views_to_remove)
392
-
393
533
  solution_model = DMSRules.model_validate(DMSInputRules.load(dump).dump())
394
534
 
395
- # Dropping containers coming from reference model
396
- solution_model.containers = None
397
-
398
- # We want to map properties to existing containers allowing extension
535
+ # This is not desirable for the containers, so we manually fix that here.
536
+ # It is easier to change the space for all entities and then revert the containers, than
537
+ # to change the space for all entities except the containers.
399
538
  for prop in solution_model.properties:
400
539
  if prop.container and prop.container.space == self.new_model_id.space:
540
+ # If the container is in the new model space, we want to map it to the reference model space
541
+ # This is reverting the .dump() -> .load() above.
401
542
  prop.container = ContainerEntity(
402
543
  space=reference_rules.metadata.space,
403
544
  externalId=prop.container.suffix,
404
545
  )
405
546
 
547
+ for view in solution_model.views:
548
+ view.implements = None
549
+
550
+ if self.exclude_views_in_other_spaces and self._has_views_in_multiple_space(reference_rules):
551
+ solution_model.views = SheetList[DMSView](
552
+ [view for view in solution_model.views if view.view.space == solution_model.metadata.space]
553
+ )
554
+
555
+ # Dropping containers coming from reference model
556
+ solution_model.containers = None
557
+
406
558
  # If reference model on which we are mapping one of Cognite Data Models
407
559
  # since we want to affix these with the organization name
408
560
  if reference_rules.metadata.as_data_model_id() in COGNITE_MODELS:
@@ -413,50 +565,73 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
413
565
  prop.value_type = self._remove_cognite_affix(prop.value_type)
414
566
  for view in solution_model.views:
415
567
  view.view = self._remove_cognite_affix(view.view)
568
+ if view.implements:
569
+ view.implements = [self._remove_cognite_affix(implemented) for implemented in view.implements]
416
570
 
417
571
  if self.mode == "write":
418
- _, new_containers, new_properties = self._get_new_components(solution_model)
419
-
572
+ _, new_containers, new_properties = self._create_new_views(solution_model)
420
573
  # Here we add ONLY dummy properties of the solution model and
421
574
  # corresponding solution model space containers to hold them
422
575
  solution_model.containers = new_containers
423
576
  solution_model.properties.extend(new_properties)
577
+ elif self.mode == "read":
578
+ # Inherit view filter from original model to ensure the same instances are returned
579
+ # when querying the new view.
580
+ ref_views_by_external_id = {
581
+ view.view.external_id: view
582
+ for view in reference_rules.views
583
+ if view.view.space == reference_rules.metadata.space
584
+ }
585
+ ref_containers_by_ref_view = defaultdict(set)
586
+ for prop in reference_rules.properties:
587
+ if prop.container:
588
+ ref_containers_by_ref_view[prop.view].add(prop.container)
589
+ for view in solution_model.views:
590
+ if ref_view := ref_views_by_external_id.get(view.view.external_id):
591
+ if self.filter_type == "view":
592
+ view.filter_ = HasDataFilter(inner=[ref_view.view])
593
+ elif self.filter_type == "container" and (
594
+ ref_containers := ref_containers_by_ref_view.get(ref_view.view)
595
+ ):
596
+ # Sorting to ensure deterministic order
597
+ view.filter_ = HasDataFilter(inner=sorted(ref_containers))
424
598
 
425
599
  return solution_model
426
600
 
427
- def _to_enterprise(self, reference_model: DMSRules) -> DMSRules:
428
- dump = reference_model.dump()
429
601
 
430
- # This will create reference model components in the enterprise model space
431
- enterprise_model = DMSRules.model_validate(DMSInputRules.load(dump).dump())
602
+ class ToDataProductModel(ToSolutionModel):
603
+ type_: ClassVar[str] = "data_product"
432
604
 
433
- # Post validation metadata update:
434
- enterprise_model.metadata.name = self.type_
435
- enterprise_model.metadata.name = f"{self.org_name} {self.type_} data model"
436
- enterprise_model.metadata.space = self.new_model_id.space
437
- enterprise_model.metadata.external_id = self.new_model_id.external_id
438
- enterprise_model.metadata.version = cast(str, self.new_model_id.version)
439
-
440
- # Here we are creating enterprise specific components
441
- enterprise_views, enterprise_containers, enterprise_properties = self._get_new_components(enterprise_model)
442
-
443
- # And we are adding them to the enterprise model
444
- # extending reference views with new ones
445
- enterprise_model.views.extend(enterprise_views)
446
-
447
- # Move connections from reference model to enterprise model
448
- if self.move_connections:
449
- enterprise_connections = self._move_connections(enterprise_model)
450
- else:
451
- enterprise_connections = SheetList[DMSProperty]()
452
-
453
- # while overwriting containers and properties with new ones
454
- enterprise_model.containers = enterprise_containers
455
- enterprise_model.properties = enterprise_properties
605
+ def __init__(
606
+ self,
607
+ new_model_id: DataModelIdentifier,
608
+ org_name: str = "My",
609
+ include: Literal["same-space", "all"] = "same-space",
610
+ ):
611
+ super().__init__(new_model_id, org_name, mode="read", dummy_property=None, exclude_views_in_other_spaces=False)
612
+ self.include = include
456
613
 
457
- enterprise_properties.extend(enterprise_connections)
614
+ def transform(self, rules: DMSRules) -> DMSRules:
615
+ # Copy to ensure immutability
616
+ expanded = self._expand_properties(rules.model_copy(deep=True))
617
+ if self.include == "same-space":
618
+ expanded.views = SheetList[DMSView](
619
+ [view for view in expanded.views if view.view.space == expanded.metadata.space]
620
+ )
621
+ used_view_entities = {view.view for view in expanded.views}
622
+ expanded.properties = SheetList[DMSProperty](
623
+ [
624
+ prop
625
+ for prop in expanded.properties
626
+ if prop.view.space == expanded.metadata.space
627
+ and (
628
+ (isinstance(prop.value_type, ViewEntity) and prop.value_type in used_view_entities)
629
+ or not isinstance(prop.value_type, ViewEntity)
630
+ )
631
+ ]
632
+ )
458
633
 
459
- return enterprise_model
634
+ return self._to_solution(expanded)
460
635
 
461
636
  @staticmethod
462
637
  def _expand_properties(rules: DMSRules) -> DMSRules:
@@ -480,97 +655,6 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
480
655
  property_ids.add(prop.view_property)
481
656
  return rules
482
657
 
483
- def _remove_cognite_affix(self, entity: _T_Entity) -> _T_Entity:
484
- """This method removes `Cognite` affix from the entity."""
485
- new_suffix = entity.suffix.replace("Cognite", self.org_name or "")
486
- if isinstance(entity, ViewEntity):
487
- return ViewEntity(space=entity.space, externalId=new_suffix, version=entity.version) # type: ignore[return-value]
488
- elif isinstance(entity, ClassEntity):
489
- return ClassEntity(prefix=entity.prefix, suffix=new_suffix, version=entity.version) # type: ignore[return-value]
490
- raise ValueError(f"Unsupported entity type: {type(entity)}")
491
-
492
- def _get_new_components(
493
- self, rules: DMSRules
494
- ) -> tuple[SheetList[DMSView], SheetList[DMSContainer], SheetList[DMSProperty]]:
495
- new_views = SheetList[DMSView]()
496
- new_containers = SheetList[DMSContainer]()
497
- new_properties = SheetList[DMSProperty]()
498
-
499
- for definition in rules.views:
500
- view_entity = self._remove_cognite_affix(definition.view)
501
-
502
- view_entity.version = cast(str, self.new_model_id.version)
503
- view_entity.prefix = self.new_model_id.space
504
- container_entity = ContainerEntity(space=view_entity.prefix, externalId=view_entity.external_id)
505
-
506
- view = DMSView(
507
- view=view_entity,
508
- implements=[definition.view],
509
- in_model=True,
510
- name=definition.name,
511
- )
512
-
513
- container = DMSContainer(
514
- container=container_entity,
515
- )
516
-
517
- property_ = DMSProperty(
518
- view=view_entity,
519
- view_property=f"{to_camel(view_entity.suffix)}{self.dummy_property}",
520
- value_type=String(),
521
- nullable=True,
522
- immutable=False,
523
- is_list=False,
524
- container=container_entity,
525
- container_property=f"{to_camel(view_entity.suffix)}{self.dummy_property}",
526
- )
527
-
528
- new_properties.append(property_)
529
- new_views.append(view)
530
- new_containers.append(container)
531
-
532
- return new_views, new_containers, new_properties
533
-
534
- def _move_connections(self, rules: DMSRules) -> SheetList[DMSProperty]:
535
- implements: dict[ViewEntity, list[ViewEntity]] = defaultdict(list)
536
- new_properties = SheetList[DMSProperty]()
537
-
538
- for view in rules.views:
539
- if view.view.space == rules.metadata.space and view.implements:
540
- for implemented_view in view.implements:
541
- implements.setdefault(implemented_view, []).append(view.view)
542
-
543
- # currently only supporting single implementation of reference view in enterprise view
544
- # connections that do not have properties
545
- if all(len(v) == 1 for v in implements.values()):
546
- for prop_ in rules.properties:
547
- if (
548
- prop_.view.space != rules.metadata.space
549
- and prop_.connection
550
- and isinstance(prop_.value_type, ViewEntity)
551
- and implements.get(prop_.view)
552
- and implements.get(prop_.value_type)
553
- ):
554
- if isinstance(prop_.connection, EdgeEntity) and prop_.connection.properties:
555
- continue
556
- new_property = prop_.model_copy(deep=True)
557
- new_property.view = implements[prop_.view][0]
558
- new_property.value_type = implements[prop_.value_type][0]
559
- new_properties.append(new_property)
560
-
561
- return new_properties
562
-
563
- @property
564
- def description(self) -> str:
565
- if self.type_ == "enterprise":
566
- return f"Prepared data model {self.new_model_id} to be enterprise data model."
567
- elif self.type_ == "solution":
568
- return f"Prepared data model {self.new_model_id} to be solution data model."
569
- elif self.type_ == "data_product":
570
- return f"Prepared data model {self.new_model_id} to be data product model."
571
- else:
572
- return f"Unsupported data model type: {self.type_}"
573
-
574
658
 
575
659
  class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
576
660
  _ASSET_VIEW = ViewId("cdf_cdm", "CogniteAsset", "v1")
@@ -662,7 +746,7 @@ class IncludeReferenced(RulesTransformer[DMSRules, DMSRules]):
662
746
 
663
747
  def transform(self, rules: DMSRules) -> DMSRules:
664
748
  dms_rules = rules
665
- view_ids, container_ids = DMSValidation(dms_rules, self._client).imported_views_and_containers_ids()
749
+ view_ids, container_ids = DMSValidation(dms_rules).imported_views_and_containers_ids()
666
750
  if not (view_ids or container_ids):
667
751
  warnings.warn(
668
752
  NeatValueWarning(
@@ -717,7 +801,6 @@ class AddClassImplements(RulesTransformer[InformationRules, InformationRules]):
717
801
  for class_ in output.classes:
718
802
  if class_.class_.suffix.endswith(self.suffix):
719
803
  class_.implements = [ClassEntity(prefix=class_.class_.prefix, suffix=self.implements)]
720
- output.metadata.version = f"{output.metadata.version}.implements_{self.implements}"
721
804
  return output
722
805
 
723
806
  @property