cognite-neat 0.96.6__py3-none-any.whl → 0.97.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (68) hide show
  1. cognite/neat/_constants.py +3 -1
  2. cognite/neat/_graph/extractors/__init__.py +3 -0
  3. cognite/neat/_graph/extractors/_base.py +1 -1
  4. cognite/neat/_graph/extractors/_classic_cdf/_assets.py +1 -1
  5. cognite/neat/_graph/extractors/_classic_cdf/_base.py +1 -1
  6. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +1 -1
  7. cognite/neat/_graph/extractors/_classic_cdf/_data_sets.py +1 -1
  8. cognite/neat/_graph/extractors/_classic_cdf/_events.py +1 -1
  9. cognite/neat/_graph/extractors/_classic_cdf/_files.py +1 -1
  10. cognite/neat/_graph/extractors/_classic_cdf/_labels.py +1 -1
  11. cognite/neat/_graph/extractors/_classic_cdf/_relationships.py +1 -1
  12. cognite/neat/_graph/extractors/_classic_cdf/_sequences.py +1 -1
  13. cognite/neat/_graph/extractors/_classic_cdf/_timeseries.py +1 -1
  14. cognite/neat/_graph/extractors/_dexpi.py +1 -1
  15. cognite/neat/_graph/extractors/_dms.py +1 -1
  16. cognite/neat/_graph/extractors/_iodd.py +1 -1
  17. cognite/neat/_graph/extractors/_mock_graph_generator.py +1 -1
  18. cognite/neat/_graph/extractors/_rdf_file.py +1 -1
  19. cognite/neat/_graph/loaders/_rdf2dms.py +1 -1
  20. cognite/neat/_graph/queries/_base.py +1 -1
  21. cognite/neat/_graph/transformers/__init__.py +3 -1
  22. cognite/neat/_graph/transformers/_rdfpath.py +60 -1
  23. cognite/neat/_issues/errors/__init__.py +2 -0
  24. cognite/neat/_issues/errors/_properties.py +12 -0
  25. cognite/neat/_issues/warnings/__init__.py +2 -0
  26. cognite/neat/_issues/warnings/_models.py +11 -0
  27. cognite/neat/_rules/importers/__init__.py +11 -0
  28. cognite/neat/_rules/importers/_base.py +7 -0
  29. cognite/neat/_rules/importers/_dms2rules.py +12 -3
  30. cognite/neat/_rules/importers/_rdf/_inference2rules.py +17 -2
  31. cognite/neat/_rules/models/asset/_rules.py +6 -2
  32. cognite/neat/_rules/models/asset/_rules_input.py +6 -1
  33. cognite/neat/_rules/models/data_types.py +6 -0
  34. cognite/neat/_rules/models/dms/_rules.py +8 -1
  35. cognite/neat/_rules/models/dms/_rules_input.py +8 -0
  36. cognite/neat/_rules/models/dms/_validation.py +64 -2
  37. cognite/neat/_rules/models/domain.py +10 -0
  38. cognite/neat/_rules/models/entities/_loaders.py +3 -5
  39. cognite/neat/_rules/models/information/_rules.py +6 -2
  40. cognite/neat/_rules/models/information/_rules_input.py +6 -1
  41. cognite/neat/_rules/transformers/_base.py +7 -0
  42. cognite/neat/_rules/transformers/_converters.py +56 -4
  43. cognite/neat/_session/_base.py +94 -23
  44. cognite/neat/_session/_inspect.py +12 -4
  45. cognite/neat/_session/_prepare.py +144 -21
  46. cognite/neat/_session/_read.py +137 -30
  47. cognite/neat/_session/_set.py +22 -3
  48. cognite/neat/_session/_show.py +171 -45
  49. cognite/neat/_session/_state.py +79 -30
  50. cognite/neat/_session/_to.py +16 -17
  51. cognite/neat/_session/engine/__init__.py +4 -0
  52. cognite/neat/_session/engine/_import.py +7 -0
  53. cognite/neat/_session/engine/_interface.py +24 -0
  54. cognite/neat/_session/engine/_load.py +129 -0
  55. cognite/neat/_session/exceptions.py +13 -3
  56. cognite/neat/_shared.py +6 -1
  57. cognite/neat/_store/_base.py +3 -24
  58. cognite/neat/_store/_provenance.py +185 -42
  59. cognite/neat/_utils/rdf_.py +34 -1
  60. cognite/neat/_utils/reader/__init__.py +3 -0
  61. cognite/neat/_utils/reader/_base.py +162 -0
  62. cognite/neat/_version.py +2 -1
  63. {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/METADATA +5 -3
  64. {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/RECORD +67 -62
  65. cognite/neat/_graph/models.py +0 -7
  66. {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/LICENSE +0 -0
  67. {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/WHEEL +0 -0
  68. {cognite_neat-0.96.6.dist-info → cognite_neat-0.97.0.dist-info}/entry_points.txt +0 -0
@@ -1,18 +1,20 @@
1
1
  from collections import defaultdict
2
- from typing import Any, ClassVar
2
+ from typing import Any, ClassVar, cast
3
3
 
4
4
  from cognite.client import data_modeling as dm
5
5
 
6
- from cognite.neat._constants import DMS_CONTAINER_PROPERTY_SIZE_LIMIT
6
+ from cognite.neat._constants import COGNITE_MODELS, DMS_CONTAINER_PROPERTY_SIZE_LIMIT
7
7
  from cognite.neat._issues import IssueList, NeatError, NeatIssue, NeatIssueList
8
8
  from cognite.neat._issues.errors import (
9
9
  PropertyDefinitionDuplicatedError,
10
10
  ResourceChangedError,
11
11
  ResourceNotDefinedError,
12
12
  )
13
+ from cognite.neat._issues.errors._properties import ReversedConnectionNotFeasibleError
13
14
  from cognite.neat._issues.warnings import (
14
15
  NotSupportedHasDataFilterLimitWarning,
15
16
  NotSupportedViewContainerLimitWarning,
17
+ UndefinedViewWarning,
16
18
  )
17
19
  from cognite.neat._issues.warnings.user_modeling import (
18
20
  NotNeatSupportedFilterWarning,
@@ -21,6 +23,10 @@ from cognite.neat._issues.warnings.user_modeling import (
21
23
  from cognite.neat._rules.models._base_rules import DataModelType, ExtensionCategory, SchemaCompleteness
22
24
  from cognite.neat._rules.models.data_types import DataType
23
25
  from cognite.neat._rules.models.entities import ContainerEntity, RawFilter
26
+ from cognite.neat._rules.models.entities._single_value import (
27
+ ReverseConnectionEntity,
28
+ ViewEntity,
29
+ )
24
30
 
25
31
  from ._rules import DMSProperty, DMSRules
26
32
  from ._schema import DMSSchema
@@ -45,6 +51,8 @@ class DMSPostValidation:
45
51
  def validate(self) -> NeatIssueList:
46
52
  self._validate_raw_filter()
47
53
  self._consistent_container_properties()
54
+ self._validate_value_type_existence()
55
+ self._validate_reverse_connections()
48
56
 
49
57
  self._referenced_views_and_containers_are_existing_and_proper_size()
50
58
  if self.metadata.schema_ is SchemaCompleteness.extended:
@@ -318,6 +326,60 @@ class DMSPostValidation:
318
326
  NotNeatSupportedFilterWarning(view.view.as_id()),
319
327
  )
320
328
 
329
+ def _validate_value_type_existence(self) -> None:
330
+ views = {prop_.view for prop_ in self.properties}.union({view_.view for view_ in self.views})
331
+
332
+ for prop_ in self.properties:
333
+ if isinstance(prop_.value_type, ViewEntity) and prop_.value_type not in views:
334
+ self.issue_list.append(
335
+ UndefinedViewWarning(
336
+ str(prop_.view),
337
+ str(prop_.value_type),
338
+ prop_.property_,
339
+ )
340
+ )
341
+
342
+ def _validate_reverse_connections(self) -> None:
343
+ # do not check for reverse connections in Cognite models
344
+ if self.metadata.as_data_model_id() in COGNITE_MODELS:
345
+ return None
346
+
347
+ properties_by_ids = {f"{prop_.view!s}.{prop_.property_}": prop_ for prop_ in self.properties}
348
+ reversed_by_ids = {
349
+ id_: prop_
350
+ for id_, prop_ in properties_by_ids.items()
351
+ if prop_.connection and isinstance(prop_.connection, ReverseConnectionEntity)
352
+ }
353
+
354
+ for id_, prop_ in reversed_by_ids.items():
355
+ source_id = f"{prop_.value_type!s}." f"{cast(ReverseConnectionEntity, prop_.connection).property_}"
356
+ if source_id not in properties_by_ids:
357
+ self.issue_list.append(
358
+ ReversedConnectionNotFeasibleError(
359
+ id_,
360
+ "reversed connection",
361
+ prop_.property_,
362
+ str(prop_.view),
363
+ str(prop_.value_type),
364
+ cast(ReverseConnectionEntity, prop_.connection).property_,
365
+ )
366
+ )
367
+
368
+ elif source_id in properties_by_ids and properties_by_ids[source_id].value_type != prop_.view:
369
+ self.issue_list.append(
370
+ ReversedConnectionNotFeasibleError(
371
+ id_,
372
+ "view property",
373
+ prop_.property_,
374
+ str(prop_.view),
375
+ str(prop_.value_type),
376
+ cast(ReverseConnectionEntity, prop_.connection).property_,
377
+ )
378
+ )
379
+
380
+ else:
381
+ continue
382
+
321
383
  @staticmethod
322
384
  def _changed_attributes_and_properties(
323
385
  new_dumped: dict[str, Any], existing_dumped: dict[str, Any]
@@ -4,7 +4,9 @@ from dataclasses import dataclass, field
4
4
  from typing import ClassVar
5
5
 
6
6
  from pydantic import Field, field_serializer, field_validator
7
+ from rdflib import URIRef
7
8
 
9
+ from cognite.neat._constants import DEFAULT_NAMESPACE
8
10
  from cognite.neat._rules.models.data_types import DataType
9
11
  from cognite.neat._rules.models.entities import ClassEntity, ClassEntityList
10
12
 
@@ -76,6 +78,10 @@ class DomainRules(BaseRules):
76
78
  last: "DomainRules | None" = Field(None, alias="Last")
77
79
  reference: "DomainRules | None" = Field(None, alias="Reference")
78
80
 
81
+ @property
82
+ def id_(self) -> URIRef:
83
+ return DEFAULT_NAMESPACE["data-model/verified/domain"]
84
+
79
85
 
80
86
  @dataclass
81
87
  class DomainInputMetadata(InputComponent[DomainMetadata]):
@@ -124,3 +130,7 @@ class DomainInputRules(InputRules[DomainRules]):
124
130
  @classmethod
125
131
  def _get_verified_cls(cls) -> type[DomainRules]:
126
132
  return DomainRules
133
+
134
+ @property
135
+ def id_(self) -> URIRef:
136
+ return DEFAULT_NAMESPACE["data-model/unverified/domain"]
@@ -63,11 +63,9 @@ def load_connection(
63
63
  default_space: str,
64
64
  default_version: str,
65
65
  ) -> Literal["direct"] | ReverseConnectionEntity | EdgeEntity | None:
66
- if (
67
- isinstance(raw, EdgeEntity | ReverseConnectionEntity)
68
- or raw is None
69
- or (isinstance(raw, str) and raw == "direct")
70
- ):
66
+ if isinstance(raw, str) and raw.lower() == "direct":
67
+ return "direct" # type: ignore[return-value]
68
+ elif isinstance(raw, EdgeEntity | ReverseConnectionEntity) or raw is None:
71
69
  return raw # type: ignore[return-value]
72
70
  elif isinstance(raw, str) and raw.startswith("edge"):
73
71
  return EdgeEntity.load(raw, space=default_space, version=default_version) # type: ignore[return-value]
@@ -7,9 +7,9 @@ from typing import TYPE_CHECKING, Any, ClassVar
7
7
  import pandas as pd
8
8
  from pydantic import Field, field_serializer, field_validator, model_validator
9
9
  from pydantic_core.core_schema import SerializationInfo
10
- from rdflib import Namespace
10
+ from rdflib import Namespace, URIRef
11
11
 
12
- from cognite.neat._constants import get_default_prefixes
12
+ from cognite.neat._constants import DEFAULT_NAMESPACE, get_default_prefixes
13
13
  from cognite.neat._issues.errors import NeatValueError, PropertyDefinitionError
14
14
  from cognite.neat._rules._constants import EntityTypes
15
15
  from cognite.neat._rules.models._base_rules import (
@@ -394,3 +394,7 @@ class InformationRules(BaseRules):
394
394
  }
395
395
 
396
396
  return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
397
+
398
+ @property
399
+ def id_(self) -> URIRef:
400
+ return DEFAULT_NAMESPACE[f"data-model/verified/info/{self.metadata.prefix}/{self.metadata.version}"]
@@ -3,8 +3,9 @@ from datetime import datetime
3
3
  from typing import Any, Literal
4
4
 
5
5
  import pandas as pd
6
- from rdflib import Namespace
6
+ from rdflib import Namespace, URIRef
7
7
 
8
+ from cognite.neat._constants import DEFAULT_NAMESPACE
8
9
  from cognite.neat._rules.models._base_input import InputComponent, InputRules
9
10
  from cognite.neat._rules.models.data_types import DataType
10
11
  from cognite.neat._rules.models.entities import (
@@ -158,3 +159,7 @@ class InformationInputRules(InputRules[InformationRules]):
158
159
  }
159
160
 
160
161
  return pd.DataFrame([summary]).T.rename(columns={0: ""})._repr_html_() # type: ignore
162
+
163
+ @property
164
+ def id_(self) -> URIRef:
165
+ return DEFAULT_NAMESPACE[f"data-model/unverified/info/{self.metadata.prefix}/{self.metadata.version}"]
@@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
2
2
  from collections.abc import MutableSequence
3
3
  from typing import Generic, TypeVar
4
4
 
5
+ from cognite.neat._constants import DEFAULT_NAMESPACE
5
6
  from cognite.neat._issues import IssueList, NeatError
6
7
  from cognite.neat._issues.errors import NeatTypeError, NeatValueError
7
8
  from cognite.neat._rules._shared import (
@@ -12,6 +13,7 @@ from cognite.neat._rules._shared import (
12
13
  Rules,
13
14
  VerifiedRules,
14
15
  )
16
+ from cognite.neat._store._provenance import Agent as ProvenanceAgent
15
17
 
16
18
  T_RulesIn = TypeVar("T_RulesIn", bound=Rules)
17
19
  T_RulesOut = TypeVar("T_RulesOut", bound=Rules)
@@ -54,6 +56,11 @@ class RulesTransformer(ABC, Generic[T_RulesIn, T_RulesOut]):
54
56
  else:
55
57
  raise NeatTypeError(f"Unsupported type: {type(rules)}")
56
58
 
59
+ @property
60
+ def agent(self) -> ProvenanceAgent:
61
+ """Provenance agent for the importer."""
62
+ return ProvenanceAgent(id_=DEFAULT_NAMESPACE[f"agent/{type(self).__name__}"])
63
+
57
64
 
58
65
  class RulesPipeline(list, MutableSequence[RulesTransformer], Generic[T_RulesIn, T_RulesOut]):
59
66
  def transform(self, rules: T_RulesIn | OutRules[T_RulesIn]) -> OutRules[T_RulesOut]:
@@ -10,7 +10,11 @@ from cognite.client.data_classes import data_modeling as dms
10
10
  from cognite.client.data_classes.data_modeling import DataModelId, DataModelIdentifier, ViewId
11
11
  from rdflib import Namespace
12
12
 
13
- from cognite.neat._constants import COGNITE_MODELS, DMS_CONTAINER_PROPERTY_SIZE_LIMIT
13
+ from cognite.neat._constants import (
14
+ COGNITE_MODELS,
15
+ DMS_CONTAINER_PROPERTY_SIZE_LIMIT,
16
+ )
17
+ from cognite.neat._issues._base import IssueList
14
18
  from cognite.neat._issues.errors import NeatValueError
15
19
  from cognite.neat._issues.warnings._models import (
16
20
  EnterpriseModelNotBuildOnTopOfCDMWarning,
@@ -18,7 +22,13 @@ from cognite.neat._issues.warnings._models import (
18
22
  )
19
23
  from cognite.neat._issues.warnings.user_modeling import ParentInDifferentSpaceWarning
20
24
  from cognite.neat._rules._constants import EntityTypes
21
- from cognite.neat._rules._shared import InputRules, JustRules, OutRules, VerifiedRules
25
+ from cognite.neat._rules._shared import (
26
+ InputRules,
27
+ JustRules,
28
+ OutRules,
29
+ ReadRules,
30
+ VerifiedRules,
31
+ )
22
32
  from cognite.neat._rules.analysis import DMSAnalysis
23
33
  from cognite.neat._rules.models import (
24
34
  AssetRules,
@@ -83,13 +93,16 @@ class ToCompliantEntities(RulesTransformer[InformationInputRules, InformationInp
83
93
 
84
94
  def transform(
85
95
  self, rules: InformationInputRules | OutRules[InformationInputRules]
86
- ) -> OutRules[InformationInputRules]:
87
- return JustRules(self._transform(self._to_rules(rules)))
96
+ ) -> ReadRules[InformationInputRules]:
97
+ return ReadRules(self._transform(self._to_rules(rules)), IssueList(), {})
88
98
 
89
99
  def _transform(self, rules: InformationInputRules) -> InformationInputRules:
90
100
  rules.metadata.prefix = self._fix_entity(rules.metadata.prefix)
91
101
  rules.classes = self._fix_classes(rules.classes)
92
102
  rules.properties = self._fix_properties(rules.properties)
103
+
104
+ rules.metadata.version += "_dms_compliant"
105
+
93
106
  return rules
94
107
 
95
108
  @classmethod
@@ -258,6 +271,7 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
258
271
  type_: Literal["enterprise", "solution"] = "enterprise",
259
272
  mode: Literal["read", "write"] = "read",
260
273
  dummy_property: str = "GUID",
274
+ move_connections: bool = False,
261
275
  ):
262
276
  self.new_model_id = DataModelId.load(new_model_id)
263
277
  if not self.new_model_id.version:
@@ -267,6 +281,7 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
267
281
  self.mode = mode
268
282
  self.type_ = type_
269
283
  self.dummy_property = dummy_property
284
+ self.move_connections = move_connections
270
285
 
271
286
  def transform(self, rules: DMSRules | OutRules[DMSRules]) -> JustRules[DMSRules]:
272
287
  # Copy to ensure immutability
@@ -393,10 +408,18 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
393
408
  # extending reference views with new ones
394
409
  enterprise_model.views.extend(enterprise_views)
395
410
 
411
+ # Move connections from reference model to enterprise model
412
+ if self.move_connections:
413
+ enterprise_connections = self._move_connections(enterprise_model)
414
+ else:
415
+ enterprise_connections = SheetList[DMSProperty]()
416
+
396
417
  # while overwriting containers and properties with new ones
397
418
  enterprise_model.containers = enterprise_containers
398
419
  enterprise_model.properties = enterprise_properties
399
420
 
421
+ enterprise_properties.extend(enterprise_connections)
422
+
400
423
  return JustRules(enterprise_model)
401
424
 
402
425
  def _remove_cognite_affix(self, entity: _T_Entity) -> _T_Entity:
@@ -455,6 +478,35 @@ class ToExtension(RulesTransformer[DMSRules, DMSRules]):
455
478
 
456
479
  return new_views, new_containers, new_properties
457
480
 
481
+ def _move_connections(self, rules: DMSRules) -> SheetList[DMSProperty]:
482
+ implements: dict[ViewEntity, list[ViewEntity]] = defaultdict(list)
483
+ new_properties = SheetList[DMSProperty]()
484
+
485
+ for view in rules.views:
486
+ if view.view.space == rules.metadata.space and view.implements:
487
+ for implemented_view in view.implements:
488
+ implements.setdefault(implemented_view, []).append(view.view)
489
+
490
+ # currently only supporting single implementation of reference view in enterprise view
491
+ # connections that do not have properties
492
+ if all(len(v) == 1 for v in implements.values()):
493
+ for prop_ in rules.properties:
494
+ if (
495
+ prop_.view.space != rules.metadata.space
496
+ and prop_.connection
497
+ and isinstance(prop_.value_type, ViewEntity)
498
+ and implements.get(prop_.view)
499
+ and implements.get(prop_.value_type)
500
+ ):
501
+ if isinstance(prop_.connection, EdgeEntity) and prop_.connection.properties:
502
+ continue
503
+ new_property = prop_.model_copy(deep=True)
504
+ new_property.view = implements[prop_.view][0]
505
+ new_property.value_type = implements[prop_.value_type][0]
506
+ new_properties.append(new_property)
507
+
508
+ return new_properties
509
+
458
510
 
459
511
  class ReduceCogniteModel(RulesTransformer[DMSRules, DMSRules]):
460
512
  _ASSET_VIEW = ViewId("cdf_cdm", "CogniteAsset", "v1")
@@ -1,3 +1,4 @@
1
+ from datetime import datetime, timezone
1
2
  from typing import Literal, cast
2
3
 
3
4
  from cognite.client import CogniteClient
@@ -14,6 +15,10 @@ from cognite.neat._rules.models.entities._single_value import UnknownEntity
14
15
  from cognite.neat._rules.models.information._rules import InformationRules
15
16
  from cognite.neat._rules.models.information._rules_input import InformationInputRules
16
17
  from cognite.neat._rules.transformers import ConvertToRules, VerifyAnyRules
18
+ from cognite.neat._store._provenance import (
19
+ INSTANCES_ENTITY,
20
+ Change,
21
+ )
17
22
 
18
23
  from ._inspect import InspectAPI
19
24
  from ._prepare import PrepareAPI
@@ -22,7 +27,8 @@ from ._set import SetAPI
22
27
  from ._show import ShowAPI
23
28
  from ._state import SessionState
24
29
  from ._to import ToAPI
25
- from .exceptions import intercept_session_exceptions
30
+ from .engine import load_neat_engine
31
+ from .exceptions import NeatSessionError, intercept_session_exceptions
26
32
 
27
33
 
28
34
  @intercept_session_exceptions
@@ -32,6 +38,7 @@ class NeatSession:
32
38
  client: CogniteClient | None = None,
33
39
  storage: Literal["memory", "oxigraph"] = "memory",
34
40
  verbose: bool = True,
41
+ load_engine: Literal["newest", "cache", "skip"] = "cache",
35
42
  ) -> None:
36
43
  self._client = client
37
44
  self._verbose = verbose
@@ -42,26 +49,69 @@ class NeatSession:
42
49
  self.show = ShowAPI(self._state)
43
50
  self.set = SetAPI(self._state, verbose)
44
51
  self.inspect = InspectAPI(self._state)
52
+ if load_engine != "skip" and (engine_version := load_neat_engine(client, load_engine)):
53
+ print(f"Neat Engine {engine_version} loaded.")
45
54
 
46
55
  @property
47
56
  def version(self) -> str:
48
57
  return _version.__version__
49
58
 
50
59
  def verify(self) -> IssueList:
51
- output = VerifyAnyRules("continue").try_transform(self._state.input_rule)
60
+ source_id, last_unverified_rule = self._state.data_model.last_unverified_rule
61
+ transformer = VerifyAnyRules("continue")
62
+ start = datetime.now(timezone.utc)
63
+ output = transformer.try_transform(last_unverified_rule)
64
+ end = datetime.now(timezone.utc)
65
+
52
66
  if output.rules:
53
- self._state.verified_rules.append(output.rules)
67
+ change = Change.from_rules_activity(
68
+ output.rules,
69
+ transformer.agent,
70
+ start,
71
+ end,
72
+ f"Verified data model {source_id} as {output.rules.id_}",
73
+ self._state.data_model.provenance.source_entity(source_id)
74
+ or self._state.data_model.provenance.target_entity(source_id),
75
+ )
76
+
77
+ self._state.data_model.write(output.rules, change)
78
+
54
79
  if isinstance(output.rules, InformationRules):
55
- self._state.store.add_rules(output.rules)
80
+ self._state.instances.store.add_rules(output.rules)
81
+
56
82
  output.issues.action = "verify"
57
- self._state.issue_lists.append(output.issues)
83
+ self._state.data_model.issue_lists.append(output.issues)
58
84
  if output.issues:
59
85
  print("You can inspect the issues with the .inspect.issues(...) method.")
60
86
  return output.issues
61
87
 
62
- def convert(self, target: Literal["dms"]) -> None:
63
- converted = ConvertToRules(DMSRules).transform(self._state.last_verified_rule)
64
- self._state.verified_rules.append(converted.rules)
88
+ def convert(self, target: Literal["dms", "information"]) -> None:
89
+ start = datetime.now(timezone.utc)
90
+ if target == "dms":
91
+ source_id, info_rules = self._state.data_model.last_verified_information_rules
92
+ converter = ConvertToRules(DMSRules)
93
+ converted_rules = converter.transform(info_rules).rules
94
+ elif target == "information":
95
+ source_id, dms_rules = self._state.data_model.last_verified_dms_rules
96
+ converter = ConvertToRules(InformationRules)
97
+ converted_rules = converter.transform(dms_rules).rules
98
+ else:
99
+ raise NeatSessionError(f"Target {target} not supported.")
100
+ end = datetime.now(timezone.utc)
101
+
102
+ # Provenance
103
+ change = Change.from_rules_activity(
104
+ converted_rules,
105
+ converter.agent,
106
+ start,
107
+ end,
108
+ f"Converted data model {source_id} to {converted_rules.id_}",
109
+ self._state.data_model.provenance.source_entity(source_id)
110
+ or self._state.data_model.provenance.target_entity(source_id),
111
+ )
112
+
113
+ self._state.data_model.write(converted_rules, change)
114
+
65
115
  if self._verbose:
66
116
  print(f"Rules converted to {target}")
67
117
 
@@ -73,6 +123,7 @@ class NeatSession:
73
123
  "v1",
74
124
  ),
75
125
  non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
126
+ max_number_of_instance: int = 100,
76
127
  ) -> IssueList:
77
128
  """Data model inference from instances.
78
129
 
@@ -83,35 +134,55 @@ class NeatSession:
83
134
 
84
135
  model_id = dm.DataModelId.load(model_id)
85
136
 
86
- input_rules: ReadRules = importers.InferenceImporter.from_graph_store(
87
- store=self._state.store,
137
+ start = datetime.now(timezone.utc)
138
+ importer = importers.InferenceImporter.from_graph_store(
139
+ store=self._state.instances.store,
88
140
  non_existing_node_type=non_existing_node_type,
89
- ).to_rules()
141
+ max_number_of_instance=max_number_of_instance,
142
+ )
143
+ inferred_rules: ReadRules = importer.to_rules()
144
+ end = datetime.now(timezone.utc)
90
145
 
91
146
  if model_id.space:
92
- cast(InformationInputRules, input_rules.rules).metadata.prefix = model_id.space
147
+ cast(InformationInputRules, inferred_rules.rules).metadata.prefix = model_id.space
93
148
  if model_id.external_id:
94
- cast(InformationInputRules, input_rules.rules).metadata.name = model_id.external_id
149
+ cast(InformationInputRules, inferred_rules.rules).metadata.name = model_id.external_id
95
150
 
96
151
  if model_id.version:
97
- cast(InformationInputRules, input_rules.rules).metadata.version = model_id.version
152
+ cast(InformationInputRules, inferred_rules.rules).metadata.version = model_id.version
153
+
154
+ # Provenance
155
+ change = Change.from_rules_activity(
156
+ inferred_rules,
157
+ importer.agent,
158
+ start,
159
+ end,
160
+ "Inferred data model",
161
+ INSTANCES_ENTITY,
162
+ )
98
163
 
99
- self.read.rdf._store_rules(self._state.store, input_rules, "Data Model Inference")
100
- return input_rules.issues
164
+ self._state.data_model.write(inferred_rules, change)
165
+ return inferred_rules.issues
101
166
 
102
167
  def _repr_html_(self) -> str:
103
168
  state = self._state
104
- if not state.has_store and not state.input_rules:
169
+ if (
170
+ not state.instances.has_store
171
+ and not state.data_model.has_unverified_rules
172
+ and not state.data_model.has_verified_rules
173
+ ):
105
174
  return "<strong>Empty session</strong>. Get started by reading something with the <em>.read</em> attribute."
106
175
 
107
176
  output = []
108
- if state.input_rules and not state.verified_rules:
109
- output.append(f"<H2>Unverified Data Model</H2><br />{state.input_rule.rules._repr_html_()}") # type: ignore
110
177
 
111
- if state.verified_rules:
112
- output.append(f"<H2>Verified Data Model</H2><br />{state.last_verified_rule._repr_html_()}") # type: ignore
178
+ if state.data_model.has_unverified_rules and not state.data_model.has_verified_rules:
179
+ rules: ReadRules = state.data_model.last_unverified_rule[1]
180
+ output.append(f"<H2>Unverified Data Model</H2><br />{rules.rules._repr_html_()}") # type: ignore
181
+
182
+ if state.data_model.has_verified_rules:
183
+ output.append(f"<H2>Verified Data Model</H2><br />{state.data_model.last_verified_rule[1]._repr_html_()}") # type: ignore
113
184
 
114
- if state.has_store:
115
- output.append(f"<H2>Instances</H2> {state.store._repr_html_()}")
185
+ if state.instances.has_store:
186
+ output.append(f"<H2>Instances</H2> {state.instances.store._repr_html_()}")
116
187
 
117
188
  return "<br />".join(output)
@@ -1,4 +1,5 @@
1
1
  import difflib
2
+ from collections.abc import Callable
2
3
  from typing import Literal, overload
3
4
 
4
5
  import pandas as pd
@@ -21,7 +22,7 @@ class InspectAPI:
21
22
  @property
22
23
  def properties(self) -> pd.DataFrame:
23
24
  """Returns the properties of the current data model."""
24
- return self._state.last_verified_rule.properties.to_pandas()
25
+ return self._state.data_model.last_verified_rule[1].properties.to_pandas()
25
26
 
26
27
 
27
28
  @intercept_session_exceptions
@@ -51,7 +52,7 @@ class InspectIssues:
51
52
  return_dataframe: bool = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
52
53
  ) -> pd.DataFrame | None:
53
54
  """Returns the issues of the current data model."""
54
- issues = self._state.last_issues
55
+ issues = self._state.data_model.last_issues
55
56
  if not issues:
56
57
  self._print("No issues found.")
57
58
 
@@ -94,7 +95,14 @@ class InspectIssues:
94
95
  @intercept_session_exceptions
95
96
  class InspectOutcome:
96
97
  def __init__(self, state: SessionState) -> None:
97
- self._state = state
98
+ self.data_model = InspectUploadOutcome(lambda: state.data_model.last_outcome)
99
+ self.instances = InspectUploadOutcome(lambda: state.instances.last_outcome)
100
+
101
+
102
+ @intercept_session_exceptions
103
+ class InspectUploadOutcome:
104
+ def __init__(self, get_last_outcome: Callable[[], UploadResultList]) -> None:
105
+ self._get_last_outcome = get_last_outcome
98
106
 
99
107
  @staticmethod
100
108
  def _as_set(value: str | list[str] | None) -> set[str] | None:
@@ -130,7 +138,7 @@ class InspectOutcome:
130
138
  return_dataframe: bool = (False if IN_NOTEBOOK else True), # type: ignore[assignment]
131
139
  ) -> pd.DataFrame | None:
132
140
  """Returns the outcome of the last upload."""
133
- outcome = self._state.last_outcome
141
+ outcome = self._get_last_outcome()
134
142
  name_set = self._as_set(name)
135
143
 
136
144
  def outcome_filter(item: UploadResultCore) -> bool: