cognite-neat 0.77.6__py3-none-any.whl → 0.77.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cognite-neat might be problematic. Click here for more details.

cognite/neat/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.77.6"
1
+ __version__ = "0.77.7"
@@ -16,6 +16,7 @@ __all__ = [
16
16
  "MultipleDataModelsWarning",
17
17
  "UnknownPropertyTypeWarning",
18
18
  "FailedToInferValueTypeWarning",
19
+ "MoreThanOneNonAlphanumericCharacterWarning",
19
20
  "UnknownContainerConstraintWarning",
20
21
  "NoDataModelError",
21
22
  "ModelImportError",
@@ -275,6 +276,22 @@ class FailedImportWarning(ModelImportWarning):
275
276
  return {"identifier": list(self.identifier)}
276
277
 
277
278
 
279
+ @dataclass(frozen=True)
280
+ class MoreThanOneNonAlphanumericCharacterWarning(ModelImportWarning):
281
+ description = """This warning is raised when doing regex validation of strings which either represent class ids,
282
+ property ids, prefix, data model name, that contain more than one non-alphanumeric character,
283
+ such as for example '_' or '-'."""
284
+
285
+ field_name: str
286
+ value: str
287
+
288
+ def message(self) -> str:
289
+ return f"Field {self.field_name} with value {self.value} contains more than one non-alphanumeric character!"
290
+
291
+ def dump(self) -> dict[str, str]:
292
+ return {"field_name": self.field_name, "value": self.value}
293
+
294
+
278
295
  @dataclass(frozen=True)
279
296
  class ModelImportError(NeatValidationError, ABC):
280
297
  description = "An error was raised during importing."
@@ -1,4 +1,5 @@
1
1
  import re
2
+ import warnings
2
3
  from collections.abc import Callable
3
4
  from typing import Annotated, Any, cast
4
5
 
@@ -17,6 +18,7 @@ from pydantic.functional_serializers import PlainSerializer
17
18
  from pydantic_core import PydanticCustomError
18
19
 
19
20
  from cognite.neat.rules import exceptions
21
+ from cognite.neat.rules.issues.importing import MoreThanOneNonAlphanumericCharacterWarning
20
22
 
21
23
  from ._base import (
22
24
  MORE_THAN_ONE_NONE_ALPHANUMERIC_REGEX,
@@ -50,7 +52,6 @@ StrOrListType = Annotated[
50
52
  ),
51
53
  ]
52
54
 
53
-
54
55
  StrListType = Annotated[
55
56
  list[str],
56
57
  BeforeValidator(lambda value: [entry.strip() for entry in value.split(",")] if isinstance(value, str) else value),
@@ -96,19 +97,12 @@ VersionType = Annotated[
96
97
  ]
97
98
 
98
99
 
99
- PropertyType = Annotated[
100
- str,
101
- AfterValidator(
102
- lambda value: (
103
- _raise(exceptions.MoreThanOneNonAlphanumericCharacter("property", value).to_pydantic_custom_error())
104
- if re.search(MORE_THAN_ONE_NONE_ALPHANUMERIC_REGEX, value)
105
- else (
106
- value
107
- if re.match(PROPERTY_ID_COMPLIANCE_REGEX, value)
108
- else _raise(
109
- exceptions.PropertyIDRegexViolation(value, PROPERTY_ID_COMPLIANCE_REGEX).to_pydantic_custom_error()
110
- )
111
- )
112
- )
113
- ),
114
- ]
100
+ def _property_validation(value: str) -> str:
101
+ if not re.match(PROPERTY_ID_COMPLIANCE_REGEX, value):
102
+ _raise(exceptions.PropertyIDRegexViolation(value, PROPERTY_ID_COMPLIANCE_REGEX).to_pydantic_custom_error())
103
+ if re.search(MORE_THAN_ONE_NONE_ALPHANUMERIC_REGEX, value):
104
+ warnings.warn(MoreThanOneNonAlphanumericCharacterWarning("property", value), stacklevel=2)
105
+ return value
106
+
107
+
108
+ PropertyType = Annotated[str, AfterValidator(_property_validation)]
@@ -57,7 +57,10 @@ class DMSPostValidation:
57
57
  container_id = container.as_id()
58
58
  row_numbers = {prop_no for prop_no, _ in properties}
59
59
  value_types = {prop.value_type for _, prop in properties if prop.value_type}
60
- if len(value_types) > 1:
60
+ # The container type 'direct' is an exception. On a container the type direct can point to any
61
+ # node. The value type is typically set on the view.
62
+ is_all_direct = all(prop.connection == "direct" for _, prop in properties)
63
+ if len(value_types) > 1 and not is_all_direct:
61
64
  errors.append(
62
65
  issues.spreadsheet.MultiValueTypeError(
63
66
  container_id,
@@ -1,5 +1,6 @@
1
1
  import re
2
- from collections import defaultdict
2
+ from collections import Counter, defaultdict
3
+ from collections.abc import Collection
3
4
  from typing import TYPE_CHECKING, Literal
4
5
 
5
6
  from cognite.neat.rules.models._base import (
@@ -40,7 +41,6 @@ class _InformationRulesConverter:
40
41
  self.last_classes = {class_.class_: class_ for class_ in self.rules.last.classes}
41
42
  else:
42
43
  self.last_classes = {}
43
- self._created_classes_from: dict[ClassEntity, ClassEntity] = {}
44
44
 
45
45
  def as_domain_rules(self) -> DomainRules:
46
46
  raise NotImplementedError("DomainRules not implemented yet")
@@ -58,11 +58,13 @@ class _InformationRulesConverter:
58
58
  default_space = self._to_space(info_metadata.prefix)
59
59
  metadata = self._convert_metadata_to_dms(info_metadata)
60
60
 
61
- properties_by_class: dict[str, list[DMSProperty]] = defaultdict(list)
61
+ properties_by_class: dict[ClassEntity, list[DMSProperty]] = defaultdict(list)
62
+ referenced_containers: dict[ContainerEntity, Counter[ClassEntity]] = defaultdict(Counter)
62
63
  for prop in self.rules.properties:
63
- properties_by_class[prop.class_.versioned_id].append(
64
- self._as_dms_property(prop, default_space, default_version)
65
- )
64
+ dms_property = self._as_dms_property(prop, default_space, default_version)
65
+ properties_by_class[prop.class_].append(dms_property)
66
+ if dms_property.container:
67
+ referenced_containers[dms_property.container][prop.class_] += 1
66
68
 
67
69
  views: list[DMSView] = [
68
70
  DMSView(
@@ -76,41 +78,26 @@ class _InformationRulesConverter:
76
78
  for cls_ in self.rules.classes
77
79
  ]
78
80
 
79
- classes_without_properties: set[str] = set()
80
- for class_ in self.rules.classes:
81
- properties: list[DMSProperty] = properties_by_class.get(class_.class_.versioned_id, [])
82
- if not properties or all(
83
- isinstance(prop.value_type, ViewPropertyEntity) and prop.connection != "direct" for prop in properties
84
- ):
85
- classes_without_properties.add(class_.class_.versioned_id)
86
-
87
81
  containers: list[DMSContainer] = []
88
- classes = list(self.rules.classes)
89
- for new_class_entity, created_from in self._created_classes_from.items():
90
- # We create new classes in case metadata is set to addition or reshape
91
- # and the class was present in the last schema to avoid creating
92
- # a changed container and instead create a new one.
93
- created_class = self.last_classes[created_from].copy(deep=True)
94
- created_class.class_ = new_class_entity
95
- classes.append(created_class)
96
-
97
- for class_ in classes:
98
- if class_.class_.versioned_id in classes_without_properties:
99
- continue
100
- containers.append(
101
- DMSContainer(
102
- class_=class_.class_,
103
- name=class_.name,
104
- container=class_.class_.as_container_entity(default_space),
105
- description=class_.description,
106
- constraint=[
107
- parent.as_container_entity(default_space)
108
- for parent in class_.parent or []
109
- if parent.versioned_id not in classes_without_properties
110
- ]
111
- or None,
112
- )
82
+ class_by_entity = {cls_.class_: cls_ for cls_ in self.rules.classes}
83
+ if self.rules.last:
84
+ for cls_ in self.rules.last.classes:
85
+ if cls_.class_ not in class_by_entity:
86
+ class_by_entity[cls_.class_] = cls_
87
+ for container_entity, class_entities in referenced_containers.items():
88
+ constrains = self._create_container_constraint(
89
+ class_entities, default_space, class_by_entity, referenced_containers
113
90
  )
91
+ most_used_class_entity = class_entities.most_common(1)[0][0]
92
+ class_ = class_by_entity[most_used_class_entity]
93
+ container = DMSContainer(
94
+ class_=class_.class_,
95
+ container=container_entity,
96
+ name=class_.name,
97
+ description=class_.description,
98
+ constraint=constrains or None,
99
+ )
100
+ containers.append(container)
114
101
 
115
102
  return DMSRules(
116
103
  metadata=metadata,
@@ -123,6 +110,22 @@ class _InformationRulesConverter:
123
110
  reference=self.rules.reference.as_dms_architect_rules() if self.rules.reference else None,
124
111
  )
125
112
 
113
+ @staticmethod
114
+ def _create_container_constraint(
115
+ class_entities: Counter[ClassEntity],
116
+ default_space: str,
117
+ class_by_entity: dict[ClassEntity, InformationClass],
118
+ referenced_containers: Collection[ContainerEntity],
119
+ ) -> list[ContainerEntity]:
120
+ constrains: list[ContainerEntity] = []
121
+ for entity in class_entities:
122
+ class_ = class_by_entity[entity]
123
+ for parent in class_.parent or []:
124
+ parent_entity = parent.as_container_entity(default_space)
125
+ if parent_entity in referenced_containers:
126
+ constrains.append(parent_entity)
127
+ return constrains
128
+
126
129
  @classmethod
127
130
  def _convert_metadata_to_dms(cls, metadata: InformationMetadata) -> "DMSMetadata":
128
131
  from cognite.neat.rules.models.dms._rules import (
@@ -211,11 +214,9 @@ class _InformationRulesConverter:
211
214
  elif (self.is_addition or self.is_reshape) and prop.class_ in self.last_classes:
212
215
  # We need to create a new container for the property, as we cannot change
213
216
  # the existing container in the last schema
214
- class_entity = prop.class_.copy()
215
- class_entity.suffix = self._bump_suffix(class_entity.suffix)
216
- if class_entity not in self._created_classes_from:
217
- self._created_classes_from[class_entity] = prop.class_
218
- return class_entity.as_container_entity(default_space), prop.property_
217
+ container_entity = prop.class_.as_container_entity(default_space)
218
+ container_entity.suffix = self._bump_suffix(container_entity.suffix)
219
+ return container_entity, prop.property_
219
220
  else:
220
221
  return prop.class_.as_container_entity(default_space), prop.property_
221
222
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cognite-neat
3
- Version: 0.77.6
3
+ Version: 0.77.7
4
4
  Summary: Knowledge graph transformation
5
5
  Home-page: https://cognite-neat.readthedocs-hosted.com/
6
6
  License: Apache-2.0
@@ -1,5 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=v-rRiDOgZ3sQSMQKq0vgUQZvpeOkoHFXissAx6Ktg84,61
2
- cognite/neat/_version.py,sha256=gRPwu1nLGE_QfxUJtmhbOlmhiHQAZRF63Q3wmAlYElY,23
2
+ cognite/neat/_version.py,sha256=hFsfqx1W0uQf6EjOq0viOjyWKF79pF13oi9ziY65GuY,23
3
3
  cognite/neat/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cognite/neat/app/api/asgi/metrics.py,sha256=nxFy7L5cChTI0a-zkCiJ59Aq8yLuIJp5c9Dg0wRXtV0,152
5
5
  cognite/neat/app/api/configuration.py,sha256=2U5M6M252swvQPQyooA1EBzFUZNtcTmuSaywfJDgckM,4232
@@ -190,7 +190,7 @@ cognite/neat/rules/issues/base.py,sha256=i2aTC-wq3UVW2bj_7wKeuhYxCpMD06Bd9-m00bW
190
190
  cognite/neat/rules/issues/dms.py,sha256=CKztcpNu9E_ygbAmiODOhaYKPX6o9eaXeiod7Ak-kNY,23617
191
191
  cognite/neat/rules/issues/fileread.py,sha256=ao199mtvhPSW0IA8ZQZ0RzuLIIipYtL0jp6fLqxb4_c,5748
192
192
  cognite/neat/rules/issues/formatters.py,sha256=_ag2bJ9hncOj8pAGJvTTEPs9kTtxbD7vkqvS9Zcnizc,3385
193
- cognite/neat/rules/issues/importing.py,sha256=l0VKmOWN-qyvOMukNAVJZrN2WGD2YcGXeCKbxV-G_vA,12726
193
+ cognite/neat/rules/issues/importing.py,sha256=uSk4TXo_CO3bglBZkaiWekXLXXd31UWIZE95clVSLz4,13417
194
194
  cognite/neat/rules/issues/spreadsheet.py,sha256=1NZQ3a_zCtErAoMKzEt5UMJsukaysoOVmW7WUF6JK1s,15825
195
195
  cognite/neat/rules/issues/spreadsheet_file.py,sha256=YCp0Pk_TsiqYuOPdWpjUpre-zvi2c5_MvrC_dxw10YY,4964
196
196
  cognite/neat/rules/models/__init__.py,sha256=aqhQUidHYgOk5_iqdi6s72s2g8qyMRFXShYzh-ctNpw,782
@@ -198,7 +198,7 @@ cognite/neat/rules/models/_base.py,sha256=oQ8f0bvdythJ2m54K1jl2OXEuEZ4N8JqHDXyhC
198
198
  cognite/neat/rules/models/_rdfpath.py,sha256=RoHnfWufjnDtwJh7UUzWKoJz8luvX7Gb5SDQORfkQTE,11030
199
199
  cognite/neat/rules/models/_types/__init__.py,sha256=l1tGxzE7ezNHIL72AoEvNHN2IFuitxOLxiHJG__s6t4,305
200
200
  cognite/neat/rules/models/_types/_base.py,sha256=2GhLUE1ukV8X8SGL_JDxpbWGZyAvOnSqAE6JmDh5wbI,929
201
- cognite/neat/rules/models/_types/_field.py,sha256=74WfCSVbTubpK4n4VsysQqCch6VI8IcPqnHQpvNbFZ8,3197
201
+ cognite/neat/rules/models/_types/_field.py,sha256=h2RrhjxdaRbzHG5EyduyHLkCJJmQoyZb9pYCkgMcnVk,3203
202
202
  cognite/neat/rules/models/data_types.py,sha256=lanwkhwG8iHKfjYfia4v2SBTJrMeXOsqaVkVEP2QMXs,6078
203
203
  cognite/neat/rules/models/dms/__init__.py,sha256=Wzyqzz2ZIjpUbDg04CMuuIAw-f2A02DayNeqO9R-2Hw,491
204
204
  cognite/neat/rules/models/dms/_converter.py,sha256=QaJT0z0Yo4gkG9tHPZkU8idF8PK7c-tDahbyIT-WJQU,5959
@@ -207,11 +207,11 @@ cognite/neat/rules/models/dms/_rules.py,sha256=iqoPilY3tov3GvJ9N3K3go6xy9kiiMt9v
207
207
  cognite/neat/rules/models/dms/_rules_input.py,sha256=apDDTQll9UAyYL5gS2vDxHsujWrGBilTp7lK2kzJWO8,13467
208
208
  cognite/neat/rules/models/dms/_schema.py,sha256=A4z8CINmLQgWzHoScxejRPMRo40ngKlyp1gOdPto8yU,43808
209
209
  cognite/neat/rules/models/dms/_serializer.py,sha256=iqp2zyyf8jEcU-R3PERuN8nu248xIqyxiWj4owAn92g,6406
210
- cognite/neat/rules/models/dms/_validation.py,sha256=TUmBxfSB42lsgIKVIDPPbaTaJ8TKi-gTbayqgPAmnfs,13656
210
+ cognite/neat/rules/models/dms/_validation.py,sha256=nPSyfM1vGZ7d9Uv_2vF2HvMetygtehXW7eNtPD6eW8E,13937
211
211
  cognite/neat/rules/models/domain.py,sha256=tkKcHvDXnZ5IkOr1wHiuNBtE1h8OCFmf6GZSqzHzxjI,2814
212
212
  cognite/neat/rules/models/entities.py,sha256=iBG84Jr1qQ7PvkMJUJzJ1oWApeONb1IACixdJSztUhk,16395
213
213
  cognite/neat/rules/models/information/__init__.py,sha256=HR6g8xgyU53U7Ck8pPdbT70817Q4NC1r1pCRq5SA8iw,291
214
- cognite/neat/rules/models/information/_converter.py,sha256=qJl95-gPz6MwKLZpHyY_CIqf-ebZalHWZyYnsqr_FWc,9960
214
+ cognite/neat/rules/models/information/_converter.py,sha256=sH1wJ8TmEfytFPFDfHgDl6QWnSD-1Pqeb74kIgmR8wQ,9963
215
215
  cognite/neat/rules/models/information/_rules.py,sha256=tdCjvAtqnFzEo2zcx-BZHmvjSs28gVun2wz8UaT-AOA,13268
216
216
  cognite/neat/rules/models/information/_rules_input.py,sha256=IZp_Wnrac0nVaHKth1YttWQOs-kULGKLMtngNQFY40A,10147
217
217
  cognite/neat/rules/models/information/_serializer.py,sha256=yti9I_xJruxrib66YIBInhze___Io-oPTQH6uWDumPE,3503
@@ -275,8 +275,8 @@ cognite/neat/workflows/steps_registry.py,sha256=fkTX14ZA7_gkUYfWIlx7A1XbCidvqR23
275
275
  cognite/neat/workflows/tasks.py,sha256=dqlJwKAb0jlkl7abbY8RRz3m7MT4SK8-7cntMWkOYjw,788
276
276
  cognite/neat/workflows/triggers.py,sha256=_BLNplzoz0iic367u1mhHMHiUrCwP-SLK6_CZzfODX0,7071
277
277
  cognite/neat/workflows/utils.py,sha256=gKdy3RLG7ctRhbCRwaDIWpL9Mi98zm56-d4jfHDqP1E,453
278
- cognite_neat-0.77.6.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
279
- cognite_neat-0.77.6.dist-info/METADATA,sha256=XhW7PcK8bdv1WEsMS8Oehu1AapiwLD9PrbjOrXkV4yY,9316
280
- cognite_neat-0.77.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
281
- cognite_neat-0.77.6.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
282
- cognite_neat-0.77.6.dist-info/RECORD,,
278
+ cognite_neat-0.77.7.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
279
+ cognite_neat-0.77.7.dist-info/METADATA,sha256=3FhpHUDfplCEVHoqUUxkNwF3DoNwC9Ad90mKiQXDLaw,9316
280
+ cognite_neat-0.77.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
281
+ cognite_neat-0.77.7.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
282
+ cognite_neat-0.77.7.dist-info/RECORD,,