cognite-neat 0.108.0__py3-none-any.whl → 0.109.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 (44) hide show
  1. cognite/neat/_constants.py +1 -1
  2. cognite/neat/_graph/extractors/_classic_cdf/_classic.py +8 -4
  3. cognite/neat/_graph/queries/_base.py +4 -0
  4. cognite/neat/_graph/transformers/__init__.py +3 -3
  5. cognite/neat/_graph/transformers/_base.py +4 -4
  6. cognite/neat/_graph/transformers/_classic_cdf.py +13 -13
  7. cognite/neat/_graph/transformers/_prune_graph.py +3 -3
  8. cognite/neat/_graph/transformers/_rdfpath.py +3 -4
  9. cognite/neat/_graph/transformers/_value_type.py +23 -16
  10. cognite/neat/_issues/errors/__init__.py +2 -0
  11. cognite/neat/_issues/errors/_external.py +8 -0
  12. cognite/neat/_issues/warnings/_resources.py +1 -1
  13. cognite/neat/_rules/exporters/_rules2yaml.py +1 -1
  14. cognite/neat/_rules/importers/_rdf/_inference2rules.py +179 -118
  15. cognite/neat/_rules/models/_base_rules.py +9 -8
  16. cognite/neat/_rules/models/dms/_exporter.py +5 -4
  17. cognite/neat/_rules/transformers/__init__.py +4 -3
  18. cognite/neat/_rules/transformers/_base.py +6 -1
  19. cognite/neat/_rules/transformers/_converters.py +436 -361
  20. cognite/neat/_rules/transformers/_mapping.py +4 -4
  21. cognite/neat/_session/_base.py +71 -69
  22. cognite/neat/_session/_create.py +133 -0
  23. cognite/neat/_session/_drop.py +55 -1
  24. cognite/neat/_session/_fix.py +28 -0
  25. cognite/neat/_session/_inspect.py +19 -5
  26. cognite/neat/_session/_mapping.py +8 -8
  27. cognite/neat/_session/_prepare.py +3 -247
  28. cognite/neat/_session/_read.py +78 -4
  29. cognite/neat/_session/_set.py +34 -12
  30. cognite/neat/_session/_show.py +14 -41
  31. cognite/neat/_session/_state.py +48 -51
  32. cognite/neat/_session/_to.py +7 -3
  33. cognite/neat/_session/exceptions.py +7 -1
  34. cognite/neat/_store/_graph_store.py +14 -13
  35. cognite/neat/_store/_provenance.py +36 -20
  36. cognite/neat/_store/_rules_store.py +172 -293
  37. cognite/neat/_store/exceptions.py +40 -4
  38. cognite/neat/_utils/auth.py +4 -2
  39. cognite/neat/_version.py +1 -1
  40. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/METADATA +1 -1
  41. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/RECORD +44 -42
  42. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/LICENSE +0 -0
  43. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/WHEEL +0 -0
  44. {cognite_neat-0.108.0.dist-info → cognite_neat-0.109.0.dist-info}/entry_points.txt +0 -0
@@ -7,10 +7,10 @@ from pathlib import Path
7
7
  from typing import Any, ClassVar, cast
8
8
 
9
9
  from cognite.client import data_modeling as dm
10
- from rdflib import RDF, Graph, Namespace, URIRef
10
+ from rdflib import RDF, RDFS, Graph, Namespace, URIRef
11
11
  from rdflib import Literal as RdfLiteral
12
12
 
13
- from cognite.neat._constants import NEAT
13
+ from cognite.neat._constants import NEAT, get_default_prefixes_and_namespaces
14
14
  from cognite.neat._issues import IssueList
15
15
  from cognite.neat._issues.warnings import PropertyValueTypeUndefinedWarning
16
16
  from cognite.neat._rules.analysis import InformationAnalysis
