cognite-neat 0.123.26__py3-none-any.whl → 1.0.22__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/__init__.py +4 -3
- cognite/neat/_client/__init__.py +5 -0
- cognite/neat/_client/api.py +8 -0
- cognite/neat/_client/client.py +21 -0
- cognite/neat/_client/config.py +40 -0
- cognite/neat/_client/containers_api.py +138 -0
- cognite/neat/_client/data_classes.py +44 -0
- cognite/neat/_client/data_model_api.py +115 -0
- cognite/neat/_client/init/credentials.py +70 -0
- cognite/neat/_client/init/env_vars.py +131 -0
- cognite/neat/_client/init/main.py +51 -0
- cognite/neat/_client/spaces_api.py +115 -0
- cognite/neat/_client/statistics_api.py +24 -0
- cognite/neat/_client/views_api.py +144 -0
- cognite/neat/_config.py +266 -0
- cognite/neat/_data_model/_analysis.py +571 -0
- cognite/neat/_data_model/_constants.py +74 -0
- cognite/neat/_data_model/_identifiers.py +61 -0
- cognite/neat/_data_model/_shared.py +41 -0
- cognite/neat/_data_model/_snapshot.py +134 -0
- cognite/neat/_data_model/deployer/_differ.py +140 -0
- cognite/neat/_data_model/deployer/_differ_container.py +360 -0
- cognite/neat/_data_model/deployer/_differ_data_model.py +54 -0
- cognite/neat/_data_model/deployer/_differ_space.py +9 -0
- cognite/neat/_data_model/deployer/_differ_view.py +299 -0
- cognite/neat/_data_model/deployer/data_classes.py +644 -0
- cognite/neat/_data_model/deployer/deployer.py +431 -0
- cognite/neat/_data_model/exporters/__init__.py +15 -0
- cognite/neat/_data_model/exporters/_api_exporter.py +37 -0
- cognite/neat/_data_model/exporters/_base.py +24 -0
- cognite/neat/_data_model/exporters/_table_exporter/exporter.py +128 -0
- cognite/neat/_data_model/exporters/_table_exporter/workbook.py +409 -0
- cognite/neat/_data_model/exporters/_table_exporter/writer.py +480 -0
- cognite/neat/_data_model/importers/__init__.py +5 -0
- cognite/neat/_data_model/importers/_api_importer.py +166 -0
- cognite/neat/_data_model/importers/_base.py +16 -0
- cognite/neat/_data_model/importers/_table_importer/data_classes.py +344 -0
- cognite/neat/_data_model/importers/_table_importer/importer.py +192 -0
- cognite/neat/_data_model/importers/_table_importer/reader.py +1102 -0
- cognite/neat/_data_model/importers/_table_importer/source.py +94 -0
- cognite/neat/_data_model/models/conceptual/_base.py +18 -0
- cognite/neat/_data_model/models/conceptual/_concept.py +67 -0
- cognite/neat/_data_model/models/conceptual/_data_model.py +51 -0
- cognite/neat/_data_model/models/conceptual/_properties.py +104 -0
- cognite/neat/_data_model/models/conceptual/_property.py +105 -0
- cognite/neat/_data_model/models/dms/__init__.py +206 -0
- cognite/neat/_data_model/models/dms/_base.py +31 -0
- cognite/neat/_data_model/models/dms/_constants.py +48 -0
- cognite/neat/_data_model/models/dms/_constraints.py +42 -0
- cognite/neat/_data_model/models/dms/_container.py +159 -0
- cognite/neat/_data_model/models/dms/_data_model.py +95 -0
- cognite/neat/_data_model/models/dms/_data_types.py +195 -0
- cognite/neat/_data_model/models/dms/_http.py +28 -0
- cognite/neat/_data_model/models/dms/_indexes.py +30 -0
- cognite/neat/_data_model/models/dms/_limits.py +96 -0
- cognite/neat/_data_model/models/dms/_references.py +141 -0
- cognite/neat/_data_model/models/dms/_schema.py +18 -0
- cognite/neat/_data_model/models/dms/_space.py +48 -0
- cognite/neat/_data_model/models/dms/_types.py +17 -0
- cognite/neat/_data_model/models/dms/_view_filter.py +310 -0
- cognite/neat/_data_model/models/dms/_view_property.py +235 -0
- cognite/neat/_data_model/models/dms/_views.py +216 -0
- cognite/neat/_data_model/models/entities/__init__.py +50 -0
- cognite/neat/_data_model/models/entities/_base.py +101 -0
- cognite/neat/_data_model/models/entities/_constants.py +22 -0
- cognite/neat/_data_model/models/entities/_data_types.py +144 -0
- cognite/neat/_data_model/models/entities/_identifiers.py +61 -0
- cognite/neat/_data_model/models/entities/_parser.py +226 -0
- cognite/neat/_data_model/validation/dms/__init__.py +75 -0
- cognite/neat/_data_model/validation/dms/_ai_readiness.py +381 -0
- cognite/neat/_data_model/validation/dms/_base.py +25 -0
- cognite/neat/_data_model/validation/dms/_connections.py +681 -0
- cognite/neat/_data_model/validation/dms/_consistency.py +58 -0
- cognite/neat/_data_model/validation/dms/_containers.py +199 -0
- cognite/neat/_data_model/validation/dms/_limits.py +368 -0
- cognite/neat/_data_model/validation/dms/_orchestrator.py +70 -0
- cognite/neat/_data_model/validation/dms/_views.py +164 -0
- cognite/neat/_exceptions.py +68 -0
- cognite/neat/_issues.py +68 -0
- cognite/neat/_session/__init__.py +3 -0
- cognite/neat/_session/_html/_render.py +30 -0
- cognite/neat/_session/_html/static/__init__.py +8 -0
- cognite/neat/_session/_html/static/deployment.css +476 -0
- cognite/neat/_session/_html/static/deployment.js +181 -0
- cognite/neat/_session/_html/static/issues.css +211 -0
- cognite/neat/_session/_html/static/issues.js +168 -0
- cognite/neat/_session/_html/static/shared.css +186 -0
- cognite/neat/_session/_html/templates/__init__.py +4 -0
- cognite/neat/_session/_html/templates/deployment.html +80 -0
- cognite/neat/_session/_html/templates/issues.html +45 -0
- cognite/neat/_session/_issues.py +81 -0
- cognite/neat/_session/_physical.py +294 -0
- cognite/neat/_session/_result/__init__.py +3 -0
- cognite/neat/_session/_result/_deployment/__init__.py +0 -0
- cognite/neat/_session/_result/_deployment/_physical/__init__.py +0 -0
- cognite/neat/_session/_result/_deployment/_physical/_changes.py +196 -0
- cognite/neat/_session/_result/_deployment/_physical/_statistics.py +180 -0
- cognite/neat/_session/_result/_deployment/_physical/serializer.py +35 -0
- cognite/neat/_session/_result/_result.py +31 -0
- cognite/neat/_session/_session.py +81 -0
- cognite/neat/_session/_usage_analytics/__init__.py +0 -0
- cognite/neat/_session/_usage_analytics/_collector.py +131 -0
- cognite/neat/_session/_usage_analytics/_constants.py +23 -0
- cognite/neat/_session/_usage_analytics/_storage.py +240 -0
- cognite/neat/_session/_wrappers.py +101 -0
- cognite/neat/_state_machine/__init__.py +10 -0
- cognite/neat/_state_machine/_base.py +37 -0
- cognite/neat/_state_machine/_states.py +52 -0
- cognite/neat/_store/__init__.py +3 -0
- cognite/neat/_store/_provenance.py +88 -0
- cognite/neat/_store/_store.py +220 -0
- cognite/neat/_utils/__init__.py +0 -0
- cognite/neat/_utils/_reader.py +194 -0
- cognite/neat/_utils/auxiliary.py +49 -0
- cognite/neat/_utils/collection.py +11 -0
- cognite/neat/_utils/http_client/__init__.py +39 -0
- cognite/neat/_utils/http_client/_client.py +245 -0
- cognite/neat/_utils/http_client/_config.py +19 -0
- cognite/neat/_utils/http_client/_data_classes.py +294 -0
- cognite/neat/_utils/http_client/_tracker.py +31 -0
- cognite/neat/_utils/repo.py +19 -0
- cognite/neat/_utils/text.py +71 -0
- cognite/neat/_utils/useful_types.py +37 -0
- cognite/neat/_utils/validation.py +154 -0
- cognite/neat/_v0/__init__.py +0 -0
- cognite/neat/_v0/core/__init__.py +0 -0
- cognite/neat/_v0/core/_client/_api/__init__.py +0 -0
- cognite/neat/{core → _v0/core}/_client/_api/data_modeling_loaders.py +8 -7
- cognite/neat/{core → _v0/core}/_client/_api/neat_instances.py +5 -5
- cognite/neat/{core → _v0/core}/_client/_api/schema.py +5 -5
- cognite/neat/{core → _v0/core}/_client/_api/statistics.py +3 -3
- cognite/neat/{core → _v0/core}/_client/_api_client.py +1 -1
- cognite/neat/_v0/core/_client/data_classes/__init__.py +0 -0
- cognite/neat/{core → _v0/core}/_client/data_classes/schema.py +4 -4
- cognite/neat/{core → _v0/core}/_client/testing.py +1 -1
- cognite/neat/{core → _v0/core}/_constants.py +5 -3
- cognite/neat/_v0/core/_data_model/__init__.py +0 -0
- cognite/neat/{core → _v0/core}/_data_model/_constants.py +7 -0
- cognite/neat/{core → _v0/core}/_data_model/_shared.py +4 -4
- cognite/neat/{core → _v0/core}/_data_model/analysis/_base.py +8 -8
- cognite/neat/{core → _v0/core}/_data_model/exporters/__init__.py +1 -2
- cognite/neat/{core → _v0/core}/_data_model/exporters/_base.py +7 -7
- cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2dms.py +9 -9
- cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2excel.py +12 -12
- cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2instance_template.py +4 -4
- cognite/neat/{core/_data_model/exporters/_data_model2ontology.py → _v0/core/_data_model/exporters/_data_model2semantic_model.py} +126 -116
- cognite/neat/{core → _v0/core}/_data_model/exporters/_data_model2yaml.py +1 -1
- cognite/neat/{core → _v0/core}/_data_model/importers/_base.py +5 -5
- cognite/neat/{core → _v0/core}/_data_model/importers/_base_file_reader.py +2 -2
- cognite/neat/{core → _v0/core}/_data_model/importers/_dict2data_model.py +5 -5
- cognite/neat/{core → _v0/core}/_data_model/importers/_dms2data_model.py +18 -15
- cognite/neat/{core → _v0/core}/_data_model/importers/_graph2data_model.py +12 -12
- cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_base.py +12 -12
- cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_inference2rdata_model.py +14 -14
- cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_owl2data_model.py +41 -21
- cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_shared.py +9 -9
- cognite/neat/{core → _v0/core}/_data_model/importers/_spreadsheet2data_model.py +92 -12
- cognite/neat/{core → _v0/core}/_data_model/models/__init__.py +3 -3
- cognite/neat/{core → _v0/core}/_data_model/models/_base_verified.py +5 -5
- cognite/neat/{core → _v0/core}/_data_model/models/_import_contexts.py +1 -1
- cognite/neat/{core → _v0/core}/_data_model/models/_types.py +5 -5
- cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_unverified.py +16 -10
- cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_validation.py +12 -12
- cognite/neat/{core → _v0/core}/_data_model/models/conceptual/_verified.py +9 -9
- cognite/neat/{core → _v0/core}/_data_model/models/data_types.py +14 -4
- cognite/neat/{core → _v0/core}/_data_model/models/entities/__init__.py +6 -0
- cognite/neat/_v0/core/_data_model/models/entities/_loaders.py +155 -0
- cognite/neat/{core → _v0/core}/_data_model/models/entities/_multi_value.py +2 -2
- cognite/neat/_v0/core/_data_model/models/entities/_restrictions.py +230 -0
- cognite/neat/{core → _v0/core}/_data_model/models/entities/_single_value.py +121 -16
- cognite/neat/{core → _v0/core}/_data_model/models/entities/_types.py +10 -0
- cognite/neat/{core → _v0/core}/_data_model/models/mapping/_classic2core.py +5 -5
- cognite/neat/{core → _v0/core}/_data_model/models/physical/__init__.py +1 -1
- cognite/neat/{core → _v0/core}/_data_model/models/physical/_exporter.py +26 -19
- cognite/neat/{core → _v0/core}/_data_model/models/physical/_unverified.py +133 -37
- cognite/neat/{core → _v0/core}/_data_model/models/physical/_validation.py +24 -20
- cognite/neat/{core → _v0/core}/_data_model/models/physical/_verified.py +95 -24
- cognite/neat/{core → _v0/core}/_data_model/transformers/_base.py +4 -4
- cognite/neat/{core → _v0/core}/_data_model/transformers/_converters.py +35 -28
- cognite/neat/{core → _v0/core}/_data_model/transformers/_mapping.py +7 -7
- cognite/neat/{core → _v0/core}/_data_model/transformers/_union_conceptual.py +5 -5
- cognite/neat/{core → _v0/core}/_data_model/transformers/_verification.py +7 -7
- cognite/neat/_v0/core/_instances/__init__.py +0 -0
- cognite/neat/{core → _v0/core}/_instances/_tracking/base.py +1 -1
- cognite/neat/{core → _v0/core}/_instances/_tracking/log.py +1 -1
- cognite/neat/{core → _v0/core}/_instances/extractors/__init__.py +3 -2
- cognite/neat/{core → _v0/core}/_instances/extractors/_base.py +6 -6
- cognite/neat/_v0/core/_instances/extractors/_classic_cdf/__init__.py +0 -0
- cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_base.py +7 -7
- cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_classic.py +12 -12
- cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_relationships.py +3 -3
- cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_sequences.py +2 -2
- cognite/neat/{core → _v0/core}/_instances/extractors/_dict.py +6 -3
- cognite/neat/{core → _v0/core}/_instances/extractors/_dms.py +6 -6
- cognite/neat/{core → _v0/core}/_instances/extractors/_dms_graph.py +11 -11
- cognite/neat/{core → _v0/core}/_instances/extractors/_mock_graph_generator.py +10 -10
- cognite/neat/{core → _v0/core}/_instances/extractors/_raw.py +3 -3
- cognite/neat/{core → _v0/core}/_instances/extractors/_rdf_file.py +7 -7
- cognite/neat/{core → _v0/core}/_instances/loaders/_base.py +5 -5
- cognite/neat/{core → _v0/core}/_instances/loaders/_rdf2dms.py +17 -17
- cognite/neat/{core → _v0/core}/_instances/loaders/_rdf_to_instance_space.py +11 -11
- cognite/neat/{core → _v0/core}/_instances/queries/_select.py +29 -3
- cognite/neat/{core → _v0/core}/_instances/queries/_update.py +1 -1
- cognite/neat/{core → _v0/core}/_instances/transformers/_base.py +4 -4
- cognite/neat/{core → _v0/core}/_instances/transformers/_classic_cdf.py +6 -6
- cognite/neat/{core → _v0/core}/_instances/transformers/_prune_graph.py +4 -4
- cognite/neat/{core → _v0/core}/_instances/transformers/_rdfpath.py +1 -1
- cognite/neat/{core → _v0/core}/_instances/transformers/_value_type.py +4 -4
- cognite/neat/{core → _v0/core}/_issues/_base.py +5 -5
- cognite/neat/{core → _v0/core}/_issues/_contextmanagers.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/_factory.py +3 -3
- cognite/neat/{core → _v0/core}/_issues/errors/__init__.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/errors/_external.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/errors/_general.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/errors/_properties.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/errors/_resources.py +2 -2
- cognite/neat/{core → _v0/core}/_issues/errors/_wrapper.py +7 -3
- cognite/neat/{core → _v0/core}/_issues/warnings/__init__.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/warnings/_external.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/warnings/_general.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/warnings/_models.py +2 -2
- cognite/neat/{core → _v0/core}/_issues/warnings/_properties.py +2 -2
- cognite/neat/{core → _v0/core}/_issues/warnings/_resources.py +1 -1
- cognite/neat/{core → _v0/core}/_issues/warnings/user_modeling.py +1 -1
- cognite/neat/{core → _v0/core}/_store/_data_model.py +12 -12
- cognite/neat/{core → _v0/core}/_store/_instance.py +43 -10
- cognite/neat/{core → _v0/core}/_store/_provenance.py +3 -3
- cognite/neat/{core → _v0/core}/_store/exceptions.py +4 -4
- cognite/neat/_v0/core/_utils/__init__.py +0 -0
- cognite/neat/{core → _v0/core}/_utils/auth.py +22 -12
- cognite/neat/{core → _v0/core}/_utils/auxiliary.py +1 -1
- cognite/neat/{core → _v0/core}/_utils/collection_.py +2 -2
- cognite/neat/{core → _v0/core}/_utils/graph_transformations_report.py +1 -1
- cognite/neat/{core → _v0/core}/_utils/rdf_.py +1 -1
- cognite/neat/{core → _v0/core}/_utils/reader/_base.py +1 -1
- cognite/neat/{core → _v0/core}/_utils/spreadsheet.py +18 -4
- cognite/neat/{core → _v0/core}/_utils/text.py +1 -1
- cognite/neat/{core → _v0/core}/_utils/upload.py +3 -3
- cognite/neat/{session → _v0}/engine/_load.py +1 -1
- cognite/neat/_v0/plugins/__init__.py +4 -0
- cognite/neat/_v0/plugins/_base.py +9 -0
- cognite/neat/_v0/plugins/_data_model.py +48 -0
- cognite/neat/{plugins → _v0/plugins}/_issues.py +1 -1
- cognite/neat/{plugins → _v0/plugins}/_manager.py +7 -16
- cognite/neat/{session → _v0/session}/_base.py +13 -15
- cognite/neat/{session → _v0/session}/_collector.py +1 -1
- cognite/neat/_v0/session/_diff.py +51 -0
- cognite/neat/{session → _v0/session}/_drop.py +3 -3
- cognite/neat/{session → _v0/session}/_explore.py +2 -2
- cognite/neat/{session → _v0/session}/_fix.py +2 -2
- cognite/neat/{session → _v0/session}/_inspect.py +3 -3
- cognite/neat/{session → _v0/session}/_mapping.py +3 -3
- cognite/neat/{session → _v0/session}/_plugin.py +4 -5
- cognite/neat/{session → _v0/session}/_prepare.py +8 -8
- cognite/neat/{session → _v0/session}/_read.py +34 -21
- cognite/neat/{session → _v0/session}/_set.py +8 -8
- cognite/neat/{session → _v0/session}/_show.py +5 -5
- cognite/neat/{session → _v0/session}/_state.py +10 -10
- cognite/neat/{session → _v0/session}/_subset.py +4 -4
- cognite/neat/{session → _v0/session}/_template.py +11 -11
- cognite/neat/{session → _v0/session}/_to.py +12 -12
- cognite/neat/{session → _v0/session}/_wizard.py +1 -1
- cognite/neat/{session → _v0/session}/exceptions.py +5 -5
- cognite/neat/_version.py +1 -1
- cognite/neat/legacy.py +6 -0
- cognite_neat-1.0.22.dist-info/METADATA +123 -0
- cognite_neat-1.0.22.dist-info/RECORD +329 -0
- cognite_neat-1.0.22.dist-info/WHEEL +4 -0
- cognite/neat/core/_data_model/models/entities/_loaders.py +0 -75
- cognite/neat/plugins/__init__.py +0 -3
- cognite/neat/plugins/data_model/importers/__init__.py +0 -5
- cognite/neat/plugins/data_model/importers/_base.py +0 -28
- cognite/neat/session/_session/_data_model/__init__.py +0 -3
- cognite/neat/session/_session/_data_model/_read.py +0 -193
- cognite/neat/session/_session/_data_model/_routes.py +0 -45
- cognite/neat/session/_session/_data_model/_show.py +0 -147
- cognite/neat/session/_session/_data_model/_write.py +0 -335
- cognite_neat-0.123.26.dist-info/METADATA +0 -144
- cognite_neat-0.123.26.dist-info/RECORD +0 -201
- cognite_neat-0.123.26.dist-info/WHEEL +0 -4
- cognite_neat-0.123.26.dist-info/licenses/LICENSE +0 -201
- /cognite/neat/{core → _client/init}/__init__.py +0 -0
- /cognite/neat/{core/_client/_api → _data_model}/__init__.py +0 -0
- /cognite/neat/{core/_client/data_classes → _data_model/deployer}/__init__.py +0 -0
- /cognite/neat/{core/_data_model → _data_model/exporters/_table_exporter}/__init__.py +0 -0
- /cognite/neat/{core/_instances → _data_model/importers/_table_importer}/__init__.py +0 -0
- /cognite/neat/{core/_instances/extractors/_classic_cdf → _data_model/models}/__init__.py +0 -0
- /cognite/neat/{core/_utils → _data_model/models/conceptual}/__init__.py +0 -0
- /cognite/neat/{plugins/data_model → _data_model/validation}/__init__.py +0 -0
- /cognite/neat/{session/_session → _session/_html}/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_client/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_client/data_classes/data_modeling.py +0 -0
- /cognite/neat/{core → _v0/core}/_client/data_classes/neat_sequence.py +0 -0
- /cognite/neat/{core → _v0/core}/_client/data_classes/statistics.py +0 -0
- /cognite/neat/{core → _v0/core}/_config.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/analysis/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/catalog/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/catalog/classic_model.xlsx +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/catalog/conceptual-imf-data-model.xlsx +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/catalog/hello_world_pump.xlsx +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/importers/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/models/_base_unverified.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/models/conceptual/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/models/entities/_constants.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/models/entities/_wrapped.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/models/mapping/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/models/mapping/_classic2core.yaml +0 -0
- /cognite/neat/{core → _v0/core}/_data_model/transformers/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/_shared.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/_tracking/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -0
- /cognite/neat/{core → _v0/core}/_instances/examples/Knowledge-Graph-Nordic44.xml +0 -0
- /cognite/neat/{core → _v0/core}/_instances/examples/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
- /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_assets.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_data_sets.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_events.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_files.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_labels.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/extractors/_classic_cdf/_timeseries.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/loaders/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/queries/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/queries/_base.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/queries/_queries.py +0 -0
- /cognite/neat/{core → _v0/core}/_instances/transformers/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_issues/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_issues/formatters.py +0 -0
- /cognite/neat/{core → _v0/core}/_shared.py +0 -0
- /cognite/neat/{core → _v0/core}/_store/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_utils/io_.py +0 -0
- /cognite/neat/{core → _v0/core}/_utils/reader/__init__.py +0 -0
- /cognite/neat/{core → _v0/core}/_utils/tarjan.py +0 -0
- /cognite/neat/{core → _v0/core}/_utils/time_.py +0 -0
- /cognite/neat/{core → _v0/core}/_utils/xml_.py +0 -0
- /cognite/neat/{session → _v0}/engine/__init__.py +0 -0
- /cognite/neat/{session → _v0}/engine/_import.py +0 -0
- /cognite/neat/{session → _v0}/engine/_interface.py +0 -0
- /cognite/neat/{session → _v0/session}/__init__.py +0 -0
- /cognite/neat/{session → _v0/session}/_experimental.py +0 -0
- /cognite/neat/{session → _v0/session}/_state/README.md +0 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
"""Validators for connections in data model specifications."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from cognite.neat._data_model.models.dms._data_types import DirectNodeRelation
|
|
6
|
+
from cognite.neat._data_model.models.dms._references import (
|
|
7
|
+
ContainerDirectReference,
|
|
8
|
+
ViewDirectReference,
|
|
9
|
+
ViewReference,
|
|
10
|
+
)
|
|
11
|
+
from cognite.neat._data_model.models.dms._view_property import ViewCorePropertyRequest
|
|
12
|
+
from cognite.neat._data_model.validation.dms._base import DataModelValidator
|
|
13
|
+
from cognite.neat._issues import ConsistencyError, Recommendation
|
|
14
|
+
|
|
15
|
+
BASE_CODE = "NEAT-DMS-CONNECTIONS"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConnectionValueTypeUnexisting(DataModelValidator):
|
|
19
|
+
"""Validates that connection value types exist.
|
|
20
|
+
|
|
21
|
+
## What it does
|
|
22
|
+
Validates that all connection value types defined in the data model exist.
|
|
23
|
+
|
|
24
|
+
## Why is this bad?
|
|
25
|
+
If a connection value type does not exist, the data model cannot be deployed to CDF.
|
|
26
|
+
This means that the connection will not be able to function.
|
|
27
|
+
|
|
28
|
+
## Example
|
|
29
|
+
If view WindTurbine has a connection property windFarm with value type WindFarm, but WindFarm view is not defined,
|
|
30
|
+
the data model cannot be deployed to CDF.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
code = f"{BASE_CODE}-001"
|
|
34
|
+
issue_type = ConsistencyError
|
|
35
|
+
|
|
36
|
+
def run(self) -> list[ConsistencyError]:
|
|
37
|
+
errors: list[ConsistencyError] = []
|
|
38
|
+
|
|
39
|
+
for (view, property_), value_type in self.validation_resources.connection_end_node_types.items():
|
|
40
|
+
if value_type is None:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
if self.validation_resources.select_view(value_type) is not None:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
errors.append(
|
|
47
|
+
ConsistencyError(
|
|
48
|
+
message=(
|
|
49
|
+
f"View {view!s} connection {property_!s} has value type {value_type!s} "
|
|
50
|
+
"which is not defined as a view in the data model neither exists in CDF."
|
|
51
|
+
),
|
|
52
|
+
fix="Define necessary view",
|
|
53
|
+
code=self.code,
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return errors
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ConnectionValueTypeUndefined(DataModelValidator):
|
|
61
|
+
"""Validates that connection value types are not None, i.e. undefined.
|
|
62
|
+
|
|
63
|
+
## What it does
|
|
64
|
+
Validates that connections have explicitly defined value types (i.e., end connection node type).
|
|
65
|
+
|
|
66
|
+
## Why is this bad?
|
|
67
|
+
If a connection value type is None (undefined), there is no type information about the end node of the connection.
|
|
68
|
+
This yields an ambiguous data model definition, which may lead to issues during consumption of data from CDF.
|
|
69
|
+
|
|
70
|
+
## Example
|
|
71
|
+
Consider a scenario where we have views WindTurbine,ArrayCable and Substation. Lets say WindTurbine has a connection
|
|
72
|
+
`connectsTo` with value type None (undefined), then it is unclear what type of view the connection points to as
|
|
73
|
+
both ArrayCable and Substation are valid targets for the connection.
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
code = f"{BASE_CODE}-002"
|
|
78
|
+
issue_type = Recommendation
|
|
79
|
+
|
|
80
|
+
def run(self) -> list[Recommendation]:
|
|
81
|
+
recommendations: list[Recommendation] = []
|
|
82
|
+
|
|
83
|
+
for (view, property_), value_type in self.validation_resources.connection_end_node_types.items():
|
|
84
|
+
if not value_type:
|
|
85
|
+
recommendations.append(
|
|
86
|
+
Recommendation(
|
|
87
|
+
message=(
|
|
88
|
+
f"View {view!s} connection {property_!s} is missing value type (end node type)."
|
|
89
|
+
" This yields ambiguous data model definition."
|
|
90
|
+
),
|
|
91
|
+
fix="Define necessary value type",
|
|
92
|
+
code=self.code,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return recommendations
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class ReverseConnectionContext:
|
|
101
|
+
"""Context for validating a bidirectional connection.
|
|
102
|
+
This context holds all necessary references to validate the connection.
|
|
103
|
+
|
|
104
|
+
Attributes:
|
|
105
|
+
target_view_ref: Reference to the target view containing the reverse property.
|
|
106
|
+
reverse_property: Identifier of the reverse property in the target view.
|
|
107
|
+
through: Direct reference defining the direct property used in the reverse connection.
|
|
108
|
+
source_view_ref: Reference to the source view containing the direct property, to which the reverse points.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
View `WindTurbine` has property `windFarm` (direct) → points to View `WindFarm`
|
|
113
|
+
View `WindFarm` has property `turbines` (reverse) → points to View `WindTurbine` through `WindTurbine.windFarm`
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
target_view_ref: ViewReference
|
|
117
|
+
reverse_property: str
|
|
118
|
+
through: ViewDirectReference
|
|
119
|
+
source_view_ref: ViewReference
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _normalize_through_reference(
|
|
123
|
+
source_view_ref: ViewReference, through: ContainerDirectReference | ViewDirectReference
|
|
124
|
+
) -> ViewDirectReference:
|
|
125
|
+
"""Normalize through reference to ViewDirectReference for consistent processing."""
|
|
126
|
+
if isinstance(through, ContainerDirectReference):
|
|
127
|
+
return ViewDirectReference(source=source_view_ref, identifier=through.identifier)
|
|
128
|
+
return through
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ReverseConnectionSourceViewMissing(DataModelValidator):
|
|
132
|
+
"""Validates that source view referenced in reverse connection exist.
|
|
133
|
+
|
|
134
|
+
## What it does
|
|
135
|
+
Checks that the source view used to configure a reverse connection exists.
|
|
136
|
+
|
|
137
|
+
## Why is this bad?
|
|
138
|
+
A reverse connection requires a corresponding direct connection in the source view.
|
|
139
|
+
If the source view doesn't exist, the reverse connection is invalid.
|
|
140
|
+
|
|
141
|
+
## Example
|
|
142
|
+
If WindFarm has a reverse property `turbines` through `WindTurbine.windFarm`,
|
|
143
|
+
but WindTurbine view doesn't exist, the reverse connection cannot function.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
code = f"{BASE_CODE}-REVERSE-001"
|
|
147
|
+
issue_type = ConsistencyError
|
|
148
|
+
|
|
149
|
+
def run(self) -> list[ConsistencyError]:
|
|
150
|
+
errors: list[ConsistencyError] = []
|
|
151
|
+
|
|
152
|
+
for (target_view_ref, reverse_prop_name), (
|
|
153
|
+
source_view_ref,
|
|
154
|
+
through,
|
|
155
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
156
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
157
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
158
|
+
|
|
159
|
+
if not source_view:
|
|
160
|
+
errors.append(
|
|
161
|
+
ConsistencyError(
|
|
162
|
+
message=(
|
|
163
|
+
f"Source view {source_view_ref!s} used to configure reverse connection "
|
|
164
|
+
f"'{reverse_prop_name}' in target view {target_view_ref!s} "
|
|
165
|
+
"does not exist in the data model or CDF."
|
|
166
|
+
),
|
|
167
|
+
fix="Define the missing source view",
|
|
168
|
+
code=self.code,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
return errors
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ReverseConnectionSourcePropertyMissing(DataModelValidator):
|
|
176
|
+
"""Validates that source property referenced in reverse connections exist.
|
|
177
|
+
|
|
178
|
+
## What it does
|
|
179
|
+
Checks that the direct connection property in the source view (used in the reverse connection's 'through')
|
|
180
|
+
actually exists in the source view.
|
|
181
|
+
|
|
182
|
+
## Why is this bad?
|
|
183
|
+
A reverse connection requires a corresponding direct connection property in the source view.
|
|
184
|
+
If this property doesn't exist, the bidirectional connection is incomplete.
|
|
185
|
+
|
|
186
|
+
## Example
|
|
187
|
+
If WindFarm has a reverse property `turbines` through `WindTurbine.windFarm`,
|
|
188
|
+
but WindTurbine view doesn't have a `windFarm` property, the reverse connection is invalid.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
code = f"{BASE_CODE}-REVERSE-002"
|
|
192
|
+
issue_type = ConsistencyError
|
|
193
|
+
|
|
194
|
+
def run(self) -> list[ConsistencyError]:
|
|
195
|
+
errors: list[ConsistencyError] = []
|
|
196
|
+
|
|
197
|
+
for (target_view_ref, reverse_prop_name), (
|
|
198
|
+
source_view_ref,
|
|
199
|
+
through,
|
|
200
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
201
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
202
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
203
|
+
|
|
204
|
+
if not source_view:
|
|
205
|
+
continue # Handled by ReverseConnectionSourceViewMissing
|
|
206
|
+
|
|
207
|
+
# critical to expand view properties to include inherited ones as otherwise we might miss the property
|
|
208
|
+
if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
|
|
209
|
+
raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
|
|
210
|
+
|
|
211
|
+
if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
|
|
212
|
+
errors.append(
|
|
213
|
+
ConsistencyError(
|
|
214
|
+
message=(
|
|
215
|
+
f"Source view {source_view_ref!s} is missing property '{through.identifier}' "
|
|
216
|
+
f"which is required to configure the reverse connection "
|
|
217
|
+
f"'{reverse_prop_name}' in target view {target_view_ref!s}."
|
|
218
|
+
),
|
|
219
|
+
fix="Add the missing property to the source view",
|
|
220
|
+
code=self.code,
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return errors
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class ReverseConnectionSourcePropertyWrongType(DataModelValidator):
|
|
228
|
+
"""Validates that source property for the reverse connections is a direct relation.
|
|
229
|
+
|
|
230
|
+
## What it does
|
|
231
|
+
Checks that the property referenced in a reverse connection's 'through' clause
|
|
232
|
+
is actually a direct connection property (not a primitive or other type).
|
|
233
|
+
|
|
234
|
+
## Why is this bad?
|
|
235
|
+
Reverse connections can only work with direct connection properties.
|
|
236
|
+
Using other property types breaks the bidirectional relationship.
|
|
237
|
+
|
|
238
|
+
## Example
|
|
239
|
+
If WindFarm has a reverse property `turbines` through `WindTurbine.name`,
|
|
240
|
+
but `name` is a Text property (not a direct connection), the reverse connection is invalid.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
code = f"{BASE_CODE}-REVERSE-003"
|
|
244
|
+
issue_type = ConsistencyError
|
|
245
|
+
|
|
246
|
+
def run(self) -> list[ConsistencyError]:
|
|
247
|
+
errors: list[ConsistencyError] = []
|
|
248
|
+
|
|
249
|
+
for (target_view_ref, reverse_prop_name), (
|
|
250
|
+
source_view_ref,
|
|
251
|
+
through,
|
|
252
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
253
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
254
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
255
|
+
|
|
256
|
+
if not source_view:
|
|
257
|
+
continue # Handled by ReverseConnectionSourceViewMissing
|
|
258
|
+
|
|
259
|
+
if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
|
|
260
|
+
raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
|
|
261
|
+
|
|
262
|
+
if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
|
|
263
|
+
continue # Handled by ReverseConnectionSourcePropertyMissing
|
|
264
|
+
|
|
265
|
+
source_property = source_view_expanded.properties[through.identifier]
|
|
266
|
+
|
|
267
|
+
if not isinstance(source_property, ViewCorePropertyRequest):
|
|
268
|
+
errors.append(
|
|
269
|
+
ConsistencyError(
|
|
270
|
+
message=(
|
|
271
|
+
f"Source view {source_view_ref!s} property '{through.identifier}' "
|
|
272
|
+
f"used for configuring the reverse connection '{reverse_prop_name}' "
|
|
273
|
+
f"in target view {target_view_ref!s} is not a direct connection property."
|
|
274
|
+
),
|
|
275
|
+
fix="Update view property to be a direct connection property",
|
|
276
|
+
code=self.code,
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return errors
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class ReverseConnectionContainerMissing(DataModelValidator):
|
|
284
|
+
"""Validates that the container referenced by the reverse connection source properties exist.
|
|
285
|
+
|
|
286
|
+
## What it does
|
|
287
|
+
Checks that the container holding the direct connection property (used in reverse connection) exists.
|
|
288
|
+
|
|
289
|
+
## Why is this bad?
|
|
290
|
+
The direct connection property must be stored in a container.
|
|
291
|
+
If the container doesn't exist, the connection cannot be persisted.
|
|
292
|
+
|
|
293
|
+
## Example
|
|
294
|
+
If WindTurbine.windFarm maps to container `WindTurbine`, but this container doesn't exist,
|
|
295
|
+
the reverse connection from WindFarm cannot function.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
code = f"{BASE_CODE}-REVERSE-004"
|
|
299
|
+
issue_type = ConsistencyError
|
|
300
|
+
|
|
301
|
+
def run(self) -> list[ConsistencyError]:
|
|
302
|
+
errors: list[ConsistencyError] = []
|
|
303
|
+
|
|
304
|
+
for (target_view_ref, reverse_prop_name), (
|
|
305
|
+
source_view_ref,
|
|
306
|
+
through,
|
|
307
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
308
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
309
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
310
|
+
|
|
311
|
+
if not source_view:
|
|
312
|
+
continue # Handled by ReverseConnectionSourceViewMissing
|
|
313
|
+
|
|
314
|
+
if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
|
|
315
|
+
raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
|
|
316
|
+
|
|
317
|
+
if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
|
|
318
|
+
continue # Handled by ReverseConnectionSourcePropertyMissing
|
|
319
|
+
|
|
320
|
+
source_property = source_view_expanded.properties[through.identifier]
|
|
321
|
+
|
|
322
|
+
if not isinstance(source_property, ViewCorePropertyRequest):
|
|
323
|
+
continue # Handled by ReverseConnectionSourcePropertyWrongType
|
|
324
|
+
|
|
325
|
+
container_ref = source_property.container
|
|
326
|
+
container_property_id = source_property.container_property_identifier
|
|
327
|
+
|
|
328
|
+
source_container = self.validation_resources.select_container(container_ref, container_property_id)
|
|
329
|
+
if not source_container:
|
|
330
|
+
errors.append(
|
|
331
|
+
ConsistencyError(
|
|
332
|
+
message=(
|
|
333
|
+
f"Container {container_ref!s} is missing in both the data model and CDF. "
|
|
334
|
+
f"This container is required by view {source_view_ref!s}"
|
|
335
|
+
f" property '{through.identifier}', "
|
|
336
|
+
f"which configures the reverse connection '{reverse_prop_name}'"
|
|
337
|
+
f" in target view {target_view_ref!s}."
|
|
338
|
+
),
|
|
339
|
+
fix="Define the missing container",
|
|
340
|
+
code=self.code,
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return errors
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class ReverseConnectionContainerPropertyMissing(DataModelValidator):
|
|
348
|
+
"""Validates that container property referenced by the reverse connections exists.
|
|
349
|
+
|
|
350
|
+
## What it does
|
|
351
|
+
Checks that the property in the container (mapped from the view's direct connection property)
|
|
352
|
+
actually exists in the container.
|
|
353
|
+
|
|
354
|
+
## Why is this bad?
|
|
355
|
+
The view property must map to an actual container property for data persistence.
|
|
356
|
+
If the container property doesn't exist, data cannot be stored.
|
|
357
|
+
|
|
358
|
+
## Example
|
|
359
|
+
If WindTurbine.windFarm maps to container property `WindTurbine.windFarm`,
|
|
360
|
+
but this container property doesn't exist, the connection cannot be stored.
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
code = f"{BASE_CODE}-REVERSE-005"
|
|
364
|
+
issue_type = ConsistencyError
|
|
365
|
+
|
|
366
|
+
def run(self) -> list[ConsistencyError]:
|
|
367
|
+
errors: list[ConsistencyError] = []
|
|
368
|
+
|
|
369
|
+
for (target_view_ref, reverse_prop_name), (
|
|
370
|
+
source_view_ref,
|
|
371
|
+
through,
|
|
372
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
373
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
374
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
375
|
+
|
|
376
|
+
if not source_view:
|
|
377
|
+
continue # Handled by ReverseConnectionSourceViewMissing
|
|
378
|
+
|
|
379
|
+
if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
|
|
380
|
+
raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
|
|
381
|
+
|
|
382
|
+
if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
|
|
383
|
+
continue # Handled by ReverseConnectionSourcePropertyMissing
|
|
384
|
+
|
|
385
|
+
source_property = source_view_expanded.properties[through.identifier]
|
|
386
|
+
|
|
387
|
+
if not isinstance(source_property, ViewCorePropertyRequest):
|
|
388
|
+
continue # Handled by ReverseConnectionSourcePropertyWrongType
|
|
389
|
+
|
|
390
|
+
container_ref = source_property.container
|
|
391
|
+
container_property_id = source_property.container_property_identifier
|
|
392
|
+
|
|
393
|
+
source_container = self.validation_resources.select_container(container_ref, container_property_id)
|
|
394
|
+
if not source_container:
|
|
395
|
+
continue # Handled by ReverseConnectionContainerMissing
|
|
396
|
+
|
|
397
|
+
if not source_container.properties or container_property_id not in source_container.properties:
|
|
398
|
+
errors.append(
|
|
399
|
+
ConsistencyError(
|
|
400
|
+
message=(
|
|
401
|
+
f"Container {container_ref!s} is missing property '{container_property_id}'. "
|
|
402
|
+
f"This property is required by the source view {source_view_ref!s}"
|
|
403
|
+
f" property '{through.identifier}', "
|
|
404
|
+
f"which configures the reverse connection '{reverse_prop_name}' "
|
|
405
|
+
f"in target view {target_view_ref!s}."
|
|
406
|
+
),
|
|
407
|
+
fix="Add the missing property to the container",
|
|
408
|
+
code=self.code,
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
return errors
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
class ReverseConnectionContainerPropertyWrongType(DataModelValidator):
|
|
416
|
+
"""Validates that the container property used in reverse connection is the direct relations.
|
|
417
|
+
|
|
418
|
+
## What it does
|
|
419
|
+
Checks that the container property (mapped from view's direct connection property)
|
|
420
|
+
has type DirectNodeRelation.
|
|
421
|
+
|
|
422
|
+
## Why is this bad?
|
|
423
|
+
Container properties backing connection view properties must be DirectNodeRelation type.
|
|
424
|
+
Other types cannot represent connections in the underlying storage.
|
|
425
|
+
|
|
426
|
+
## Example
|
|
427
|
+
If WindTurbine.windFarm maps to container property with type Text instead of DirectNodeRelation,
|
|
428
|
+
the connection cannot be stored correctly.
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
code = f"{BASE_CODE}-REVERSE-006"
|
|
432
|
+
issue_type = ConsistencyError
|
|
433
|
+
|
|
434
|
+
def run(self) -> list[ConsistencyError]:
|
|
435
|
+
errors: list[ConsistencyError] = []
|
|
436
|
+
|
|
437
|
+
for (target_view_ref, reverse_prop_name), (
|
|
438
|
+
source_view_ref,
|
|
439
|
+
through,
|
|
440
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
441
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
442
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
443
|
+
|
|
444
|
+
if not source_view:
|
|
445
|
+
continue # Handled by ReverseConnectionSourceViewMissing
|
|
446
|
+
|
|
447
|
+
if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
|
|
448
|
+
raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
|
|
449
|
+
|
|
450
|
+
if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
|
|
451
|
+
continue # Handled by ReverseConnectionSourcePropertyMissing
|
|
452
|
+
|
|
453
|
+
source_property = source_view_expanded.properties[through.identifier]
|
|
454
|
+
|
|
455
|
+
if not isinstance(source_property, ViewCorePropertyRequest):
|
|
456
|
+
continue # Handled by ReverseConnectionSourcePropertyWrongType
|
|
457
|
+
|
|
458
|
+
container_ref = source_property.container
|
|
459
|
+
container_property_id = source_property.container_property_identifier
|
|
460
|
+
|
|
461
|
+
source_container = self.validation_resources.select_container(container_ref, container_property_id)
|
|
462
|
+
if not source_container or not source_container.properties:
|
|
463
|
+
continue # Handled by other validators
|
|
464
|
+
|
|
465
|
+
container_property = source_container.properties.get(container_property_id)
|
|
466
|
+
if not container_property:
|
|
467
|
+
continue # Handled by ReverseConnectionContainerPropertyMissing
|
|
468
|
+
|
|
469
|
+
if not isinstance(container_property.type, DirectNodeRelation):
|
|
470
|
+
errors.append(
|
|
471
|
+
ConsistencyError(
|
|
472
|
+
message=(
|
|
473
|
+
f"Container property '{container_property_id}' in container {container_ref!s} "
|
|
474
|
+
f"must be a direct connection, but found type '{container_property.type!s}'. "
|
|
475
|
+
f"This property is used by source view {source_view_ref!s} property '{through.identifier}' "
|
|
476
|
+
f"to configure reverse connection '{reverse_prop_name}' in target view {target_view_ref!s}."
|
|
477
|
+
),
|
|
478
|
+
fix="Change container property type to be a direct connection",
|
|
479
|
+
code=self.code,
|
|
480
|
+
)
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
return errors
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
class ReverseConnectionTargetMissing(DataModelValidator):
|
|
487
|
+
"""Validates that the direct connection in reverse connection pair have target views specified.
|
|
488
|
+
|
|
489
|
+
## What it does
|
|
490
|
+
Checks whether the direct connection property (referenced by reverse connection) has a value type.
|
|
491
|
+
|
|
492
|
+
## Why is this bad?
|
|
493
|
+
While CDF allows value type None as a SEARCH hack for multi-value relations,
|
|
494
|
+
it's better to explicitly specify the target view for clarity and maintainability.
|
|
495
|
+
|
|
496
|
+
## Example
|
|
497
|
+
If WindTurbine.windFarm has value type None instead of WindFarm,
|
|
498
|
+
this validator recommends specifying WindFarm explicitly.
|
|
499
|
+
"""
|
|
500
|
+
|
|
501
|
+
code = f"{BASE_CODE}-REVERSE-007"
|
|
502
|
+
issue_type = Recommendation
|
|
503
|
+
|
|
504
|
+
def run(self) -> list[Recommendation]:
|
|
505
|
+
recommendations: list[Recommendation] = []
|
|
506
|
+
|
|
507
|
+
for (target_view_ref, reverse_prop_name), (
|
|
508
|
+
source_view_ref,
|
|
509
|
+
through,
|
|
510
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
511
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
512
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
513
|
+
|
|
514
|
+
if not source_view:
|
|
515
|
+
continue # Handled by ReverseConnectionSourceViewMissing
|
|
516
|
+
|
|
517
|
+
if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
|
|
518
|
+
raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
|
|
519
|
+
|
|
520
|
+
if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
|
|
521
|
+
continue # Handled by ReverseConnectionSourcePropertyMissing
|
|
522
|
+
|
|
523
|
+
source_property = source_view_expanded.properties[through.identifier]
|
|
524
|
+
|
|
525
|
+
if not isinstance(source_property, ViewCorePropertyRequest):
|
|
526
|
+
continue # Handled by ReverseConnectionSourcePropertyWrongType
|
|
527
|
+
|
|
528
|
+
actual_target_view = source_property.source
|
|
529
|
+
|
|
530
|
+
if not actual_target_view:
|
|
531
|
+
recommendations.append(
|
|
532
|
+
Recommendation(
|
|
533
|
+
message=(
|
|
534
|
+
f"Source view {source_view_ref!s} property '{through.identifier}' "
|
|
535
|
+
f"has no target view specified (value type is None). "
|
|
536
|
+
f"This property is used for reverse connection '{reverse_prop_name}' "
|
|
537
|
+
f"in target view {target_view_ref!s}. "
|
|
538
|
+
f"While this works as a hack for multi-value relations in CDF Search, "
|
|
539
|
+
f"it's recommended to explicitly define the target view as {target_view_ref!s}."
|
|
540
|
+
),
|
|
541
|
+
fix="Set the property's value type to the target view for better clarity",
|
|
542
|
+
code=self.code,
|
|
543
|
+
)
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
return recommendations
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
class ReverseConnectionPointsToAncestor(DataModelValidator):
|
|
550
|
+
"""Validates that direct connections point to specific views rather than ancestors.
|
|
551
|
+
|
|
552
|
+
## What it does
|
|
553
|
+
Checks whether the direct connection property points to an ancestor of the expected target view
|
|
554
|
+
and recommends pointing to the specific target instead.
|
|
555
|
+
|
|
556
|
+
## Why is this bad?
|
|
557
|
+
While technically valid, pointing to ancestors can be confusing and may lead to mistakes.
|
|
558
|
+
It's clearer to point to the specific target view.
|
|
559
|
+
|
|
560
|
+
## Example
|
|
561
|
+
If WindFarm.turbines expects WindTurbine.windFarm to point to WindFarm,
|
|
562
|
+
but it points to Asset (ancestor of WindFarm), this validator recommends the change.
|
|
563
|
+
"""
|
|
564
|
+
|
|
565
|
+
code = f"{BASE_CODE}-REVERSE-008"
|
|
566
|
+
issue_type = Recommendation
|
|
567
|
+
|
|
568
|
+
def run(self) -> list[Recommendation]:
|
|
569
|
+
recommendations: list[Recommendation] = []
|
|
570
|
+
|
|
571
|
+
for (target_view_ref, reverse_prop_name), (
|
|
572
|
+
source_view_ref,
|
|
573
|
+
through,
|
|
574
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
575
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
576
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
577
|
+
|
|
578
|
+
if not source_view:
|
|
579
|
+
continue # Handled by ReverseConnectionSourceViewMissing
|
|
580
|
+
|
|
581
|
+
if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
|
|
582
|
+
raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
|
|
583
|
+
|
|
584
|
+
if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
|
|
585
|
+
continue # Handled by ReverseConnectionSourcePropertyMissing
|
|
586
|
+
|
|
587
|
+
source_property = source_view_expanded.properties[through.identifier]
|
|
588
|
+
|
|
589
|
+
if not isinstance(source_property, ViewCorePropertyRequest):
|
|
590
|
+
continue # Handled by other validators
|
|
591
|
+
|
|
592
|
+
actual_target_view = source_property.source
|
|
593
|
+
|
|
594
|
+
if not actual_target_view:
|
|
595
|
+
continue # Handled by ReverseConnectionTargetMissing
|
|
596
|
+
|
|
597
|
+
if self.validation_resources.is_ancestor(target_view_ref, actual_target_view):
|
|
598
|
+
recommendations.append(
|
|
599
|
+
Recommendation(
|
|
600
|
+
message=(
|
|
601
|
+
f"The direct connection property '{through.identifier}' in view {source_view_ref!s} "
|
|
602
|
+
f"configures the reverse connection '{reverse_prop_name}' in {target_view_ref!s}. "
|
|
603
|
+
f"Therefore, it is expected that '{through.identifier}' points to {target_view_ref!s}. "
|
|
604
|
+
f"However, it currently points to {actual_target_view!s}, which is an ancestor of "
|
|
605
|
+
f"{target_view_ref!s}. "
|
|
606
|
+
"While this will allow for model to be valid, it can be a source of confusion and mistakes."
|
|
607
|
+
),
|
|
608
|
+
fix="Update the direct connection property to point to the target view instead of its ancestor",
|
|
609
|
+
code=self.code,
|
|
610
|
+
)
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
return recommendations
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
class ReverseConnectionTargetMismatch(DataModelValidator):
|
|
617
|
+
"""Validates that direct connections point to the correct target views.
|
|
618
|
+
|
|
619
|
+
## What it does
|
|
620
|
+
Checks that the direct connection property points to the expected target view
|
|
621
|
+
(the view containing the reverse connection).
|
|
622
|
+
|
|
623
|
+
## Why is this bad?
|
|
624
|
+
The reverse connection expects a bidirectional relationship.
|
|
625
|
+
If the direct connection points to a different view, the relationship is broken.
|
|
626
|
+
|
|
627
|
+
## Example
|
|
628
|
+
If WindFarm.turbines is a reverse through WindTurbine.windFarm,
|
|
629
|
+
but WindTurbine.windFarm points to SolarFarm instead of WindFarm, the connection is invalid.
|
|
630
|
+
"""
|
|
631
|
+
|
|
632
|
+
code = f"{BASE_CODE}-REVERSE-009"
|
|
633
|
+
issue_type = Recommendation
|
|
634
|
+
|
|
635
|
+
def run(self) -> list[Recommendation]:
|
|
636
|
+
recommendations: list[Recommendation] = []
|
|
637
|
+
|
|
638
|
+
for (target_view_ref, reverse_prop_name), (
|
|
639
|
+
source_view_ref,
|
|
640
|
+
through,
|
|
641
|
+
) in self.validation_resources.reverse_to_direct_mapping.items():
|
|
642
|
+
through = _normalize_through_reference(source_view_ref, through)
|
|
643
|
+
source_view = self.validation_resources.select_view(source_view_ref, through.identifier)
|
|
644
|
+
|
|
645
|
+
if not source_view:
|
|
646
|
+
continue # Handled by ReverseConnectionSourceViewMissing
|
|
647
|
+
|
|
648
|
+
if not (source_view_expanded := self.validation_resources.expand_view_properties(source_view_ref)):
|
|
649
|
+
raise RuntimeError(f"{type(self).__name__}: View {source_view_ref!s} not found. This is a bug in NEAT.")
|
|
650
|
+
|
|
651
|
+
if not source_view_expanded.properties or through.identifier not in source_view_expanded.properties:
|
|
652
|
+
continue # Handled by ReverseConnectionSourcePropertyMissing
|
|
653
|
+
|
|
654
|
+
source_property = source_view_expanded.properties[through.identifier]
|
|
655
|
+
|
|
656
|
+
if not isinstance(source_property, ViewCorePropertyRequest):
|
|
657
|
+
continue # Handled by other validators
|
|
658
|
+
|
|
659
|
+
actual_target_view = source_property.source
|
|
660
|
+
|
|
661
|
+
if not actual_target_view:
|
|
662
|
+
continue # Handled by ReverseConnectionTargetMissing
|
|
663
|
+
|
|
664
|
+
if self.validation_resources.is_ancestor(target_view_ref, actual_target_view):
|
|
665
|
+
continue # Handled by ReverseConnectionTargetAncestor
|
|
666
|
+
|
|
667
|
+
if actual_target_view != target_view_ref:
|
|
668
|
+
recommendations.append(
|
|
669
|
+
Recommendation(
|
|
670
|
+
message=(
|
|
671
|
+
f"The reverse connection '{reverse_prop_name}' in view {target_view_ref!s} "
|
|
672
|
+
f"expects its corresponding direct connection in view {source_view_ref!s} "
|
|
673
|
+
f"(property '{through.identifier}') to point back to {target_view_ref!s}, "
|
|
674
|
+
f"but it actually points to {actual_target_view!s}."
|
|
675
|
+
),
|
|
676
|
+
fix="Update the direct connection property to point back to the correct target view",
|
|
677
|
+
code=self.code,
|
|
678
|
+
)
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
return recommendations
|