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

Files changed (47) hide show
  1. cognite/neat/_client/_api/data_modeling_loaders.py +403 -182
  2. cognite/neat/_client/data_classes/data_modeling.py +4 -0
  3. cognite/neat/_graph/extractors/_base.py +7 -0
  4. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +23 -13
  5. cognite/neat/_graph/loaders/_rdf2dms.py +50 -11
  6. cognite/neat/_graph/transformers/__init__.py +3 -3
  7. cognite/neat/_graph/transformers/_classic_cdf.py +120 -52
  8. cognite/neat/_issues/warnings/__init__.py +2 -0
  9. cognite/neat/_issues/warnings/_resources.py +15 -0
  10. cognite/neat/_rules/analysis/_base.py +15 -5
  11. cognite/neat/_rules/analysis/_dms.py +20 -0
  12. cognite/neat/_rules/analysis/_information.py +22 -0
  13. cognite/neat/_rules/exporters/_base.py +3 -5
  14. cognite/neat/_rules/exporters/_rules2dms.py +192 -200
  15. cognite/neat/_rules/importers/_rdf/_inference2rules.py +22 -5
  16. cognite/neat/_rules/models/_base_rules.py +19 -0
  17. cognite/neat/_rules/models/_types.py +5 -0
  18. cognite/neat/_rules/models/dms/_exporter.py +215 -93
  19. cognite/neat/_rules/models/dms/_rules.py +4 -4
  20. cognite/neat/_rules/models/dms/_rules_input.py +8 -3
  21. cognite/neat/_rules/models/dms/_validation.py +42 -11
  22. cognite/neat/_rules/models/entities/_multi_value.py +3 -0
  23. cognite/neat/_rules/models/information/_rules.py +17 -2
  24. cognite/neat/_rules/models/information/_rules_input.py +11 -2
  25. cognite/neat/_rules/models/information/_validation.py +99 -3
  26. cognite/neat/_rules/models/mapping/_classic2core.yaml +1 -1
  27. cognite/neat/_rules/transformers/__init__.py +2 -1
  28. cognite/neat/_rules/transformers/_converters.py +163 -61
  29. cognite/neat/_rules/transformers/_mapping.py +132 -2
  30. cognite/neat/_session/_base.py +42 -31
  31. cognite/neat/_session/_mapping.py +105 -5
  32. cognite/neat/_session/_prepare.py +43 -9
  33. cognite/neat/_session/_read.py +50 -4
  34. cognite/neat/_session/_set.py +1 -0
  35. cognite/neat/_session/_to.py +36 -13
  36. cognite/neat/_session/_wizard.py +5 -0
  37. cognite/neat/_session/engine/_interface.py +3 -2
  38. cognite/neat/_store/_base.py +79 -19
  39. cognite/neat/_utils/collection_.py +22 -0
  40. cognite/neat/_utils/rdf_.py +24 -0
  41. cognite/neat/_version.py +2 -2
  42. cognite/neat/_workflows/steps/lib/current/rules_exporter.py +3 -3
  43. {cognite_neat-0.99.1.dist-info → cognite_neat-0.100.1.dist-info}/METADATA +1 -1
  44. {cognite_neat-0.99.1.dist-info → cognite_neat-0.100.1.dist-info}/RECORD +47 -47
  45. {cognite_neat-0.99.1.dist-info → cognite_neat-0.100.1.dist-info}/LICENSE +0 -0
  46. {cognite_neat-0.99.1.dist-info → cognite_neat-0.100.1.dist-info}/WHEEL +0 -0
  47. {cognite_neat-0.99.1.dist-info → cognite_neat-0.100.1.dist-info}/entry_points.txt +0 -0
@@ -33,8 +33,8 @@ class InformationInputMetadata(InputComponent[InformationMetadata]):
33
33
  description: str | None = None
34
34
  created: datetime | str | None = None
35
35
  updated: datetime | str | None = None
36
- physical: str | None = None
37
- conceptual: str | None = None
36
+ physical: str | URIRef | None = None
37
+ conceptual: str | URIRef | None = None
38
38
 