@@ -81,7 +81,7 @@ class InferenceImporter(BaseRDFImporter):
81
81
  def from_graph_store(
82
82
  cls,
83
83
  store: NeatGraphStore,
84
- data_model_id: (dm.DataModelId | tuple[str, str, str]) = DEFAULT_INFERENCE_DATA_MODEL_ID,
84
+ data_model_id: dm.DataModelId | tuple[str, str, str] = DEFAULT_INFERENCE_DATA_MODEL_ID,
85
85
  max_number_of_instance: int = -1,
86
86
  non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
87
87
  language: str = "en",
@@ -194,6 +194,7 @@ class InferenceImporter(BaseRDFImporter):
194
194
  INSTANCE_PROPERTIES_DEFINITION.replace("instance_id", instance)
195
195
  ): # type: ignore[misc]
196
196
  # this is to skip rdf:type property
197
+
197
198
  if property_uri == RDF.type:
198
199
  continue
199
200
  property_id = remove_namespace_from_uri(property_uri)
@@ -245,13 +246,8 @@ class InferenceImporter(BaseRDFImporter):
245
246
  elif id_ in properties and definition["value_type"] not in properties[id_]["value_type"]:
246
247
  properties[id_]["value_type"].add(definition["value_type"])
247
248
 
248
- # USE CASE 3: existing but max count is different
249
- elif (
250
- id_ in properties
251
- and definition["value_type"] in properties[id_]["value_type"]
252
- and properties[id_]["max_count"] != definition["max_count"]
253
- ):
254
- properties[id_]["max_count"] = max(properties[id_]["max_count"], definition["max_count"])
249
+ # always update max_count with the upmost value
250
+ properties[id_]["max_count"] = max(properties[id_]["max_count"], definition["max_count"])
255
251
 
256
252
  # Create multi-value properties otherwise single value
257
253
  for property_ in properties.values():
@@ -292,11 +288,10 @@ class InferenceImporter(BaseRDFImporter):
292
288
  # Internal helper class
293
289
  @dataclass
294
290
  class _ReadProperties:
295
- class_uri: URIRef
296
- subclass_uri: URIRef
291
+ type_uri: URIRef
297
292
  property_uri: URIRef
298
- data_type: URIRef | None
299
- object_type: URIRef | None
293
+ value_type: URIRef
294
+ parent_uri: URIRef | None
300
295
  max_occurrence: int
301
296
  instance_count: int
302
297
 
@@ -315,7 +310,6 @@ class SubclassInferenceImporter(BaseRDFImporter):
315
310
  Args:
316
311
  issue_list: Issue list to store issues
317
312
  graph: Knowledge graph
318
- max_number_of_instance: Maximum number of instances to be used in inference
319
313
  """
320
314
 
321
315
  overwrite_data_types: ClassVar[Mapping[URIRef, URIRef]] = {
@@ -323,35 +317,25 @@ class SubclassInferenceImporter(BaseRDFImporter):
323
317
  data_types.Float.as_xml_uri_ref(): data_types.Double.as_xml_uri_ref(),
324
318
  }
325
319
 
326
- def __init__(
327
- self,
328
- issue_list: IssueList,
329
- graph: Graph,
330
- rules: InformationRules,
331
- max_number_of_instance: int,
332
- non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
333
- ) -> None:
334
- super().__init__(
335
- issue_list,
336
- graph,
337
- rules.metadata.as_data_model_id().as_tuple(), # type: ignore[arg-type]
338
- max_number_of_instance,
339
- non_existing_node_type,
340
- language="en",
341
- )
342
- self._rules = rules
320
+ _ordered_class_query = """SELECT DISTINCT ?class (count(?s) as ?instances )
321
+ WHERE { ?s a ?class }
322
+ group by ?class order by DESC(?instances)"""
343
323
 
344
- _ordered_subclass_query = f"""SELECT DISTINCT ?class ?subclass (count(?s) as ?instances )
345
- WHERE {{ ?s a ?class . ?s <{NEAT.type}> ?subclass }}
346
- group by ?class ?subclass order by DESC(?instances)"""
324
+ _type_parent_query = f"""SELECT ?parent ?type
325
+ WHERE {{ ?s a ?type .
326
+ ?type <{RDFS.subClassOf}> ?parent }}"""
347
327
 
348
- _properties_query = """SELECT DISTINCT ?property ?dataType ?objectType
328
+ _properties_query = """SELECT DISTINCT ?property ?valueType
349
329
  WHERE {{
350
330
  ?s a <{type}> .
351
- ?s <{neat_type}> <{subtype}> .
352
- ?s ?property ?value .
353
- BIND(datatype(?value) AS ?dataType) .
354
- OPTIONAL {{?value rdf:type ?objectType}}
331
+ ?s ?property ?object .
332
+ OPTIONAL {{ ?object a ?objectType }}
333
+ BIND(
334
+ IF(
335
+ isLiteral(?object), datatype(?object),
336
+ IF(BOUND(?objectType), ?objectType, <{unknown_type}>)
337
+ ) AS ?valueType
338
+ )
355
339
  }}"""
