cognite-neat 0.127.18__py3-none-any.whl → 0.127.20__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.
- cognite/neat/_data_model/exporters/_table_exporter/writer.py +26 -4
- cognite/neat/_data_model/importers/_table_importer/data_classes.py +4 -0
- cognite/neat/_data_model/importers/_table_importer/reader.py +218 -30
- cognite/neat/_data_model/models/dms/__init__.py +40 -0
- cognite/neat/_data_model/models/dms/_constants.py +1 -0
- cognite/neat/_data_model/models/dms/_data_model.py +2 -1
- cognite/neat/_data_model/models/dms/_view_filter.py +282 -0
- cognite/neat/_data_model/models/dms/_views.py +4 -3
- cognite/neat/_version.py +1 -1
- {cognite_neat-0.127.18.dist-info → cognite_neat-0.127.20.dist-info}/METADATA +1 -1
- {cognite_neat-0.127.18.dist-info → cognite_neat-0.127.20.dist-info}/RECORD +13 -12
- {cognite_neat-0.127.18.dist-info → cognite_neat-0.127.20.dist-info}/WHEEL +0 -0
- {cognite_neat-0.127.18.dist-info → cognite_neat-0.127.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,6 +5,8 @@ from typing import Any, Literal
|
|
|
5
5
|
|
|
6
6
|
from cognite.neat._data_model._constants import DEFAULT_MAX_LIST_SIZE, DEFAULT_MAX_LIST_SIZE_DIRECT_RELATIONS
|
|
7
7
|
from cognite.neat._data_model.importers._table_importer.data_classes import (
|
|
8
|
+
CREATOR_KEY,
|
|
9
|
+
CREATOR_MARKER,
|
|
8
10
|
DMSContainer,
|
|
9
11
|
DMSEnum,
|
|
10
12
|
DMSNode,
|
|
@@ -78,14 +80,34 @@ class DMSTableWriter:
|
|
|
78
80
|
)
|
|
79
81
|
|
|
80
82
|
### Metadata Sheet ###
|
|
81
|
-
@
|
|
82
|
-
def write_metadata(data_model: DataModelRequest) -> list[MetadataValue]:
|
|
83
|
-
|
|
83
|
+
@classmethod
|
|
84
|
+
def write_metadata(cls, data_model: DataModelRequest) -> list[MetadataValue]:
|
|
85
|
+
metadata = [
|
|
84
86
|
MetadataValue(key=key, value=value)
|
|
85
87
|
for key, value in data_model.model_dump(
|
|
86
|
-
mode="json", by_alias=True, exclude_none=True, exclude={"views"}
|
|
88
|
+
mode="json", by_alias=True, exclude_none=True, exclude={"views", "description"}
|
|
87
89
|
).items()
|
|
88
90
|
]
|
|
91
|
+
if data_model.description:
|
|
92
|
+
description, creator = cls._serialize_description(data_model.description)
|
|
93
|
+
if description:
|
|
94
|
+
metadata.append(MetadataValue(key="description", value=description))
|
|
95
|
+
if creator:
|
|
96
|
+
metadata.append(MetadataValue(key=CREATOR_KEY, value=creator))
|
|
97
|
+
return metadata
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _serialize_description(description: str | None) -> tuple[str | None, str | None]:
|
|
101
|
+
"""DataModelRequest does not have a 'creator' field, this is a special addition that the Neat tables
|
|
102
|
+
format supports (and recommends using). If the data model was created using Neat, the suffix of the
|
|
103
|
+
description will be Creator: <creator>. This function extracts that information."""
|
|
104
|
+
if description is None:
|
|
105
|
+
return None, None
|
|
106
|
+
if CREATOR_MARKER not in description:
|
|
107
|
+
return description, None
|
|
108
|
+
|
|
109
|
+
description, creator = description.rsplit(CREATOR_MARKER, 1)
|
|
110
|
+
return description.rstrip(), creator.strip()
|
|
89
111
|
|
|
90
112
|
### Container Properties Sheet ###
|
|
91
113
|
|
|
@@ -24,6 +24,10 @@ from cognite.neat.v0.core._data_model.models.entities import (
|
|
|
24
24
|
RawFilter,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
+
# This marker is used to identify creator in the description field.
|
|
28
|
+
CREATOR_MARKER = "Creator: "
|
|
29
|
+
CREATOR_KEY = "creator"
|
|
30
|
+
|
|
27
31
|
|
|
28
32
|
def parse_entity_str(v: str) -> ParsedEntity:
|
|
29
33
|
if isinstance(v, ParsedEntity):
|
|
@@ -9,6 +9,7 @@ from cognite.neat._data_model.models.dms import (
|
|
|
9
9
|
Constraint,
|
|
10
10
|
ConstraintAdapter,
|
|
11
11
|
ContainerPropertyDefinition,
|
|
12
|
+
ContainerReference,
|
|
12
13
|
ContainerRequest,
|
|
13
14
|
DataModelRequest,
|
|
14
15
|
Index,
|
|
@@ -17,17 +18,19 @@ from cognite.neat._data_model.models.dms import (
|
|
|
17
18
|
RequestSchema,
|
|
18
19
|
SpaceRequest,
|
|
19
20
|
UniquenessConstraintDefinition,
|
|
21
|
+
ViewReference,
|
|
20
22
|
ViewRequest,
|
|
21
23
|
ViewRequestProperty,
|
|
22
24
|
ViewRequestPropertyAdapter,
|
|
23
25
|
)
|
|
26
|
+
from cognite.neat._data_model.models.dms._constants import DATA_MODEL_DESCRIPTION_MAX_LENGTH
|
|
24
27
|
from cognite.neat._data_model.models.entities import ParsedEntity, parse_entity
|
|
25
28
|
from cognite.neat._exceptions import DataModelImportException
|
|
26
29
|
from cognite.neat._issues import ModelSyntaxError
|
|
27
30
|
from cognite.neat._utils.text import humanize_collection
|
|
28
31
|
from cognite.neat._utils.validation import ValidationContext, humanize_validation_error
|
|
29
32
|
|
|
30
|
-
from .data_classes import DMSContainer, DMSEnum, DMSNode, DMSProperty, DMSView, TableDMS
|
|
33
|
+
from .data_classes import CREATOR_KEY, CREATOR_MARKER, DMSContainer, DMSEnum, DMSNode, DMSProperty, DMSView, TableDMS
|
|
31
34
|
from .source import TableSource
|
|
32
35
|
|
|
33
36
|
T_BaseModel = TypeVar("T_BaseModel", bound=BaseModel)
|
|
@@ -169,7 +172,9 @@ class DMSTableReader:
|
|
|
169
172
|
space_request = self.read_space(self.default_space)
|
|
170
173
|
node_types = self.read_nodes(tables.nodes)
|
|
171
174
|
enum_collections = self.read_enum_collections(tables.enum)
|
|
172
|
-
|
|
175
|
+
container_ref_by_entity = self.read_entity_by_container_ref(tables.containers)
|
|
176
|
+
view_ref_by_entity = self.read_entity_by_view_ref(tables.views)
|
|
177
|
+
read = self.read_properties(tables.properties, enum_collections, container_ref_by_entity, view_ref_by_entity)
|
|
173
178
|
processed = self.process_properties(read)
|
|
174
179
|
containers = self.read_containers(tables.containers, processed)
|
|
175
180
|
views, valid_view_entities = self.read_views(tables.views, processed.view)
|
|
@@ -207,18 +212,50 @@ class DMSTableReader:
|
|
|
207
212
|
}
|
|
208
213
|
return enum_collections
|
|
209
214
|
|
|
215
|
+
def read_entity_by_container_ref(self, containers: list[DMSContainer]) -> dict[ContainerReference, ParsedEntity]:
|
|
216
|
+
entity_by_container_ref: dict[ContainerReference, ParsedEntity] = {}
|
|
217
|
+
for container in containers:
|
|
218
|
+
data = self._create_container_ref(container.container)
|
|
219
|
+
try:
|
|
220
|
+
container_ref = ContainerReference.model_validate(data)
|
|
221
|
+
except ValidationError:
|
|
222
|
+
# Error will be reported when reading the containers
|
|
223
|
+
continue
|
|
224
|
+
entity_by_container_ref[container_ref] = container.container
|
|
225
|
+
return entity_by_container_ref
|
|
226
|
+
|
|
227
|
+
def read_entity_by_view_ref(self, views: list[DMSView]) -> dict[ViewReference, ParsedEntity]:
|
|
228
|
+
entity_by_view_ref: dict[ViewReference, ParsedEntity] = {}
|
|
229
|
+
for view in views:
|
|
230
|
+
data = self._create_view_ref(view.view)
|
|
231
|
+
try:
|
|
232
|
+
view_ref = ViewReference.model_validate(data)
|
|
233
|
+
except ValidationError:
|
|
234
|
+
# Error will be reported when reading the views
|
|
235
|
+
continue
|
|
236
|
+
entity_by_view_ref[view_ref] = view.view
|
|
237
|
+
return entity_by_view_ref
|
|
238
|
+
|
|
210
239
|
def read_properties(
|
|
211
|
-
self,
|
|
240
|
+
self,
|
|
241
|
+
properties: list[DMSProperty],
|
|
242
|
+
enum_collections: dict[str, dict[str, Any]],
|
|
243
|
+
container_ref_by_entity: dict[ContainerReference, ParsedEntity],
|
|
244
|
+
view_ref_by_entity: dict[ViewReference, ParsedEntity],
|
|
212
245
|
) -> ReadProperties:
|
|
213
246
|
read = ReadProperties()
|
|
247
|
+
view_entities = set(view_ref_by_entity.values())
|
|
248
|
+
container_entities = set(container_ref_by_entity.values())
|
|
214
249
|
for row_no, prop in enumerate(properties):
|
|
215
|
-
self._process_view_property(prop, read, row_no)
|
|
250
|
+
self._process_view_property(prop, read, row_no, view_ref_by_entity, view_entities)
|
|
216
251
|
if prop.container is None or prop.container_property is None:
|
|
217
252
|
# This is when the property is an edge or reverse direct relation property.
|
|
218
253
|
continue
|
|
219
|
-
self._process_container_property(
|
|
220
|
-
|
|
221
|
-
|
|
254
|
+
self._process_container_property(
|
|
255
|
+
prop, read, enum_collections, row_no, container_ref_by_entity, container_entities
|
|
256
|
+
)
|
|
257
|
+
self._process_index(prop, read, row_no, container_ref_by_entity, container_entities)
|
|
258
|
+
self._process_constraint(prop, read, row_no, container_ref_by_entity, container_entities)
|
|
222
259
|
return read
|
|
223
260
|
|
|
224
261
|
def process_properties(self, read: ReadProperties) -> ProcessedProperties:
|
|
@@ -370,31 +407,91 @@ class DMSTableReader:
|
|
|
370
407
|
constraints[container_entity][constraint_id] = constraint
|
|
371
408
|
return constraints
|
|
372
409
|
|
|
373
|
-
def _process_view_property(
|
|
410
|
+
def _process_view_property(
|
|
411
|
+
self,
|
|
412
|
+
prop: DMSProperty,
|
|
413
|
+
read: ReadProperties,
|
|
414
|
+
row_no: int,
|
|
415
|
+
view_ref_by_entity: dict[ViewReference, ParsedEntity],
|
|
416
|
+
view_entities: set[ParsedEntity],
|
|
417
|
+
) -> None:
|
|
374
418
|
loc = (self.Sheets.properties, row_no)
|
|
375
419
|
data = self.read_view_property(prop, loc)
|
|
376
420
|
view_prop = self._validate_adapter(ViewRequestPropertyAdapter, data, loc)
|
|
377
|
-
if view_prop is
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
421
|
+
if view_prop is None:
|
|
422
|
+
return None
|
|
423
|
+
if prop.view in view_entities:
|
|
424
|
+
read.view[(prop.view, prop.view_property)].append(ReadViewProperty(prop.view_property, row_no, view_prop))
|
|
425
|
+
return None
|
|
426
|
+
# The view entity was not found in the views table. This could either be because the view is missing,
|
|
427
|
+
# or because either the view entity in the Properties table and the View table are specified with/without
|
|
428
|
+
# default space/version inconsistently.
|
|
429
|
+
try:
|
|
430
|
+
view_ref = ViewReference.model_validate(self._create_view_ref(prop.view))
|
|
431
|
+
except ValidationError:
|
|
432
|
+
# Error will be reported when reading the views
|
|
433
|
+
return None
|
|
434
|
+
if view_ref in view_ref_by_entity:
|
|
435
|
+
view_ref_entity = view_ref_by_entity[view_ref]
|
|
436
|
+
read.view[(view_ref_entity, prop.view_property)].append(
|
|
437
|
+
ReadViewProperty(prop.view_property, row_no, view_prop)
|
|
438
|
+
)
|
|
439
|
+
else:
|
|
440
|
+
self.errors.append(
|
|
441
|
+
ModelSyntaxError(
|
|
442
|
+
message=(
|
|
443
|
+
f"In {self.source.location(loc)} the View '{prop.view!s}' "
|
|
444
|
+
f"was not found in the {self.Sheets.views!r} table."
|
|
445
|
+
)
|
|
446
|
+
)
|
|
382
447
|
)
|
|
383
448
|
return None
|
|
384
449
|
|
|
385
450
|
def _process_container_property(
|
|
386
|
-
self,
|
|
451
|
+
self,
|
|
452
|
+
prop: DMSProperty,
|
|
453
|
+
read: ReadProperties,
|
|
454
|
+
enum_collections: dict[str, dict[str, Any]],
|
|
455
|
+
row_no: int,
|
|
456
|
+
container_ref_by_entity: dict[ContainerReference, ParsedEntity],
|
|
457
|
+
container_entities: set[ParsedEntity],
|
|
387
458
|
) -> None:
|
|
388
459
|
loc = (self.Sheets.properties, row_no)
|
|
389
460
|
data = self.read_container_property(prop, enum_collections, loc=loc)
|
|
390
461
|
container_prop = self._validate_obj(ContainerPropertyDefinition, data, loc)
|
|
391
|
-
if container_prop is
|
|
462
|
+
if container_prop is None:
|
|
463
|
+
return None
|
|
464
|
+
if not (prop.container and prop.container_property):
|
|
465
|
+
return None
|
|
466
|
+
if prop.container in container_entities:
|
|
392
467
|
read.container[(prop.container, prop.container_property)].append(
|
|
393
468
|
ReadContainerProperty(prop.container_property, row_no, container_prop)
|
|
394
469
|
)
|
|
470
|
+
return None
|
|
471
|
+
# The container entity was not found in the containers table. This could either be because the container
|
|
472
|
+
# is missing, or because either the container entity in the Properties table and the Container table are
|
|
473
|
+
# specified with/without default space/version inconsistently.
|
|
474
|
+
try:
|
|
475
|
+
container_ref = ContainerReference.model_validate(self._create_container_ref(prop.container))
|
|
476
|
+
except ValidationError:
|
|
477
|
+
# Error will be reported when reading the containers
|
|
478
|
+
return None
|
|
479
|
+
if container_ref in container_ref_by_entity:
|
|
480
|
+
container_ref_entity = container_ref_by_entity[container_ref]
|
|
481
|
+
read.container[(container_ref_entity, prop.container_property)].append(
|
|
482
|
+
ReadContainerProperty(prop.container_property, row_no, container_prop)
|
|
483
|
+
)
|
|
484
|
+
# Container can be in CDF, this will be reported by a validator later.
|
|
395
485
|
return None
|
|
396
486
|
|
|
397
|
-
def _process_index(
|
|
487
|
+
def _process_index(
|
|
488
|
+
self,
|
|
489
|
+
prop: DMSProperty,
|
|
490
|
+
read: ReadProperties,
|
|
491
|
+
row_no: int,
|
|
492
|
+
container_ref_by_entity: dict[ContainerReference, ParsedEntity],
|
|
493
|
+
container_entities: set[ParsedEntity],
|
|
494
|
+
) -> None:
|
|
398
495
|
if prop.index is None or prop.container_property is None or prop.container is None:
|
|
399
496
|
return
|
|
400
497
|
|
|
@@ -405,11 +502,41 @@ class DMSTableReader:
|
|
|
405
502
|
if created is None:
|
|
406
503
|
continue
|
|
407
504
|
order = self._read_order(index.properties, loc)
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
505
|
+
|
|
506
|
+
if prop.container in container_entities:
|
|
507
|
+
read.indices[(prop.container, index.suffix)].append(
|
|
508
|
+
ReadIndex(
|
|
509
|
+
prop_id=prop.container_property,
|
|
510
|
+
order=order,
|
|
511
|
+
row_no=row_no,
|
|
512
|
+
index_id=index.suffix,
|
|
513
|
+
index=created,
|
|
514
|
+
)
|
|
411
515
|
)
|
|
412
|
-
|
|
516
|
+
continue
|
|
517
|
+
# The container entity was not found in the containers table. This could either be because the
|
|
518
|
+
# container is missing, or because either the container entity in the Properties table and the
|
|
519
|
+
# Container table are specified with/without default space/version inconsistently.
|
|
520
|
+
|
|
521
|
+
try:
|
|
522
|
+
container_ref = ContainerReference.model_validate(self._create_container_ref(prop.container))
|
|
523
|
+
except ValidationError:
|
|
524
|
+
# Error will be reported when reading the containers
|
|
525
|
+
continue
|
|
526
|
+
if container_ref in container_ref_by_entity:
|
|
527
|
+
container_ref_entity = container_ref_by_entity[container_ref]
|
|
528
|
+
read.indices[(container_ref_entity, index.suffix)].append(
|
|
529
|
+
ReadIndex(
|
|
530
|
+
prop_id=prop.container_property,
|
|
531
|
+
order=order,
|
|
532
|
+
row_no=row_no,
|
|
533
|
+
index_id=index.suffix,
|
|
534
|
+
index=created,
|
|
535
|
+
)
|
|
536
|
+
)
|
|
537
|
+
else:
|
|
538
|
+
# Error is reported when reading the property.
|
|
539
|
+
...
|
|
413
540
|
|
|
414
541
|
def _read_order(self, properties: dict[str, Any], loc: tuple[str | int, ...]) -> int | None:
|
|
415
542
|
if "order" not in properties:
|
|
@@ -433,7 +560,14 @@ class DMSTableReader:
|
|
|
433
560
|
**index.properties,
|
|
434
561
|
}
|
|
435
562
|
|
|
436
|
-
def _process_constraint(
|
|
563
|
+
def _process_constraint(
|
|
564
|
+
self,
|
|
565
|
+
prop: DMSProperty,
|
|
566
|
+
read: ReadProperties,
|
|
567
|
+
row_no: int,
|
|
568
|
+
container_ref_by_entity: dict[ContainerReference, ParsedEntity],
|
|
569
|
+
container_entities: set[ParsedEntity],
|
|
570
|
+
) -> None:
|
|
437
571
|
if prop.constraint is None or prop.container_property is None or prop.container is None:
|
|
438
572
|
return
|
|
439
573
|
loc = (self.Sheets.properties, row_no, self.PropertyColumns.constraint)
|
|
@@ -443,15 +577,40 @@ class DMSTableReader:
|
|
|
443
577
|
if created is None:
|
|
444
578
|
continue
|
|
445
579
|
order = self._read_order(constraint.properties, loc)
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
580
|
+
|
|
581
|
+
if prop.container in container_entities:
|
|
582
|
+
read.constraints[(prop.container, constraint.suffix)].append(
|
|
583
|
+
ReadConstraint(
|
|
584
|
+
prop_id=prop.container_property,
|
|
585
|
+
order=order,
|
|
586
|
+
constraint_id=constraint.suffix,
|
|
587
|
+
row_no=row_no,
|
|
588
|
+
constraint=created,
|
|
589
|
+
)
|
|
453
590
|
)
|
|
454
|
-
|
|
591
|
+
continue
|
|
592
|
+
# The container entity was not found in the containers table. This could either be because the
|
|
593
|
+
# container is missing, or because either the container entity in the Properties table and the
|
|
594
|
+
# Container table are specified with/without default space/version inconsistently.
|
|
595
|
+
try:
|
|
596
|
+
container_ref = ContainerReference.model_validate(self._create_container_ref(prop.container))
|
|
597
|
+
except ValidationError:
|
|
598
|
+
# Error will be reported when reading the containers
|
|
599
|
+
continue
|
|
600
|
+
if container_ref in container_ref_by_entity:
|
|
601
|
+
container_ref_entity = container_ref_by_entity[container_ref]
|
|
602
|
+
read.constraints[(container_ref_entity, constraint.suffix)].append(
|
|
603
|
+
ReadConstraint(
|
|
604
|
+
prop_id=prop.container_property,
|
|
605
|
+
order=order,
|
|
606
|
+
constraint_id=constraint.suffix,
|
|
607
|
+
row_no=row_no,
|
|
608
|
+
constraint=created,
|
|
609
|
+
)
|
|
610
|
+
)
|
|
611
|
+
else:
|
|
612
|
+
# Error is reported when reading the property.
|
|
613
|
+
...
|
|
455
614
|
|
|
456
615
|
@staticmethod
|
|
457
616
|
def read_property_constraint(constraint: ParsedEntity, prop_id: str) -> dict[str, Any]:
|
|
@@ -733,16 +892,45 @@ class DMSTableReader:
|
|
|
733
892
|
return views_requests, set(rows_by_seen.keys())
|
|
734
893
|
|
|
735
894
|
def read_data_model(self, tables: TableDMS, valid_view_entities: set[ParsedEntity]) -> DataModelRequest:
|
|
736
|
-
data = {
|
|
895
|
+
data: dict[str, Any] = {
|
|
737
896
|
**{meta.key: meta.value for meta in tables.metadata},
|
|
738
897
|
"views": [self._create_view_ref(view.view) for view in tables.views if view.view in valid_view_entities],
|
|
739
898
|
}
|
|
899
|
+
if description := self._create_description_field(data):
|
|
900
|
+
data["description"] = description
|
|
740
901
|
model = self._validate_obj(DataModelRequest, data, (self.Sheets.metadata,), field_name="value")
|
|
741
902
|
if model is None:
|
|
742
903
|
# This is the last step, so we can raise the error here.
|
|
743
904
|
raise DataModelImportException(self.errors) from None
|
|
744
905
|
return model
|
|
745
906
|
|
|
907
|
+
def _create_description_field(self, data: dict[str, Any]) -> str | None:
|
|
908
|
+
"""DataModelRequest does not have a 'creator' field, this is a special addition that the Neat tables
|
|
909
|
+
format supports (and recommends using). To keep it, Neat adds it to the suffix of the description field.
|
|
910
|
+
"""
|
|
911
|
+
if CREATOR_KEY not in data and CREATOR_KEY.title() not in data:
|
|
912
|
+
return None
|
|
913
|
+
creator_val = data.pop(CREATOR_KEY, data.pop(CREATOR_KEY.title(), None))
|
|
914
|
+
|
|
915
|
+
if not creator_val:
|
|
916
|
+
return None
|
|
917
|
+
|
|
918
|
+
creator = str(creator_val)
|
|
919
|
+
# We do a split/join to clean up any spaces around commas. Ensuring that we have a consistent
|
|
920
|
+
# canonical format.
|
|
921
|
+
cleaned_creator = ", ".join(item.strip() for item in creator.split(","))
|
|
922
|
+
if not cleaned_creator:
|
|
923
|
+
return None
|
|
924
|
+
suffix = f"{CREATOR_MARKER}{cleaned_creator}"
|
|
925
|
+
description = data.get("description", "")
|
|
926
|
+
if len(description) + len(suffix) > DATA_MODEL_DESCRIPTION_MAX_LENGTH:
|
|
927
|
+
description = description[: DATA_MODEL_DESCRIPTION_MAX_LENGTH - len(suffix) - 4] + "..."
|
|
928
|
+
if description:
|
|
929
|
+
description = f"{description} {suffix}"
|
|
930
|
+
else:
|
|
931
|
+
description = suffix
|
|
932
|
+
return description
|
|
933
|
+
|
|
746
934
|
def _parse_entity(self, entity: str, loc: tuple[str | int, ...]) -> ParsedEntity | None:
|
|
747
935
|
try:
|
|
748
936
|
parsed = parse_entity(entity)
|
|
@@ -58,6 +58,27 @@ from ._references import (
|
|
|
58
58
|
ViewReference,
|
|
59
59
|
)
|
|
60
60
|
from ._schema import RequestSchema
|
|
61
|
+
from ._view_filter import (
|
|
62
|
+
AVAILABLE_FILTERS,
|
|
63
|
+
AndFilter,
|
|
64
|
+
ContainsAllFilterData,
|
|
65
|
+
ContainsAnyFilterData,
|
|
66
|
+
EqualsFilterData,
|
|
67
|
+
ExistsFilterData,
|
|
68
|
+
Filter,
|
|
69
|
+
FilterAdapter,
|
|
70
|
+
FilterDataDefinition,
|
|
71
|
+
HasDataFilter,
|
|
72
|
+
InFilterData,
|
|
73
|
+
InstanceReferencesFilterData,
|
|
74
|
+
MatchAllFilterData,
|
|
75
|
+
NestedFilterData,
|
|
76
|
+
NotFilter,
|
|
77
|
+
OrFilter,
|
|
78
|
+
OverlapsFilterData,
|
|
79
|
+
PrefixFilterData,
|
|
80
|
+
RangeFilterData,
|
|
81
|
+
)
|
|
61
82
|
from ._view_property import (
|
|
62
83
|
ConnectionPropertyDefinition,
|
|
63
84
|
ConstraintOrIndexState,
|
|
@@ -81,8 +102,10 @@ from ._views import (
|
|
|
81
102
|
)
|
|
82
103
|
|
|
83
104
|
__all__ = [
|
|
105
|
+
"AVAILABLE_FILTERS",
|
|
84
106
|
"DMS_DATA_TYPES",
|
|
85
107
|
"APIResource",
|
|
108
|
+
"AndFilter",
|
|
86
109
|
"BaseModelObject",
|
|
87
110
|
"BooleanProperty",
|
|
88
111
|
"BtreeIndex",
|
|
@@ -99,6 +122,8 @@ __all__ = [
|
|
|
99
122
|
"ContainerReference",
|
|
100
123
|
"ContainerRequest",
|
|
101
124
|
"ContainerResponse",
|
|
125
|
+
"ContainsAllFilterData",
|
|
126
|
+
"ContainsAnyFilterData",
|
|
102
127
|
"DataModelBody",
|
|
103
128
|
"DataModelReference",
|
|
104
129
|
"DataModelRequest",
|
|
@@ -110,23 +135,38 @@ __all__ = [
|
|
|
110
135
|
"DirectNodeRelation",
|
|
111
136
|
"EnumProperty",
|
|
112
137
|
"EnumValue",
|
|
138
|
+
"EqualsFilterData",
|
|
139
|
+
"ExistsFilterData",
|
|
113
140
|
"FileCDFExternalIdReference",
|
|
141
|
+
"Filter",
|
|
142
|
+
"FilterAdapter",
|
|
143
|
+
"FilterDataDefinition",
|
|
114
144
|
"Float32Property",
|
|
115
145
|
"Float64Property",
|
|
116
146
|
"FloatProperty",
|
|
147
|
+
"HasDataFilter",
|
|
148
|
+
"InFilterData",
|
|
117
149
|
"Index",
|
|
118
150
|
"IndexAdapter",
|
|
119
151
|
"IndexDefinition",
|
|
152
|
+
"InstanceReferencesFilterData",
|
|
120
153
|
"Int32Property",
|
|
121
154
|
"Int64Property",
|
|
122
155
|
"InvertedIndex",
|
|
123
156
|
"JSONProperty",
|
|
124
157
|
"ListablePropertyTypeDefinition",
|
|
158
|
+
"MatchAllFilterData",
|
|
125
159
|
"MultiEdgeProperty",
|
|
126
160
|
"MultiReverseDirectRelationPropertyRequest",
|
|
127
161
|
"MultiReverseDirectRelationPropertyResponse",
|
|
162
|
+
"NestedFilterData",
|
|
128
163
|
"NodeReference",
|
|
164
|
+
"NotFilter",
|
|
165
|
+
"OrFilter",
|
|
166
|
+
"OverlapsFilterData",
|
|
167
|
+
"PrefixFilterData",
|
|
129
168
|
"PropertyTypeDefinition",
|
|
169
|
+
"RangeFilterData",
|
|
130
170
|
"RequestSchema",
|
|
131
171
|
"RequiresConstraintDefinition",
|
|
132
172
|
"Resource",
|
|
@@ -4,6 +4,7 @@ CONTAINER_AND_VIEW_PROPERTIES_IDENTIFIER_PATTERN = r"^[a-zA-Z0-9][a-zA-Z0-9_-]{0
|
|
|
4
4
|
INSTANCE_ID_PATTERN = r"^[^\x00]{1,256}$"
|
|
5
5
|
ENUM_VALUE_IDENTIFIER_PATTERN = r"^[_A-Za-z][_0-9A-Za-z]{0,127}$"
|
|
6
6
|
DM_VERSION_PATTERN = r"^[a-zA-Z0-9]([.a-zA-Z0-9_-]{0,41}[a-zA-Z0-9])?$"
|
|
7
|
+
DATA_MODEL_DESCRIPTION_MAX_LENGTH = 1024
|
|
7
8
|
FORBIDDEN_ENUM_VALUES = frozenset({"true", "false", "null"})
|
|
8
9
|
FORBIDDEN_SPACES = frozenset(["space", "cdf", "dms", "pg3", "shared", "system", "node", "edge"])
|
|
9
10
|
FORBIDDEN_CONTAINER_AND_VIEW_EXTERNAL_IDS = frozenset(
|
|
@@ -6,6 +6,7 @@ from pydantic_core.core_schema import FieldSerializationInfo
|
|
|
6
6
|
|
|
7
7
|
from ._base import APIResource, Resource, WriteableResource
|
|
8
8
|
from ._constants import (
|
|
9
|
+
DATA_MODEL_DESCRIPTION_MAX_LENGTH,
|
|
9
10
|
DM_EXTERNAL_ID_PATTERN,
|
|
10
11
|
DM_VERSION_PATTERN,
|
|
11
12
|
SPACE_FORMAT_PATTERN,
|
|
@@ -46,7 +47,7 @@ class DataModel(Resource, APIResource[DataModelReference], ABC):
|
|
|
46
47
|
description: str | None = Field(
|
|
47
48
|
default=None,
|
|
48
49
|
description="Description of the data model.",
|
|
49
|
-
max_length=
|
|
50
|
+
max_length=DATA_MODEL_DESCRIPTION_MAX_LENGTH,
|
|
50
51
|
)
|
|
51
52
|
# The API supports View here, but in Neat we will only use ViewReference
|
|
52
53
|
views: list[ViewReference] | None = Field(
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Annotated, Any, Literal, TypeAlias, get_args
|
|
3
|
+
|
|
4
|
+
from pydantic import BeforeValidator, Field, JsonValue, TypeAdapter, model_serializer
|
|
5
|
+
from pydantic_core.core_schema import FieldSerializationInfo
|
|
6
|
+
|
|
7
|
+
from cognite.neat._utils.text import humanize_collection
|
|
8
|
+
from cognite.neat._utils.useful_types import BaseModelObject
|
|
9
|
+
|
|
10
|
+
from ._references import ContainerReference, NodeReference, ViewReference
|
|
11
|
+
|
|
12
|
+
# Base classes and helpers
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Parameter(BaseModelObject):
|
|
16
|
+
parameter: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class FilterDataDefinition(BaseModelObject, ABC):
|
|
20
|
+
"""Base class for filter data models."""
|
|
21
|
+
|
|
22
|
+
# This is an internal field used for discriminating between filter types. It is not part of the actual
|
|
23
|
+
# data sent to or received from the API. See the _move_filter_key function for more details.
|
|
24
|
+
filter_type: str = Field(..., exclude=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class PropertyReference(FilterDataDefinition, ABC):
|
|
28
|
+
"""Represents the property path in filters."""
|
|
29
|
+
|
|
30
|
+
property: list[str] = Field(..., min_length=2, max_length=3)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## Leaf filters that follows the standard pattern
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class EqualsFilterData(PropertyReference):
|
|
37
|
+
filter_type: Literal["equals"] = Field("equals", exclude=True)
|
|
38
|
+
value: JsonValue | PropertyReference
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class InFilterData(PropertyReference):
|
|
42
|
+
filter_type: Literal["in"] = Field("in", exclude=True)
|
|
43
|
+
values: list[JsonValue] | PropertyReference
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RangeFilterData(PropertyReference):
|
|
47
|
+
filter_type: Literal["range"] = Field("range", exclude=True)
|
|
48
|
+
gt: str | int | float | PropertyReference | None = None
|
|
49
|
+
gte: str | int | float | PropertyReference | None = None
|
|
50
|
+
lt: str | int | float | PropertyReference | None = None
|
|
51
|
+
lte: str | int | float | PropertyReference | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PrefixFilterData(PropertyReference):
|
|
55
|
+
filter_type: Literal["prefix"] = Field("prefix", exclude=True)
|
|
56
|
+
value: str | Parameter
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ExistsFilterData(PropertyReference):
|
|
60
|
+
filter_type: Literal["exists"] = Field("exists", exclude=True)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ContainsAnyFilterData(PropertyReference):
|
|
64
|
+
filter_type: Literal["containsAny"] = Field("containsAny", exclude=True)
|
|
65
|
+
values: list[JsonValue] | PropertyReference
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ContainsAllFilterData(PropertyReference):
|
|
69
|
+
filter_type: Literal["containsAll"] = Field("containsAll", exclude=True)
|
|
70
|
+
values: list[JsonValue] | PropertyReference
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class MatchAllFilterData(FilterDataDefinition):
|
|
74
|
+
filter_type: Literal["matchAll"] = Field("matchAll", exclude=True)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class NestedFilterData(PropertyReference):
|
|
78
|
+
filter_type: Literal["nested"] = Field("nested", exclude=True)
|
|
79
|
+
scope: list[str] = Field(..., min_length=1, max_length=3)
|
|
80
|
+
filter: "Filter"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class OverlapsFilterData(PropertyReference):
|
|
84
|
+
filter_type: Literal["overlaps"] = Field("overlaps", exclude=True)
|
|
85
|
+
start_property: list[str] = Field(..., min_length=1, max_length=3)
|
|
86
|
+
end_property: list[str] = Field(..., min_length=1, max_length=3)
|
|
87
|
+
gt: str | int | float | PropertyReference | None = None
|
|
88
|
+
gte: str | int | float | PropertyReference | None = None
|
|
89
|
+
lt: str | int | float | PropertyReference | None = None
|
|
90
|
+
lte: str | int | float | PropertyReference | None = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ListFilterDataDefinition(FilterDataDefinition, ABC):
|
|
94
|
+
"""Base class for filters that operate on lists of values."""
|
|
95
|
+
|
|
96
|
+
data: list[Any]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
## Leaf filters with custom serialization logic due to creativity in the API design
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class HasDataFilter(ListFilterDataDefinition):
|
|
103
|
+
filter_type: Literal["hasData"] = Field("hasData", exclude=True)
|
|
104
|
+
data: list[ViewReference | ContainerReference]
|
|
105
|
+
|
|
106
|
+
# MyPy complains about thet signature of the method here, even though its compatible with the pydantic source code.
|
|
107
|
+
# And tests are passing fine.
|
|
108
|
+
@model_serializer(mode="plain") # type: ignore[type-var]
|
|
109
|
+
def serialize_model(self, info: FieldSerializationInfo) -> list[dict[str, Any]]:
|
|
110
|
+
output: list[dict[str, Any]] = []
|
|
111
|
+
for item in self.data:
|
|
112
|
+
item_dict = item.model_dump(**vars(info))
|
|
113
|
+
if isinstance(item, ViewReference):
|
|
114
|
+
item_dict["type"] = "view"
|
|
115
|
+
elif isinstance(item, ContainerReference):
|
|
116
|
+
item_dict["type"] = "container"
|
|
117
|
+
output.append(item_dict)
|
|
118
|
+
return output
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class InstanceReferencesFilterData(ListFilterDataDefinition):
|
|
122
|
+
filter_type: Literal["instanceReferences"] = Field("instanceReferences", exclude=True)
|
|
123
|
+
data: list[NodeReference]
|
|
124
|
+
|
|
125
|
+
# MyPy complains about thet signature of the method here, even though its compatible with the pydantic source code.
|
|
126
|
+
# And tests are passing fine.
|
|
127
|
+
@model_serializer(mode="plain") # type: ignore[type-var]
|
|
128
|
+
def serialize_model(self, info: FieldSerializationInfo) -> list[dict[str, Any]]:
|
|
129
|
+
return [item.model_dump(**vars(info)) for item in self.data]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
## Logical filters combining other filters
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class AndFilter(ListFilterDataDefinition):
|
|
136
|
+
filter_type: Literal["and"] = Field("and", exclude=True)
|
|
137
|
+
data: "list[Filter]"
|
|
138
|
+
|
|
139
|
+
# MyPy complains about thet signature of the method here, even though its compatible with the pydantic source code.
|
|
140
|
+
# And tests are passing fine.
|
|
141
|
+
@model_serializer(mode="plain") # type: ignore[type-var]
|
|
142
|
+
def serialize_model(self, info: FieldSerializationInfo) -> list[dict[str, Any]]:
|
|
143
|
+
return [FilterAdapter.dump_python(item, **vars(info)) for item in self.data]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class OrFilter(ListFilterDataDefinition):
|
|
147
|
+
filter_type: Literal["or"] = Field("or", exclude=True)
|
|
148
|
+
data: "list[Filter]"
|
|
149
|
+
|
|
150
|
+
# MyPy complains about thet signature of the method here, even though its compatible with the pydantic source code.
|
|
151
|
+
# And tests are passing fine.
|
|
152
|
+
@model_serializer(mode="plain") # type: ignore[type-var]
|
|
153
|
+
def serialize_model(self, info: FieldSerializationInfo) -> list[dict[str, Any]]:
|
|
154
|
+
return [FilterAdapter.dump_python(item, **vars(info)) for item in self.data]
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class NotFilter(FilterDataDefinition):
|
|
158
|
+
filter_type: Literal["not"] = Field("not", exclude=True)
|
|
159
|
+
data: "Filter"
|
|
160
|
+
|
|
161
|
+
# MyPy complains about thet signature of the method here, even though its compatible with the pydantic source code.
|
|
162
|
+
# And tests are passing fine.
|
|
163
|
+
@model_serializer(mode="plain") # type: ignore[type-var]
|
|
164
|
+
def serialize_model(self, info: FieldSerializationInfo) -> dict[str, Any]:
|
|
165
|
+
return FilterAdapter.dump_python(self.data, **vars(info))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
FilterData = Annotated[
|
|
169
|
+
EqualsFilterData
|
|
170
|
+
| PrefixFilterData
|
|
171
|
+
| InFilterData
|
|
172
|
+
| RangeFilterData
|
|
173
|
+
| ExistsFilterData
|
|
174
|
+
| ContainsAnyFilterData
|
|
175
|
+
| ContainsAllFilterData
|
|
176
|
+
| MatchAllFilterData
|
|
177
|
+
| NestedFilterData
|
|
178
|
+
| OverlapsFilterData
|
|
179
|
+
| HasDataFilter
|
|
180
|
+
| InstanceReferencesFilterData
|
|
181
|
+
| AndFilter
|
|
182
|
+
| OrFilter
|
|
183
|
+
| NotFilter,
|
|
184
|
+
Field(discriminator="filter_type"),
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
FilterTypes: TypeAlias = Literal[
|
|
189
|
+
"equals",
|
|
190
|
+
"prefix",
|
|
191
|
+
"in",
|
|
192
|
+
"range",
|
|
193
|
+
"exists",
|
|
194
|
+
"containsAny",
|
|
195
|
+
"containsAll",
|
|
196
|
+
"matchAll",
|
|
197
|
+
"nested",
|
|
198
|
+
"overlaps",
|
|
199
|
+
"hasData",
|
|
200
|
+
"instanceReferences",
|
|
201
|
+
"and",
|
|
202
|
+
"or",
|
|
203
|
+
"not",
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
AVAILABLE_FILTERS: frozenset[str] = frozenset(get_args(FilterTypes))
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _move_filter_key(value: Any) -> Any:
|
|
210
|
+
"""The DMS API filters have an unusual structure.
|
|
211
|
+
|
|
212
|
+
It has the filter type as the key of the outer dict, and then the actual filter data as the value, e.g.,
|
|
213
|
+
{
|
|
214
|
+
"equals": {
|
|
215
|
+
"property": [...],
|
|
216
|
+
"value": ...
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
We could have modeled it that way with Pydantic, and had an union of pydantic models of all possible filter types.
|
|
220
|
+
However, validating union types in Pydantic without a discriminator leads to poor error messages. If the filter
|
|
221
|
+
data does not comply with any of the union types, Pydantic will give one error message per union type. For exampl,
|
|
222
|
+
if the user writes
|
|
223
|
+
{
|
|
224
|
+
"equals": {
|
|
225
|
+
"property": "my_property" # Should be a list,
|
|
226
|
+
"value": 'my_value'
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
Pydantic will give 15 error messages, one for each filter type in the union, saying that the data does not
|
|
230
|
+
comply with that filter type. This is not very user-friendly.
|
|
231
|
+
|
|
232
|
+
Instead, we introduce an internal field "filter_type" inside the filter data models, and use that as a
|
|
233
|
+
discriminator. This will enable the validation to be two steps. First, we validate the outer key and
|
|
234
|
+
that it is a known filter type. Then, we move that key inside the filter data as the "filter_type" field, and
|
|
235
|
+
validate the filter data against the correct model based on that discriminator.
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
This function transforms the data from the outer-key format to the inner-key format. For example, it transforms
|
|
239
|
+
the equals filter form above into
|
|
240
|
+
|
|
241
|
+
{
|
|
242
|
+
"equals": {
|
|
243
|
+
"property": [...],
|
|
244
|
+
"value": ...,
|
|
245
|
+
"filterType": "equals"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
"""
|
|
249
|
+
if not isinstance(value, dict):
|
|
250
|
+
return value
|
|
251
|
+
if len(value) != 1:
|
|
252
|
+
raise ValueError("Filter data must have exactly one key.")
|
|
253
|
+
if "filterType" in value:
|
|
254
|
+
# Already in the correct format
|
|
255
|
+
return value
|
|
256
|
+
key, data = next(iter(value.items()))
|
|
257
|
+
if key not in AVAILABLE_FILTERS:
|
|
258
|
+
raise ValueError(
|
|
259
|
+
f"Unknown filter type: {key!r}. Available filter types: {humanize_collection(AVAILABLE_FILTERS)}."
|
|
260
|
+
)
|
|
261
|
+
if isinstance(data, dict) and key == "not":
|
|
262
|
+
# Not is a recursive filter, so we need to move the filter key inside its data as well
|
|
263
|
+
output = _move_filter_key(data.copy())
|
|
264
|
+
return {key: {"filterType": key, "data": output}}
|
|
265
|
+
elif isinstance(data, dict):
|
|
266
|
+
output = data.copy()
|
|
267
|
+
output["filterType"] = key
|
|
268
|
+
return {key: output}
|
|
269
|
+
elif isinstance(data, list) and key in {"and", "or"}:
|
|
270
|
+
# And and Or are recursive filters, so we need to move the filter key inside each of their data items as well
|
|
271
|
+
return {key: {"filterType": key, "data": [_move_filter_key(item) for item in data]}}
|
|
272
|
+
elif isinstance(data, list):
|
|
273
|
+
# Leaf list filters, hasData and instanceReferences
|
|
274
|
+
return {key: {"filterType": key, "data": data}}
|
|
275
|
+
else:
|
|
276
|
+
# Let the regular validation handle it (possible not an issue)
|
|
277
|
+
return value
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
Filter = Annotated[dict[FilterTypes, FilterData], BeforeValidator(_move_filter_key)]
|
|
281
|
+
|
|
282
|
+
FilterAdapter: TypeAdapter[Filter] = TypeAdapter(Filter)
|
|
@@ -2,7 +2,7 @@ import re
|
|
|
2
2
|
from abc import ABC
|
|
3
3
|
from typing import Any, Literal, TypeVar
|
|
4
4
|
|
|
5
|
-
from pydantic import Field,
|
|
5
|
+
from pydantic import Field, field_serializer, field_validator, model_validator
|
|
6
6
|
from pydantic_core.core_schema import FieldSerializationInfo
|
|
7
7
|
|
|
8
8
|
from cognite.neat._utils.text import humanize_collection
|
|
@@ -18,6 +18,7 @@ from ._constants import (
|
|
|
18
18
|
SPACE_FORMAT_PATTERN,
|
|
19
19
|
)
|
|
20
20
|
from ._references import ContainerReference, NodeReference, ViewReference
|
|
21
|
+
from ._view_filter import Filter
|
|
21
22
|
from ._view_property import (
|
|
22
23
|
EdgeProperty,
|
|
23
24
|
ViewCorePropertyResponse,
|
|
@@ -56,9 +57,9 @@ class View(Resource, APIResource[ViewReference], ABC):
|
|
|
56
57
|
description="Description of the view.",
|
|
57
58
|
max_length=1024,
|
|
58
59
|
)
|
|
59
|
-
filter:
|
|
60
|
+
filter: Filter | None = Field(
|
|
60
61
|
default=None,
|
|
61
|
-
description="A filter Domain Specific Language (DSL) used to
|
|
62
|
+
description="A filter Domain Specific Language (DSL) used to select which instances the view should include.",
|
|
62
63
|
)
|
|
63
64
|
implements: list[ViewReference] | None = Field(
|
|
64
65
|
default=None,
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.127.
|
|
1
|
+
__version__ = "0.127.20"
|
|
2
2
|
__engine__ = "^2.0.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognite-neat
|
|
3
|
-
Version: 0.127.
|
|
3
|
+
Version: 0.127.20
|
|
4
4
|
Summary: Knowledge graph transformation
|
|
5
5
|
Project-URL: Documentation, https://cognite-neat.readthedocs-hosted.com/
|
|
6
6
|
Project-URL: Homepage, https://cognite-neat.readthedocs-hosted.com/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
cognite/neat/__init__.py,sha256=Lo4DbjDOwnhCYUoAgPp5RG1fDdF7OlnomalTe7n1ydw,211
|
|
2
2
|
cognite/neat/_exceptions.py,sha256=ox-5hXpee4UJlPE7HpuEHV2C96aLbLKo-BhPDoOAzhA,1650
|
|
3
3
|
cognite/neat/_issues.py,sha256=wH1mnkrpBsHUkQMGUHFLUIQWQlfJ_qMfdF7q0d9wNhY,1871
|
|
4
|
-
cognite/neat/_version.py,sha256=
|
|
4
|
+
cognite/neat/_version.py,sha256=x1it8CkOgLQbQkRRhDB46FfLXgM0aI8us2ZRTHFumD4,47
|
|
5
5
|
cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
cognite/neat/v1.py,sha256=owqW5Mml2DSZx1AvPvwNRTBngfhBNrQ6EH-7CKL7Jp0,61
|
|
7
7
|
cognite/neat/_client/__init__.py,sha256=75Bh7eGhaN4sOt3ZcRzHl7pXaheu1z27kmTHeaI05vo,114
|
|
@@ -33,14 +33,14 @@ cognite/neat/_data_model/exporters/_base.py,sha256=rG_qAU5i5Hh5hUMep2UmDFFZID4x3
|
|
|
33
33
|
cognite/neat/_data_model/exporters/_table_exporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
cognite/neat/_data_model/exporters/_table_exporter/exporter.py,sha256=4BPu_Chtjh1EyOaKbThXYohsqllVOkCbSoNekNZuBXc,5159
|
|
35
35
|
cognite/neat/_data_model/exporters/_table_exporter/workbook.py,sha256=1Afk1WqeNe9tiNeSAm0HrF8jTQ1kTbIv1D9hMztKwO8,18482
|
|
36
|
-
cognite/neat/_data_model/exporters/_table_exporter/writer.py,sha256=
|
|
36
|
+
cognite/neat/_data_model/exporters/_table_exporter/writer.py,sha256=QsO2BWB-_Jw_hpawHtG26NOnLu6wwtDosT-c1acNLPw,19270
|
|
37
37
|
cognite/neat/_data_model/importers/__init__.py,sha256=dHnKnC_AXk42z6wzEHK15dxIOh8xSEkuUf_AFRZls0E,193
|
|
38
38
|
cognite/neat/_data_model/importers/_api_importer.py,sha256=H8Ow3Tt7utuAuBhC6s7yWvhGqunHAtE0r0XRsVAr6IE,7280
|
|
39
39
|
cognite/neat/_data_model/importers/_base.py,sha256=NRB0FcEBj4GaethU68nRffBfTedBBA866A3zfJNfmiQ,433
|
|
40
40
|
cognite/neat/_data_model/importers/_table_importer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
cognite/neat/_data_model/importers/_table_importer/data_classes.py,sha256=
|
|
41
|
+
cognite/neat/_data_model/importers/_table_importer/data_classes.py,sha256=7oy0dYXj2lW2F-9jzrosSAIlDBzcAewMu8e5sU5lHPw,9618
|
|
42
42
|
cognite/neat/_data_model/importers/_table_importer/importer.py,sha256=lQ4_Gpv0haEwQEDYZJaxtR9dL6Y0ys9jbjFfWxH6s2o,8870
|
|
43
|
-
cognite/neat/_data_model/importers/_table_importer/reader.py,sha256=
|
|
43
|
+
cognite/neat/_data_model/importers/_table_importer/reader.py,sha256=I9-zHCpJLo7bj4BabAzSgNBDVUAocdhlvBfy95JkWRw,49451
|
|
44
44
|
cognite/neat/_data_model/importers/_table_importer/source.py,sha256=h7u5ur5oetmvBs3wgj7Ody5uPF21QwxeAceoIhJ5qzo,3300
|
|
45
45
|
cognite/neat/_data_model/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
46
|
cognite/neat/_data_model/models/conceptual/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -49,12 +49,12 @@ cognite/neat/_data_model/models/conceptual/_concept.py,sha256=0Pk4W2TJ_Y0Z7oPHpz
|
|
|
49
49
|
cognite/neat/_data_model/models/conceptual/_data_model.py,sha256=mSX0z8i29ufcRUhvC_NPeo2xGidlK3B1n89kngY_SqQ,1695
|
|
50
50
|
cognite/neat/_data_model/models/conceptual/_properties.py,sha256=CpF37vJYBTLT4DH4ZOu2U-JyWtkb_27V8fw52qiaE_k,4007
|
|
51
51
|
cognite/neat/_data_model/models/conceptual/_property.py,sha256=blSZQxX52zaILAtjUkldPzPeysz7wnG-UGSNU5tacI8,4138
|
|
52
|
-
cognite/neat/_data_model/models/dms/__init__.py,sha256=
|
|
52
|
+
cognite/neat/_data_model/models/dms/__init__.py,sha256=CW5NPMRrMyY4iyZgqYb8eZkRuwbbXUDSVNMWep3zEPI,5326
|
|
53
53
|
cognite/neat/_data_model/models/dms/_base.py,sha256=931ODXnhrBrzf6vkjqu2IaFz8r2gGxuapn85yN_jkgg,889
|
|
54
|
-
cognite/neat/_data_model/models/dms/_constants.py,sha256=
|
|
54
|
+
cognite/neat/_data_model/models/dms/_constants.py,sha256=TaoE9kmNVEaTl_dDrZQL7YzgP4K13ff0Rc7nr4zbIgg,1384
|
|
55
55
|
cognite/neat/_data_model/models/dms/_constraints.py,sha256=cyGgDlByXAuSMWJg7Oc25fkp33LsA61M927bCzTWlbo,1458
|
|
56
56
|
cognite/neat/_data_model/models/dms/_container.py,sha256=wtQbNUwtpymltT1jav8wD4kIfjaIYnvhhz1KS0ffAbo,6044
|
|
57
|
-
cognite/neat/_data_model/models/dms/_data_model.py,sha256=
|
|
57
|
+
cognite/neat/_data_model/models/dms/_data_model.py,sha256=tq_JGNN-1JxG46bhBhunZiLedklYbDXFEfINB0x3a3Q,3219
|
|
58
58
|
cognite/neat/_data_model/models/dms/_data_types.py,sha256=FMt_d5aJD-o3s9VQWyyCVlHk7D_p3RlSNXBP1OACPs4,6424
|
|
59
59
|
cognite/neat/_data_model/models/dms/_http.py,sha256=YIRRowqkphFAYkx3foTeLyPMe9fNnmzhUCBDXe0u9Kk,926
|
|
60
60
|
cognite/neat/_data_model/models/dms/_indexes.py,sha256=ZtXe8ABuRcsAwRIZ9FCanS3uwZHpkOAhvDvjSXtx_Fs,900
|
|
@@ -63,8 +63,9 @@ cognite/neat/_data_model/models/dms/_references.py,sha256=x2sK_YnEpWtLED4j8dqrqV
|
|
|
63
63
|
cognite/neat/_data_model/models/dms/_schema.py,sha256=2JFLcm52smzPdtZ69Lf02UbYAD8I_hpRbI7ZAzdxJJs,641
|
|
64
64
|
cognite/neat/_data_model/models/dms/_space.py,sha256=jEOK_WTATaFDIfuUd6dCaDkW2ysKKQEkSUZf8dMyYWM,1903
|
|
65
65
|
cognite/neat/_data_model/models/dms/_types.py,sha256=5-cgC53AG186OZUqkltv7pMjcGNLuH7Etbn8IUcgk1c,447
|
|
66
|
+
cognite/neat/_data_model/models/dms/_view_filter.py,sha256=cfeEOtRz5SGFI0rmPD3jNl-V6_zJxyxgxYjG3Oz8OEU,10301
|
|
66
67
|
cognite/neat/_data_model/models/dms/_view_property.py,sha256=nJBPmw4KzJOdaQmvRfCE3A4FL-E13OsNUEufI64vLKo,9271
|
|
67
|
-
cognite/neat/_data_model/models/dms/_views.py,sha256=
|
|
68
|
+
cognite/neat/_data_model/models/dms/_views.py,sha256=1yxuwnsUM4WKItEY1hmJbMQKW0q3Dn321NmLmKLJeCM,8657
|
|
68
69
|
cognite/neat/_data_model/models/entities/__init__.py,sha256=7dDyES7fYl9LEREal59F038RdEvfGRpUOc6n_MtSgjU,836
|
|
69
70
|
cognite/neat/_data_model/models/entities/_base.py,sha256=PaNrD29iwxuqTpRWbmESMTxRhhKXmRyDF_cLZEC69dg,3927
|
|
70
71
|
cognite/neat/_data_model/models/entities/_constants.py,sha256=EK9Bus8UgFgxK5cVFMTAqWSl6aWkDe7d59hpUmlHlBs,517
|
|
@@ -311,7 +312,7 @@ cognite/neat/v0/session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4
|
|
|
311
312
|
cognite/neat/v0/session/engine/_import.py,sha256=1QxA2_EK613lXYAHKQbZyw2yjo5P9XuiX4Z6_6-WMNQ,169
|
|
312
313
|
cognite/neat/v0/session/engine/_interface.py,sha256=3W-cYr493c_mW3P5O6MKN1xEQg3cA7NHR_ev3zdF9Vk,533
|
|
313
314
|
cognite/neat/v0/session/engine/_load.py,sha256=u0x7vuQCRoNcPt25KJBJRn8sJabonYK4vtSZpiTdP4k,5201
|
|
314
|
-
cognite_neat-0.127.
|
|
315
|
-
cognite_neat-0.127.
|
|
316
|
-
cognite_neat-0.127.
|
|
317
|
-
cognite_neat-0.127.
|
|
315
|
+
cognite_neat-0.127.20.dist-info/METADATA,sha256=947oG_DqzFdW87-nrRmFBMEpnHVuNcFj0VcHLYd9PAk,9150
|
|
316
|
+
cognite_neat-0.127.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
317
|
+
cognite_neat-0.127.20.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
|
|
318
|
+
cognite_neat-0.127.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|