cognite-neat 0.109.2__py3-none-any.whl → 0.109.4__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/_alpha.py CHANGED
@@ -12,3 +12,4 @@ class AlphaWarning(UserWarning):
12
12
  class AlphaFlags:
13
13
  manual_rules_edit = AlphaWarning("enable_manual_edit")
14
14
  same_space_properties_only_export = AlphaWarning("same-space-properties-only")
15
+ standardize_naming = AlphaWarning("standardize_naming")
@@ -19,7 +19,7 @@ class NeatClientMock(CogniteClientMock):
19
19
  def __init__(self, *args: Any, **kwargs: Any) -> None:
20
20
  if "parent" in kwargs:
21
21
  super().__init__(*args, **kwargs)
22
- return None
22
+ return
23
23
  super().__init__(*args, **kwargs)
24
24
  self.schema = SchemaAPI(self)
25
25
  self.loaders = DataModelLoaderAPI(self)
@@ -103,6 +103,8 @@ class NeatIssue:
103
103
  variables[name] = var_.as_posix()
104
104
  elif isinstance(var_, Collection):
105
105
  variables[name] = humanize_collection(var_)
106
+ elif isinstance(var_, NeatError):
107
+ variables[name] = var_.as_message(include_type=False)
106
108
  else:
107
109
  variables[name] = repr(var_)
108
110
  return variables, has_all_optional
@@ -138,6 +140,8 @@ class NeatIssue:
138
140
  return value.dump(camel_case=True)
139
141
  elif isinstance(value, DataModelId):
140
142
  return value.dump(camel_case=True, include_type=False)
143
+ elif isinstance(value, NeatError):
144
+ return value.dump()
141
145
  raise ValueError(f"Unsupported type: {type(value)}")
142
146
 
143
147
  @classmethod
@@ -192,6 +196,8 @@ class NeatIssue:
192
196
  return ContainerId.load(value)
193
197
  elif inspect.isclass(type_) and issubclass(type_, Entity):
194
198
  return type_.load(value)
199
+ elif type_ is NeatError:
200
+ return cls.load(value)
195
201
  return value
196
202
 
197
203
  def __lt__(self, other: "NeatIssue") -> bool:
@@ -227,22 +233,19 @@ class NeatError(NeatIssue, Exception):
227
233
  # Skip the error for SheetList, as it is not relevant for the user. This is an
228
234
  # internal class used to have helper methods for a lists as .to_pandas()
229
235
  continue
236
+
230
237
  neat_error: NeatError | None = None
231
238
  if isinstance(error, dict) and isinstance(ctx := error.get("ctx"), dict) and "error" in ctx:
232
239
  neat_error = ctx["error"]
233
240
  elif isinstance(error, NeatError | MultiValueError):
234
241
  neat_error = error
235
242
 
243
+ loc = error["loc"] if isinstance(error, dict) else tuple()
236
244
  if isinstance(neat_error, MultiValueError):
237
- if read_info_by_sheet:
238
- for caught_error in neat_error.errors:
239
- cls._adjust_row_numbers(caught_error, read_info_by_sheet) # type: ignore[arg-type]
240
- all_errors.extend(neat_error.errors) # type: ignore[arg-type]
245
+ all_errors.extend([cls._adjust_error(e, loc, read_info_by_sheet) for e in neat_error.errors])
241
246
  elif isinstance(neat_error, NeatError):
242
- if read_info_by_sheet:
243
- cls._adjust_row_numbers(neat_error, read_info_by_sheet)
244
- all_errors.append(neat_error)
245
- elif isinstance(error, dict) and len(error["loc"]) >= 4 and read_info_by_sheet:
247
+ all_errors.append(cls._adjust_error(neat_error, loc, read_info_by_sheet))
248
+ elif isinstance(error, dict) and len(loc) >= 4 and read_info_by_sheet:
246
249
  all_errors.append(RowError.from_pydantic_error(error, read_info_by_sheet))
247
250
  elif isinstance(error, dict):
248
251
  all_errors.append(DefaultPydanticError.from_pydantic_error(error))
@@ -251,6 +254,18 @@ class NeatError(NeatIssue, Exception):
251
254
  raise ValueError(f"Unsupported error type: {error}")
252
255
  return all_errors
253
256
 
257
+ @classmethod
258
+ def _adjust_error(
259
+ cls, error: "NeatError", loc: tuple[str | int, ...], read_info_by_sheet: dict[str, SpreadsheetRead] | None
260
+ ) -> "NeatError":
261
+ from .errors._wrapper import MetadataValueError
262
+
263
+ if read_info_by_sheet:
264
+ cls._adjust_row_numbers(error, read_info_by_sheet)
265
+ if len(loc) == 2 and isinstance(loc[0], str) and loc[0].casefold() == "metadata":
266
+ return MetadataValueError(field_name=str(loc[1]), error=error)
267
+ return error
268
+
254
269
  @staticmethod
255
270
  def _adjust_row_numbers(caught_error: "NeatError", read_info_by_sheet: dict[str, SpreadsheetRead]) -> None:
256
271
  from cognite.neat._issues.errors._properties import PropertyDefinitionDuplicatedError
@@ -288,7 +303,16 @@ class DefaultPydanticError(NeatError, ValueError):
288
303
  msg: str
289
304
 
290
305
  @classmethod