356
340
 
357
341
  _max_occurrence_query = """SELECT (MAX(?count) AS ?maxCount)
@@ -360,130 +344,194 @@ class SubclassInferenceImporter(BaseRDFImporter):
360
344
  SELECT ?subject (COUNT(?object) AS ?count)
361
345
  WHERE {{
362
346
  ?subject a <{type}> .
363
- ?subject <{neat_type}> <{subtype}> .
364
347
  ?subject <{property}> ?object .
365
348
  }}
366
349
  GROUP BY ?subject
367
350
  }}
368
351
  }}"""
369
352
 
353
+ def __init__(
354
+ self,
355
+ issue_list: IssueList,
356
+ graph: Graph,
357
+ rules: InformationRules | None = None,
358
+ data_model_id: dm.DataModelId | tuple[str, str, str] | None = None,
359
+ non_existing_node_type: UnknownEntity | AnyURI = DEFAULT_NON_EXISTING_NODE_TYPE,
360
+ ) -> None:
361
+ if sum([1 for v in [rules, data_model_id] if v is not None]) != 1:
362
+ raise ValueError("Exactly one of rules or data_model_id must be provided.")
363
+ if data_model_id is not None:
364
+ identifier = data_model_id
365
+ elif rules is not None:
366
+ identifier = rules.metadata.as_data_model_id().as_tuple() # type: ignore[assignment]
367
+ else:
368
+ raise ValueError("Exactly one of rules or data_model_id must be provided.")
369
+ super().__init__(issue_list, graph, identifier, -1, non_existing_node_type, language="en")
370
+ self._rules = rules
371
+
370
372
  def _to_rules_components(
371
373
  self,
372
374
  ) -> dict:
373
- properties_by_class_subclass_pair = self._read_class_properties_from_graph()
374
- existing_classes = {class_.class_.suffix: class_ for class_ in self._rules.classes}
375
- prefixes = self._rules.prefixes.copy()
375
+ if self._rules:
376
+ prefixes = self._rules.prefixes.copy()
377
+ else:
378
+ prefixes = get_default_prefixes_and_namespaces()
379
+
380
+ parent_by_child = self._read_parent_by_child_from_graph()
381
+ read_properties = self._read_class_properties_from_graph(parent_by_child)
382
+ classes, properties = self._create_classes_properties(read_properties, prefixes)
383
+
384
+ if self._rules:
385
+ metadata = self._rules.metadata.model_dump()
386
+ default_space = self._rules.metadata.prefix
387
+ else:
388
+ metadata = self._default_metadata()
389
+ default_space = metadata["space"]
390
+ return {
391
+ "metadata": metadata,
392
+ "classes": [cls.dump(default_space) for cls in classes],
393
+ "properties": [prop.dump(default_space) for prop in properties],
394
+ "prefixes": prefixes,
395
+ }
376
396
 
397
+ def _create_classes_properties(
398
+ self, read_properties: list[_ReadProperties], prefixes: dict[str, Namespace]
399
+ ) -> tuple[list[InformationInputClass], list[InformationInputProperty]]:
400
+ if self._rules:
401
+ existing_classes = {class_.class_.suffix: class_ for class_ in self._rules.classes}
402
+ else:
403
+ existing_classes = {}
377
404
  classes: list[InformationInputClass] = []
