cognite-neat 0.109.3__py3-none-any.whl → 0.110.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.
- cognite/neat/_alpha.py +2 -0
- cognite/neat/_client/_api/schema.py +17 -1
- cognite/neat/_client/data_classes/schema.py +3 -3
- cognite/neat/_constants.py +11 -0
- cognite/neat/_graph/extractors/_classic_cdf/_classic.py +9 -10
- cognite/neat/_graph/extractors/_iodd.py +3 -3
- cognite/neat/_graph/extractors/_mock_graph_generator.py +9 -7
- cognite/neat/_graph/loaders/_rdf2dms.py +285 -346
- cognite/neat/_graph/queries/_base.py +28 -92
- cognite/neat/_graph/transformers/__init__.py +1 -3
- cognite/neat/_graph/transformers/_rdfpath.py +2 -49
- cognite/neat/_issues/__init__.py +1 -6
- cognite/neat/_issues/_base.py +21 -252
- cognite/neat/_issues/_contextmanagers.py +46 -0
- cognite/neat/_issues/_factory.py +61 -0
- cognite/neat/_issues/errors/__init__.py +18 -4
- cognite/neat/_issues/errors/_wrapper.py +81 -3
- cognite/neat/_issues/formatters.py +4 -4
- cognite/neat/_issues/warnings/__init__.py +3 -2
- cognite/neat/_issues/warnings/_properties.py +8 -0
- cognite/neat/_rules/_constants.py +9 -0
- cognite/neat/_rules/_shared.py +3 -2
- cognite/neat/_rules/analysis/__init__.py +2 -3
- cognite/neat/_rules/analysis/_base.py +450 -258
- cognite/neat/_rules/catalog/info-rules-imf.xlsx +0 -0
- cognite/neat/_rules/exporters/_rules2excel.py +2 -8
- cognite/neat/_rules/exporters/_rules2instance_template.py +2 -2
- cognite/neat/_rules/exporters/_rules2ontology.py +5 -4
- cognite/neat/_rules/importers/_base.py +2 -47
- cognite/neat/_rules/importers/_dms2rules.py +7 -10
- cognite/neat/_rules/importers/_dtdl2rules/dtdl_importer.py +2 -2
- cognite/neat/_rules/importers/_rdf/_inference2rules.py +59 -25
- cognite/neat/_rules/importers/_rdf/_shared.py +1 -1
- cognite/neat/_rules/importers/_spreadsheet2rules.py +12 -9
- cognite/neat/_rules/models/dms/_rules.py +3 -1
- cognite/neat/_rules/models/dms/_rules_input.py +4 -0
- cognite/neat/_rules/models/dms/_validation.py +14 -4
- cognite/neat/_rules/models/entities/_loaders.py +1 -1
- cognite/neat/_rules/models/entities/_multi_value.py +2 -2
- cognite/neat/_rules/models/information/_rules.py +18 -17
- cognite/neat/_rules/models/information/_rules_input.py +2 -1
- cognite/neat/_rules/models/information/_validation.py +3 -1
- cognite/neat/_rules/transformers/__init__.py +8 -2
- cognite/neat/_rules/transformers/_converters.py +242 -43
- cognite/neat/_rules/transformers/_verification.py +5 -10
- cognite/neat/_session/_base.py +4 -4
- cognite/neat/_session/_prepare.py +12 -0
- cognite/neat/_session/_read.py +21 -17
- cognite/neat/_session/_show.py +11 -123
- cognite/neat/_session/_state.py +0 -2
- cognite/neat/_session/_subset.py +64 -0
- cognite/neat/_session/_to.py +63 -12
- cognite/neat/_store/_graph_store.py +5 -246
- cognite/neat/_utils/rdf_.py +2 -2
- cognite/neat/_utils/spreadsheet.py +44 -1
- cognite/neat/_utils/text.py +51 -32
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/METADATA +1 -1
- {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/RECORD +62 -64
- {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/WHEEL +1 -1
- cognite/neat/_graph/queries/_construct.py +0 -187
- cognite/neat/_graph/queries/_shared.py +0 -173
- cognite/neat/_rules/analysis/_dms.py +0 -57
- cognite/neat/_rules/analysis/_information.py +0 -249
- cognite/neat/_rules/models/_rdfpath.py +0 -372
- {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/LICENSE +0 -0
- {cognite_neat-0.109.3.dist-info → cognite_neat-0.110.0.dist-info}/entry_points.txt +0 -0
|
@@ -18,9 +18,6 @@ from cognite.neat._graph.queries import Queries
|
|
|
18
18
|
from cognite.neat._graph.transformers import Transformers
|
|
19
19
|
from cognite.neat._issues import IssueList, catch_issues
|
|
20
20
|
from cognite.neat._issues.errors import OxigraphStorageLockedError
|
|
21
|
-
from cognite.neat._rules.analysis import InformationAnalysis
|
|
22
|
-
from cognite.neat._rules.models import InformationRules
|
|
23
|
-
from cognite.neat._rules.models.entities import ClassEntity
|
|
24
21
|
from cognite.neat._shared import InstanceType, Triple
|
|
25
22
|
from cognite.neat._utils.auxiliary import local_import
|
|
26
23
|
from cognite.neat._utils.rdf_ import add_triples_in_batch, remove_namespace_from_uri
|
|
@@ -55,9 +52,6 @@ class NeatGraphStore:
|
|
|
55
52
|
dataset: Dataset,
|
|
56
53
|
default_named_graph: URIRef | None = None,
|
|
57
54
|
):
|
|
58
|
-
self.rules: dict[URIRef, InformationRules] = {}
|
|
59
|
-
self.base_namespace: dict[URIRef, Namespace] = {}
|
|
60
|
-
|
|
61
55
|
_start = datetime.now(timezone.utc)
|
|
62
56
|
self.dataset = dataset
|
|
63
57
|
self.provenance = Provenance[Entity](
|
|
@@ -72,8 +66,7 @@ class NeatGraphStore:
|
|
|
72
66
|
)
|
|
73
67
|
|
|
74
68
|
self.default_named_graph = default_named_graph or DATASET_DEFAULT_GRAPH_ID
|
|
75
|
-
|
|
76
|
-
self.queries = Queries(self.dataset, self.rules, self.default_named_graph)
|
|
69
|
+
self.queries = Queries(self.dataset, self.default_named_graph)
|
|
77
70
|
|
|
78
71
|
def graph(self, named_graph: URIRef | None = None) -> Graph:
|
|
79
72
|
"""Get named graph from the dataset to query over"""
|
|
@@ -116,36 +109,6 @@ class NeatGraphStore:
|
|
|
116
109
|
else:
|
|
117
110
|
return self.dataset.serialize(format="ox-trig" if self.type_ == "OxigraphStore" else "trig")
|
|
118
111
|
|
|
119
|
-
def add_rules(self, rules: InformationRules, named_graph: URIRef | None = None) -> None:
|
|
120
|
-
"""This method is used to add rules to a named graph stored in the graph store.
|
|
121
|
-
|
|
122
|
-
Args:
|
|
123
|
-
rules: InformationRules object containing rules to be added to the named graph
|
|
124
|
-
named_graph: URIRef of the named graph to store the rules in, by default None
|
|
125
|
-
rules will be added to the default graph
|
|
126
|
-
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
named_graph = named_graph or self.default_named_graph
|
|
130
|
-
|
|
131
|
-
if named_graph in self.named_graphs:
|
|
132
|
-
# attaching appropriate namespace to the rules
|
|
133
|
-
# as well base_namespace
|
|
134
|
-
self.rules[named_graph] = rules
|
|
135
|
-
self.base_namespace[named_graph] = rules.metadata.namespace
|
|
136
|
-
self.queries = Queries(self.dataset, self.rules)
|
|
137
|
-
self.provenance.append(
|
|
138
|
-
Change.record(
|
|
139
|
-
activity=f"{type(self)}.rules",
|
|
140
|
-
start=datetime.now(timezone.utc),
|
|
141
|
-
end=datetime.now(timezone.utc),
|
|
142
|
-
description=f"Added {type(self.rules).__name__} to {named_graph} named graph",
|
|
143
|
-
)
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
if self.rules[named_graph].prefixes:
|
|
147
|
-
self._upsert_prefixes(self.rules[named_graph].prefixes, named_graph)
|
|
148
|
-
|
|
149
112
|
def _upsert_prefixes(self, prefixes: dict[str, Namespace], named_graph: URIRef) -> None:
|
|
150
113
|
"""Adds prefixes to the graph store."""
|
|
151
114
|
_start = datetime.now(timezone.utc)
|
|
@@ -271,226 +234,22 @@ class NeatGraphStore:
|
|
|
271
234
|
last_change.target_entity.issues.extend(issue_list)
|
|
272
235
|
return issue_list
|
|
273
236
|
|
|
274
|
-
def
|
|
237
|
+
def read(
|
|
275
238
|
self,
|
|
276
|
-
|
|
277
|
-
property_link_pairs: dict[str, URIRef] | None,
|
|
239
|
+
class_uri: URIRef,
|
|
278
240
|
named_graph: URIRef | None = None,
|
|
279
|
-
) -> Iterable[tuple[str, dict[str | InstanceType, list[str]]]]:
|
|
280
|
-
named_graph = named_graph or self.default_named_graph
|
|
281
|
-
|
|
282
|
-
if named_graph not in self.named_graphs:
|
|
283
|
-
warnings.warn(
|
|
284
|
-
f"Named graph {named_graph} not found in graph store, cannot read",
|
|
285
|
-
stacklevel=2,
|
|
286
|
-
)
|
|
287
|
-
return
|
|
288
|
-
|
|
289
|
-
if not self.rules or named_graph not in self.rules:
|
|
290
|
-
warnings.warn(
|
|
291
|
-
f"Rules for named graph {named_graph} not found in graph store!",
|
|
292
|
-
stacklevel=2,
|
|
293
|
-
)
|
|
294
|
-
return
|
|
295
|
-
|
|
296
|
-
if self.multi_type_instances:
|
|
297
|
-
warnings.warn(
|
|
298
|
-
"Multi typed instances detected, issues with loading can occur!",
|
|
299
|
-
stacklevel=2,
|
|
300
|
-
)
|
|
301
|
-
|
|
302
|
-
analysis = InformationAnalysis(self.rules[named_graph])
|
|
303
|
-
|
|
304
|
-
if cls := analysis.classes_by_neat_id.get(class_neat_id):
|
|
305
|
-
if property_link_pairs:
|
|
306
|
-
property_renaming_config = {
|
|
307
|
-
prop_uri: prop_name
|
|
308
|
-
for prop_name, prop_neat_id in property_link_pairs.items()
|
|
309
|
-
if (prop_uri := analysis.neat_id_to_instance_source_property_uri(prop_neat_id))
|
|
310
|
-
}
|
|
311
|
-
if information_properties := analysis.classes_with_properties(consider_inheritance=True).get(
|
|
312
|
-
cls.class_
|
|
313
|
-
):
|
|
314
|
-
for prop in information_properties:
|
|
315
|
-
if prop.neatId is None:
|
|
316
|
-
continue
|
|
317
|
-
# Include renaming done in the Information rules that are not present in the
|
|
318
|
-
# property_link_pairs. The use case for this renaming to startNode and endNode
|
|
319
|
-
# properties that are not part of DMSRules but will typically be present
|
|
320
|
-
# in the Information rules.
|
|
321
|
-
if (
|
|
322
|
-
uri := analysis.neat_id_to_instance_source_property_uri(prop.neatId)
|
|
323
|
-
) and uri not in property_renaming_config:
|
|
324
|
-
property_renaming_config[uri] = prop.property_
|
|
325
|
-
|
|
326
|
-
yield from self._read_via_class_entity(cls.class_, property_renaming_config)
|
|
327
|
-
return
|
|
328
|
-
else:
|
|
329
|
-
warnings.warn("Rules not linked", stacklevel=2)
|
|
330
|
-
return
|
|
331
|
-
else:
|
|
332
|
-
warnings.warn("Class with neat id {class_neat_id} found in rules", stacklevel=2)
|
|
333
|
-
return
|
|
334
|
-
|
|
335
|
-
def _read_via_class_entity(
|
|
336
|
-
self,
|
|
337
|
-
class_entity: ClassEntity,
|
|
338
241
|
property_renaming_config: dict[URIRef, str] | None = None,
|
|
339
|
-
named_graph: URIRef | None = None,
|
|
340
242
|
) -> Iterable[tuple[str, dict[str | InstanceType, list[str]]]]:
|
|
341
243
|
named_graph = named_graph or self.default_named_graph
|
|
342
244
|
|
|
343
|
-
|
|
344
|
-
warnings.warn(
|
|
345
|
-
f"Named graph {named_graph} not found in graph store, cannot read",
|
|
346
|
-
stacklevel=2,
|
|
347
|
-
)
|
|
348
|
-
return
|
|
349
|
-
|
|
350
|
-
if not self.rules or named_graph not in self.rules:
|
|
351
|
-
warnings.warn(
|
|
352
|
-
f"Rules for named graph {named_graph} not found in graph store!",
|
|
353
|
-
stacklevel=2,
|
|
354
|
-
)
|
|
355
|
-
return
|
|
356
|
-
if self.multi_type_instances:
|
|
357
|
-
warnings.warn(
|
|
358
|
-
"Multi typed instances detected, issues with loading can occur!",
|
|
359
|
-
stacklevel=2,
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
if class_entity not in [definition.class_ for definition in self.rules[named_graph].classes]:
|
|
363
|
-
warnings.warn("Desired type not found in graph!", stacklevel=2)
|
|
364
|
-
return
|
|
365
|
-
|
|
366
|
-
if not (class_uri := InformationAnalysis(self.rules[named_graph]).class_uri(class_entity)):
|
|
367
|
-
warnings.warn(
|
|
368
|
-
f"Class {class_entity.suffix} does not have namespace defined for prefix {class_entity.prefix} Rules!",
|
|
369
|
-
stacklevel=2,
|
|
370
|
-
)
|
|
371
|
-
return
|
|
372
|
-
|
|
373
|
-
has_hop_transformations = InformationAnalysis(self.rules[named_graph]).has_hop_transformations()
|
|
374
|
-
has_self_reference_transformations = InformationAnalysis(
|
|
375
|
-
self.rules[named_graph]
|
|
376
|
-
).has_self_reference_property_transformations()
|
|
377
|
-
if has_hop_transformations or has_self_reference_transformations:
|
|
378
|
-
msg = (
|
|
379
|
-
f"Rules contain [{'Hop' if has_hop_transformations else ''}"
|
|
380
|
-
f", {'SelfReferenceProperty' if has_self_reference_transformations else ''}]"
|
|
381
|
-
" rdfpath."
|
|
382
|
-
f" Run [{'ReduceHopTraversal' if has_hop_transformations else ''}"
|
|
383
|
-
f", {'AddSelfReferenceProperty' if has_self_reference_transformations else ''}]"
|
|
384
|
-
" transformer(s) first!"
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
warnings.warn(
|
|
388
|
-
msg,
|
|
389
|
-
stacklevel=2,
|
|
390
|
-
)
|
|
391
|
-
return
|
|
392
|
-
|
|
393
|
-
# get all the instances for give class_uri
|
|
394
|
-
instance_ids = self.queries.list_instances_ids_of_class(class_uri)
|
|
395
|
-
|
|
396
|
-
# get potential property renaming config
|
|
397
|
-
property_renaming_config = property_renaming_config or InformationAnalysis(
|
|
398
|
-
self.rules[named_graph]
|
|
399
|
-
).define_property_renaming_config(class_entity)
|
|
245
|
+
instance_ids = self.queries.list_instances_ids_of_class(class_uri, named_graph=named_graph)
|
|
400
246
|
|
|
401
247
|
for instance_id in instance_ids:
|
|
402
248
|
if res := self.queries.describe(
|
|
403
|
-
instance_id=instance_id,
|
|
404
|
-
instance_type=class_entity.suffix,
|
|
405
|
-
property_renaming_config=property_renaming_config,
|
|
249
|
+
instance_id=instance_id, instance_type=class_uri, property_renaming_config=property_renaming_config
|
|
406
250
|
):
|
|
407
251
|
yield res
|
|
408
252
|
|
|
409
|
-
def read(
|
|
410
|
-
self, class_: str, named_graph: URIRef | None = None
|
|
411
|
-
) -> Iterable[tuple[str, dict[str | InstanceType, list[str]]]]:
|
|
412
|
-
"""Read instances for given class from the graph store.
|
|
413
|
-
|
|
414
|
-
!!! note "Assumption"
|
|
415
|
-
This method assumes that the class_ belongs to the same (name)space as
|
|
416
|
-
the rules which are attached to the graph store.
|
|
417
|
-
|
|
418
|
-
"""
|
|
419
|
-
named_graph = named_graph or self.default_named_graph
|
|
420
|
-
|
|
421
|
-
if named_graph not in self.named_graphs:
|
|
422
|
-
warnings.warn(
|
|
423
|
-
f"Named graph {named_graph} not found in graph store, cannot read",
|
|
424
|
-
stacklevel=2,
|
|
425
|
-
)
|
|
426
|
-
return
|
|
427
|
-
|
|
428
|
-
if not self.rules or named_graph not in self.rules:
|
|
429
|
-
warnings.warn(
|
|
430
|
-
f"Rules for named graph {named_graph} not found in graph store!",
|
|
431
|
-
stacklevel=2,
|
|
432
|
-
)
|
|
433
|
-
return
|
|
434
|
-
if self.multi_type_instances:
|
|
435
|
-
warnings.warn(
|
|
436
|
-
"Multi typed instances detected, issues with loading can occur!",
|
|
437
|
-
stacklevel=2,
|
|
438
|
-
)
|
|
439
|
-
|
|
440
|
-
class_entity = ClassEntity(prefix=self.rules[named_graph].metadata.prefix, suffix=class_)
|
|
441
|
-
|
|
442
|
-
if class_entity not in [definition.class_ for definition in self.rules[named_graph].classes]:
|
|
443
|
-
warnings.warn("Desired type not found in graph!", stacklevel=2)
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
yield from self._read_via_class_entity(class_entity)
|
|
447
|
-
|
|
448
|
-
def count_of_id(self, neat_id: URIRef, named_graph: URIRef | None = None) -> int:
|
|
449
|
-
"""Count the number of instances of a given type
|
|
450
|
-
|
|
451
|
-
Args:
|
|
452
|
-
neat_id: Type for which instances are to be counted
|
|
453
|
-
|
|
454
|
-
Returns:
|
|
455
|
-
Number of instances
|
|
456
|
-
"""
|
|
457
|
-
named_graph = named_graph or self.default_named_graph
|
|
458
|
-
|
|
459
|
-
if named_graph not in self.named_graphs:
|
|
460
|
-
warnings.warn(
|
|
461
|
-
f"Named graph {named_graph} not found in graph store, cannot count",
|
|
462
|
-
stacklevel=2,
|
|
463
|
-
)
|
|
464
|
-
return 0
|
|
465
|
-
|
|
466
|
-
if not self.rules or named_graph not in self.rules:
|
|
467
|
-
warnings.warn(
|
|
468
|
-
f"Rules for named graph {named_graph} not found in graph store!",
|
|
469
|
-
stacklevel=2,
|
|
470
|
-
)
|
|
471
|
-
return 0
|
|
472
|
-
|
|
473
|
-
class_entity = next(
|
|
474
|
-
(definition.class_ for definition in self.rules[named_graph].classes if definition.neatId == neat_id),
|
|
475
|
-
None,
|
|
476
|
-
)
|
|
477
|
-
if not class_entity:
|
|
478
|
-
warnings.warn("Desired type not found in graph!", stacklevel=2)
|
|
479
|
-
return 0
|
|
480
|
-
|
|
481
|
-
if not (class_uri := InformationAnalysis(self.rules[named_graph]).class_uri(class_entity)):
|
|
482
|
-
warnings.warn(
|
|
483
|
-
f"Class {class_entity.suffix} does not have namespace defined for prefix {class_entity.prefix} Rules!",
|
|
484
|
-
stacklevel=2,
|
|
485
|
-
)
|
|
486
|
-
return 0
|
|
487
|
-
|
|
488
|
-
return self.count_of_type(class_uri)
|
|
489
|
-
|
|
490
|
-
def count_of_type(self, class_uri: URIRef) -> int:
|
|
491
|
-
query = f"SELECT (COUNT(?instance) AS ?instanceCount) WHERE {{ ?instance a <{class_uri}> }}"
|
|
492
|
-
return int(next(iter(self.dataset.query(query)))[0]) # type: ignore[arg-type, index]
|
|
493
|
-
|
|
494
253
|
def _parse_file(
|
|
495
254
|
self,
|
|
496
255
|
named_graph: URIRef,
|
cognite/neat/_utils/rdf_.py
CHANGED
|
@@ -154,7 +154,7 @@ def _traverse(hierarchy: dict, graph: dict, names: list[str]) -> dict:
|
|
|
154
154
|
return hierarchy
|
|
155
155
|
|
|
156
156
|
|
|
157
|
-
def get_inheritance_path(child: Any, child_parent: dict[Any,
|
|
157
|
+
def get_inheritance_path(child: Any, child_parent: dict[Any, set[Any]]) -> list[Any]:
|
|
158
158
|
"""Returns the inheritance path for a given child
|
|
159
159
|
|
|
160
160
|
Args:
|
|
@@ -167,7 +167,7 @@ def get_inheritance_path(child: Any, child_parent: dict[Any, list[Any]]) -> list
|
|
|
167
167
|
!!! note "No Circular Inheritance"
|
|
168
168
|
This method assumes that the child_parent dictionary is a tree and does not contain any cycles.
|
|
169
169
|
"""
|
|
170
|
-
path = []
|
|
170
|
+
path: list[Any] = []
|
|
171
171
|
if child in child_parent:
|
|
172
172
|
path.extend(child_parent[child])
|
|
173
173
|
for parent in child_parent[child]:
|
|
@@ -5,6 +5,8 @@ import pandas as pd
|
|
|
5
5
|
from openpyxl import load_workbook
|
|
6
6
|
from openpyxl.worksheet.worksheet import Worksheet
|
|
7
7
|
|
|
8
|
+
from cognite.neat._rules._constants import get_internal_properties
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
@dataclass
|
|
10
12
|
class SpreadsheetRead:
|
|
@@ -16,6 +18,7 @@ class SpreadsheetRead:
|
|
|
16
18
|
|
|
17
19
|
header_row: int = 1
|
|
18
20
|
empty_rows: list[int] = field(default_factory=list)
|
|
21
|
+
skipped_rows: list[int] = field(default_factory=list)
|
|
19
22
|
is_one_indexed: bool = True
|
|
20
23
|
|
|
21
24
|
def __post_init__(self):
|
|
@@ -28,6 +31,13 @@ class SpreadsheetRead:
|
|
|
28
31
|
output += 1
|
|
29
32
|
else:
|
|
30
33
|
break
|
|
34
|
+
|
|
35
|
+
for skipped_rows in self.skipped_rows:
|
|
36
|
+
if skipped_rows <= output:
|
|
37
|
+
output += 1
|
|
38
|
+
else:
|
|
39
|
+
break
|
|
40
|
+
|
|
31
41
|
return output + self.header_row + (1 if self.is_one_indexed else 0)
|
|
32
42
|
|
|
33
43
|
|
|
@@ -63,19 +73,52 @@ def read_individual_sheet(
|
|
|
63
73
|
|
|
64
74
|
raw = pd.read_excel(excel_file, sheet_name, skiprows=skiprows)
|
|
65
75
|
is_na = raw.isnull().all(axis=1)
|
|
76
|
+
skip_rows = _find_rows_to_skip(raw)
|
|
66
77
|
empty_rows = is_na[is_na].index.tolist()
|
|
67
78
|
|
|
79
|
+
if skip_rows:
|
|
80
|
+
raw = raw.drop(skip_rows)
|
|
81
|
+
|
|
68
82
|
raw.dropna(axis=0, how="all", inplace=True)
|
|
83
|
+
|
|
69
84
|
if "Value Type" in raw.columns:
|
|
70
85
|
# Special handling for Value Type column, #N/A is treated specially by NEAT it means Unknown
|
|
71
86
|
raw["Value Type"] = raw["Value Type"].replace(float("nan"), "#N/A")
|
|
87
|
+
|
|
72
88
|
output = raw.replace(float("nan"), None).to_dict(orient="records")
|
|
73
89
|
if return_read_info:
|
|
74
90
|
# If no rows are skipped, row 1 is the header row.
|
|
75
|
-
return output, SpreadsheetRead(
|
|
91
|
+
return output, SpreadsheetRead(
|
|
92
|
+
header_row=skiprows + 1,
|
|
93
|
+
empty_rows=empty_rows,
|
|
94
|
+
is_one_indexed=True,
|
|
95
|
+
skipped_rows=skip_rows,
|
|
96
|
+
)
|
|
76
97
|
return output
|
|
77
98
|
|
|
78
99
|
|
|
100
|
+
def _find_rows_to_skip(
|
|
101
|
+
df: pd.DataFrame,
|
|
102
|
+
) -> list:
|
|
103
|
+
"""Find rows which are having all values as None except for internal properties."""
|
|
104
|
+
rows_to_skip = []
|
|
105
|
+
|
|
106
|
+
internal_cols = {val.lower() for val in get_internal_properties()}
|
|
107
|
+
for i, row in df.iterrows():
|
|
108
|
+
user_cols_state = []
|
|
109
|
+
internal_cols_state = []
|
|
110
|
+
for col in df.columns:
|
|
111
|
+
if col.lower() not in internal_cols:
|
|
112
|
+
user_cols_state.append(row[col] == "#N/A" or row[col].__str__().lower() in ["none", "nan"])
|
|
113
|
+
else:
|
|
114
|
+
internal_cols_state.append(row[col] is not None)
|
|
115
|
+
|
|
116
|
+
if all(user_cols_state) and any(internal_cols_state):
|
|
117
|
+
rows_to_skip.append(i)
|
|
118
|
+
|
|
119
|
+
return rows_to_skip
|
|
120
|
+
|
|
121
|
+
|
|
79
122
|
def _get_row_number(sheet: Worksheet, values_to_find: list[str]) -> int | None:
|
|
80
123
|
for row_number, row in enumerate(sheet.iter_rows(values_only=True), start=1):
|
|
81
124
|
if any(value in row for value in values_to_find):
|
cognite/neat/_utils/text.py
CHANGED
|
@@ -3,7 +3,7 @@ from collections.abc import Collection
|
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def
|
|
6
|
+
def to_camel_case(string: str) -> str:
|
|
7
7
|
"""Convert snake_case_name to camelCaseName.
|
|
8
8
|
|
|
9
9
|
Args:
|
|
@@ -12,22 +12,34 @@ def to_camel(string: str) -> str:
|
|
|
12
12
|
camelCase of the input string.
|
|
13
13
|
|
|
14
14
|
Examples:
|
|
15
|
-
>>>
|
|
15
|
+
>>> to_camel_case("a_b")
|
|
16
16
|
'aB'
|
|
17
|
-
>>>
|
|
17
|
+
>>> to_camel_case("ScenarioInstance_priceForecast")
|
|
18
18
|
'scenarioInstancePriceForecast'
|
|
19
19
|
"""
|
|
20
|
-
string = re.sub(r"[
|
|
20
|
+
string = re.sub(r"[^a-zA-Z0-9_]", "_", string)
|
|
21
21
|
string = re.sub("_+", "_", string)
|
|
22
|
+
is_all_upper = string.upper() == string
|
|
23
|
+
is_first_upper = (
|
|
24
|
+
len(string) >= 2 and string[:2].upper() == string[:2] and "_" not in string[:2] and not is_all_upper
|
|
25
|
+
)
|
|
26
|
+
return _to_camel_case(string, is_all_upper, is_first_upper)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _to_camel_case(string, is_all_upper: bool, is_first_upper: bool):
|
|
22
30
|
if "_" in string:
|
|
23
|
-
pascal_splits = [
|
|
31
|
+
pascal_splits = [
|
|
32
|
+
_to_pascal_case(part, is_all_upper, is_first_upper and no == 0)
|
|
33
|
+
for no, part in enumerate(string.split("_"), 0)
|
|
34
|
+
]
|
|
24
35
|
else:
|
|
25
36
|
# Ensure pascal
|
|
26
|
-
|
|
37
|
+
if string:
|
|
38
|
+
string = string[0].upper() + string[1:]
|
|
27
39
|
pascal_splits = [string]
|
|
28
40
|
cleaned: list[str] = []
|
|
29
41
|
for part in pascal_splits:
|
|
30
|
-
if part.upper() == part:
|
|
42
|
+
if part.upper() == part and is_all_upper:
|
|
31
43
|
cleaned.append(part.capitalize())
|
|
32
44
|
else:
|
|
33
45
|
cleaned.append(part)
|
|
@@ -37,13 +49,16 @@ def to_camel(string: str) -> str:
|
|
|
37
49
|
string_split.extend(re.findall(r"[A-Z][a-z0-9]*", part))
|
|
38
50
|
if not string_split:
|
|
39
51
|
string_split = [string]
|
|
40
|
-
|
|
41
|
-
return string_split[0].casefold() + "".join(word.capitalize() for word in string_split[1:])
|
|
42
|
-
except IndexError:
|
|
52
|
+
if len(string_split) == 0:
|
|
43
53
|
return ""
|
|
54
|
+
# The first word is a single letter, keep the original case
|
|
55
|
+
if is_first_upper:
|
|
56
|
+
return "".join(word for word in string_split)
|
|
57
|
+
else:
|
|
58
|
+
return string_split[0].casefold() + "".join(word for word in string_split[1:])
|
|
44
59
|
|
|
45
60
|
|
|
46
|
-
def
|
|
61
|
+
def to_pascal_case(string: str) -> str:
|
|
47
62
|
"""Convert string to PascalCaseName.
|
|
48
63
|
|
|
49
64
|
Args:
|
|
@@ -52,16 +67,20 @@ def to_pascal(string: str) -> str:
|
|
|
52
67
|
PascalCase of the input string.
|
|
53
68
|
|
|
54
69
|
Examples:
|
|
55
|
-
>>>
|
|
70
|
+
>>> to_pascal_case("a_b")
|
|
56
71
|
'AB'
|
|
57
|
-
>>>
|
|
72
|
+
>>> to_pascal_case('camel_case')
|
|
58
73
|
'CamelCase'
|
|
59
74
|
"""
|
|
60
|
-
|
|
75
|
+
return _to_pascal_case(string, string == string.upper(), True)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _to_pascal_case(string: str, is_all_upper: bool, is_first_upper: bool) -> str:
|
|
79
|
+
camel = _to_camel_case(string, is_all_upper, is_first_upper)
|
|
61
80
|
return f"{camel[0].upper()}{camel[1:]}" if camel else ""
|
|
62
81
|
|
|
63
82
|
|
|
64
|
-
def
|
|
83
|
+
def to_snake_case(string: str) -> str:
|
|
65
84
|
"""
|
|
66
85
|
Convert input string to snake_case
|
|
67
86
|
|
|
@@ -71,33 +90,33 @@ def to_snake(string: str) -> str:
|
|
|
71
90
|
snake_case of the input string.
|
|
72
91
|
|
|
73
92
|
Examples:
|
|
74
|
-
>>>
|
|
93
|
+
>>> to_snake_case("aB")
|
|
75
94
|
'a_b'
|
|
76
|
-
>>>
|
|
95
|
+
>>> to_snake_case('CamelCase')
|
|
77
96
|
'camel_case'
|
|
78
|
-
>>>
|
|
97
|
+
>>> to_snake_case('camelCamelCase')
|
|
79
98
|
'camel_camel_case'
|
|
80
|
-
>>>
|
|
99
|
+
>>> to_snake_case('Camel2Camel2Case')
|
|
81
100
|
'camel_2_camel_2_case'
|
|
82
|
-
>>>
|
|
101
|
+
>>> to_snake_case('getHTTPResponseCode')
|
|
83
102
|
'get_http_response_code'
|
|
84
|
-
>>>
|
|
103
|
+
>>> to_snake_case('get200HTTPResponseCode')
|
|
85
104
|
'get_200_http_response_code'
|
|
86
|
-
>>>
|
|
105
|
+
>>> to_snake_case('getHTTP200ResponseCode')
|
|
87
106
|
'get_http_200_response_code'
|
|
88
|
-
>>>
|
|
107
|
+
>>> to_snake_case('HTTPResponseCode')
|
|
89
108
|
'http_response_code'
|
|
90
|
-
>>>
|
|
109
|
+
>>> to_snake_case('ResponseHTTP')
|
|
91
110
|
'response_http'
|
|
92
|
-
>>>
|
|
111
|
+
>>> to_snake_case('ResponseHTTP2')
|
|
93
112
|
'response_http_2'
|
|
94
|
-
>>>
|
|
113
|
+
>>> to_snake_case('Fun?!awesome')
|
|
95
114
|
'fun_awesome'
|
|
96
|
-
>>>
|
|
115
|
+
>>> to_snake_case('Fun?!Awesome')
|
|
97
116
|
'fun_awesome'
|
|
98
|
-
>>>
|
|
117
|
+
>>> to_snake_case('10CoolDudes')
|
|
99
118
|
'10_cool_dudes'
|
|
100
|
-
>>>
|
|
119
|
+
>>> to_snake_case('20coolDudes')
|
|
101
120
|
'20_cool_dudes'
|
|
102
121
|
"""
|
|
103
122
|
pattern = re.compile(r"[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\d|\W|$)|\d+")
|
|
@@ -117,7 +136,7 @@ def sentence_or_string_to_camel(string: str) -> str:
|
|
|
117
136
|
except IndexError:
|
|
118
137
|
return ""
|
|
119
138
|
else:
|
|
120
|
-
return
|
|
139
|
+
return to_camel_case(string)
|
|
121
140
|
|
|
122
141
|
|
|
123
142
|
def replace_non_alphanumeric_with_underscore(text: str) -> str:
|
|
@@ -151,7 +170,7 @@ class NamingStandardization:
|
|
|
151
170
|
# Underscore ensure that 'Class' it treated as a separate word
|
|
152
171
|
# in the to_pascale function
|
|
153
172
|
clean = f"Class_{clean}"
|
|
154
|
-
return
|
|
173
|
+
return to_pascal_case(clean)
|
|
155
174
|
|
|
156
175
|
@classmethod
|
|
157
176
|
def standardize_property_str(cls, raw: str) -> str:
|
|
@@ -160,7 +179,7 @@ class NamingStandardization:
|
|
|
160
179
|
# Underscore ensure that 'property' it treated as a separate word
|
|
161
180
|
# in the to_camel function
|
|
162
181
|
clean = f"property_{clean}"
|
|
163
|
-
return
|
|
182
|
+
return to_camel_case(clean)
|
|
164
183
|
|
|
165
184
|
@classmethod
|
|
166
185
|
def _clean_string(cls, raw: str) -> str:
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.110.0"
|
|
2
2
|
__engine__ = "^2.0.3"
|