39
39
  @classmethod
40
40
  def _get_verified_cls(cls) -> type[InformationMetadata]:
@@ -81,6 +81,11 @@ class InformationInputProperty(InputComponent[InformationProperty]):
81
81
  transformation: str | None = None
82
82
  # Only used internally
83
83
  inherited: bool = False
84
+ neatId: str | URIRef | None = None
85
+
86
+ # linking
87
+ physical: str | URIRef | None = None
88
+ conceptual: str | URIRef | None = None
84
89
 
85
90
  @classmethod
86
91
  def _get_verified_cls(cls) -> type[InformationProperty]:
@@ -99,6 +104,10 @@ class InformationInputClass(InputComponent[InformationClass]):
99
104
  name: str | None = None
100
105
  description: str | None = None
101
106
  implements: str | list[ClassEntity] | None = None
107
+ neatId: str | URIRef | None = None
108
+ # linking
109
+ physical: str | URIRef | None = None
110
+ conceptual: str | URIRef | None = None
102
111
 
103
112
  @classmethod
104
113
  def _get_verified_cls(cls) -> type[InformationClass]:
@@ -3,10 +3,15 @@ from collections import Counter
3
3
 
4
4
  from cognite.neat._issues import IssueList
5
5
  from cognite.neat._issues.errors import NeatValueError
6
+ from cognite.neat._issues.errors._resources import ResourceNotDefinedError
6
7
  from cognite.neat._issues.warnings._models import UndefinedClassWarning
7
- from cognite.neat._issues.warnings._resources import ResourceNotDefinedWarning
8
- from cognite.neat._rules._constants import EntityTypes
8
+ from cognite.neat._issues.warnings._resources import (
9
+ ResourceNotDefinedWarning,
10
+ ResourceRegexViolationWarning,
11
+ )
12
+ from cognite.neat._rules._constants import PATTERNS, EntityTypes
9
13
  from cognite.neat._rules.models.entities import ClassEntity, UnknownEntity
14
+ from cognite.neat._rules.models.entities._multi_value import MultiValueTypeInfo
10
15
 
11
16
  from ._rules import InformationRules
12
17
 
@@ -25,14 +30,15 @@ class InformationValidation:
25
30
  def validate(self) -> IssueList:
26
31
  self._namespaces_reassigned()
27
32
  self._classes_without_properties()
33
+ self._undefined_classes()
28
34
  self._parent_class_defined()
29
35
  self._referenced_classes_exist()
30
36
  self._referenced_value_types_exist()
37
+ self._regex_compliance_with_dms()
31
38
 
32
39
  return self.issue_list
33
40
 
34
41
  def _classes_without_properties(self) -> None:
35
- # needs to be complete for this validation to pass
36
42
  defined_classes = {class_.class_ for class_ in self.classes}
37
43
  referred_classes = {property_.class_ for property_ in self.properties}
38
44
  class_parent_pairs = self._class_parent_pairs()
@@ -51,6 +57,20 @@ class InformationValidation:
51
57
  )
52
58
  )
53
59
 
60
+ def _undefined_classes(self) -> None:
61
+ defined_classes = {class_.class_ for class_ in self.classes}
62
+ referred_classes = {property_.class_ for property_ in self.properties}
63
+
64
+ if undefined_classes := referred_classes.difference(defined_classes):
65
+ for class_ in undefined_classes:
66
+ self.issue_list.append(
67
+ ResourceNotDefinedError(
68
+ identifier=class_,
69
+ resource_type="class",
70
+ location="Classes sheet",
71
+ )
72
+ )
73
+
54
74
  def _parent_class_defined(self) -> None:
55
75
  """This is a validation to check if the parent class of a class is defined in the classes sheet."""
56
76
  class_parent_pairs = self._class_parent_pairs()
@@ -106,6 +126,82 @@ class InformationValidation:
106
126
  )
107
127
  )
108
128
 