291
- def from_pydantic_error(cls, error: ErrorDetails) -> "DefaultPydanticError":
306
+ def from_pydantic_error(cls, error: ErrorDetails) -> "NeatError":
307
+ loc = error["loc"]
308
+ if len(loc) >= 2 and isinstance(loc[0], str) and loc[0].casefold() == "metadata":
309
+ from .errors._general import NeatValueError
310
+ from .errors._wrapper import MetadataValueError
311
+
312
+ return MetadataValueError(
313
+ field_name=str(loc[1]), error=NeatValueError(f"{error['msg']} got '{error['input']}'")
314
+ )
315
+
292
316
  return cls(
293
317
  type=error["type"],
294
318
  loc=error["loc"],
@@ -31,12 +31,7 @@ from ._resources import (
31
31
  ResourceNotFoundError,
32
32
  ResourceRetrievalError,
33
33
  )
34
- from ._workflow import (
35
- WorkflowConfigurationNotSetError,
36
- WorkFlowMissingDataError,
37
- WorkflowStepNotInitializedError,
38
- WorkflowStepOutputError,
39
- )
34
+ from ._wrapper import MetadataValueError
40
35
 
41
36
  __all__ = [
42
37
  "AuthorizationError",
@@ -47,6 +42,7 @@ __all__ = [
47
42
  "FileNotFoundNeatError",
48
43
  "FileReadError",
49
44
  "FileTypeUnexpectedError",
45
+ "MetadataValueError",
50
46
  "NeatError",
51
47
  "NeatImportError",
52
48
  "NeatTypeError",
@@ -70,10 +66,6 @@ __all__ = [
70
66
  "ResourceRetrievalError",
71
67
  "ReversedConnectionNotFeasibleError",
72
68
  "RowError",
73
- "WorkFlowMissingDataError",
74
- "WorkflowConfigurationNotSetError",
75
- "WorkflowStepNotInitializedError",
76
- "WorkflowStepOutputError",
77
69
  ]
78
70
 
79
71
  _NEAT_ERRORS_BY_NAME = {error.__name__: error for error in _get_subclasses(NeatError, include_base=True)}
@@ -19,7 +19,7 @@ class NeatTypeError(NeatError, TypeError):
19
19
 
20
20
  @dataclass(unsafe_hash=True)
21
21
  class RegexViolationError(NeatError, ValueError):
22
- """Value, {value} in {location} failed regex, {regex}, validation.
22
+ """The value '{value}' failed regex, {regex}, validation in {location}.
23
23
  Make sure that the name follows the regex pattern."""
24
24
 
25
25
  value: str
@@ -0,0 +1,11 @@
1
+ from dataclasses import dataclass
2
+
3
+ from cognite.neat._issues import NeatError
4
+
5
+
6
+ @dataclass(unsafe_hash=True)
7
+ class MetadataValueError(NeatError, ValueError):
8
+ """Field {field_name} - {error}"""
9
+
10
+ field_name: str
11
+ error: NeatError
@@ -2,6 +2,7 @@
2
2
 
3
3
  import re
4
4
  import sys
5
+ import urllib.parse
5
6
  from collections import Counter
6
7
  from functools import total_ordering
7
8
  from typing import ClassVar, Literal
@@ -327,6 +328,7 @@ def parse_table_lookup(raw: str) -> TableLookup:
327
328
 
328
329
 
329
330
  def parse_rule(rule_raw: str, rule_type: TransformationRuleType | None) -> RDFPath:
331
+ rule_raw = urllib.parse.unquote(rule_raw)
330
332
  match rule_type:
331
333
  case TransformationRuleType.rdfpath:
332
334
  rule_raw = rule_raw.replace(" ", "")
@@ -38,7 +38,9 @@ def _custom_error(exc_factory: Callable[[str | None, Exception], Any]) -> Any:
38
38
  def _validator(value: Any, next_: Any, ctx: ValidationInfo) -> Any:
39
39
  try:
40
40
  return next_(value, ctx)
41
- except Exception:
41
+ except ValueError as e:
42
+ if value is None:
43
+ raise e
42
44
  raise exc_factory(ctx.field_name, value) from None
43
45
 
44
46
  return WrapValidator(_validator)
@@ -130,7 +132,7 @@ def _external_id_validation_factory(entity_type: EntityTypes, location: str):
130
132
 
131
133
  SpaceType = Annotated[
132
134
  str,
133
- AfterValidator(_external_id_validation_factory(EntityTypes.space, "space entry in metadata")),
135
+ AfterValidator(_external_id_validation_factory(EntityTypes.space, "")),
134
136
  ]
135
137
 
136
138
  InformationPropertyType = Annotated[
@@ -1,4 +1,3 @@
1
- import warnings
2
1
  from collections.abc import Hashable
3
2
  from typing import TYPE_CHECKING, Any, ClassVar, Literal
4
3
 
@@ -8,11 +7,7 @@ from pydantic import Field, field_serializer, field_validator, model_validator
8
7
  from pydantic_core.core_schema import SerializationInfo, ValidationInfo
9
8
 
10
9
  from cognite.neat._client.data_classes.schema import DMSSchema
11
- from cognite.neat._constants import COGNITE_SPACES
12
10
  from cognite.neat._issues.errors import NeatValueError
13
- from cognite.neat._issues.warnings import (
14
- PrincipleMatchingSpaceAndVersionWarning,
15
- )
16
11
  from cognite.neat._rules.models._base_rules import (
17
12
  BaseMetadata,
18
13
  BaseRules,
@@ -407,37 +402,6 @@ class DMSRules(BaseRules):
407
402
  None, alias="Nodes", description="Contains the definition of the node types."
408
403
  )
409
404
 
410
- @field_validator("views")
411
- def matching_version_and_space(cls, value: SheetList[DMSView], info: ValidationInfo) -> SheetList[DMSView]:
412
- if not (metadata := info.data.get("metadata")):
413
- return value
414
- model_version = metadata.version
415
- if different_version := [
416
- view.view.as_id()
417
- for view in value
418
- if view.view.version != model_version and view.view.space not in COGNITE_SPACES
419
- ]:
420
- for view_id in different_version:
421
- warnings.warn(
422
- PrincipleMatchingSpaceAndVersionWarning(
423
- f"The view {view_id!r} has a different version than the data model version, {model_version}",
424
- ),
425
- stacklevel=2,
426
- )
427
- if different_space := [
428
- view.view.as_id()
429
- for view in value
430
- if view.view.space != metadata.space and view.view.space not in COGNITE_SPACES
431
- ]:
432
- for view_id in different_space:
433
- warnings.warn(
434
- PrincipleMatchingSpaceAndVersionWarning(
435
- f"The view {view_id!r} is in a different space than the data model space, {metadata.space}",
436
- ),
437
- stacklevel=2,
438
- )
439
- return value
440
-
441
405
  @model_validator(mode="after")
442
406
  def set_neat_id(self) -> "DMSRules":
443
407
  namespace = self.metadata.namespace
@@ -8,6 +8,9 @@ class _UnknownType(BaseModel):
8
8
  def __str__(self) -> str:
9
9
  return "#N/A"
10
10
 
11
+ def __hash__(self):
12
+ return hash(str(self))
13
+
11
14
 
12
15
  # This is a trick to make Undefined and Unknown singletons
13
16
  Undefined = _UndefinedType()
@@ -134,7 +134,12 @@ class Entity(BaseModel, extra="ignore"):
134
134
  content = result.group("content")
135
135
  if content is None:
136
136
  return dict(prefix=prefix, suffix=suffix)
137
- extra_args = dict(SPLIT_ON_EQUAL_PATTERN.split(pair.strip()) for pair in SPLIT_ON_COMMA_PATTERN.split(content))
137
+ try:
138
+ extra_args = dict(
139
+ SPLIT_ON_EQUAL_PATTERN.split(pair.strip()) for pair in SPLIT_ON_COMMA_PATTERN.split(content)
140
+ )
141
+ except ValueError:
142
+ raise NeatValueError(f"Invalid {cls.type_.value} entity: {raw!r}") from None
138
143
  expected_args = {
139
144
  field_.alias or field_name: field_.annotation for field_name, field_ in cls.model_fields.items()
140
145
  }
@@ -13,10 +13,12 @@ from ._converters import (
13
13
  MergeInformationRules,
14
14
  PrefixEntities,
15
15
  SetIDDMSModel,
16
+ StandardizeNaming,
16
17
  ToCompliantEntities,
17
18
  ToDataProductModel,
18
19
  ToEnterpriseModel,
19
20
  ToExtensionModel,
21
+ ToInformationCompliantEntities,
20
22
  ToSolutionModel,
21
23
  )
22
24
  from ._mapping import AsParentPropertyId, MapOneToOne, RuleMapper
@@ -40,10 +42,12 @@ __all__ = [
40
42
  "RuleMapper",
41
43
  "RulesTransformer",
42
44
  "SetIDDMSModel",
45
+ "StandardizeNaming",
43
46
  "ToCompliantEntities",
44
47
  "ToDataProductModel",
45
48
  "ToEnterpriseModel",
46
49
  "ToExtensionModel",
50
+ "ToInformationCompliantEntities",
47
51
  "ToSolutionModel",
48
52
  "VerifiedRulesTransformer",
49
53
  "VerifyAnyRules",
@@ -1,9 +1,11 @@
1
1
  import re
2
+ import urllib.parse
2
3
  import warnings
3
4
  from abc import ABC
4
5
  from collections import Counter, defaultdict
5
6
  from collections.abc import Collection, Mapping
6
7
  from datetime import date, datetime
8
+ from functools import cached_property
7
9
  from typing import ClassVar, Literal, TypeVar, cast, overload
8
10
 
9
11
  from cognite.client.data_classes import data_modeling as dms
@@ -23,11 +25,12 @@ from cognite.neat._constants import (
23
25
  from cognite.neat._issues.errors import NeatValueError
24
26
  from cognite.neat._issues.warnings import NeatValueWarning
25
27
  from cognite.neat._issues.warnings._models import (
26
- EnterpriseModelNotBuildOnTopOfCDMWarning,
27
28
  SolutionModelBuildOnTopOfCDMWarning,
28
29
  )
30
+ from cognite.neat._rules._constants import PATTERNS, get_reserved_words
29
31
  from cognite.neat._rules._shared import (
30
32
  ReadInputRules,
33
+ ReadRules,
31
34
  VerifiedRules,
32
35
  )
33
36
  from cognite.neat._rules.analysis import DMSAnalysis
@@ -35,6 +38,7 @@ from cognite.neat._rules.importers import DMSImporter
35
38
  from cognite.neat._rules.models import (
36
39
  DMSInputRules,
37
40
  DMSRules,
41
+ InformationInputRules,
38
42
  InformationRules,
39
43
  SheetList,
40
44
  data_types,
@@ -56,9 +60,9 @@ from cognite.neat._rules.models.entities import (
56
60
  ViewEntity,
57
61
  )
58
62
  from cognite.neat._rules.models.information import InformationClass, InformationMetadata, InformationProperty
59
- from cognite.neat._utils.text import to_camel
63
+ from cognite.neat._utils.text import NamingStandardization, to_camel
60
64
 
61
- from ._base import T_VerifiedIn, T_VerifiedOut, VerifiedRulesTransformer
65
+ from ._base import RulesTransformer, T_VerifiedIn, T_VerifiedOut, VerifiedRulesTransformer
62
66
  from ._verification import VerifyDMSRules
63
67
 
64
68
  T_InputInRules = TypeVar("T_InputInRules", bound=ReadInputRules)
@@ -71,6 +75,105 @@ class ConversionTransformer(VerifiedRulesTransformer[T_VerifiedIn, T_VerifiedOut
71
75
  ...
72
76
 
73
77
 
78
+ class ToInformationCompliantEntities(
79
+ RulesTransformer[ReadRules[InformationInputRules], ReadRules[InformationInputRules]]
80
+ ):
81
+ """Converts input rules to rules that is compliant with the Information Model.
82
+
83
+ This is typically used with importers from arbitrary sources to ensure that classes and properties have valid
84
+ names.
85
+
86
+ Args:
87
+ renaming: How to handle renaming of entities that are not compliant with the Information Model.
88
+ - "warning": Raises a warning and renames the entity.
89
+ - "skip": Renames the entity without raising a warning.
90
+ """
91
+
92
+ def __init__(self, renaming: Literal["warning", "skip"] = "skip") -> None:
93
+ self._renaming = renaming
94
+
95
+ @property
96
+ def description(self) -> str:
97
+ return "Ensures that all entities are compliant with the Information Model."
98
+
99
+ def transform(self, rules: ReadRules[InformationInputRules]) -> ReadRules[InformationInputRules]:
100
+ if rules.rules is None:
101
+ return rules
102
+ # Doing dump to obtain a copy, and ensure that all entities are created. Input allows
103
+ # string for entities, the dump call will convert these to entities.
104
+ dumped = rules.rules.dump()
105
+ copy = InformationInputRules.load(dumped)
106
+
107
+ new_by_old_class_suffix: dict[str, str] = {}
108
+ for cls in copy.classes:
109
+ cls_entity = cast(ClassEntity, cls.class_) # Safe due to the dump above
110
+ if not PATTERNS.class_id_compliance.match(cls_entity.suffix):
111
+ new_suffix = self._fix_cls_suffix(cls_entity.suffix)
112
+ if self._renaming == "warning":
113
+ warnings.warn(
114
+ NeatValueWarning(f"Invalid class name {cls_entity.suffix!r}.Renaming to {new_suffix}"),
115
+ stacklevel=2,
116
+ )
117
+ cls.class_.suffix = new_suffix # type: ignore[union-attr]
118
+
119
+ for cls_ in copy.classes:
120
+ if cls_.implements:
121
+ for i, parent in enumerate(cls_.implements):
122
+ if isinstance(parent, ClassEntity) and parent.suffix in new_by_old_class_suffix:
123
+ cls_.implements[i].suffix = new_by_old_class_suffix[parent.suffix] # type: ignore[union-attr]
124
+
125
+ for prop in copy.properties:
126
+ if not PATTERNS.information_property_id_compliance.match(prop.property_):
127
+ new_property = self._fix_property(prop.property_)
128
+ if self._renaming == "warning":
129
+ warnings.warn(
130
+ NeatValueWarning(
131
+ f"Invalid property name {prop.class_.suffix}.{prop.property_!r}. Renaming to {new_property}" # type: ignore[union-attr]
132
+ ),
133
+ stacklevel=2,
134
+ )
135
+ prop.property_ = new_property
136
+
137
+ if isinstance(prop.class_, ClassEntity) and prop.class_.suffix in new_by_old_class_suffix:
138
+ prop.class_.suffix = new_by_old_class_suffix[prop.class_.suffix]
139
+
140
+ if isinstance(prop.value_type, ClassEntity) and prop.value_type.suffix in new_by_old_class_suffix:
141
+ prop.value_type.suffix = new_by_old_class_suffix[prop.value_type.suffix]
142
+
143
+ if isinstance(prop.value_type, MultiValueTypeInfo):
144
+ for i, value_type in enumerate(prop.value_type.types):
145
+ if isinstance(value_type, ClassEntity) and value_type.suffix in new_by_old_class_suffix:
146
+ prop.value_type.types[i].suffix = new_by_old_class_suffix[value_type.suffix] # type: ignore[union-attr]
147
+
148
+ return ReadRules(rules=copy, read_context=rules.read_context)
149
+
150
+ @cached_property
151
+ def _reserved_class_words(self) -> set[str]:
152
+ return set(get_reserved_words("class"))
153
+
154
+ @cached_property
155
+ def _reserved_property_words(self) -> set[str]:
156
+ return set(get_reserved_words("property"))
157
+
158
+ def _fix_cls_suffix(self, suffix: str) -> str:
159
+ if suffix in self._reserved_class_words:
160
+ return f"My{suffix}"
161
+ suffix = urllib.parse.unquote(suffix)
162
+ suffix = NamingStandardization.standardize_class_str(suffix)
163
+ if len(suffix) > 252:
164
+ suffix = suffix[:252]
165
+ return suffix
166
+
167
+ def _fix_property(self, property_: str) -> str:
168
+ if property_ in self._reserved_property_words:
169
+ return f"my{property_}"
170
+ property_ = urllib.parse.unquote(property_)
171
+ property_ = NamingStandardization.standardize_property_str(property_)
172
+ if len(property_) > 252:
173
+ property_ = property_[:252]
174
+ return property_
175
+
176
+
74
177
  class ToCompliantEntities(VerifiedRulesTransformer[InformationRules, InformationRules]): # type: ignore[misc]
75
178
  """Converts input rules to rules with compliant entity IDs that match regex patters used
76
179
  by DMS schema components."""
@@ -246,6 +349,116 @@ class PrefixEntities(ConversionTransformer): # type: ignore[type-var]
246
349
  return entity
247
350
 
248
351
 
352
+ class StandardizeNaming(ConversionTransformer):
353
+ """Sets views/classes/container names to PascalCase and properties to camelCase."""
354
+
355
+ @property
356
+ def description(self) -> str:
357
+ return "Sets views/classes/containers names to PascalCase and properties to camelCase."
358
+
359
+ @overload
360
+ def transform(self, rules: DMSRules) -> DMSRules: ...
361
+
362
+ @overload
363
+ def transform(self, rules: InformationRules) -> InformationRules: ...
364
+
365
+ def transform(self, rules: InformationRules | DMSRules) -> InformationRules | DMSRules:
366
+ output = rules.model_copy(deep=True)
367
+ if isinstance(output, InformationRules):
368
+ return self._standardize_information_rules(output)
369
+ elif isinstance(output, DMSRules):
370
+ return self._standardize_dms_rules(output)
371
+ raise NeatValueError(f"Unsupported rules type: {type(output)}")
372
+
373
+ def _standardize_information_rules(self, rules: InformationRules) -> InformationRules:
374
+ new_by_old_class_suffix: dict[str, str] = {}
375
+ for cls in rules.classes:
376
+ new_suffix = NamingStandardization.standardize_class_str(cls.class_.suffix)
377
+ new_by_old_class_suffix[cls.class_.suffix] = new_suffix
378
+ cls.class_.suffix = new_suffix
379
+
380
+ for cls in rules.classes:
381
+ if cls.implements:
382
+ for i, parent in enumerate(cls.implements):
383
+ if parent.suffix in new_by_old_class_suffix:
384
+ cls.implements[i].suffix = new_by_old_class_suffix[parent.suffix]
385
+
386
+ for prop in rules.properties:
387
+ prop.property_ = NamingStandardization.standardize_property_str(prop.property_)
388
+ if prop.class_.suffix in new_by_old_class_suffix:
389
+ prop.class_.suffix = new_by_old_class_suffix[prop.class_.suffix]
390
+
391
+ if isinstance(prop.value_type, ClassEntity) and prop.value_type.suffix in new_by_old_class_suffix:
392
+ prop.value_type.suffix = new_by_old_class_suffix[prop.value_type.suffix]
393
+
394
+ if isinstance(prop.value_type, MultiValueTypeInfo):
395
+ for i, value_type in enumerate(prop.value_type.types):
396
+ if isinstance(value_type, ClassEntity) and value_type.suffix in new_by_old_class_suffix:
397
+ prop.value_type.types[i].suffix = new_by_old_class_suffix[value_type.suffix] # type: ignore[union-attr]
398
+
399
+ return rules
400
+
401
+ def _standardize_dms_rules(self, rules: DMSRules) -> DMSRules:
402
+ new_by_old_view: dict[str, str] = {}
403
+ for view in rules.views:
404
+ new_suffix = NamingStandardization.standardize_class_str(view.view.suffix)
405
+ new_by_old_view[view.view.suffix] = new_suffix
406
+ view.view.suffix = new_suffix
407
+ new_by_old_container: dict[str, str] = {}
408
+ if rules.containers:
409
+ for container in rules.containers:
410
+ new_suffix = NamingStandardization.standardize_class_str(container.container.suffix)
411
+ new_by_old_container[container.container.suffix] = new_suffix
412
+ container.container.suffix = new_suffix
413
+
414
+ for view in rules.views:
415
+ if view.implements:
416
+ for i, parent in enumerate(view.implements):
417
+ if parent.suffix in new_by_old_view:
418
+ view.implements[i].suffix = new_by_old_view[parent.suffix]
419
+ if view.filter_ and isinstance(view.filter_, HasDataFilter) and view.filter_.inner:
420
+ for i, item in enumerate(view.filter_.inner):
421
+ if isinstance(item, ContainerEntity) and item.suffix in new_by_old_container:
422
+ view.filter_.inner[i].suffix = new_by_old_container[item.suffix]
423
+ if isinstance(item, ViewEntity) and item.suffix in new_by_old_view:
424
+ view.filter_.inner[i].suffix = new_by_old_view[item.suffix]
425
+ if rules.containers:
426
+ for container in rules.containers:
427
+ if container.constraint:
428
+ for i, constraint in enumerate(container.constraint):
429
+ if constraint.suffix in new_by_old_container:
430
+ container.constraint[i].suffix = new_by_old_container[constraint.suffix]
431
+ new_property_by_view_by_old_property: dict[ViewEntity, dict[str, str]] = defaultdict(dict)
432
+ for prop in rules.properties:
433
+ if prop.view.suffix in new_by_old_view:
434
+ prop.view.suffix = new_by_old_view[prop.view.suffix]
435
+ new_view_property = NamingStandardization.standardize_property_str(prop.view_property)
436
+ new_property_by_view_by_old_property[prop.view][prop.view_property] = new_view_property
437
+ prop.view_property = new_view_property
438
+ if isinstance(prop.value_type, ViewEntity) and prop.value_type.suffix in new_by_old_view:
439
+ prop.value_type.suffix = new_by_old_view[prop.value_type.suffix]
440
+ if (
441
+ isinstance(prop.connection, EdgeEntity)
442
+ and prop.connection.properties
443
+ and prop.connection.properties.suffix in new_by_old_view
444
+ ):
445
+ prop.connection.properties.suffix = new_by_old_view[prop.connection.properties.suffix]
446
+ if isinstance(prop.container, ContainerEntity) and prop.container.suffix in new_by_old_container:
447
+ prop.container.suffix = new_by_old_container[prop.container.suffix]
448
+ if prop.container_property:
449
+ prop.container_property = NamingStandardization.standardize_property_str(prop.container_property)
450
+ for prop in rules.properties:
451
+ if (
452
+ isinstance(prop.connection, ReverseConnectionEntity)
453
+ and isinstance(prop.value_type, ViewEntity)
454
+ and prop.value_type in new_property_by_view_by_old_property
455
+ ):
456
+ new_by_old_property = new_property_by_view_by_old_property[prop.value_type]
457
+ if prop.connection.property_ in new_by_old_property:
458
+ prop.connection.property_ = new_by_old_property[prop.connection.property_]
459
+ return rules
460
+
461
+
249
462
  class InformationToDMS(ConversionTransformer[InformationRules, DMSRules]):
250
463
  """Converts InformationRules to DMSRules."""
251
464
 
@@ -339,13 +552,6 @@ class ToEnterpriseModel(ToExtensionModel):
339
552
  self.move_connections = move_connections
340
553
 
341
554
  def transform(self, rules: DMSRules) -> DMSRules:
342
- reference_model_id = rules.metadata.as_data_model_id()
343
- if reference_model_id not in COGNITE_MODELS:
344
- warnings.warn(
345
- EnterpriseModelNotBuildOnTopOfCDMWarning(reference_model_id=reference_model_id).as_message(),
346
- stacklevel=2,
347
- )
348
-
349
555
  return self._to_enterprise(rules)
350
556
 
351
557
  def _to_enterprise(self, reference_model: DMSRules) -> DMSRules:
@@ -369,9 +575,23 @@ class ToEnterpriseModel(ToExtensionModel):
369
575
 
370
576
  # ... however, we do not want to keep the reference containers and properties
371
577
  # these we are getting for free through the implements.
578
+ enterprise_containers.sort(key=lambda container: (container.container.space, container.container.external_id))
372
579
  enterprise_model.containers = enterprise_containers
580
+ enterprise_properties.sort(
581
+ key=lambda prop: (prop.view.space, prop.view.external_id, prop.view.version, prop.view_property)
582
+ )
373
583
  enterprise_model.properties = enterprise_properties
374
584
 
585
+ # Sorting all your views first.
586
+ enterprise_model.views.sort(
587
+ key=lambda view: (
588
+ # Sorting your views first
589
+ int(view.view.space != self.new_model_id.space),
590
+ view.view.space,
591
+ view.view.external_id,
592
+ view.view.version,
593
+ )
594
+ )
375
595
  return enterprise_model
376
596
 
377
597
  @staticmethod
@@ -565,12 +785,10 @@ class ToSolutionModel(ToExtensionModel):
565
785
  renaming: dict[ViewEntity, ViewEntity] = {}
566
786
  new_views = SheetList[DMSView]()
567
787
  read_view_by_new_view: dict[ViewEntity, ViewEntity] = {}
568
- skipped_views: set[ViewEntity] = set()
569
788
  for ref_view in reference.views:
570
789
  if (self.skip_cognite_views and ref_view.view.space in COGNITE_SPACES) or (
571
790
  self.exclude_views_in_other_spaces and ref_view.view.space != reference.metadata.space
572
791
  ):
573
- skipped_views.add(ref_view.view)
574
792
  continue
575
793
  new_entity = ViewEntity(
576
794
  # MyPy we validate that version is string in the constructor
@@ -603,15 +821,17 @@ class ToSolutionModel(ToExtensionModel):
603
821
  new_views.append(ref_view.model_copy(deep=True, update={"implements": None, "view": new_entity}))
604
822
 
605
823
  new_properties = SheetList[DMSProperty]()
824
+ new_view_entities = {view.view for view in new_views}
606
825
  for prop in reference.properties:
607
- if prop.view in skipped_views:
608
- continue
609
826
  new_property = prop.model_copy(deep=True)
610
827
  if new_property.value_type in renaming and isinstance(new_property.value_type, ViewEntity):
611
828
  new_property.value_type = renaming[new_property.value_type]
612
829
  if new_property.view in renaming:
613
830
  new_property.view = renaming[new_property.view]
614
- new_properties.append(new_property)
831
+ if new_property.view in new_view_entities and (
832
+ not isinstance(new_property.value_type, ViewEntity) or new_property.value_type in new_view_entities
833
+ ):
834
+ new_properties.append(new_property)
615
835
  return new_views, new_properties, read_view_by_new_view
616
836
 
617
837
  def _create_containers_update_view_filter(
@@ -16,6 +16,7 @@ from cognite.neat._rules.transformers import (
16
16
  InformationToDMS,
17
17
  MergeDMSRules,
18
18
  MergeInformationRules,
19
+ ToInformationCompliantEntities,
19
20
  VerifyInformationRules,
20
21
  )
21
22
  from cognite.neat._store._rules_store import RulesEntity
@@ -235,13 +236,19 @@ class NeatSession:
235
236
 
236
237
  def action() -> tuple[InformationRules, DMSRules | None]:
237
238
  unverified_information = importer.to_rules()
239
+ unverified_information = ToInformationCompliantEntities(renaming="warning").transform(
240
+ unverified_information
241
+ )
242
+
238
243
  extra_info = VerifyInformationRules().transform(unverified_information)
239
244
  if not last_entity:
240
245
  return extra_info, None
241
246
  merged_info = MergeInformationRules(extra_info).transform(last_entity.information)
242
247
  if not last_entity.dms:
243
248
  return merged_info, None
249
+
244
250
  extra_dms = InformationToDMS(reserved_properties="warning").transform(extra_info)
251
+
245
252
  merged_dms = MergeDMSRules(extra_dms).transform(last_entity.dms)
246
253
  return merged_info, merged_dms
247
254
 
@@ -3,6 +3,7 @@ from typing import Literal
3
3
  from cognite.client.data_classes.data_modeling import DataModelIdentifier
4
4
 
5
5
  from cognite.neat._issues import IssueList
6
+ from cognite.neat._rules.models import DMSRules, InformationRules
6
7
  from cognite.neat._rules.models.dms import DMSValidation
7
8
  from cognite.neat._rules.transformers import (
8
9
  IncludeReferenced,
@@ -38,22 +39,27 @@ class CreateAPI:
38
39
  org_name: Organization name to use for the views in the enterprise data model.
39
40
  dummy_property: The dummy property to use as placeholder for the views in the new data model.
40
41
 
42
+ What does this function do?
43
+ 1. It creates a new view for each view in the current data model that implements the view it is based on.
44
+ 2. If dummy_property is set, it will create a container with one property for each view and connect the
45
+ view to the container.
46
+ 3. It will repeat all connection properties in the new views and update the ValueTypes to match the new
47
+ views.
48
+
41
49
  !!! note "Enterprise Data Model Creation"
42
50
 
43
51
  Always create an enterprise data model from a Cognite Data Model as this will
44
52
  assure all the Cognite Data Fusion applications to run smoothly, such as
45
53
  - Search
46
54
  - Atlas AI
47
- - ...
48
-
49
- !!! note "Move Connections"
50
-
51
- If you want to move the connections to the new data model, set the move_connections
52
- to True. This will move the connections to the new data model and use new model
53
- views as the source and target views.
55
+ - Infield
56
+ - Canvas
57
+ - Maintain
58
+ - Charts
54
59
 
55
60
  """
56
- return self._state.rule_transform(
61
+ last_rules = self._get_last_rules()
62
+ issues = self._state.rule_transform(
57
63
  ToEnterpriseModel(
58
64
  new_model_id=data_model_id,
59
65
  org_name=org_name,
@@ -61,6 +67,15 @@ class CreateAPI:
61
67
  move_connections=True,
62
68
  )
63
69
  )
70
+ if last_rules and not issues.has_errors:
71
+ self._state.last_reference = last_rules
72
+ return issues
73
+
74
+ def _get_last_rules(self) -> InformationRules | DMSRules | None:
75
+ if not self._state.rule_store.provenance:
76
+ return None
77
+ last_entity = self._state.rule_store.provenance[-1].target_entity
78
+ return last_entity.dms or last_entity.information
64
79
 
65
80
  def solution_model(
66
81
  self,
@@ -76,6 +91,13 @@ class CreateAPI:
76
91
  and the enterprise data model.
77
92
  view_prefix: The prefix to use for the views in the enterprise data model.
78
93
 
94
+ What does this function do?
95
+ 1. It will create two new views for each view in the current data model. The first view will be read-only and
96
+ prefixed with the 'view_prefix'. The second view will be writable and have one property that connects to the
97
+ read-only view named 'direct_property'.
98
+ 2. It will repeat all connection properties in the new views and update the ValueTypes to match the new views.
99
+ 3. Each writable view will have a container with the single property that connects to the read-only view.
100
+
79
101
  !!! note "Solution Data Model Mode"
80
102
 
81
103
  The read-only solution model will only be able to read from the existing containers
@@ -88,7 +110,8 @@ class CreateAPI:
88
110
  the containers in the solution data model space.
89
111
 
90
112
  """
91
- return self._state.rule_transform(
113
+ last_rules = self._get_last_rules()
114
+ issues = self._state.rule_transform(
92
115
  ToSolutionModel(
93
116
  new_model_id=data_model_id,
94
117
  properties="connection",
@@ -96,24 +119,32 @@ class CreateAPI:
96
119
  view_prefix=view_prefix,
97
120
  )
98
121
  )
122
+ if last_rules and not issues.has_errors:
123
+ self._state.last_reference = last_rules
124
+ return issues
99
125
 
100
126
  def data_product_model(
101
127
  self,
102
128
  data_model_id: DataModelIdentifier,
103
129
  include: Literal["same-space", "all"] = "same-space",
104
- ) -> None:
130
+ ) -> IssueList:
105
131
  """Uses the current data model as a basis to create data product data model.
106
132
 
107
133
  A data product model is a data model that ONLY maps to containers and do not use implements. This is
108
134
  typically used for defining the data in a data product.
109
135
 
136
+ What does this function do?
137
+ 1. It creates a new view for each view in the current data model. The new views uses the same filter
138
+ as the view it is based on.
139
+ 2. It will repeat all connection properties in the new views and update the ValueTypes to match the new views.
140
+
110
141
  Args:
111
142
  data_model_id: The data product data model id that is being created.
112
143
  include: The views to include in the data product data model. Can be either "same-space" or "all".
113
144
  If you set same-space, only the properties of the views in the same space as the data model
114
145
  will be included.
115
146
  """
116
-
147
+ last_rules = self._get_last_rules()
117
148
  view_ids, container_ids = DMSValidation(
118
149
  self._state.rule_store.last_verified_dms_rules
119
150
  ).imported_views_and_containers_ids()
@@ -130,4 +161,7 @@ class CreateAPI:
130
161
 
131
162
  transformers.append(ToDataProductModel(new_model_id=data_model_id, include=include))
132
163
 
133
- self._state.rule_transform(*transformers)
164
+ issues = self._state.rule_transform(*transformers)
165
+ if last_rules and not issues.has_errors:
166
+ self._state.last_reference = last_rules
167
+ return issues
@@ -3,6 +3,7 @@ from typing import Any
3
3
 
4
4
  from rdflib import URIRef
5
5
 
6
+ from cognite.neat._alpha import AlphaFlags
6
7
  from cognite.neat._graph.transformers import (
7
8
  ConnectionToLiteral,
8
9
  ConvertLiteral,
@@ -12,9 +13,7 @@ from cognite.neat._graph.transformers import (
12
13
  from cognite.neat._graph.transformers._rdfpath import MakeConnectionOnExactMatch
13
14
  from cognite.neat._issues import IssueList
14
15
  from cognite.neat._issues.errors import NeatValueError
15
- from cognite.neat._rules.transformers import (
16
- PrefixEntities,
17
- )
16
+ from cognite.neat._rules.transformers import PrefixEntities, StandardizeNaming
18
17
  from cognite.neat._utils.text import humanize_collection
19
18
 
20
19
  from ._state import SessionState
@@ -264,3 +263,12 @@ class DataModelPrepareAPI:
264
263
  """
265
264
 
266
265
  return self._state.rule_transform(PrefixEntities(prefix)) # type: ignore[arg-type]
266
+
267
+ def standardize_naming(self) -> IssueList:
268
+ """Standardize the naming of all views/classes/properties in the data model.
269
+
270
+ For classes/views/containers, the naming will be standardized to PascalCase.
271
+ For properties, the naming will be standardized to camelCase.
272
+ """
273
+ AlphaFlags.standardize_naming.warn()
274
+ return self._state.rule_transform(StandardizeNaming())
@@ -78,7 +78,7 @@ class InstancesState:
78
78
  self.outcome = UploadResultList()
79
79
 
80
80
  # Ensure that error handling is done in the constructor
81
- self.store = _session_method_wrapper(self._create_store, "NeatSession")()
81
+ self.store: NeatGraphStore = _session_method_wrapper(self._create_store, "NeatSession")()
82
82
 
83
83
  if self.storage_path:
84
84
  print("Remember to close neat session .close() once you are done to avoid oxigraph lock.")
@@ -46,9 +46,9 @@ class ToAPI:
46
46
  include_reference: If True, the reference data model will be included. Defaults to True.
47
47
  Note that this only applies if you have created the data model using the
48
48
  create.enterprise_model(...), create.solution_model(), or create.data_product_model() methods.
49
- include_properties: The properties to include in the Excel file. Defaults to "all".
50
- - "same-space": Only properties that are in the same space as the data model will be included.
51
- add_empty_rows: If True, empty rows will be added between each component. Defaults to False.
49
+ include_properties: The properties to include in the Excel file. Defaults to "all".
50
+ - "same-space": Only properties that are in the same space as the data model will be included.
51
+ add_empty_rows: If True, empty rows will be added between each component. Defaults to False.
52
52
 
53
53
  Example:
54
54
  Export information model to excel rules sheet
@@ -17,21 +17,23 @@ def to_camel(string: str) -> str:
17
17
  >>> to_camel("ScenarioInstance_priceForecast")
18
18
  'scenarioInstancePriceForecast'
19
19
  """
20
+ string = re.sub(r"[\s_-]", "_", string)
21
+ string = re.sub("_+", "_", string)
20
22
  if "_" in string:
21
- # Could be a combination of snake and pascal/camel case
22
- parts = string.split("_")
23
- pascal_splits = [to_pascal(subpart) for part in parts for subpart in part.split("-") if subpart]
24
- elif "-" in string:
25
- # Could be a combination of kebab and pascal/camel case
26
- parts = string.split("-")
27
- pascal_splits = [to_pascal(subpart) for part in parts for subpart in part.split("_") if subpart]
23
+ pascal_splits = [to_pascal(part) for part in string.split("_")]
28
24
  else:
29
- # Assume is pascal/camel case
30
25
  # Ensure pascal
31
26
  string = string[0].upper() + string[1:]
32
27
  pascal_splits = [string]
33
- string_split = []
28
+ cleaned: list[str] = []
34
29
  for part in pascal_splits:
30
+ if part.upper() == part:
31
+ cleaned.append(part.capitalize())
32
+ else:
33
+ cleaned.append(part)
34
+
35
+ string_split = []
36
+ for part in cleaned:
35
37
  string_split.extend(re.findall(r"[A-Z][a-z0-9]*", part))
36
38
  if not string_split:
37
39
  string_split = [string]
@@ -135,3 +137,32 @@ def humanize_collection(collection: Collection[Any], /, *, sort: bool = True) ->
135
137
  sequence = list(strings)
136
138
 
137
139
  return f"{', '.join(sequence[:-1])} and {sequence[-1]}"
140
+
141
+
142
+ class NamingStandardization:
143
+ _clean_pattern = re.compile(r"[^a-zA-Z0-9_]+")
144
+ _multi_underscore_pattern = re.compile(r"_+")
145
+ _start_letter_pattern = re.compile(r"^[a-zA-Z]")
146
+
147
+ @classmethod
148
+ def standardize_class_str(cls, raw: str) -> str:
149
+ clean = cls._clean_string(raw)
150
+ if not cls._start_letter_pattern.match(clean):
151
+ # Underscore ensure that 'Class' it treated as a separate word
152
+ # in the to_pascale function
153
+ clean = f"Class_{clean}"
154
+ return to_pascal(clean)
155
+
156
+ @classmethod
157
+ def standardize_property_str(cls, raw: str) -> str:
158
+ clean = cls._clean_string(raw)
159
+ if not cls._start_letter_pattern.match(clean):
160
+ # Underscore ensure that 'property' it treated as a separate word
161
+ # in the to_camel function
162
+ clean = f"property_{clean}"
163
+ return to_camel(clean)
164
+
165
+ @classmethod
166
+ def _clean_string(cls, raw: str) -> str:
167
+ raw = cls._clean_pattern.sub("_", raw)
168
+ return cls._multi_underscore_pattern.sub("_", raw)
cognite/neat/_version.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.109.2"
1
+ __version__ = "0.109.4"
2
2
  __engine__ = "^2.0.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cognite-neat
3
- Version: 0.109.2
3
+ Version: 0.109.4
4
4
  Summary: Knowledge graph transformation
5
5
  License: Apache-2.0
6
6
  Author: Nikola Vasiljevic
@@ -1,5 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=AXLMIF5t8RmjOpSaIlfqT8ltbPc__Tb8nAVbc1pkE0Q,176
2
- cognite/neat/_alpha.py,sha256=v-PRdeEikqmm4lHxGAQUOkC7WsySf5X3RPIHQwr2j2o,423
2
+ cognite/neat/_alpha.py,sha256=MXVWltw8esJPFNbLIt74_Xib7z-PXWJOBrTCxK-Z2Qs,483
3
3
  cognite/neat/_client/__init__.py,sha256=RQ7MwL8mwGqGHokRzsPqO3XStDzmI4-c12_gz1UPJ74,177
4
4
  cognite/neat/_client/_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  cognite/neat/_client/_api/data_modeling_loaders.py,sha256=0BZ5_NeszBvs4DHuHr0cGXytmvkTxrQ8k7v6iaX_mn4,35932
@@ -9,7 +9,7 @@ cognite/neat/_client/data_classes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
9
9
  cognite/neat/_client/data_classes/data_modeling.py,sha256=RvpUp9ygd-yffQFJ7O2mQqMLDPIa-dmip5zPb8QVIiw,6672
10
10
  cognite/neat/_client/data_classes/neat_sequence.py,sha256=QZWSfWnwk6KlYJvsInco4Wdwc1U8DnOQKWmHebArbQY,10830
11
11
  cognite/neat/_client/data_classes/schema.py,sha256=uD8ExxEiIP3zhK4b--Q5fND-vmcC05a9WU5ItLsqG88,23179
12
- cognite/neat/_client/testing.py,sha256=c5ADJkRJFYGlJVQz-uPqxKExKXT297pxKh_ka4oGBjs,1082
12
+ cognite/neat/_client/testing.py,sha256=M5WUzi3YbtZAN22TXas0SjvMSxmjPFT5m3lNwR0MVkI,1077
13
13
  cognite/neat/_config.py,sha256=WT1BS8uADcFvGoUYOOfwFOVq_VBl472TisdoA3wLick,280
14
14
  cognite/neat/_constants.py,sha256=OStolbvP16sM9LltZG6irG27W1tioNGwVr84-Hv6Ovk,5752
15
15
  cognite/neat/_graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -55,13 +55,13 @@ cognite/neat/_graph/transformers/_prune_graph.py,sha256=X9wVOBl3mhcPB-SSqWvJHV1h
55
55
  cognite/neat/_graph/transformers/_rdfpath.py,sha256=KLNnuLmeMCobJ6Q81l5kUqUt_fkKAovzuiy9zO97sTI,5003
56
56
  cognite/neat/_graph/transformers/_value_type.py,sha256=wsuiIKHG6O8F7PaedicMfynTxiVpLZ55zwVU3mCDEYw,15679
57
57
  cognite/neat/_issues/__init__.py,sha256=OVgWivp_Br31p8zPeHjOEXs-Wj7lJU1pU1L3pfhfuqE,518
58
- cognite/neat/_issues/_base.py,sha256=vV0E8cfXKlOnRXFIDZKg5QPMruELEbCaUfQ01l5dR9A,20073
59
- cognite/neat/_issues/errors/__init__.py,sha256=kwEuvUo9ihxDiONOjlpyLtFTfqviaXR97OJJS-176fs,2282
58
+ cognite/neat/_issues/_base.py,sha256=fZODjRPyiZjVj1ujqKVQOrTh8SaliR9ayG0zRRmyptc,21044
59
+ cognite/neat/_issues/errors/__init__.py,sha256=05PgHRvAwMX9FPRLyAhzOvSXL-JCOs1SqtJGUB975fk,2046
60
60
  cognite/neat/_issues/errors/_external.py,sha256=KrF0_ubXlyd903ARUFZlBI45Y-I3q2FS8vSWb9t4WBI,2043
61
- cognite/neat/_issues/errors/_general.py,sha256=zwEoaygHA2Nt9vCwiveDuzJsqZYIaX0BoUYkwJkQ4jU,844
61
+ cognite/neat/_issues/errors/_general.py,sha256=MnBm9V1S-Agr9kUfTMvviBB4_NkuOXamlgJCu2UojEM,849
62
62
  cognite/neat/_issues/errors/_properties.py,sha256=T_nquQeEQS3DQY--DQ13acxhGX_-gpUMjWGTBKCh6r8,2536
63
63
  cognite/neat/_issues/errors/_resources.py,sha256=qndhGGPdulWpU7G4PkFHsF4Bjflf3lehGV4A0l-BeNI,3966
64
- cognite/neat/_issues/errors/_workflow.py,sha256=m_Hlsvl5A1Oy7P3IROnz-4_do8_orZ1Pr1IHqsMyEys,971
64
+ cognite/neat/_issues/errors/_wrapper.py,sha256=e7Zrj7YkzjwS_T880Bh4r0gwstCjU8256_-2A5MA-iA,239
65
65
  cognite/neat/_issues/formatters.py,sha256=ziNWT_YXwovTfU8Av5iYuSLgszzJYKTawM_z67VBdlU,3331
66
66
  cognite/neat/_issues/warnings/__init__.py,sha256=1iYRzwaMftwcueiA8KMA_bGdxdWxmSfMRqUPeTmHV_w,2909
67
67
  cognite/neat/_issues/warnings/_external.py,sha256=O5GSRmIsAC6HyToQ7itpFFNILWCAg0OehPTVUy8pTuc,1319
@@ -108,19 +108,19 @@ cognite/neat/_rules/importers/_yaml2rules.py,sha256=k2oDgz--mxFVeyqQG3uvyYcb0BkF
108
108
  cognite/neat/_rules/models/__init__.py,sha256=tf6tyWiYO0eC2PHCcpy208dwOHjEi9hg0sEaQLcB3uA,1024
109
109
  cognite/neat/_rules/models/_base_input.py,sha256=kAVI6QYAzsgQ79eLPc_hANJjl-o1e9IKeD7lF61ru9Y,6656
110
110
  cognite/neat/_rules/models/_base_rules.py,sha256=TpDhGaodzfzfyrSGChx76Tw_zN2YvtxqFz2lvOMHFPE,15134
111
- cognite/neat/_rules/models/_rdfpath.py,sha256=hqUMZCMeI8ESdJltu7FifuUhna5JNN_Heup2aYkV56Y,11882
112
- cognite/neat/_rules/models/_types.py,sha256=gGRS8qK3Z-2buBmYtaVj8762qPNz_ToYAChZYCm8eyE,5381
111
+ cognite/neat/_rules/models/_rdfpath.py,sha256=clZGAzulIFKV2rzwoVZpLRlQUEyrAaiM7SjOoj1KUnY,11948
112
+ cognite/neat/_rules/models/_types.py,sha256=6fHLiVPRlk5QsFjmvDFFqw1htPcPp-RfnUzYoMWNfeg,5418
113
113
  cognite/neat/_rules/models/data_types.py,sha256=LJuWUbStlZM4hUJGExOJIJXmAA4uiA0tvO9zKqLUrQg,9805
114
114
  cognite/neat/_rules/models/dms/__init__.py,sha256=fRaUH0IwG0YwWd_DNKM6j-jHHFyiIVz4_8DPiS1KR0Y,695
115
115
  cognite/neat/_rules/models/dms/_exporter.py,sha256=WFDhkFeY2kJCguru-btcvjDcbMG5ISIAQzIUtDjIlxQ,28097
116
- cognite/neat/_rules/models/dms/_rules.py,sha256=GhmVmm9vV-CR3uEPzdA_vJbM_EjWVPN_eQmpwVZ4J6Y,22001
116
+ cognite/neat/_rules/models/dms/_rules.py,sha256=VK8QKLf6NxeoaMci5g7EG08XKzSKJZYJwP4wZxa5eUQ,20507
117
117
  cognite/neat/_rules/models/dms/_rules_input.py,sha256=noSpYjHKW_GMaeuADOG-Z21ClxiD_tIGxl-dxOKxk-g,13390
118
118
  cognite/neat/_rules/models/dms/_validation.py,sha256=PNFHsDNHJZfbHY1qt6D4t3eFOGjqe6Koa702Gzcz0I8,27734
119
119
  cognite/neat/_rules/models/entities/__init__.py,sha256=Hlucp3LyV6ncLl79aqRTbSy2qgiGzoyN8x54D_zaJCY,1469
120
- cognite/neat/_rules/models/entities/_constants.py,sha256=ToiLaaF-hGLPfn3AsKIIrfB4ZdTk4cY1RjM9gA1Qjkg,288
120
+ cognite/neat/_rules/models/entities/_constants.py,sha256=g52-pFP_Qghp86NYk76pLDM52b3T4MXMztxhpeDW3Oc,344
121
121
  cognite/neat/_rules/models/entities/_loaders.py,sha256=jFllRty5XpS6uLklr9wJkx7Bzm-qwg65um6hnVistvw,2728
122
122
  cognite/neat/_rules/models/entities/_multi_value.py,sha256=6j-nlUA392yQP_uB_CFB6_qFReNhi54ZlbFTcOKpbKY,2755
123
- cognite/neat/_rules/models/entities/_single_value.py,sha256=GW1R8ko1vwjGhAs2Fl5BGgpz3qtiYnYoahT4hBgJdkA,19441
123
+ cognite/neat/_rules/models/entities/_single_value.py,sha256=XZA4rqXzL-97bsCEAz1imUtn4lir_ikiBfs5LVtziSI,19604
124
124
  cognite/neat/_rules/models/entities/_types.py,sha256=df9rnXJJKciv2Bp-Ve2q4xdEJt6WWniq12Z0hW2d6sk,1917
125
125
  cognite/neat/_rules/models/entities/_wrapped.py,sha256=SpXIpglXBgaZt6geRparCWdY2F6SH0Fy99fcnA8x8Uk,7648
126
126
  cognite/neat/_rules/models/information/__init__.py,sha256=lV7l8RTfWBvN_DFkzea8OzADjq0rZGgWe_0Fiwtfje0,556
@@ -130,25 +130,25 @@ cognite/neat/_rules/models/information/_validation.py,sha256=HbaLShj6uumu-t9I3FU
130
130
  cognite/neat/_rules/models/mapping/__init__.py,sha256=T68Hf7rhiXa7b03h4RMwarAmkGnB-Bbhc1H07b2PyC4,100
131
131
  cognite/neat/_rules/models/mapping/_classic2core.py,sha256=AhLWoXk4PlBVA6SgBtY9dcLcpqEMaYcsiEatI19miPk,1211
132
132
  cognite/neat/_rules/models/mapping/_classic2core.yaml,sha256=jodkmcTborWJmG3At16OChtnol696X6D4lgAa7aaQ78,20491
133
- cognite/neat/_rules/transformers/__init__.py,sha256=RdpV98nHB_fmkJ8MPKuWuQ5AzSc67oHB5d03D0MBFHk,1291
133
+ cognite/neat/_rules/transformers/__init__.py,sha256=S6iUB7wpAh-7uF5k_EgXT4cGfi8ok5lczGemVxgKckQ,1413
134
134
  cognite/neat/_rules/transformers/_base.py,sha256=9LnjKbYIf9238PQXedkTZsMXAyEND6glUD187iEaHfc,2783
135
- cognite/neat/_rules/transformers/_converters.py,sha256=dmw48thBYHFAaFmT9saD53PSBcTtjXDhKpKzVSlFJgk,66886
135
+ cognite/neat/_rules/transformers/_converters.py,sha256=8rQ_cPghbvuBGIHfXtQE_COpFwPy-60dgyLnSTUUINs,78242
136
136
  cognite/neat/_rules/transformers/_mapping.py,sha256=lf-RKN__5Bg3-tZjEOCa1Sf_JtM_ScQ_TYcnciEnaYQ,18189
137
137
  cognite/neat/_rules/transformers/_verification.py,sha256=jKTppklUCVwVlRfYyMfnUtV8r2ACTY-AtsoMF6L-KXo,4593
138
138
  cognite/neat/_session/__init__.py,sha256=fxQ5URVlUnmEGYyB8Baw7IDq-uYacqkigbc4b-Pr9Fw,58
139
- cognite/neat/_session/_base.py,sha256=WEAvz1b9Q8LsPOozLEUEOWLh9XWn1EtxwHw774qLi3E,11744
139
+ cognite/neat/_session/_base.py,sha256=lNCjSiQIn1hI3zSru1hXGTQ0nVzy3CcXTO7vL4XRzno,11935
140
140
  cognite/neat/_session/_collector.py,sha256=RcOGY0DjTCCKJt9j_p0gnQXn4omhsIX2G8Aq3ZFHIt4,4218
141
- cognite/neat/_session/_create.py,sha256=NATzf_5u2aYKXiBmDfNCrcdzQjQsE71XPi--f_iNGcE,5239
141
+ cognite/neat/_session/_create.py,sha256=doDCbDIWMbHCYe3cyk1obQaFdYJjvARg3X4lRUVicCk,7214
142
142
  cognite/neat/_session/_drop.py,sha256=gOkDAnddASpFxYxkPjlTyhkpNfnmDEj94GRI8tnHFR0,4167
143
143
  cognite/neat/_session/_fix.py,sha256=gpmbJ4TbB_v2nw4fEA2Qtf0ifO3UDEMHGdztha28S_U,898
144
144
  cognite/neat/_session/_inspect.py,sha256=VkmDfIjh49NPS8wNa1OdWqhW-oJy1EvMDhKiooMvcjI,9040
145
145
  cognite/neat/_session/_mapping.py,sha256=AkQwmqYH-0EgqoXHqCFwJY92hNSGzfojOelhVFlqH4c,2655
146
- cognite/neat/_session/_prepare.py,sha256=WoVp2Go2RnVaL2RvnCE2-X1kS4ZWxvupOlOmWTce0i0,11626
146
+ cognite/neat/_session/_prepare.py,sha256=fozBTqF8hUTfOf4wzDDulc0Fi5bDfO1C1EeBU0Bii3Q,12088
147
147
  cognite/neat/_session/_read.py,sha256=V2F7InkvIODdML7jy4aGXH4PE91tWFzIGcNzOWnsTvM,22514
148
148
  cognite/neat/_session/_set.py,sha256=ZYR5G97fi-y68edbG-ftLqM_FRtn9G8V5zDtpslR9go,4070
149
149
  cognite/neat/_session/_show.py,sha256=3hUMWBlHCyMiVTbX1GmpVFfthpv3yBIZ293aU0mDxZ8,15156
150
- cognite/neat/_session/_state.py,sha256=gDyt2gf2kuiHu42zDMO-Pg0zjRllEKC4O8lJ0pMGCkU,4059
151
- cognite/neat/_session/_to.py,sha256=w7ukhG0cjkKlMPhhivaL5Poq4W_-p8ZEWQSWuVnQGiw,11670
150
+ cognite/neat/_session/_state.py,sha256=udCCS5tWA2z7P1YK65iyC-weDwgQg07P_MS3TLjJC90,4075
151
+ cognite/neat/_session/_to.py,sha256=Wls8Obfx4Aodgyf_8jq7HxPt8JSHoFQ-DhJIfVoe3JU,11682
152
152
  cognite/neat/_session/_wizard.py,sha256=9idlzhZy54h2Iwupe9iXKX3RDb5jJQuBZFEouni50L0,1476
153
153
  cognite/neat/_session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4dvca6n48bu_bI,120
154
154
  cognite/neat/_session/engine/_import.py,sha256=1QxA2_EK613lXYAHKQbZyw2yjo5P9XuiX4Z6_6-WMNQ,169
@@ -171,14 +171,14 @@ cognite/neat/_utils/rdf_.py,sha256=b3sE3aTW9lu4gJWQJEaq_NCLbI54cc1o12utz-xbLh8,9
171
171
  cognite/neat/_utils/reader/__init__.py,sha256=fPkrNB_9hLB7CyHTCFV_xEbIfOMqUQzNly5JN33-QfM,146
172
172
  cognite/neat/_utils/reader/_base.py,sha256=Q35hz8tqAiQiELjE4DsDDKQHLtRmSTrty4Gep9rg_CU,5444
173
173
  cognite/neat/_utils/spreadsheet.py,sha256=20L_44m0hg3UdBFwCxnmAravxvCOolNZwVOGHfjYGR4,3089
174
- cognite/neat/_utils/text.py,sha256=0IffvBIAmeGh92F4T6xiEdd-vv3B7FOGEMbfuTktO5Y,4017
174
+ cognite/neat/_utils/text.py,sha256=9sBlE05S_9irzRxRvafBlWWy-kPgvUPV2Xo-_IgcwHM,4944
175
175
  cognite/neat/_utils/time_.py,sha256=O30LUiDH9TdOYz8_a9pFqTtJdg8vEjC3qHCk8xZblG8,345
176
176
  cognite/neat/_utils/upload.py,sha256=iWKmsQgw4EHLv-11NjYu7zAj5LtqTAfNa87a1kWeuaU,5727
177
177
  cognite/neat/_utils/xml_.py,sha256=FQkq84u35MUsnKcL6nTMJ9ajtG9D5i1u4VBnhGqP2DQ,1710
178
- cognite/neat/_version.py,sha256=C9cGHhgqgxsB1rbURMXLvpzrANgFHbmOFq3MJSffn3w,46
178
+ cognite/neat/_version.py,sha256=QVwn7nO52-wrWZD6BCidVN8e8DGr4r7lQqTaIGS5Sds,46
179
179
  cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
180
- cognite_neat-0.109.2.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
181
- cognite_neat-0.109.2.dist-info/METADATA,sha256=lskGgPFezWb9blta8e5kdLXvTROMUKyMpVxrh8yIQvI,5361
182
- cognite_neat-0.109.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
183
- cognite_neat-0.109.2.dist-info/entry_points.txt,sha256=SsQlnl8SNMSSjE3acBI835JYFtsIinLSbVmHmMEXv6E,51
184
- cognite_neat-0.109.2.dist-info/RECORD,,
180
+ cognite_neat-0.109.4.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
181
+ cognite_neat-0.109.4.dist-info/METADATA,sha256=d9-11fBzN6fR8usoDMQUtGkUpEKjFxdjS3mTbN7TVsU,5361
182
+ cognite_neat-0.109.4.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
183
+ cognite_neat-0.109.4.dist-info/entry_points.txt,sha256=SsQlnl8SNMSSjE3acBI835JYFtsIinLSbVmHmMEXv6E,51
184
+ cognite_neat-0.109.4.dist-info/RECORD,,
@@ -1,36 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
- from cognite.neat._issues import NeatError
4
-
5
-
6
- @dataclass(unsafe_hash=True)
7
- class WorkFlowMissingDataError(NeatError, ValueError):
8
- """In the workflow step {step_name} the following data is missing: {missing_data}."""
9
-
10
- step_name: str
11
- missing_data: frozenset[str]
12
-
13
-
14
- @dataclass(unsafe_hash=True)
15
- class WorkflowStepNotInitializedError(NeatError, RuntimeError):
16
- """Step {step_name} has not been initialized."""
17
-
18
- step_name: str
19
-
20
-
21
- @dataclass(unsafe_hash=True)
22
- class WorkflowConfigurationNotSetError(NeatError, RuntimeError):
23
- """The configuration variable '{config_variable}' is not set. Please set the configuration
24
- before running the workflow."""
25
-
26
- config_variable: str
27
-
28
-
29
- @dataclass(unsafe_hash=True)
30
- class WorkflowStepOutputError(NeatError, RuntimeError):
31
- """Object type {step_type} is not supported as step output.
32
-
33
- Step output must be of type DataContract or a FlowMessage.
34
- """
35
-
36
- step_type: str