cognite-neat 0.121.0__py3-none-any.whl → 0.121.2__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/_version.py +1 -1
- cognite/neat/core/_client/_api/statistics.py +91 -0
- cognite/neat/core/_client/_api_client.py +2 -0
- cognite/neat/core/_client/data_classes/statistics.py +125 -0
- cognite/neat/core/_client/testing.py +4 -0
- cognite/neat/core/_constants.py +6 -7
- cognite/neat/core/{_rules → _data_model}/_constants.py +25 -18
- cognite/neat/core/_data_model/_shared.py +59 -0
- cognite/neat/core/_data_model/analysis/__init__.py +3 -0
- cognite/neat/core/{_rules → _data_model}/analysis/_base.py +202 -195
- cognite/neat/core/{_rules → _data_model}/catalog/__init__.py +1 -1
- cognite/neat/core/{_rules → _data_model}/exporters/__init__.py +5 -5
- cognite/neat/core/{_rules → _data_model}/exporters/_base.py +10 -8
- cognite/neat/core/{_rules/exporters/_rules2dms.py → _data_model/exporters/_data_model2dms.py} +22 -18
- cognite/neat/core/{_rules/exporters/_rules2excel.py → _data_model/exporters/_data_model2excel.py} +61 -56
- cognite/neat/core/{_rules/exporters/_rules2instance_template.py → _data_model/exporters/_data_model2instance_template.py} +11 -9
- cognite/neat/core/{_rules/exporters/_rules2ontology.py → _data_model/exporters/_data_model2ontology.py} +64 -61
- cognite/neat/core/{_rules/exporters/_rules2yaml.py → _data_model/exporters/_data_model2yaml.py} +21 -18
- cognite/neat/core/{_rules → _data_model}/importers/__init__.py +6 -8
- cognite/neat/core/{_rules → _data_model}/importers/_base.py +8 -6
- cognite/neat/core/_data_model/importers/_base_file_reader.py +56 -0
- cognite/neat/core/{_rules/importers/_yaml2rules.py → _data_model/importers/_dict2data_model.py} +41 -21
- cognite/neat/core/{_rules/importers/_dms2rules.py → _data_model/importers/_dms2data_model.py} +79 -66
- cognite/neat/core/{_rules/importers/_dtdl2rules → _data_model/importers/_dtdl2data_model}/dtdl_converter.py +41 -41
- cognite/neat/core/{_rules/importers/_dtdl2rules → _data_model/importers/_dtdl2data_model}/dtdl_importer.py +16 -16
- cognite/neat/core/{_rules/importers/_dtdl2rules → _data_model/importers/_dtdl2data_model}/spec.py +3 -3
- cognite/neat/core/{_rules → _data_model}/importers/_rdf/_base.py +18 -16
- cognite/neat/core/{_rules → _data_model}/importers/_rdf/_imf2rules.py +17 -17
- cognite/neat/core/{_rules → _data_model}/importers/_rdf/_inference2rules.py +50 -50
- cognite/neat/core/{_rules → _data_model}/importers/_rdf/_owl2rules.py +14 -14
- cognite/neat/core/{_rules → _data_model}/importers/_rdf/_shared.py +25 -25
- cognite/neat/core/{_rules/importers/_spreadsheet2rules.py → _data_model/importers/_spreadsheet2data_model.py} +69 -38
- cognite/neat/core/_data_model/models/__init__.py +36 -0
- cognite/neat/core/{_rules/models/_base_input.py → _data_model/models/_base_unverified.py} +12 -12
- cognite/neat/core/{_rules/models/_base_rules.py → _data_model/models/_base_verified.py} +13 -13
- cognite/neat/core/{_rules → _data_model}/models/_types.py +13 -13
- cognite/neat/core/_data_model/models/conceptual/__init__.py +25 -0
- cognite/neat/core/{_rules/models/information/_rules_input.py → _data_model/models/conceptual/_unverified.py} +46 -43
- cognite/neat/core/{_rules/models/information → _data_model/models/conceptual}/_validation.py +93 -79
- cognite/neat/core/{_rules/models/information/_rules.py → _data_model/models/conceptual/_verified.py} +83 -83
- cognite/neat/core/{_rules → _data_model}/models/data_types.py +4 -4
- cognite/neat/core/{_rules → _data_model}/models/entities/__init__.py +8 -8
- cognite/neat/core/{_rules → _data_model}/models/entities/_loaders.py +12 -11
- cognite/neat/core/{_rules → _data_model}/models/entities/_multi_value.py +7 -7
- cognite/neat/core/{_rules → _data_model}/models/entities/_single_value.py +45 -39
- cognite/neat/core/{_rules → _data_model}/models/entities/_types.py +9 -3
- cognite/neat/core/{_rules → _data_model}/models/entities/_wrapped.py +3 -3
- cognite/neat/core/{_rules → _data_model}/models/mapping/_classic2core.py +12 -9
- cognite/neat/core/_data_model/models/physical/__init__.py +40 -0
- cognite/neat/core/{_rules/models/dms → _data_model/models/physical}/_exporter.py +83 -64
- cognite/neat/core/{_rules/models/dms/_rules_input.py → _data_model/models/physical/_unverified.py} +56 -44
- cognite/neat/core/{_rules/models/dms → _data_model/models/physical}/_validation.py +20 -17
- cognite/neat/core/{_rules/models/dms/_rules.py → _data_model/models/physical/_verified.py} +79 -71
- cognite/neat/core/{_rules → _data_model}/transformers/__init__.py +27 -23
- cognite/neat/core/{_rules → _data_model}/transformers/_base.py +29 -19
- cognite/neat/core/{_rules → _data_model}/transformers/_converters.py +758 -659
- cognite/neat/core/{_rules → _data_model}/transformers/_mapping.py +79 -60
- cognite/neat/core/_data_model/transformers/_verification.py +120 -0
- cognite/neat/core/{_graph → _instances}/extractors/_base.py +2 -2
- cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_base.py +1 -1
- cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_classic.py +17 -11
- cognite/neat/core/{_graph → _instances}/extractors/_dms_graph.py +47 -39
- cognite/neat/core/{_graph → _instances}/extractors/_mock_graph_generator.py +102 -99
- cognite/neat/core/{_graph → _instances}/extractors/_rdf_file.py +2 -2
- cognite/neat/core/{_graph → _instances}/loaders/_base.py +2 -2
- cognite/neat/core/{_graph → _instances}/loaders/_rdf2dms.py +16 -14
- cognite/neat/core/{_graph → _instances}/transformers/_base.py +7 -4
- cognite/neat/core/{_graph → _instances}/transformers/_classic_cdf.py +1 -1
- cognite/neat/core/{_graph → _instances}/transformers/_value_type.py +2 -6
- cognite/neat/core/_issues/_base.py +4 -4
- cognite/neat/core/_issues/errors/__init__.py +2 -2
- cognite/neat/core/_issues/errors/_wrapper.py +2 -2
- cognite/neat/core/_issues/warnings/__init__.py +2 -0
- cognite/neat/core/_issues/warnings/_models.py +4 -4
- cognite/neat/core/_issues/warnings/_properties.py +7 -0
- cognite/neat/core/_store/__init__.py +3 -3
- cognite/neat/core/_store/{_rules_store.py → _data_model.py} +128 -121
- cognite/neat/core/_store/{_graph_store.py → _instance.py} +7 -8
- cognite/neat/core/_store/_provenance.py +2 -2
- cognite/neat/core/_store/exceptions.py +4 -4
- cognite/neat/core/_utils/rdf_.py +14 -0
- cognite/neat/core/_utils/spreadsheet.py +1 -1
- cognite/neat/core/_utils/text.py +2 -2
- cognite/neat/session/_base.py +29 -25
- cognite/neat/session/_drop.py +3 -3
- cognite/neat/session/_fix.py +2 -2
- cognite/neat/session/_inspect.py +5 -5
- cognite/neat/session/_mapping.py +11 -9
- cognite/neat/session/_prepare.py +4 -4
- cognite/neat/session/_read.py +15 -15
- cognite/neat/session/_set.py +5 -5
- cognite/neat/session/_show.py +11 -11
- cognite/neat/session/_state.py +17 -17
- cognite/neat/session/_subset.py +14 -11
- cognite/neat/session/_template.py +19 -19
- cognite/neat/session/_to.py +21 -21
- cognite/neat/session/_wizard.py +1 -1
- {cognite_neat-0.121.0.dist-info → cognite_neat-0.121.2.dist-info}/METADATA +1 -1
- cognite_neat-0.121.2.dist-info/RECORD +189 -0
- cognite/neat/core/_rules/_shared.py +0 -43
- cognite/neat/core/_rules/analysis/__init__.py +0 -3
- cognite/neat/core/_rules/exporters/_validation.py +0 -14
- cognite/neat/core/_rules/models/__init__.py +0 -34
- cognite/neat/core/_rules/models/dms/__init__.py +0 -32
- cognite/neat/core/_rules/models/information/__init__.py +0 -20
- cognite/neat/core/_rules/transformers/_verification.py +0 -111
- cognite_neat-0.121.0.dist-info/RECORD +0 -187
- /cognite/neat/core/{_graph → _data_model}/__init__.py +0 -0
- /cognite/neat/core/{_rules → _data_model}/catalog/classic_model.xlsx +0 -0
- /cognite/neat/core/{_rules/catalog/info-rules-imf.xlsx → _data_model/catalog/conceptual-imf-data-model.xlsx} +0 -0
- /cognite/neat/core/{_rules → _data_model}/catalog/hello_world_pump.xlsx +0 -0
- /cognite/neat/core/{_rules/importers/_dtdl2rules → _data_model/importers/_dtdl2data_model}/__init__.py +0 -0
- /cognite/neat/core/{_rules/importers/_dtdl2rules → _data_model/importers/_dtdl2data_model}/_unit_lookup.py +0 -0
- /cognite/neat/core/{_rules → _data_model}/importers/_rdf/__init__.py +0 -0
- /cognite/neat/core/{_rules → _data_model}/models/entities/_constants.py +0 -0
- /cognite/neat/core/{_rules → _data_model}/models/mapping/__init__.py +0 -0
- /cognite/neat/core/{_rules → _data_model}/models/mapping/_classic2core.yaml +0 -0
- /cognite/neat/core/{_graph/extractors/_classic_cdf → _instances}/__init__.py +0 -0
- /cognite/neat/core/{_graph → _instances}/_shared.py +0 -0
- /cognite/neat/core/{_graph → _instances}/_tracking/__init__.py +0 -0
- /cognite/neat/core/{_graph → _instances}/_tracking/base.py +0 -0
- /cognite/neat/core/{_graph → _instances}/_tracking/log.py +0 -0
- /cognite/neat/core/{_graph → _instances}/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -0
- /cognite/neat/core/{_graph → _instances}/examples/Knowledge-Graph-Nordic44.xml +0 -0
- /cognite/neat/core/{_graph → _instances}/examples/__init__.py +0 -0
- /cognite/neat/core/{_graph → _instances}/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/__init__.py +0 -0
- /cognite/neat/core/{_rules → _instances/extractors/_classic_cdf}/__init__.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_assets.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_data_sets.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_events.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_files.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_labels.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_relationships.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_sequences.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_classic_cdf/_timeseries.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_dict.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_dms.py +0 -0
- /cognite/neat/core/{_graph → _instances}/extractors/_raw.py +0 -0
- /cognite/neat/core/{_graph → _instances}/loaders/__init__.py +0 -0
- /cognite/neat/core/{_graph → _instances}/queries/__init__.py +0 -0
- /cognite/neat/core/{_graph → _instances}/queries/_base.py +0 -0
- /cognite/neat/core/{_graph → _instances}/queries/_queries.py +0 -0
- /cognite/neat/core/{_graph → _instances}/queries/_select.py +0 -0
- /cognite/neat/core/{_graph → _instances}/queries/_update.py +0 -0
- /cognite/neat/core/{_graph → _instances}/transformers/__init__.py +0 -0
- /cognite/neat/core/{_graph → _instances}/transformers/_prune_graph.py +0 -0
- /cognite/neat/core/{_graph → _instances}/transformers/_rdfpath.py +0 -0
- {cognite_neat-0.121.0.dist-info → cognite_neat-0.121.2.dist-info}/WHEEL +0 -0
- {cognite_neat-0.121.0.dist-info → cognite_neat-0.121.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,13 +11,13 @@ import numpy
|
|
|
11
11
|
import pandas as pd
|
|
12
12
|
from rdflib import RDF, Literal, Namespace, URIRef
|
|
13
13
|
|
|
14
|
-
from cognite.neat.core.
|
|
15
|
-
from cognite.neat.core.
|
|
16
|
-
from cognite.neat.core.
|
|
17
|
-
from cognite.neat.core.
|
|
18
|
-
from cognite.neat.core.
|
|
19
|
-
from cognite.neat.core.
|
|
20
|
-
from cognite.neat.core.
|
|
14
|
+
from cognite.neat.core._data_model._constants import EntityTypes
|
|
15
|
+
from cognite.neat.core._data_model.analysis import DataModelAnalysis
|
|
16
|
+
from cognite.neat.core._data_model.models import ConceptualDataModel, PhysicalDataModel
|
|
17
|
+
from cognite.neat.core._data_model.models.conceptual import ConceptualProperty
|
|
18
|
+
from cognite.neat.core._data_model.models.data_types import DataType
|
|
19
|
+
from cognite.neat.core._data_model.models.entities import ConceptEntity
|
|
20
|
+
from cognite.neat.core._data_model.transformers import SubsetConceptualDataModel
|
|
21
21
|
from cognite.neat.core._shared import Triple
|
|
22
22
|
from cognite.neat.core._utils.rdf_ import remove_namespace_from_uri
|
|
23
23
|
|
|
@@ -38,31 +38,31 @@ class MockGraphGenerator(BaseExtractor):
|
|
|
38
38
|
|
|
39
39
|
def __init__(
|
|
40
40
|
self,
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
data_model: ConceptualDataModel | PhysicalDataModel,
|
|
42
|
+
concept_count: dict[str | ConceptEntity, int] | None = None,
|
|
43
43
|
stop_on_exception: bool = False,
|
|
44
44
|
allow_isolated_classes: bool = True,
|
|
45
45
|
):
|
|
46
|
-
if isinstance(
|
|
46
|
+
if isinstance(data_model, PhysicalDataModel):
|
|
47
47
|
# fixes potential issues with circular dependencies
|
|
48
|
-
from cognite.neat.core.
|
|
48
|
+
from cognite.neat.core._data_model.transformers import PhysicalToConceptual
|
|
49
49
|
|
|
50
|
-
self.rules =
|
|
51
|
-
elif isinstance(
|
|
52
|
-
self.rules =
|
|
50
|
+
self.rules = PhysicalToConceptual().transform(data_model)
|
|
51
|
+
elif isinstance(data_model, ConceptualDataModel):
|
|
52
|
+
self.rules = data_model
|
|
53
53
|
else:
|
|
54
54
|
raise ValueError("Rules must be of type InformationRules or DMSRules!")
|
|
55
55
|
|
|
56
|
-
if not
|
|
57
|
-
self.
|
|
58
|
-
|
|
56
|
+
if not concept_count:
|
|
57
|
+
self.concept_count = {
|
|
58
|
+
concept: 1 for concept in DataModelAnalysis(self.rules).defined_concepts(include_ancestors=True)
|
|
59
59
|
}
|
|
60
|
-
elif all(isinstance(key, str) for key in
|
|
61
|
-
self.
|
|
62
|
-
|
|
60
|
+
elif all(isinstance(key, str) for key in concept_count.keys()):
|
|
61
|
+
self.concept_count = {
|
|
62
|
+
ConceptEntity.load(f"{self.rules.metadata.prefix}:{key}"): value for key, value in concept_count.items()
|
|
63
63
|
}
|
|
64
|
-
elif all(isinstance(key,
|
|
65
|
-
self.
|
|
64
|
+
elif all(isinstance(key, ConceptEntity) for key in concept_count.keys()):
|
|
65
|
+
self.concept_count = cast(dict[ConceptEntity, int], concept_count)
|
|
66
66
|
else:
|
|
67
67
|
raise ValueError("Class count keys must be of type str! or ClassEntity! or empty dict!")
|
|
68
68
|
|
|
@@ -78,87 +78,90 @@ class MockGraphGenerator(BaseExtractor):
|
|
|
78
78
|
"""
|
|
79
79
|
return generate_triples(
|
|
80
80
|
self.rules,
|
|
81
|
-
self.
|
|
81
|
+
self.concept_count,
|
|
82
82
|
stop_on_exception=self.stop_on_exception,
|
|
83
|
-
|
|
83
|
+
allow_isolated_concepts=self.allow_isolated_classes,
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
def generate_triples(
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
data_model: ConceptualDataModel,
|
|
89
|
+
concept_count: dict[ConceptEntity, int],
|
|
90
90
|
stop_on_exception: bool = False,
|
|
91
|
-
|
|
91
|
+
allow_isolated_concepts: bool = True,
|
|
92
92
|
) -> list[Triple]:
|
|
93
|
-
"""Generate mock triples based on data model defined
|
|
93
|
+
"""Generate mock triples based on the conceptual data model defined and desired number
|
|
94
94
|
of class instances
|
|
95
95
|
|
|
96
96
|
Args:
|
|
97
97
|
rules : Rules defining the data model
|
|
98
|
-
|
|
98
|
+
concept_count: Target class count for each class in the ontology
|
|
99
99
|
stop_on_exception: To stop if exception is encountered or not, default is False
|
|
100
|
-
|
|
100
|
+
allow_isolated_concepts: To allow generation of instances for classes that are not
|
|
101
101
|
connected to any other class, default is True
|
|
102
102
|
|
|
103
103
|
Returns:
|
|
104
104
|
List of RDF triples, represented as tuples `(subject, predicate, object)`, that define data model instances
|
|
105
105
|
"""
|
|
106
106
|
|
|
107
|
-
namespace =
|
|
108
|
-
analysis =
|
|
109
|
-
|
|
107
|
+
namespace = data_model.metadata.namespace
|
|
108
|
+
analysis = DataModelAnalysis(data_model)
|
|
109
|
+
defined_concepts = analysis.defined_concepts(include_ancestors=True)
|
|
110
110
|
|
|
111
|
-
if
|
|
112
|
-
msg =
|
|
111
|
+
if non_existing_concepts := set(concept_count.keys()) - defined_concepts:
|
|
112
|
+
msg = (
|
|
113
|
+
f"Concept count contains concepts {non_existing_concepts} for which"
|
|
114
|
+
" properties are not defined in Data Model!"
|
|
115
|
+
)
|
|
113
116
|
if stop_on_exception:
|
|
114
117
|
raise ValueError(msg)
|
|
115
118
|
else:
|
|
116
119
|
msg += " These classes will be ignored."
|
|
117
120
|
warnings.warn(msg, stacklevel=2)
|
|
118
|
-
for
|
|
119
|
-
|
|
121
|
+
for concept in non_existing_concepts:
|
|
122
|
+
concept_count.pop(concept)
|
|
120
123
|
|
|
121
124
|
# Subset data model to only classes that are defined in class count
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if
|
|
125
|
-
else
|
|
125
|
+
data_model = (
|
|
126
|
+
SubsetConceptualDataModel(concepts=set(concept_count.keys())).transform(data_model)
|
|
127
|
+
if defined_concepts != set(concept_count.keys())
|
|
128
|
+
else data_model
|
|
126
129
|
)
|
|
127
130
|
|
|
128
|
-
|
|
131
|
+
concept_linkage = analysis.concept_linkage().to_pandas()
|
|
129
132
|
|
|
130
133
|
# Remove one of symmetric pairs from class linkage to maintain proper linking
|
|
131
134
|
# among instances of symmetrically linked classes
|
|
132
|
-
if sym_pairs := analysis.
|
|
133
|
-
|
|
135
|
+
if sym_pairs := analysis.symmetrically_connected_concepts():
|
|
136
|
+
concept_linkage = _remove_higher_occurring_sym_pair(concept_linkage, sym_pairs)
|
|
134
137
|
|
|
135
138
|
# Remove any of symmetric pairs containing classes that are not present class count
|
|
136
|
-
|
|
139
|
+
concept_linkage = _remove_non_requested_sym_pairs(concept_linkage, concept_count)
|
|
137
140
|
|
|
138
141
|
# Generate generation order for classes instances
|
|
139
|
-
generation_order = _prettify_generation_order(_get_generation_order(
|
|
142
|
+
generation_order = _prettify_generation_order(_get_generation_order(concept_linkage))
|
|
140
143
|
|
|
141
144
|
# Generated simple view of data model
|
|
142
|
-
|
|
145
|
+
properties_by_concepts = analysis.properties_by_concepts(include_ancestors=True)
|
|
143
146
|
|
|
144
147
|
# pregenerate instance ids for each remaining class
|
|
145
148
|
instance_ids = {
|
|
146
|
-
key: [URIRef(namespace[f"{key.suffix}-{i + 1}"]) for i in range(value)] for key, value in
|
|
149
|
+
key: [URIRef(namespace[f"{key.suffix}-{i + 1}"]) for i in range(value)] for key, value in concept_count.items()
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
# create triple for each class instance defining its type
|
|
150
153
|
triples: list[Triple] = []
|
|
151
|
-
for
|
|
154
|
+
for concept in concept_count:
|
|
152
155
|
triples += [
|
|
153
|
-
(
|
|
154
|
-
for
|
|
156
|
+
(concept_instance_id, RDF.type, URIRef(namespace[str(concept.suffix)]))
|
|
157
|
+
for concept_instance_id in instance_ids[concept]
|
|
155
158
|
]
|
|
156
159
|
|
|
157
160
|
# generate triples for connected classes
|
|
158
|
-
for
|
|
161
|
+
for concept in generation_order:
|
|
159
162
|
triples += _generate_triples_per_class(
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
concept,
|
|
164
|
+
properties_by_concepts,
|
|
162
165
|
sym_pairs,
|
|
163
166
|
instance_ids,
|
|
164
167
|
namespace,
|
|
@@ -166,11 +169,11 @@ def generate_triples(
|
|
|
166
169
|
)
|
|
167
170
|
|
|
168
171
|
# generate triples for isolated classes
|
|
169
|
-
if
|
|
170
|
-
for
|
|
172
|
+
if allow_isolated_concepts:
|
|
173
|
+
for concept in set(concept_count.keys()) - set(generation_order):
|
|
171
174
|
triples += _generate_triples_per_class(
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
concept,
|
|
176
|
+
properties_by_concepts,
|
|
174
177
|
sym_pairs,
|
|
175
178
|
instance_ids,
|
|
176
179
|
namespace,
|
|
@@ -181,11 +184,11 @@ def generate_triples(
|
|
|
181
184
|
|
|
182
185
|
|
|
183
186
|
def _get_generation_order(
|
|
184
|
-
|
|
187
|
+
concept_linkage: pd.DataFrame,
|
|
185
188
|
parent_col: str = "source_class",
|
|
186
189
|
child_col: str = "target_class",
|
|
187
190
|
) -> dict:
|
|
188
|
-
parent_child_list: list[list[str]] =
|
|
191
|
+
parent_child_list: list[list[str]] = concept_linkage[[parent_col, child_col]].values.tolist() # type: ignore[assignment]
|
|
189
192
|
# Build a directed graph and a list of all names that have no parent
|
|
190
193
|
graph: dict[str, set] = {name: set() for tup in parent_child_list for name in tup}
|
|
191
194
|
has_parent: dict[str, bool] = {name: False for tup in parent_child_list for name in tup}
|
|
@@ -217,44 +220,44 @@ def _prettify_generation_order(generation_order: dict, depth: dict | None = None
|
|
|
217
220
|
|
|
218
221
|
|
|
219
222
|
def _remove_higher_occurring_sym_pair(
|
|
220
|
-
|
|
223
|
+
concept_linkage: pd.DataFrame, sym_pairs: set[tuple[ConceptEntity, ConceptEntity]]
|
|
221
224
|
) -> pd.DataFrame:
|
|
222
225
|
"""Remove symmetric pair which is higher in occurrence."""
|
|
223
226
|
rows_to_remove = set()
|
|
224
227
|
for source, target in sym_pairs:
|
|
225
|
-
first_sym_property_occurrence =
|
|
226
|
-
(
|
|
228
|
+
first_sym_property_occurrence = concept_linkage[
|
|
229
|
+
(concept_linkage.source_class == source) & (concept_linkage.target_class == target)
|
|
227
230
|
].max_occurrence.values[0]
|
|
228
|
-
second_sym_property_occurrence =
|
|
229
|
-
(
|
|
231
|
+
second_sym_property_occurrence = concept_linkage[
|
|
232
|
+
(concept_linkage.source_class == target) & (concept_linkage.target_class == source)
|
|
230
233
|
].max_occurrence.values[0]
|
|
231
234
|
|
|
232
235
|
if first_sym_property_occurrence is None:
|
|
233
236
|
# this means that source occurrence is unbounded
|
|
234
|
-
index =
|
|
235
|
-
(
|
|
237
|
+
index = concept_linkage[
|
|
238
|
+
(concept_linkage.source_class == source) & (concept_linkage.target_class == target)
|
|
236
239
|
].index.values[0]
|
|
237
240
|
elif second_sym_property_occurrence is None or (
|
|
238
241
|
first_sym_property_occurrence <= second_sym_property_occurrence
|
|
239
242
|
and second_sym_property_occurrence > first_sym_property_occurrence
|
|
240
243
|
):
|
|
241
244
|
# this means that target occurrence is unbounded
|
|
242
|
-
index =
|
|
243
|
-
(
|
|
245
|
+
index = concept_linkage[
|
|
246
|
+
(concept_linkage.source_class == target) & (concept_linkage.target_class == source)
|
|
244
247
|
].index.values[0]
|
|
245
248
|
else:
|
|
246
|
-
index =
|
|
247
|
-
(
|
|
249
|
+
index = concept_linkage[
|
|
250
|
+
(concept_linkage.source_class == source) & (concept_linkage.target_class == target)
|
|
248
251
|
].index.values[0]
|
|
249
252
|
rows_to_remove.add(index)
|
|
250
253
|
|
|
251
|
-
return
|
|
254
|
+
return concept_linkage.drop(list(rows_to_remove))
|
|
252
255
|
|
|
253
256
|
|
|
254
|
-
def _remove_non_requested_sym_pairs(class_linkage: pd.DataFrame,
|
|
257
|
+
def _remove_non_requested_sym_pairs(class_linkage: pd.DataFrame, concept_count: dict) -> pd.DataFrame:
|
|
255
258
|
"""Remove symmetric pairs which classes are not found in class count."""
|
|
256
|
-
rows_to_remove = set(class_linkage[~(class_linkage["source_class"].isin(set(
|
|
257
|
-
rows_to_remove |= set(class_linkage[~(class_linkage["target_class"].isin(set(
|
|
259
|
+
rows_to_remove = set(class_linkage[~(class_linkage["source_class"].isin(set(concept_count.keys())))].index.values)
|
|
260
|
+
rows_to_remove |= set(class_linkage[~(class_linkage["target_class"].isin(set(concept_count.keys())))].index.values)
|
|
258
261
|
|
|
259
262
|
return class_linkage.drop(list(rows_to_remove))
|
|
260
263
|
|
|
@@ -293,41 +296,41 @@ def _generate_mock_data_property_triples(
|
|
|
293
296
|
|
|
294
297
|
|
|
295
298
|
def _generate_mock_object_property_triples(
|
|
296
|
-
|
|
297
|
-
property_definition:
|
|
298
|
-
|
|
299
|
-
sym_pairs: set[tuple[
|
|
300
|
-
instance_ids: dict[
|
|
299
|
+
concept: ConceptEntity,
|
|
300
|
+
property_definition: ConceptualProperty,
|
|
301
|
+
concept_property_pairs: dict[ConceptEntity, list[ConceptualProperty]],
|
|
302
|
+
sym_pairs: set[tuple[ConceptEntity, ConceptEntity]],
|
|
303
|
+
instance_ids: dict[ConceptEntity, list[URIRef]],
|
|
301
304
|
namespace: Namespace,
|
|
302
305
|
stop_on_exception: bool,
|
|
303
306
|
) -> list[tuple[URIRef, URIRef, URIRef]]:
|
|
304
307
|
"""Generates triples for object properties."""
|
|
305
308
|
if property_definition.value_type not in instance_ids:
|
|
306
|
-
msg = f"
|
|
309
|
+
msg = f"Concept {property_definition.value_type} not found in concept count! "
|
|
307
310
|
if stop_on_exception:
|
|
308
311
|
raise ValueError(msg)
|
|
309
312
|
else:
|
|
310
313
|
msg += (
|
|
311
314
|
f"Skipping creating triples for property {property_definition.name} "
|
|
312
|
-
f"of
|
|
315
|
+
f"of concept {concept.suffix} which expects values of this type!"
|
|
313
316
|
)
|
|
314
317
|
warnings.warn(msg, stacklevel=2)
|
|
315
318
|
return []
|
|
316
319
|
|
|
317
320
|
# Handling symmetric property
|
|
318
321
|
|
|
319
|
-
if tuple((
|
|
320
|
-
|
|
322
|
+
if tuple((concept, property_definition.value_type)) in sym_pairs:
|
|
323
|
+
symmetric_concept_properties = concept_property_pairs[cast(ConceptEntity, property_definition.value_type)]
|
|
321
324
|
candidates = list(
|
|
322
325
|
filter(
|
|
323
|
-
lambda instance: instance.value_type ==
|
|
324
|
-
|
|
326
|
+
lambda instance: instance.value_type == concept,
|
|
327
|
+
symmetric_concept_properties,
|
|
325
328
|
)
|
|
326
329
|
)
|
|
327
330
|
symmetric_property = candidates[0]
|
|
328
331
|
if len(candidates) > 1:
|
|
329
332
|
warnings.warn(
|
|
330
|
-
f"Multiple symmetric properties found for
|
|
333
|
+
f"Multiple symmetric properties found for concept {property_definition.value_type}! "
|
|
331
334
|
f"Only one will be used for creating symmetric triples.",
|
|
332
335
|
stacklevel=2,
|
|
333
336
|
)
|
|
@@ -336,9 +339,9 @@ def _generate_mock_object_property_triples(
|
|
|
336
339
|
|
|
337
340
|
triples = []
|
|
338
341
|
|
|
339
|
-
for i, source in enumerate(instance_ids[
|
|
340
|
-
target = instance_ids[cast(
|
|
341
|
-
i % len(instance_ids[cast(
|
|
342
|
+
for i, source in enumerate(instance_ids[concept]):
|
|
343
|
+
target = instance_ids[cast(ConceptEntity, property_definition.value_type)][
|
|
344
|
+
i % len(instance_ids[cast(ConceptEntity, property_definition.value_type)])
|
|
342
345
|
]
|
|
343
346
|
triples += [
|
|
344
347
|
(
|
|
@@ -358,26 +361,26 @@ def _generate_mock_object_property_triples(
|
|
|
358
361
|
]
|
|
359
362
|
|
|
360
363
|
if symmetric_property:
|
|
361
|
-
|
|
364
|
+
concept_property_pairs[cast(ConceptEntity, property_definition.value_type)].remove(symmetric_property)
|
|
362
365
|
|
|
363
366
|
return triples
|
|
364
367
|
|
|
365
368
|
|
|
366
369
|
def _generate_triples_per_class(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
sym_pairs: set[tuple[
|
|
370
|
-
instance_ids: dict[
|
|
370
|
+
concept: ConceptEntity,
|
|
371
|
+
concept_properties_pairs: dict[ConceptEntity, list[ConceptualProperty]],
|
|
372
|
+
sym_pairs: set[tuple[ConceptEntity, ConceptEntity]],
|
|
373
|
+
instance_ids: dict[ConceptEntity, list[URIRef]],
|
|
371
374
|
namespace: Namespace,
|
|
372
375
|
stop_on_exception: bool,
|
|
373
376
|
) -> list[Triple]:
|
|
374
377
|
"""Generate triples for a given class."""
|
|
375
378
|
triples: list[Triple] = []
|
|
376
379
|
|
|
377
|
-
for property_ in
|
|
380
|
+
for property_ in concept_properties_pairs[concept]:
|
|
378
381
|
if property_.type_ == EntityTypes.data_property:
|
|
379
382
|
triples += _generate_mock_data_property_triples(
|
|
380
|
-
instance_ids[
|
|
383
|
+
instance_ids[concept],
|
|
381
384
|
property_.property_,
|
|
382
385
|
namespace,
|
|
383
386
|
cast(DataType, property_.value_type),
|
|
@@ -385,9 +388,9 @@ def _generate_triples_per_class(
|
|
|
385
388
|
|
|
386
389
|
elif property_.type_ == EntityTypes.object_property:
|
|
387
390
|
triples += _generate_mock_object_property_triples(
|
|
388
|
-
|
|
391
|
+
concept,
|
|
389
392
|
property_,
|
|
390
|
-
|
|
393
|
+
concept_properties_pairs,
|
|
391
394
|
sym_pairs,
|
|
392
395
|
instance_ids,
|
|
393
396
|
namespace,
|
|
@@ -8,8 +8,8 @@ from rdflib.util import guess_format
|
|
|
8
8
|
from typing_extensions import Self
|
|
9
9
|
|
|
10
10
|
from cognite.neat.core._constants import DEFAULT_BASE_URI
|
|
11
|
-
from cognite.neat.core.
|
|
12
|
-
from cognite.neat.core.
|
|
11
|
+
from cognite.neat.core._instances._shared import RDFTypes
|
|
12
|
+
from cognite.neat.core._instances.extractors._base import BaseExtractor
|
|
13
13
|
from cognite.neat.core._issues._base import IssueList
|
|
14
14
|
from cognite.neat.core._issues.errors import (
|
|
15
15
|
FileNotFoundNeatError,
|
|
@@ -8,7 +8,7 @@ from cognite.client.data_classes.capabilities import Capability
|
|
|
8
8
|
|
|
9
9
|
from cognite.neat.core._issues import IssueList, NeatIssue
|
|
10
10
|
from cognite.neat.core._issues.errors import AuthorizationError
|
|
11
|
-
from cognite.neat.core._store import
|
|
11
|
+
from cognite.neat.core._store import NeatInstanceStore
|
|
12
12
|
from cognite.neat.core._utils.auxiliary import class_html_doc
|
|
13
13
|
from cognite.neat.core._utils.upload import UploadResult, UploadResultList
|
|
14
14
|
|
|
@@ -29,7 +29,7 @@ class BaseLoader(ABC, Generic[T_Output]):
|
|
|
29
29
|
_new_line = "\n"
|
|
30
30
|
_encoding = "utf-8"
|
|
31
31
|
|
|
32
|
-
def __init__(self, graph_store:
|
|
32
|
+
def __init__(self, graph_store: NeatInstanceStore):
|
|
33
33
|
self.graph_store = graph_store
|
|
34
34
|
|
|
35
35
|
@abstractmethod
|
|
@@ -25,6 +25,17 @@ from cognite.neat.core._constants import (
|
|
|
25
25
|
DMS_DIRECT_RELATION_LIST_DEFAULT_LIMIT,
|
|
26
26
|
is_readonly_property,
|
|
27
27
|
)
|
|
28
|
+
from cognite.neat.core._data_model.analysis import DataModelAnalysis
|
|
29
|
+
from cognite.neat.core._data_model.analysis._base import ViewQuery, ViewQueryDict
|
|
30
|
+
from cognite.neat.core._data_model.models import PhysicalDataModel
|
|
31
|
+
from cognite.neat.core._data_model.models.conceptual._verified import (
|
|
32
|
+
ConceptualDataModel,
|
|
33
|
+
)
|
|
34
|
+
from cognite.neat.core._data_model.models.data_types import (
|
|
35
|
+
_DATA_TYPE_BY_DMS_TYPE,
|
|
36
|
+
Json,
|
|
37
|
+
String,
|
|
38
|
+
)
|
|
28
39
|
from cognite.neat.core._issues import IssueList, NeatError, NeatIssue, catch_issues
|
|
29
40
|
from cognite.neat.core._issues.errors import (
|
|
30
41
|
AuthorizationError,
|
|
@@ -39,17 +50,8 @@ from cognite.neat.core._issues.warnings import (
|
|
|
39
50
|
PropertyTypeNotSupportedWarning,
|
|
40
51
|
ResourceNeatWarning,
|
|
41
52
|
)
|
|
42
|
-
from cognite.neat.core._rules.analysis import RulesAnalysis
|
|
43
|
-
from cognite.neat.core._rules.analysis._base import ViewQuery, ViewQueryDict
|
|
44
|
-
from cognite.neat.core._rules.models import DMSRules
|
|
45
|
-
from cognite.neat.core._rules.models.data_types import (
|
|
46
|
-
_DATA_TYPE_BY_DMS_TYPE,
|
|
47
|
-
Json,
|
|
48
|
-
String,
|
|
49
|
-
)
|
|
50
|
-
from cognite.neat.core._rules.models.information._rules import InformationRules
|
|
51
53
|
from cognite.neat.core._shared import InstanceType
|
|
52
|
-
from cognite.neat.core._store import
|
|
54
|
+
from cognite.neat.core._store import NeatInstanceStore
|
|
53
55
|
from cognite.neat.core._utils.auxiliary import create_sha256_hash
|
|
54
56
|
from cognite.neat.core._utils.collection_ import (
|
|
55
57
|
iterate_progress_bar_if_above_config_threshold,
|
|
@@ -113,9 +115,9 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
|
113
115
|
|
|
114
116
|
def __init__(
|
|
115
117
|
self,
|
|
116
|
-
dms_rules:
|
|
117
|
-
info_rules:
|
|
118
|
-
graph_store:
|
|
118
|
+
dms_rules: PhysicalDataModel,
|
|
119
|
+
info_rules: ConceptualDataModel,
|
|
120
|
+
graph_store: NeatInstanceStore,
|
|
119
121
|
instance_space: str,
|
|
120
122
|
space_property: str | None = None,
|
|
121
123
|
use_source_space: bool = False,
|
|
@@ -207,7 +209,7 @@ class DMSLoader(CDFLoader[dm.InstanceApply]):
|
|
|
207
209
|
yield _END_OF_CLASS
|
|
208
210
|
|
|
209
211
|
def _create_view_iterations(self) -> tuple[list[_ViewIterator], IssueList]:
|
|
210
|
-
view_query_by_id =
|
|
212
|
+
view_query_by_id = DataModelAnalysis(self.info_rules, self.dms_rules).view_query_by_id
|
|
211
213
|
iterations_by_view_id = self._select_views_with_instances(view_query_by_id)
|
|
212
214
|
if self._client:
|
|
213
215
|
issues = IssueList()
|
|
@@ -100,10 +100,13 @@ class BaseTransformerStandardised(ABC):
|
|
|
100
100
|
if self._skip_count_query():
|
|
101
101
|
skipped_count_res = list(graph.query(self._skip_count_query()))
|
|
102
102
|
skipped_count = int(skipped_count_res[0][0]) # type: ignore [index, arg-type]
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
if skipped_count > 0:
|
|
104
|
+
warnings.warn(
|
|
105
|
+
NeatValueWarning(
|
|
106
|
+
f"Skipping {skipped_count} properties in transformation {self.__class__.__name__}"
|
|
107
|
+
),
|
|
108
|
+
stacklevel=2,
|
|
109
|
+
)
|
|
107
110
|
outcome.skipped = skipped_count
|
|
108
111
|
|
|
109
112
|
if iteration_count == 0:
|
|
@@ -9,7 +9,7 @@ from rdflib import RDF, Graph, Literal, Namespace, URIRef
|
|
|
9
9
|
from rdflib.query import ResultRow
|
|
10
10
|
|
|
11
11
|
from cognite.neat.core._constants import CLASSIC_CDF_NAMESPACE, DEFAULT_NAMESPACE
|
|
12
|
-
from cognite.neat.core.
|
|
12
|
+
from cognite.neat.core._instances import extractors
|
|
13
13
|
from cognite.neat.core._issues.errors import NeatValueError
|
|
14
14
|
from cognite.neat.core._issues.warnings import ResourceNotFoundWarning
|
|
15
15
|
from cognite.neat.core._utils.collection_ import iterate_progress_bar
|
|
@@ -10,11 +10,7 @@ from rdflib.query import ResultRow
|
|
|
10
10
|
from cognite.neat.core._constants import NEAT
|
|
11
11
|
from cognite.neat.core._issues.warnings import PropertyDataTypeConversionWarning
|
|
12
12
|
from cognite.neat.core._utils.auxiliary import string_to_ideal_type
|
|
13
|
-
from cognite.neat.core._utils.rdf_ import
|
|
14
|
-
Triple,
|
|
15
|
-
get_namespace,
|
|
16
|
-
remove_namespace_from_uri,
|
|
17
|
-
)
|
|
13
|
+
from cognite.neat.core._utils.rdf_ import Triple, get_namespace, remove_namespace_from_uri, uri_to_cdf_id
|
|
18
14
|
|
|
19
15
|
from ._base import BaseTransformerStandardised, RowTransformationOutput
|
|
20
16
|
|
|
@@ -303,7 +299,7 @@ class ConnectionToLiteral(BaseTransformerStandardised):
|
|
|
303
299
|
row_output = RowTransformationOutput()
|
|
304
300
|
|
|
305
301
|
instance, object_entity = cast(tuple[URIRef, URIRef], query_result_row)
|
|
306
|
-
value =
|
|
302
|
+
value = uri_to_cdf_id(object_entity)
|
|
307
303
|
|
|
308
304
|
row_output.add_triples.add((instance, self.subject_predicate, rdflib.Literal(value)))
|
|
309
305
|
row_output.remove_triples.add((instance, self.subject_predicate, object_entity))
|
|
@@ -119,7 +119,7 @@ class NeatIssue:
|
|
|
119
119
|
|
|
120
120
|
@classmethod
|
|
121
121
|
def _dump_value(cls, value: Any) -> list | int | bool | float | str | dict:
|
|
122
|
-
from cognite.neat.core.
|
|
122
|
+
from cognite.neat.core._data_model.models.entities import ConceptualEntity
|
|
123
123
|
|
|
124
124
|
if isinstance(value, str | int | bool | float):
|
|
125
125
|
return value
|
|
@@ -131,7 +131,7 @@ class NeatIssue:
|
|
|
131
131
|
return [cls._dump_value(item) for item in value]
|
|
132
132
|
elif isinstance(value, ViewId | ContainerId):
|
|
133
133
|
return value.dump(camel_case=True, include_type=True)
|
|
134
|
-
elif isinstance(value,
|
|
134
|
+
elif isinstance(value, ConceptualEntity):
|
|
135
135
|
return value.dump()
|
|
136
136
|
elif isinstance(value, PropertyId):
|
|
137
137
|
return value.dump(camel_case=True)
|
|
@@ -173,7 +173,7 @@ class NeatIssue:
|
|
|
173
173
|
|
|
174
174
|
@classmethod
|
|
175
175
|
def _load_value(cls, type_: Any, value: Any) -> Any:
|
|
176
|
-
from cognite.neat.core.
|
|
176
|
+
from cognite.neat.core._data_model.models.entities import ConceptualEntity
|
|
177
177
|
|
|
178
178
|
if isinstance(type_, UnionType) or get_origin(type_) is UnionType:
|
|
179
179
|
args = get_args(type_)
|
|
@@ -194,7 +194,7 @@ class NeatIssue:
|
|
|
194
194
|
return PropertyId.load(value)
|
|
195
195
|
elif type_ is ContainerId:
|
|
196
196
|
return ContainerId.load(value)
|
|
197
|
-
elif inspect.isclass(type_) and issubclass(type_,
|
|
197
|
+
elif inspect.isclass(type_) and issubclass(type_, ConceptualEntity):
|
|
198
198
|
return type_.load(value)
|
|
199
199
|
elif type_ is NeatError:
|
|
200
200
|
return cls.load(value)
|
|
@@ -33,7 +33,7 @@ from ._resources import (
|
|
|
33
33
|
ResourceRetrievalError,
|
|
34
34
|
)
|
|
35
35
|
from ._wrapper import (
|
|
36
|
-
|
|
36
|
+
ConceptValueError,
|
|
37
37
|
ContainerValueError,
|
|
38
38
|
EnumValueError,
|
|
39
39
|
MetadataValueError,
|
|
@@ -47,7 +47,7 @@ __all__ = [
|
|
|
47
47
|
"AuthorizationError",
|
|
48
48
|
"CDFMissingClientError",
|
|
49
49
|
"CDFMissingResourcesError",
|
|
50
|
-
"
|
|
50
|
+
"ConceptValueError",
|
|
51
51
|
"ContainerValueError",
|
|
52
52
|
"EnumValueError",
|
|
53
53
|
"FileMissingRequiredFieldError",
|
|
@@ -67,8 +67,8 @@ class PropertyValueError(SpreadsheetListError):
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
@dataclass(unsafe_hash=True)
|
|
70
|
-
class
|
|
71
|
-
_name = "
|
|
70
|
+
class ConceptValueError(SpreadsheetListError):
|
|
71
|
+
_name = "Concepts"
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
@dataclass(unsafe_hash=True)
|
|
@@ -40,6 +40,7 @@ from ._properties import (
|
|
|
40
40
|
PropertyOverwritingWarning,
|
|
41
41
|
PropertyTypeNotSupportedWarning,
|
|
42
42
|
PropertyValueTypeUndefinedWarning,
|
|
43
|
+
ReversedConnectionNotFeasibleWarning,
|
|
43
44
|
)
|
|
44
45
|
from ._resources import (
|
|
45
46
|
ResourceNeatWarning,
|
|
@@ -85,6 +86,7 @@ __all__ = [
|
|
|
85
86
|
"ResourceTypeNotSupportedWarning",
|
|
86
87
|
"ResourceUnknownWarning",
|
|
87
88
|
"ResourcesDuplicatedWarning",
|
|
89
|
+
"ReversedConnectionNotFeasibleWarning",
|
|
88
90
|
"UndefinedViewWarning",
|
|
89
91
|
"UserModelingWarning",
|
|
90
92
|
"user_modeling",
|
|
@@ -93,12 +93,12 @@ class NotSupportedHasDataFilterLimitWarning(CDFNotSupportedWarning):
|
|
|
93
93
|
|
|
94
94
|
|
|
95
95
|
@dataclass(unsafe_hash=True)
|
|
96
|
-
class
|
|
97
|
-
"""Class {
|
|
96
|
+
class UndefinedConceptWarning(UserModelingWarning):
|
|
97
|
+
"""Class {concept_id} has no explicit properties defined neither implements other concepts"""
|
|
98
98
|
|
|
99
|
-
fix = "Define properties for
|
|
99
|
+
fix = "Define properties for concept or inherit properties by implementing another concept."
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
concept_id: str
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
@dataclass(unsafe_hash=True)
|
|
@@ -88,3 +88,10 @@ class PropertyMultipleValueWarning(PropertyWarning[T_Identifier]):
|
|
|
88
88
|
Selecting the first value {value}, the rest will be ignored."""
|
|
89
89
|
|
|
90
90
|
value: str
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(unsafe_hash=True)
|
|
94
|
+
class ReversedConnectionNotFeasibleWarning(PropertyWarning[T_Identifier]):
|
|
95
|
+
"""The {resource_type} {identifier}.{property_name} cannot be created: {reason}"""
|
|
96
|
+
|
|
97
|
+
reason: str
|