129
+ def _regex_compliance_with_dms(self) -> None:
130
+ """Check regex compliance with DMS of properties, classes and value types."""
131
+
132
+ for prop_ in self.properties:
133
+ if not PATTERNS.dms_property_id_compliance.match(prop_.property_):
134
+ self.issue_list.append(
135
+ ResourceRegexViolationWarning(
136
+ prop_.property_,
137
+ "Property",
138
+ "Properties sheet, Property column",
139
+ PATTERNS.dms_property_id_compliance.pattern,
140
+ )
141
+ )
142
+ if not PATTERNS.view_id_compliance.match(prop_.class_.suffix):
143
+ self.issue_list.append(
144
+ ResourceRegexViolationWarning(
145
+ prop_.class_,
146
+ "Class",
147
+ "Properties sheet, Class column",
148
+ PATTERNS.view_id_compliance.pattern,
149
+ )
150
+ )
151
+
152
+ # Handling Value Type
153
+ if (
154
+ isinstance(prop_.value_type, ClassEntity)
155
+ and prop_.value_type != UnknownEntity()
156
+ and not PATTERNS.view_id_compliance.match(prop_.value_type.suffix)
157
+ ):
158
+ self.issue_list.append(
159
+ ResourceRegexViolationWarning(
160
+ prop_.value_type,
161
+ "Value Type",
162
+ "Properties sheet, Value Type column",
163
+ PATTERNS.view_id_compliance.pattern,
164
+ )
165
+ )
166
+ if isinstance(prop_.value_type, MultiValueTypeInfo):
167
+ for value_type in prop_.value_type.types:
168
+ if (
169
+ isinstance(prop_.value_type, ClassEntity)
170
+ and prop_.value_type != UnknownEntity()
171
+ and not PATTERNS.view_id_compliance.match(value_type.suffix)
172
+ ):
173
+ self.issue_list.append(
174
+ ResourceRegexViolationWarning(
175
+ value_type,
176
+ "Value Type",
177
+ "Properties sheet, Value Type column",
178
+ PATTERNS.view_id_compliance.pattern,
179
+ )
180
+ )
181
+
182
+ for class_ in self.classes:
183
+ if not PATTERNS.view_id_compliance.match(class_.class_.suffix):
184
+ self.issue_list.append(
185
+ ResourceRegexViolationWarning(
186
+ class_.class_,
187
+ "Class",
188
+ "Classes sheet, Class column",
189
+ PATTERNS.view_id_compliance.pattern,
190
+ )
191
+ )
192
+
193
+ if class_.implements:
194
+ for parent in class_.implements:
195
+ if not PATTERNS.view_id_compliance.match(parent.suffix):
196
+ self.issue_list.append(
197
+ ResourceRegexViolationWarning(
198
+ parent,
199
+ "Class",
200
+ "Classes sheet, Implements column",
201
+ PATTERNS.view_id_compliance.pattern,
202
+ )
203
+ )
204
+
109
205
  def _class_parent_pairs(self) -> dict[ClassEntity, list[ClassEntity]]:
110
206
  class_parent_pairs: dict[ClassEntity, list[ClassEntity]] = {}
111
207
  classes = self.rules.model_copy(deep=True).classes
@@ -316,7 +316,7 @@ views:
316
316
  in_model: true
317
317
  view: cdf_cdm:CogniteSourceSystem(version=v1)
318
318
  - description: Represents a single unit of measurement
319
- implements: CogniteDescribable
319
+ implements: cdf_cdm:CogniteDescribable(version=v1)
320
320
  in_model: true
321
321
  view: cdf_cdm:CogniteUnit(version=v1)
322
322
  - description: Assets represent systems that support industrial functions or processes.
@@ -9,7 +9,7 @@ from ._converters import (
9
9
  ToCompliantEntities,
10
10
  ToExtension,
11
11
  )
12
- from ._mapping import MapOneToOne, RuleMapper
12
+ from ._mapping import AsParentPropertyId, MapOneToOne, RuleMapper
13
13
  from ._pipelines import ImporterPipeline
14
14
  from ._verification import VerifyAnyRules, VerifyDMSRules, VerifyInformationRules
15
15
 