378
405
  properties: list[InformationInputProperty] = []
379
406
  # Help for IDE
380
- subclass_uri: URIRef
381
- for class_uri, class_properties_iterable in itertools.groupby(
382
- properties_by_class_subclass_pair, key=lambda x: x.class_uri
407
+ type_uri: URIRef
408
+ parent_uri: URIRef
409
+ for parent_uri, parent_class_properties_iterable in itertools.groupby(
410
+ sorted(read_properties, key=lambda x: x.parent_uri or NEAT.EmptyType),
411
+ key=lambda x: x.parent_uri or NEAT.EmptyType,
383
412
  ):
384
- properties_by_subclass_by_property = self._get_properties_by_subclass_by_property(class_properties_iterable)
385
-
386
- shared_property_uris = set.intersection(
387
- *[
388
- set(properties_by_property.keys())
389
- for properties_by_property in properties_by_subclass_by_property.values()
390
- ]
413
+ properties_by_class_by_property = self._get_properties_by_class_by_property(
414
+ parent_class_properties_iterable
391
415
  )
392
- class_suffix = remove_namespace_from_uri(class_uri)
393
- self._add_uri_namespace_to_prefixes(class_uri, prefixes)
394
- if class_suffix not in existing_classes:
395
- classes.append(InformationInputClass(class_=class_suffix))
416
+
417
+ parent_suffix: str | None = None
418
+ if parent_uri != NEAT.EmptyType:
419
+ shared_property_uris = set.intersection(
420
+ *[
421
+ set(properties_by_property.keys())
422
+ for properties_by_property in properties_by_class_by_property.values()
423
+ ]
424
+ )
425
+ parent_suffix = remove_namespace_from_uri(parent_uri)
426
+ self._add_uri_namespace_to_prefixes(parent_uri, prefixes)
427
+ if parent_suffix not in existing_classes:
428
+ classes.append(InformationInputClass(class_=parent_suffix))
429
+ else:
430
+ classes.append(InformationInputClass.load(existing_classes[parent_suffix].model_dump()))
396
431
  else:
397
- classes.append(InformationInputClass.load(existing_classes[class_suffix].model_dump()))
432
+ shared_property_uris = set()
398
433
  shared_properties: dict[URIRef, list[_ReadProperties]] = defaultdict(list)
399
- for subclass_uri, properties_by_property_uri in properties_by_subclass_by_property.items():
400
- subclass_suffix = remove_namespace_from_uri(subclass_uri)
401
- self._add_uri_namespace_to_prefixes(subclass_uri, prefixes)
402
- if subclass_suffix not in existing_classes:
403
- classes.append(InformationInputClass(class_=subclass_suffix, implements=class_suffix))
434
+ for type_uri, properties_by_property_uri in properties_by_class_by_property.items():
435
+ class_suffix = remove_namespace_from_uri(type_uri)
436
+ self._add_uri_namespace_to_prefixes(type_uri, prefixes)
437
+
438
+ if class_suffix not in existing_classes:
439
+ classes.append(
440
+ InformationInputClass(
441
+ class_=class_suffix,
442
+ implements=parent_suffix,
443
+ )
444
+ )
404
445
  else:
405
- classes.append(InformationInputClass.load(existing_classes[subclass_suffix].model_dump()))
446
+ classes.append(InformationInputClass.load(existing_classes[class_suffix].model_dump()))
406
447
  for property_uri, read_properties in properties_by_property_uri.items():
407
448
  if property_uri in shared_property_uris:
408
449
  shared_properties[property_uri].extend(read_properties)
409
450
  continue
410
451
  properties.append(
411
- self._create_property(read_properties, subclass_suffix, class_uri, property_uri, prefixes)
452
+ self._create_property(read_properties, class_suffix, type_uri, property_uri, prefixes)
412
453
  )
413
- for property_uri, read_properties in shared_properties.items():
414
- properties.append(
415
- self._create_property(read_properties, class_suffix, class_uri, property_uri, prefixes)
416
- )
417
454
 
418
- return {
419
- "metadata": self._rules.metadata.model_dump(),
420
- "classes": [cls.dump(self._rules.metadata.prefix) for cls in classes],
421
- "properties": [prop.dump(self._rules.metadata.prefix) for prop in properties],
422
- "prefixes": self._rules.prefixes,
423
- }
455
+ if parent_suffix:
456
+ for property_uri, read_properties in shared_properties.items():
457
+ properties.append(
458
+ self._create_property(
459
+ read_properties, parent_suffix, read_properties[0].type_uri, property_uri, prefixes
460
+ )
461
+ )
462
+ return classes, properties
424
463
 
425
464
  @staticmethod
426
- def _get_properties_by_subclass_by_property(
427
- class_properties_iterable: Iterable[_ReadProperties],
465
+ def _get_properties_by_class_by_property(
466
+ parent_class_properties_iterable: Iterable[_ReadProperties],
428
467
  ) -> dict[URIRef, dict[URIRef, list[_ReadProperties]]]:
429
- properties_by_subclass_by_property: dict[URIRef, dict[URIRef, list[_ReadProperties]]] = {}
430
- for subclass_uri, subclass_properties_iterable in itertools.groupby(
431
- class_properties_iterable, key=lambda x: x.subclass_uri
468
+ properties_by_class_by_property: dict[URIRef, dict[URIRef, list[_ReadProperties]]] = {}
469
+ for class_uri, class_properties_iterable in itertools.groupby(
470
+ sorted(parent_class_properties_iterable, key=lambda x: x.type_uri), key=lambda x: x.type_uri
432
471
  ):
433
- properties_by_subclass_by_property[subclass_uri] = defaultdict(list)
434
- for read_prop in subclass_properties_iterable:
435
- properties_by_subclass_by_property[subclass_uri][read_prop.property_uri].append(read_prop)
436
- return properties_by_subclass_by_property
437
-
438
- def _read_class_properties_from_graph(self) -> list[_ReadProperties]:
439
- count_by_class_subclass_pair: dict[tuple[URIRef, URIRef], int] = {}
440
- # Infers all the classes w in the graph
441
- for result_row in self.graph.query(self._ordered_subclass_query):
442
- class_uri, subclass_uri, instance_count_literal = cast(tuple[URIRef, URIRef, RdfLiteral], result_row)
443
- count_by_class_subclass_pair[(class_uri, subclass_uri)] = instance_count_literal.toPython()
444
- analysis = InformationAnalysis(self._rules)
445
- existing_class_properties = {
446
- (class_entity.suffix, property)
447
- for class_entity, properties in analysis.class_property_pairs(consider_inheritance=True).items()
448
- for property in properties.keys()
449
- }
472
+ properties_by_class_by_property[class_uri] = defaultdict(list)
473
+ for read_prop in class_properties_iterable:
474
+ properties_by_class_by_property[class_uri][read_prop.property_uri].append(read_prop)
475
+ return properties_by_class_by_property
476
+
477
+ def _read_class_properties_from_graph(self, parent_by_child: dict[URIRef, URIRef]) -> list[_ReadProperties]:
478
+ count_by_type: dict[URIRef, int] = {}
479
+ # Infers all the classes in the graph
480
+ for result_row in self.graph.query(self._ordered_class_query):
481
+ type_uri, instance_count_literal = cast(tuple[URIRef, RdfLiteral], result_row)
482
+ count_by_type[type_uri] = instance_count_literal.toPython()
483
+ if self._rules:
484
+ analysis = InformationAnalysis(self._rules)
485
+ existing_class_properties = {
486
+ (class_entity.suffix, prop.property_)
487
+ for class_entity, properties in analysis.classes_with_properties(
488
+ consider_inheritance=True, allow_different_namespace=True
489
+ ).items()
490
+ for prop in properties
491
+ }
492
+ else:
493
+ existing_class_properties = set()
450
494
  properties_by_class_by_subclass: list[_ReadProperties] = []
451
- for (class_uri, subclass_uri), instance_count in count_by_class_subclass_pair.items():
452
- property_query = self._properties_query.format(type=class_uri, subtype=subclass_uri, neat_type=NEAT.type)
453
- class_suffix = remove_namespace_from_uri(class_uri)
495
+ for type_uri, instance_count in count_by_type.items():
496
+ property_query = self._properties_query.format(type=type_uri, unknown_type=NEAT.UnknownType)
497
+ class_suffix = remove_namespace_from_uri(type_uri)
454
498
  for result_row in self.graph.query(property_query):
455
- property_uri, data_type_uri, object_type_uri = cast(tuple[URIRef, URIRef, URIRef], result_row)
456
- if property_uri == RDF.type or property_uri == NEAT.type:
499
+ property_uri, value_type_uri = cast(tuple[URIRef, URIRef], result_row)
500
+ if property_uri == RDF.type:
457
501
  continue
458
502
  property_str = remove_namespace_from_uri(property_uri)
459
503
  if (class_suffix, property_str) in existing_class_properties:
460
504
  continue
461
- occurrence_query = self._max_occurrence_query.format(
462
- type=class_uri, subtype=subclass_uri, property=property_uri, neat_type=NEAT.type
463
- )
505
+ occurrence_query = self._max_occurrence_query.format(type=type_uri, property=property_uri)
464
506
  max_occurrence = 1 # default value
465
- result_row, *_ = list(self.graph.query(occurrence_query))
466
- if result_row:
467
- max_occurrence_literal, *__ = cast(tuple[RdfLiteral, Any], result_row)
507
+ occurrence_row, *_ = list(self.graph.query(occurrence_query))
508
+ if occurrence_row:
509
+ max_occurrence_literal, *__ = cast(tuple[RdfLiteral, Any], occurrence_row)
468
510
  max_occurrence = int(max_occurrence_literal.toPython())
469
511
  properties_by_class_by_subclass.append(
470
512
  _ReadProperties(
471
- class_uri=class_uri,
472
- subclass_uri=subclass_uri,
513
+ type_uri=type_uri,
473
514
  property_uri=property_uri,
474
- data_type=data_type_uri,
475
- object_type=object_type_uri,
515
+ parent_uri=parent_by_child.get(type_uri),
516
+ value_type=value_type_uri,
476
517
  max_occurrence=max_occurrence,
477
518
  instance_count=instance_count,
478
519
  )
479
520
  )
480
521
  return properties_by_class_by_subclass
481
522
 
523
+ def _read_parent_by_child_from_graph(self) -> dict[URIRef, URIRef]:
524
+ parent_by_child: dict[URIRef, URIRef] = {}
525
+ for result_row in self.graph.query(self._type_parent_query):
526
+ parent_uri, child_uri = cast(tuple[URIRef, URIRef], result_row)
527
+ parent_by_child[child_uri] = parent_uri
528
+ return parent_by_child
529
+
482
530
  def _create_property(
483
531
  self,
484
532
  read_properties: list[_ReadProperties],
485
533
  class_suffix: str,
486
- class_uri: URIRef,
534
+ type_uri: URIRef,
487
535
  property_uri: URIRef,
488
536
  prefixes: dict[str, Namespace],
489
537
  ) -> InformationInputProperty:
@@ -497,17 +545,17 @@ class SubclassInferenceImporter(BaseRDFImporter):
497
545
  property_=property_name,
498
546
  max_count=first.max_occurrence,
499
547
  value_type=value_type,
500
- instance_source=(f"{uri_to_short_form(class_uri, prefixes)}({uri_to_short_form(property_uri, prefixes)})"),
548
+ instance_source=(f"{uri_to_short_form(type_uri, prefixes)}({uri_to_short_form(property_uri, prefixes)})"),
501
549
  )
502
550
 
503
551
  def _get_value_type(
504
552
  self, read_properties: list[_ReadProperties], prefixes: dict[str, Namespace]
505
553
  ) -> str | UnknownEntity:
506
- value_types = {prop.data_type for prop in read_properties if prop.data_type} | {
507
- prop.object_type for prop in read_properties if prop.object_type
508
- }
554
+ value_types = {self.overwrite_data_types.get(prop.value_type, prop.value_type) for prop in read_properties}
509
555
  if len(value_types) == 1:
510
556
  uri_ref = value_types.pop()
557
+ if uri_ref == NEAT.UnknownType:
558
+ return UnknownEntity()
511
559
  self._add_uri_namespace_to_prefixes(uri_ref, prefixes)
512
560
  return remove_namespace_from_uri(uri_ref)
513
561
  elif len(value_types) == 0:
@@ -515,3 +563,16 @@ class SubclassInferenceImporter(BaseRDFImporter):
515
563
  for uri_ref in value_types:
516
564
  self._add_uri_namespace_to_prefixes(uri_ref, prefixes)
517
565
  return " | ".join(remove_namespace_from_uri(uri_ref) for uri_ref in value_types)
566
+
567
+ def _default_metadata(self) -> dict[str, Any]:
568
+ now = datetime.now(timezone.utc)
569
+ return InformationMetadata(
570
+ space=self.data_model_id.space,
571
+ external_id=self.data_model_id.external_id,
572
+ version=cast(str, self.data_model_id.version),
573
+ name="Inferred Model",
574
+ creator=["NEAT"],
575
+ created=now,
576
+ updated=now,
577
+ description="Inferred model from knowledge graph",
578
+ ).model_dump()
@@ -301,6 +301,7 @@ class BaseRules(SchemaModel, ABC):
301
301
  def dump(
302
302
  self,
303
303
  entities_exclude_defaults: bool = True,
304
+ sort: bool = False,
304
305
  mode: Literal["python", "json"] = "python",
305
306
  by_alias: bool = False,
306
307
  exclude: IncEx | None = None,
@@ -317,6 +318,7 @@ class BaseRules(SchemaModel, ABC):
317
318
  For example, given a class that is dumped as 'my_prefix:MyClass', if the prefix for the rules
318
319
  set in metadata.prefix = 'my_prefix', then this class will be dumped as 'MyClass' when this flag is set.
319
320
  Defaults to True.
321
+ sort: Whether to sort the entities in the output.
320
322
  mode: The mode in which `to_python` should run.
321
323
  If mode is 'json', the output will only contain JSON serializable types.
322
324
  If mode is 'python', the output may contain non-JSON-serializable Python objects.
@@ -326,11 +328,12 @@ class BaseRules(SchemaModel, ABC):
326
328
  exclude_unset: Whether to exclude fields that have not been explicitly set.
327
329
  exclude_defaults: Whether to exclude fields that are set to their default value.
328
330
  """
329
- for field_name in self.model_fields.keys():
330
- value = getattr(self, field_name)
331
- # Ensure deterministic order of properties, classes, views, and so on
332
- if isinstance(value, SheetList):
333
- value.sort(key=lambda x: x._identifier())
331
+ if sort:
332
+ for field_name in self.model_fields.keys():
333
+ value = getattr(self, field_name)
334
+ # Ensure deterministic order of properties, classes, views, and so on
335
+ if isinstance(value, SheetList):
336
+ value.sort(key=lambda x: x._identifier())
334
337
 
335
338
  context: dict[str, Any] = {}
336
339
  if entities_exclude_defaults:
@@ -338,7 +341,7 @@ class BaseRules(SchemaModel, ABC):
338
341
 
339
342
  exclude_input: IncEx | None = exclude
340
343
 
341
- output = self.model_dump(
344
+ return self.model_dump(
342
345
  mode=mode,
343
346
  by_alias=by_alias,
344
347
  exclude=exclude_input,
@@ -348,8 +351,6 @@ class BaseRules(SchemaModel, ABC):
348
351
  context=context,
349
352
  )
350
353
 
351
- return output
352
-
353
354
 
354
355
  class SheetRow(SchemaModel):
355
356
  neatId: URIRefType | None = Field(
@@ -292,10 +292,11 @@ class _DMSExporter:
292
292
  for container in containers:
293
293
  container_id = container.as_id()
294
294
  if not (container_properties := container_properties_by_id.get(container_id)):
295
- warnings.warn(
296
- EmptyContainerWarning(container_id),
297
- stacklevel=2,
298
- )
295
+ if container_id.space not in COGNITE_SPACES:
296
+ warnings.warn(
297
+ EmptyContainerWarning(container_id),
298
+ stacklevel=2,
299
+ )
299
300
  container_to_drop.add(container_id)
300
301
  continue
301
302
  for prop in container_properties:
@@ -1,4 +1,4 @@
1
- from ._base import RulesTransformer
1
+ from ._base import RulesTransformer, VerifiedRulesTransformer
2
2
  from ._converters import (
3
3
  AddClassImplements,
4
4
  ChangeViewPrefix,
@@ -6,12 +6,12 @@ from ._converters import (
6
6
  ConversionTransformer,
7
7
  ConvertToRules,
8
8
  DMSToInformation,
9
+ DropModelViews,
9
10
  IncludeReferenced,
10
11
  InformationToDMS,
11
12
  MergeDMSRules,
12
13
  MergeInformationRules,
13
14
  PrefixEntities,
14
- ReduceCogniteModel,
15
15
  SetIDDMSModel,
16
16
  ToCompliantEntities,
17
17
  ToDataProductModel,
@@ -30,13 +30,13 @@ __all__ = [
30
30
  "ConversionTransformer",
31
31
  "ConvertToRules",
32
32
  "DMSToInformation",
33
+ "DropModelViews",
33
34
  "IncludeReferenced",
34
35
  "InformationToDMS",
35
36
  "MapOneToOne",
36
37
  "MergeDMSRules",
37
38
  "MergeInformationRules",
38
39
  "PrefixEntities",
39
- "ReduceCogniteModel",
40
40
  "RuleMapper",
41
41
  "RulesTransformer",
42
42
  "SetIDDMSModel",
@@ -45,6 +45,7 @@ __all__ = [
45
45
  "ToEnterpriseModel",
46
46
  "ToExtensionModel",
47
47
  "ToSolutionModel",
48
+ "VerifiedRulesTransformer",
48
49
  "VerifyAnyRules",
49
50
  "VerifyDMSRules",
50
51
  "VerifyInformationRules",
@@ -5,12 +5,14 @@ from types import UnionType
5
5
  from typing import Generic, TypeVar, Union, get_args, get_origin
6
6
 
7
7
  from cognite.neat._constants import DEFAULT_NAMESPACE
8
- from cognite.neat._rules._shared import ReadRules, Rules
8
+ from cognite.neat._rules._shared import ReadRules, Rules, VerifiedRules
9
9
  from cognite.neat._rules.models import DMSInputRules, InformationInputRules
10
10
  from cognite.neat._store._provenance import Agent as ProvenanceAgent
11
11
 
12
12
  T_RulesIn = TypeVar("T_RulesIn", bound=Rules)
13
13
  T_RulesOut = TypeVar("T_RulesOut", bound=Rules)
14
+ T_VerifiedIn = TypeVar("T_VerifiedIn", bound=VerifiedRules)
15
+ T_VerifiedOut = TypeVar("T_VerifiedOut", bound=VerifiedRules)
14
16
 
15
17
 
16
18
  class RulesTransformer(ABC, Generic[T_RulesIn, T_RulesOut]):
@@ -62,3 +64,6 @@ class RulesTransformer(ABC, Generic[T_RulesIn, T_RulesOut]):
62
64
  return ReadRules[DMSInputRules], ReadRules[InformationInputRules]
63
65
 
64
66
  return (annotation,)
67
+
68
+
69
+ class VerifiedRulesTransformer(RulesTransformer[T_VerifiedIn, T_VerifiedOut], ABC): ...