@@ -30,4 +30,5 @@ __all__ = [
30
30
  "ToExtension",
31
31
  "ReduceCogniteModel",
32
32
  "SetIDDMSModel",
33
+ "AsParentPropertyId",
33
34
  ]
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
4
4
  from collections import Counter, defaultdict
5
5
  from collections.abc import Collection, Mapping
6
6
  from datetime import date, datetime
7
- from typing import Literal, TypeVar, cast, overload
7
+ from typing import ClassVar, Literal, TypeVar, cast, overload
8
8
 
9
9
  from cognite.client.data_classes import data_modeling as dms
10
10
  from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier, ViewId
@@ -15,6 +15,7 @@ from cognite.neat._constants import (
15
15
  )
16
16
  from cognite.neat._issues._base import IssueList
17
17
  from cognite.neat._issues.errors import NeatValueError
18
+ from cognite.neat._issues.warnings import NeatValueWarning
18
19
  from cognite.neat._issues.warnings._models import (
19
20
  EnterpriseModelNotBuildOnTopOfCDMWarning,
20
21
  SolutionModelBuildOnTopOfCDMWarning,
@@ -34,7 +35,7 @@ from cognite.neat._rules.models import (
34
35
  SheetList,
35
36
  data_types,
36
37
  )
37
- from cognite.neat._rules.models.data_types import DataType, String
38
+ from cognite.neat._rules.models.data_types import AnyURI, DataType, String
38
39
  from cognite.neat._rules.models.dms import DMSMetadata, DMSProperty, DMSView
39
40
  from cognite.neat._rules.models.dms._rules import DMSContainer
40
41
  from cognite.neat._rules.models.entities import (
@@ -249,11 +250,12 @@ class PrefixEntities(RulesTransformer[InputRules, InputRules]): # type: ignore[
249
250
  class InformationToDMS(ConversionTransformer[InformationRules, DMSRules]):
250
251
  """Converts InformationRules to DMSRules."""
251
252
 
252
- def __init__(self, ignore_undefined_value_types: bool = False):
253
+ def __init__(self, ignore_undefined_value_types: bool = False, mode: Literal["edge_properties"] | None = None):
253
254
  self.ignore_undefined_value_types = ignore_undefined_value_types
255
+ self.mode = mode
254
256
 
255
257
  def _transform(self, rules: InformationRules) -> DMSRules:
256
- return _InformationRulesConverter(rules).as_dms_rules(self.ignore_undefined_value_types)
258
+ return _InformationRulesConverter(rules).as_dms_rules(self.ignore_undefined_value_types, self.mode)
257
259
 
258
260
 
259
261
  class DMSToInformation(ConversionTransformer[DMSRules, InformationRules]):
@@ -637,11 +639,15 @@ class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
637
639
 
638
640
 
639
641
  class _InformationRulesConverter:
642
+ _edge_properties: ClassVar[frozenset[str]] = frozenset({"endNode", "end_node", "startNode", "start_node"})
643
+
640
644
  def __init__(self, information: InformationRules):
641
645
  self.rules = information
642
646
  self.property_count_by_container: dict[ContainerEntity, int] = defaultdict(int)
643
647
 
644
- def as_dms_rules(self, ignore_undefined_value_types: bool = False) -> "DMSRules":
648
+ def as_dms_rules(
649
+ self, ignore_undefined_value_types: bool = False, mode: Literal["edge_properties"] | None = None
650
+ ) -> "DMSRules":
645
651
  from cognite.neat._rules.models.dms._rules import (
646
652
  DMSContainer,
647
653
  DMSProperty,
@@ -652,27 +658,55 @@ class _InformationRulesConverter:
652
658
  info_metadata = self.rules.metadata
653
659
  default_version = info_metadata.version
654
660
  default_space = self._to_space(info_metadata.prefix)
655
- metadata = self._convert_metadata_to_dms(info_metadata)
661
+ dms_metadata = self._convert_metadata_to_dms(info_metadata)
662
+ edge_classes: set[ClassEntity] = set()
663
+ property_to_edge: dict[tuple[ClassEntity, str], ClassEntity] = {}
664
+ end_node_by_edge: dict[ClassEntity, ClassEntity] = {}
665
+ if mode == "edge_properties":
666
+ edge_classes = {
667
+ cls_.class_ for cls_ in self.rules.classes if cls_.implements and cls_.implements[0].suffix == "Edge"
668
+ }
669
+ property_to_edge = {
670
+ (prop.class_, prop.property_): prop.value_type
671
+ for prop in self.rules.properties
672
+ if prop.value_type in edge_classes and isinstance(prop.value_type, ClassEntity)
673
+ }
674
+ end_node_by_edge = {
675
+ prop.class_: prop.value_type
676
+ for prop in self.rules.properties
677
+ if prop.class_ in edge_classes
678
+ and (prop.property_ == "endNode" or prop.property_ == "end_node")
679
+ and isinstance(prop.value_type, ClassEntity)
680
+ }
656
681
 
657
682
  properties_by_class: dict[ClassEntity, list[DMSProperty]] = defaultdict(list)
658
683
  referenced_containers: dict[ContainerEntity, Counter[ClassEntity]] = defaultdict(Counter)
659
684
  for prop in self.rules.properties:
660
685
  if ignore_undefined_value_types and isinstance(prop.value_type, UnknownEntity):
661
686
  continue
662
- dms_property = self._as_dms_property(prop, default_space, default_version)
687
+ if prop.class_ in edge_classes and prop.property_ in self._edge_properties:
688
+ continue
689
+ dms_property = self._as_dms_property(
690
+ prop, default_space, default_version, edge_classes, property_to_edge, end_node_by_edge
691
+ )
663
692
  properties_by_class[prop.class_].append(dms_property)
664
693
  if dms_property.container:
665
694
  referenced_containers[dms_property.container][prop.class_] += 1
666
695
 
667
- views: list[DMSView] = [
668
- DMSView(
696
+ views: list[DMSView] = []
697
+
698
+ for cls_ in self.rules.classes:
699
+ dms_view = DMSView(
669
700
  name=cls_.name,
670
701
  view=cls_.class_.as_view_entity(default_space, default_version),
671
702
  description=cls_.description,
672
- implements=self._get_view_implements(cls_, info_metadata),
703
+ implements=self._get_view_implements(cls_, info_metadata, mode),
673
704
  )
674
- for cls_ in self.rules.classes
675
- ]
705
+
706
+ dms_view.logical = cls_.neatId
707
+ cls_.physical = dms_view.neatId
708
+
709
+ views.append(dms_view)
676
710
 
677
711
  class_by_entity = {cls_.class_: cls_ for cls_ in self.rules.classes}
678
712
 
@@ -696,7 +730,7 @@ class _InformationRulesConverter:
696
730
  containers.append(container)
697
731
 
698
732
  return DMSRules(
699
- metadata=metadata,
733
+ metadata=dms_metadata,
700
734
  properties=SheetList[DMSProperty]([prop for prop_set in properties_by_class.values() for prop in prop_set]),
701
735
  views=SheetList[DMSView](views),
702
736
  containers=SheetList[DMSContainer](containers),
@@ -724,7 +758,7 @@ class _InformationRulesConverter:
724
758
  DMSMetadata,
725
759
  )
726
760
 
727
- return DMSMetadata(
761
+ dms_metadata = DMSMetadata(
728
762
  space=metadata.space,
729
763
  version=metadata.version,
730
764
  external_id=metadata.external_id,
@@ -734,74 +768,138 @@ class _InformationRulesConverter:
734
768
  updated=metadata.updated,
735
769
  )
736
770
 
737
- def _as_dms_property(self, prop: InformationProperty, default_space: str, default_version: str) -> "DMSProperty":
738
- """This creates the first"""
771
+ dms_metadata.logical = metadata.identifier
772
+ metadata.physical = dms_metadata.identifier
773
+
774
+ return dms_metadata
739
775
 
776
+ def _as_dms_property(
777
+ self,
778
+ info_property: InformationProperty,
779
+ default_space: str,
780
+ default_version: str,
781
+ edge_classes: set[ClassEntity],
782
+ property_to_edge: dict[tuple[ClassEntity, str], ClassEntity],
783
+ end_node_by_edge: dict[ClassEntity, ClassEntity],
784
+ ) -> "DMSProperty":
740
785
  from cognite.neat._rules.models.dms._rules import DMSProperty
741
786
 
742
787
  # returns property type, which can be ObjectProperty or DatatypeProperty
743
- value_type: DataType | ViewEntity | DMSUnknownEntity
788
+ value_type = self._get_value_type(
789
+ info_property,
790
+ default_space,
791
+ default_version,
792
+ edge_classes,
793
+ end_node_by_edge,
794
+ )
795
+
796
+ connection = self._get_connection(info_property, value_type, property_to_edge, default_space, default_version)
797
+
798
+ container: ContainerEntity | None = None
799
+ container_property: str | None = None
800
+ is_list: bool | None = info_property.is_list
801
+ nullable: bool | None = not info_property.is_mandatory
802
+ if isinstance(connection, EdgeEntity):
803
+ nullable = None
804
+ elif connection == "direct":
805
+ nullable = True
806
+ container, container_property = self._get_container(info_property, default_space)
807
+ else:
808
+ container, container_property = self._get_container(info_property, default_space)
809
+
810
+ dms_property = DMSProperty(
811
+ name=info_property.name,
812
+ value_type=value_type,
813
+ nullable=nullable,
814
+ is_list=is_list,
815
+ connection=connection,
816
+ default=info_property.default,
817
+ container=container,
818
+ container_property=container_property,
819
+ view=info_property.class_.as_view_entity(default_space, default_version),
820
+ view_property=info_property.property_,
821
+ )
822
+
823
+ # linking
824
+ dms_property.logical = info_property.neatId
825
+ info_property.physical = dms_property.neatId
826
+
827
+ return dms_property
828
+
829
+ @staticmethod
830
+ def _get_connection(
831
+ prop: InformationProperty,
832
+ value_type: DataType | ViewEntity | DMSUnknownEntity,
833
+ property_to_edge: dict[tuple[ClassEntity, str], ClassEntity],
834
+ default_space: str,
835
+ default_version: str,
836
+ ) -> Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None:
837
+ if isinstance(value_type, ViewEntity) and (prop.class_, prop.property_) in property_to_edge:
838
+ edge_properties = property_to_edge[(prop.class_, prop.property_)]
839
+ return EdgeEntity(properties=edge_properties.as_view_entity(default_space, default_version))
840
+ if isinstance(value_type, ViewEntity) and prop.is_list:
841
+ return EdgeEntity()
842
+ elif isinstance(value_type, ViewEntity):
843
+ return "direct"
844
+ # defaulting to direct connection
845
+ elif isinstance(value_type, DMSUnknownEntity):
846
+ return "direct"
847
+ return None
848
+
849
+ def _get_value_type(
850
+ self,
851
+ prop: InformationProperty,
852
+ default_space: str,
853
+ default_version: str,
854
+ edge_classes: set[ClassEntity],
855
+ end_node_by_edge: dict[ClassEntity, ClassEntity],
856
+ ) -> DataType | ViewEntity | DMSUnknownEntity:
744
857
  if isinstance(prop.value_type, DataType):
745
- value_type = prop.value_type
858
+ return prop.value_type
746
859
 
747
860
  # UnknownEntity should resolve to DMSUnknownEntity
748
861
  # meaning end node type is unknown
749
862
  elif isinstance(prop.value_type, UnknownEntity):
750
- value_type = DMSUnknownEntity()
751
-
863
+ return DMSUnknownEntity()
864
+
865
+ elif isinstance(prop.value_type, ClassEntity) and (prop.value_type in edge_classes):
866
+ if prop.value_type in end_node_by_edge:
867
+ return end_node_by_edge[prop.value_type].as_view_entity(default_space, default_version)
868
+ warnings.warn(
869
+ NeatValueWarning(
870
+ f"Edge class {prop.value_type} does not have 'endNode' property, defaulting to DMSUnknownEntity"
871
+ ),
872
+ stacklevel=2,
873
+ )
874
+ return DMSUnknownEntity()
752
875
  elif isinstance(prop.value_type, ClassEntity):
753
- value_type = prop.value_type.as_view_entity(default_space, default_version)
876
+ return prop.value_type.as_view_entity(default_space, default_version)
754
877
 
755
878
  elif isinstance(prop.value_type, MultiValueTypeInfo):
756
879
  # Multi Object type should resolve to DMSUnknownEntity
757
880
  # meaning end node type is unknown
758
881
  if prop.value_type.is_multi_object_type():
759
- value_type = DMSUnknownEntity()
882
+ non_unknown = [type_ for type_ in prop.value_type.types if isinstance(type_, UnknownEntity)]
883
+ if list(non_unknown) == 1:
884
+ #
885
+ return non_unknown[0].as_view_entity(default_space, default_version)
886
+ return DMSUnknownEntity()
760
887
 
761
888
  # Multi Data type should resolve to a single data type, or it should
762
889
  elif prop.value_type.is_multi_data_type():
763
- value_type = self.convert_multi_data_type(prop.value_type)
890
+ return self.convert_multi_data_type(prop.value_type)
764
891
 
765
892
  # Mixed types default to string
766
893
  else:
767
- value_type = String()
768
-
769
- else:
770
- raise ValueError(f"Unsupported value type: {prop.value_type.type_}")
894
+ non_any_uri = [type_ for type_ in prop.value_type.types if type_ != AnyURI()]
895
+ if list(non_any_uri) == 1:
896
+ if isinstance(non_any_uri[0], ClassEntity):
897
+ return non_any_uri[0].as_view_entity(default_space, default_version)
898
+ else:
899
+ return non_any_uri[0]
900
+ return String()
771
901
 
772
- connection: Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None = None
773
- if isinstance(value_type, ViewEntity):
774
- # Default connection type.
775
- connection = EdgeEntity() if prop.is_list else "direct"
776
-
777
- # defaulting to direct connection
778
- elif isinstance(value_type, DMSUnknownEntity):
779
- connection = "direct"
780
-
781
- container: ContainerEntity | None = None
782
- container_property: str | None = None
783
- is_list: bool | None = prop.is_list
784
- nullable: bool | None = not prop.is_mandatory
785
- if isinstance(connection, EdgeEntity):
786
- nullable = None
787
- elif connection == "direct":
788
- nullable = True
789
- container, container_property = self._get_container(prop, default_space)
790
- else:
791
- container, container_property = self._get_container(prop, default_space)
792
-
793
- return DMSProperty(
794
- name=prop.name,
795
- value_type=value_type,
796
- nullable=nullable,
797
- is_list=is_list,
798
- connection=connection,
799
- default=prop.default,
800
- container=container,
801
- container_property=container_property,
802
- view=prop.class_.as_view_entity(default_space, default_version),
803
- view_property=prop.property_,
804
- )
902
+ raise ValueError(f"Unsupported value type: {prop.value_type.type_}")
805
903
 
806
904
  @classmethod
807
905
  def _to_space(cls, prefix: str) -> str:
@@ -823,9 +921,13 @@ class _InformationRulesConverter:
823
921
  self.property_count_by_container[container_entity] += 1
824
922
  return container_entity, prop.property_
825
923
 
826
- def _get_view_implements(self, cls_: InformationClass, metadata: InformationMetadata) -> list[ViewEntity]:
924
+ def _get_view_implements(
925
+ self, cls_: InformationClass, metadata: InformationMetadata, mode: Literal["edge_properties"] | None
926
+ ) -> list[ViewEntity]:
827
927
  implements = []
828
928
  for parent in cls_.implements or []:
929
+ if mode == "edge_properties" and parent.suffix == "Edge":
930
+ continue
829
931
  view_entity = parent.as_view_entity(metadata.prefix, metadata.version)
830
932
  implements.append(view_entity)
831
933
  return implements