cognite-neat 0.123.24__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/_v0/core/_data_model/importers/_rdf/_owl2data_model.py +144 -0
- cognite/neat/{core → _v0/core}/_data_model/importers/_rdf/_shared.py +17 -13
- 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/importers/_rdf/_owl2data_model.py +0 -91
- 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.24.dist-info/METADATA +0 -144
- cognite_neat-0.123.24.dist-info/RECORD +0 -201
- cognite_neat-0.123.24.dist-info/WHEEL +0 -4
- cognite_neat-0.123.24.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,1102 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Literal, TypeVar, cast, overload
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, TypeAdapter, ValidationError
|
|
7
|
+
|
|
8
|
+
from cognite.neat._data_model.models.dms import (
|
|
9
|
+
Constraint,
|
|
10
|
+
ConstraintAdapter,
|
|
11
|
+
ContainerPropertyDefinition,
|
|
12
|
+
ContainerReference,
|
|
13
|
+
ContainerRequest,
|
|
14
|
+
DataModelRequest,
|
|
15
|
+
Index,
|
|
16
|
+
IndexAdapter,
|
|
17
|
+
NodeReference,
|
|
18
|
+
RequestSchema,
|
|
19
|
+
SpaceRequest,
|
|
20
|
+
UniquenessConstraintDefinition,
|
|
21
|
+
ViewReference,
|
|
22
|
+
ViewRequest,
|
|
23
|
+
ViewRequestProperty,
|
|
24
|
+
ViewRequestPropertyAdapter,
|
|
25
|
+
)
|
|
26
|
+
from cognite.neat._data_model.models.dms._constants import DATA_MODEL_DESCRIPTION_MAX_LENGTH
|
|
27
|
+
from cognite.neat._data_model.models.entities import ParsedEntity, parse_entity
|
|
28
|
+
from cognite.neat._exceptions import DataModelImportException
|
|
29
|
+
from cognite.neat._issues import ModelSyntaxError
|
|
30
|
+
from cognite.neat._utils.text import humanize_collection
|
|
31
|
+
from cognite.neat._utils.validation import ValidationContext, humanize_validation_error
|
|
32
|
+
|
|
33
|
+
from .data_classes import (
|
|
34
|
+
CREATOR_KEY,
|
|
35
|
+
CREATOR_MARKER,
|
|
36
|
+
DMSContainer,
|
|
37
|
+
DMSEnum,
|
|
38
|
+
DMSNode,
|
|
39
|
+
DMSProperty,
|
|
40
|
+
DMSView,
|
|
41
|
+
EntityTableFilter,
|
|
42
|
+
RAWFilterTableFilter,
|
|
43
|
+
TableDMS,
|
|
44
|
+
TableViewFilter,
|
|
45
|
+
)
|
|
46
|
+
from .source import TableSource
|
|
47
|
+
|
|
48
|
+
T_BaseModel = TypeVar("T_BaseModel", bound=BaseModel)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class ReadViewProperty:
|
|
53
|
+
prop_id: str
|
|
54
|
+
row_no: int
|
|
55
|
+
view_property: ViewRequestProperty
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class ReadContainerProperty:
|
|
60
|
+
prop_id: str
|
|
61
|
+
row_no: int
|
|
62
|
+
container_property: ContainerPropertyDefinition
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class ReadIndex:
|
|
67
|
+
prop_id: str
|
|
68
|
+
order: int | None
|
|
69
|
+
row_no: int
|
|
70
|
+
index_id: str
|
|
71
|
+
index: Index
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class ReadConstraint:
|
|
76
|
+
prop_id: str
|
|
77
|
+
order: int | None
|
|
78
|
+
row_no: int
|
|
79
|
+
constraint_id: str
|
|
80
|
+
constraint: Constraint
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class ReadProperties:
|
|
85
|
+
"""Read properties from the properties table.
|
|
86
|
+
|
|
87
|
+
Attributes:
|
|
88
|
+
container: A mapping from container entity to a mapping of property identifier to container property definition.
|
|
89
|
+
view: A mapping from view entity to a mapping of property identifier to view property definition.
|
|
90
|
+
indices: A mapping from (container entity, index identifier) to a list of read indices
|
|
91
|
+
constraints: A mapping from (container entity, constraint identifier) to a list of read constraints
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
container: dict[tuple[ParsedEntity, str], list[ReadContainerProperty]] = field(
|
|
95
|
+
default_factory=lambda: defaultdict(list)
|
|
96
|
+
)
|
|
97
|
+
view: dict[tuple[ParsedEntity, str], list[ReadViewProperty]] = field(default_factory=lambda: defaultdict(list))
|
|
98
|
+
indices: dict[tuple[ParsedEntity, str], list[ReadIndex]] = field(default_factory=lambda: defaultdict(list))
|
|
99
|
+
constraints: dict[tuple[ParsedEntity, str], list[ReadConstraint]] = field(default_factory=lambda: defaultdict(list))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class ProcessedProperties:
|
|
104
|
+
container: dict[ParsedEntity, dict[str, ContainerPropertyDefinition]] = field(
|
|
105
|
+
default_factory=lambda: defaultdict(dict)
|
|
106
|
+
)
|
|
107
|
+
view: dict[ParsedEntity, dict[str, ViewRequestProperty]] = field(default_factory=lambda: defaultdict(dict))
|
|
108
|
+
indices: dict[ParsedEntity, dict[str, Index]] = field(default_factory=lambda: defaultdict(dict))
|
|
109
|
+
constraints: dict[ParsedEntity, dict[str, Constraint]] = field(default_factory=lambda: defaultdict(dict))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class DMSTableReader:
|
|
113
|
+
"""Reads a TableDMS object and converts it to a RequestSchema.
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
default_space (str): The default space to use when no space is given in an entity.
|
|
118
|
+
default_version (str): The default version to use when no version is given in an entity.
|
|
119
|
+
source (TableSource): The source of the table data, used for error reporting.
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
DataModelImportError: If there are any errors in the data model.
|
|
123
|
+
|
|
124
|
+
Attributes:
|
|
125
|
+
errors (list[ModelSyntaxError]): A list of errors encountered during parsing.
|
|
126
|
+
|
|
127
|
+
Class Attributes:
|
|
128
|
+
Sheets: This is used to create error messages. It ensures that the column names matches
|
|
129
|
+
the names in the table, even if they are renamed in the code.
|
|
130
|
+
PropertyColumns: This is used to create error messages for the properties table.
|
|
131
|
+
It ensures that the column names matches the names in the table, even if they are renamed in the code.
|
|
132
|
+
ContainerColumns: This is used to create error messages for the containers table.
|
|
133
|
+
It ensures that the column names matches the names in the table, even if they are renamed in the code.
|
|
134
|
+
ViewColumns: This is used to create error messages for the views table.
|
|
135
|
+
It ensures that the column names matches the names in the table, even if they are renamed in the code.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
CELL_MISSING = "#N/A"
|
|
140
|
+
|
|
141
|
+
# The following classes are used when creating error messages. They ensure that the column names
|
|
142
|
+
# matches the names in the table, even if they are renamed in the code.
|
|
143
|
+
# Note that this is not a complete list of all columns, only those that are used in error messages.
|
|
144
|
+
class Sheets:
|
|
145
|
+
metadata = cast(str, TableDMS.model_fields["metadata"].validation_alias)
|
|
146
|
+
properties = cast(str, TableDMS.model_fields["properties"].validation_alias)
|
|
147
|
+
containers = cast(str, TableDMS.model_fields["containers"].validation_alias)
|
|
148
|
+
views = cast(str, TableDMS.model_fields["views"].validation_alias)
|
|
149
|
+
nodes = cast(str, TableDMS.model_fields["nodes"].validation_alias)
|
|
150
|
+
|
|
151
|
+
class PropertyColumns:
|
|
152
|
+
view = cast(str, DMSProperty.model_fields["view"].validation_alias)
|
|
153
|
+
view_property = cast(str, DMSProperty.model_fields["view_property"].validation_alias)
|
|
154
|
+
connection = cast(str, DMSProperty.model_fields["connection"].validation_alias)
|
|
155
|
+
value_type = cast(str, DMSProperty.model_fields["value_type"].validation_alias)
|
|
156
|
+
min_count = cast(str, DMSProperty.model_fields["min_count"].validation_alias)
|
|
157
|
+
max_count = cast(str, DMSProperty.model_fields["max_count"].validation_alias)
|
|
158
|
+
default = cast(str, DMSProperty.model_fields["default"].validation_alias)
|
|
159
|
+
auto_increment = cast(str, DMSProperty.model_fields["auto_increment"].validation_alias)
|
|
160
|
+
container = cast(str, DMSProperty.model_fields["container"].validation_alias)
|
|
161
|
+
container_property = cast(str, DMSProperty.model_fields["container_property"].validation_alias)
|
|
162
|
+
container_property_name = cast(str, DMSProperty.model_fields["container_property_name"].validation_alias)
|
|
163
|
+
container_property_description = cast(
|
|
164
|
+
str, DMSProperty.model_fields["container_property_description"].validation_alias
|
|
165
|
+
)
|
|
166
|
+
index = cast(str, DMSProperty.model_fields["index"].validation_alias)
|
|
167
|
+
constraint = cast(str, DMSProperty.model_fields["constraint"].validation_alias)
|
|
168
|
+
|
|
169
|
+
class ContainerColumns:
|
|
170
|
+
container = cast(str, DMSContainer.model_fields["container"].validation_alias)
|
|
171
|
+
constraint = cast(str, DMSContainer.model_fields["constraint"].validation_alias)
|
|
172
|
+
|
|
173
|
+
class ViewColumns:
|
|
174
|
+
view = cast(str, DMSView.model_fields["view"].validation_alias)
|
|
175
|
+
filter = cast(str, DMSView.model_fields["filter"].validation_alias)
|
|
176
|
+
|
|
177
|
+
def __init__(self, default_space: str, default_version: str, source: TableSource) -> None:
|
|
178
|
+
self.default_space = default_space
|
|
179
|
+
self.default_version = default_version
|
|
180
|
+
self.source = source
|
|
181
|
+
self.errors: list[ModelSyntaxError] = []
|
|
182
|
+
|
|
183
|
+
def read_tables(self, tables: TableDMS) -> RequestSchema:
|
|
184
|
+
space_request = self.read_space(self.default_space)
|
|
185
|
+
node_types = self.read_nodes(tables.nodes)
|
|
186
|
+
enum_collections = self.read_enum_collections(tables.enum)
|
|
187
|
+
container_ref_by_entity = self.read_entity_by_container_ref(tables.containers)
|
|
188
|
+
view_ref_by_entity = self.read_entity_by_view_ref(tables.views)
|
|
189
|
+
read = self.read_properties(tables.properties, enum_collections, container_ref_by_entity, view_ref_by_entity)
|
|
190
|
+
processed = self.process_properties(read)
|
|
191
|
+
containers = self.read_containers(tables.containers, processed)
|
|
192
|
+
views, valid_view_entities = self.read_views(tables.views, processed.view)
|
|
193
|
+
data_model = self.read_data_model(tables, valid_view_entities)
|
|
194
|
+
|
|
195
|
+
if self.errors:
|
|
196
|
+
raise DataModelImportException(self.errors) from None
|
|
197
|
+
return RequestSchema(
|
|
198
|
+
dataModel=data_model, views=views, containers=containers, spaces=[space_request], nodeTypes=node_types
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def read_space(self, space: str) -> SpaceRequest:
|
|
202
|
+
space_request = self._validate_obj(SpaceRequest, {"space": space}, (self.Sheets.metadata,), field_name="value")
|
|
203
|
+
if space_request is None:
|
|
204
|
+
# If space is invalid, we stop parsing to avoid raising an error for every place the space is used.
|
|
205
|
+
raise DataModelImportException(self.errors) from None
|
|
206
|
+
return space_request
|
|
207
|
+
|
|
208
|
+
def read_nodes(self, nodes: list[DMSNode]) -> list[NodeReference]:
|
|
209
|
+
node_refs: list[NodeReference] = []
|
|
210
|
+
for row_no, row in enumerate(nodes):
|
|
211
|
+
data = self._create_node_ref(row.node)
|
|
212
|
+
instantiated = self._validate_obj(NodeReference, data, (self.Sheets.nodes, row_no))
|
|
213
|
+
if instantiated is not None:
|
|
214
|
+
node_refs.append(instantiated)
|
|
215
|
+
return node_refs
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def read_enum_collections(enum_rows: list[DMSEnum]) -> dict[str, dict[str, Any]]:
|
|
219
|
+
enum_collections: dict[str, dict[str, Any]] = defaultdict(dict)
|
|
220
|
+
for row in enum_rows:
|
|
221
|
+
enum_collections[row.collection][row.value] = {
|
|
222
|
+
"name": row.name,
|
|
223
|
+
"description": row.description,
|
|
224
|
+
}
|
|
225
|
+
return enum_collections
|
|
226
|
+
|
|
227
|
+
def read_entity_by_container_ref(self, containers: list[DMSContainer]) -> dict[ContainerReference, ParsedEntity]:
|
|
228
|
+
entity_by_container_ref: dict[ContainerReference, ParsedEntity] = {}
|
|
229
|
+
for container in containers:
|
|
230
|
+
data = self._create_container_ref(container.container)
|
|
231
|
+
try:
|
|
232
|
+
container_ref = ContainerReference.model_validate(data)
|
|
233
|
+
except ValidationError:
|
|
234
|
+
# Error will be reported when reading the containers
|
|
235
|
+
continue
|
|
236
|
+
entity_by_container_ref[container_ref] = container.container
|
|
237
|
+
return entity_by_container_ref
|
|
238
|
+
|
|
239
|
+
def read_entity_by_view_ref(self, views: list[DMSView]) -> dict[ViewReference, ParsedEntity]:
|
|
240
|
+
entity_by_view_ref: dict[ViewReference, ParsedEntity] = {}
|
|
241
|
+
for view in views:
|
|
242
|
+
data = self._create_view_ref(view.view)
|
|
243
|
+
try:
|
|
244
|
+
view_ref = ViewReference.model_validate(data)
|
|
245
|
+
except ValidationError:
|
|
246
|
+
# Error will be reported when reading the views
|
|
247
|
+
continue
|
|
248
|
+
entity_by_view_ref[view_ref] = view.view
|
|
249
|
+
return entity_by_view_ref
|
|
250
|
+
|
|
251
|
+
def read_properties(
|
|
252
|
+
self,
|
|
253
|
+
properties: list[DMSProperty],
|
|
254
|
+
enum_collections: dict[str, dict[str, Any]],
|
|
255
|
+
container_ref_by_entity: dict[ContainerReference, ParsedEntity],
|
|
256
|
+
view_ref_by_entity: dict[ViewReference, ParsedEntity],
|
|
257
|
+
) -> ReadProperties:
|
|
258
|
+
read = ReadProperties()
|
|
259
|
+
view_entities = set(view_ref_by_entity.values())
|
|
260
|
+
container_entities = set(container_ref_by_entity.values())
|
|
261
|
+
for row_no, prop in enumerate(properties):
|
|
262
|
+
self._process_view_property(prop, read, row_no, view_ref_by_entity, view_entities)
|
|
263
|
+
if prop.container is None or prop.container_property is None:
|
|
264
|
+
# This is when the property is an edge or reverse direct relation property.
|
|
265
|
+
continue
|
|
266
|
+
self._process_container_property(
|
|
267
|
+
prop, read, enum_collections, row_no, container_ref_by_entity, container_entities
|
|
268
|
+
)
|
|
269
|
+
self._process_index(prop, read, row_no, container_ref_by_entity, container_entities)
|
|
270
|
+
self._process_constraint(prop, read, row_no, container_ref_by_entity, container_entities)
|
|
271
|
+
return read
|
|
272
|
+
|
|
273
|
+
def process_properties(self, read: ReadProperties) -> ProcessedProperties:
|
|
274
|
+
return ProcessedProperties(
|
|
275
|
+
container=self.create_container_properties(read),
|
|
276
|
+
view=self.create_view_properties(read),
|
|
277
|
+
indices=self.create_indices(read),
|
|
278
|
+
constraints=self.create_constraints(read),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def create_container_properties(
|
|
282
|
+
self, read: ReadProperties
|
|
283
|
+
) -> dict[ParsedEntity, dict[str, ContainerPropertyDefinition]]:
|
|
284
|
+
container_props: dict[ParsedEntity, dict[str, ContainerPropertyDefinition]] = defaultdict(dict)
|
|
285
|
+
for (container_entity, prop_id), prop_list in read.container.items():
|
|
286
|
+
if len(prop_list) == 0:
|
|
287
|
+
# Should not happen
|
|
288
|
+
continue
|
|
289
|
+
container_props[container_entity][prop_id] = prop_list[0].container_property
|
|
290
|
+
if len(prop_list) > 1 and self._are_definitions_different(prop_list):
|
|
291
|
+
# If multiple view properties are mapping to the same container property,
|
|
292
|
+
# the container property definitions must be the same.
|
|
293
|
+
rows_str = humanize_collection(
|
|
294
|
+
[self.source.adjust_row_number(self.Sheets.properties, p.row_no) for p in prop_list]
|
|
295
|
+
)
|
|
296
|
+
container_columns_str = humanize_collection(
|
|
297
|
+
[
|
|
298
|
+
self.PropertyColumns.connection,
|
|
299
|
+
self.PropertyColumns.value_type,
|
|
300
|
+
self.PropertyColumns.min_count,
|
|
301
|
+
self.PropertyColumns.max_count,
|
|
302
|
+
self.PropertyColumns.default,
|
|
303
|
+
self.PropertyColumns.auto_increment,
|
|
304
|
+
self.PropertyColumns.container_property_name,
|
|
305
|
+
self.PropertyColumns.container_property_description,
|
|
306
|
+
self.PropertyColumns.index,
|
|
307
|
+
self.PropertyColumns.constraint,
|
|
308
|
+
]
|
|
309
|
+
)
|
|
310
|
+
self.errors.append(
|
|
311
|
+
ModelSyntaxError(
|
|
312
|
+
message=(
|
|
313
|
+
f"In {self.source.location((self.Sheets.properties,))} "
|
|
314
|
+
f"when the column {self.PropertyColumns.container!r} and "
|
|
315
|
+
f"{self.PropertyColumns.container_property!r} are the same, "
|
|
316
|
+
f"all the container columns ({container_columns_str}) must be the same. "
|
|
317
|
+
f"Inconsistent definitions for container '{container_entity!s} "
|
|
318
|
+
f"and {prop_id!r}' found in rows {rows_str}."
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
return container_props
|
|
323
|
+
|
|
324
|
+
def _are_definitions_different(self, prop_list: list[ReadContainerProperty]) -> bool:
|
|
325
|
+
if len(prop_list) < 2:
|
|
326
|
+
return False
|
|
327
|
+
first_def = prop_list[0].container_property
|
|
328
|
+
for prop in prop_list[1:]:
|
|
329
|
+
if first_def != prop.container_property:
|
|
330
|
+
return True
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
def create_view_properties(self, read: ReadProperties) -> dict[ParsedEntity, dict[str, ViewRequestProperty]]:
|
|
334
|
+
view_props: dict[ParsedEntity, dict[str, ViewRequestProperty]] = defaultdict(dict)
|
|
335
|
+
for (view_entity, prop_id), prop_list in read.view.items():
|
|
336
|
+
if len(prop_list) == 0:
|
|
337
|
+
# Should not happen
|
|
338
|
+
continue
|
|
339
|
+
view_props[view_entity][prop_id] = prop_list[0].view_property
|
|
340
|
+
if len(prop_list) > 1:
|
|
341
|
+
# Safeguard against duplicated rows for view properties.
|
|
342
|
+
rows_str = humanize_collection(
|
|
343
|
+
[self.source.adjust_row_number(self.Sheets.properties, p.row_no) for p in prop_list]
|
|
344
|
+
)
|
|
345
|
+
self.errors.append(
|
|
346
|
+
ModelSyntaxError(
|
|
347
|
+
message=(
|
|
348
|
+
f"In {self.source.location((self.Sheets.properties,))} the combination of columns "
|
|
349
|
+
f"{self.PropertyColumns.view!r} and {self.PropertyColumns.view_property!r} must be unique. "
|
|
350
|
+
f"Duplicated entries for view '{view_entity!s}' and "
|
|
351
|
+
f"property '{prop_id!s}' found in rows {rows_str}."
|
|
352
|
+
)
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return view_props
|
|
357
|
+
|
|
358
|
+
def create_indices(self, read: ReadProperties) -> dict[ParsedEntity, dict[str, Index]]:
|
|
359
|
+
indices: dict[ParsedEntity, dict[str, Index]] = defaultdict(dict)
|
|
360
|
+
for (container_entity, index_id), index_list in read.indices.items():
|
|
361
|
+
if len(index_list) == 0:
|
|
362
|
+
continue
|
|
363
|
+
# Remove duplicates based on prop_id, keeping the first occurrence
|
|
364
|
+
# Note that we have already validated that the index definitions are the same
|
|
365
|
+
index_list = list({read_index.prop_id: read_index for read_index in index_list}.values())
|
|
366
|
+
index = index_list[0].index
|
|
367
|
+
if len(index_list) == 1:
|
|
368
|
+
indices[container_entity][index_id] = index
|
|
369
|
+
continue
|
|
370
|
+
if missing_order := [idx for idx in index_list if idx.order is None]:
|
|
371
|
+
row_str = humanize_collection(
|
|
372
|
+
[self.source.adjust_row_number(self.Sheets.properties, idx.row_no) for idx in missing_order]
|
|
373
|
+
)
|
|
374
|
+
self.errors.append(
|
|
375
|
+
ModelSyntaxError(
|
|
376
|
+
message=(
|
|
377
|
+
f"In table {self.Sheets.properties!r} column {self.PropertyColumns.index!r}: "
|
|
378
|
+
f"the index {index_id!r} on container {container_entity!s} is defined on multiple "
|
|
379
|
+
f"properties. This requires the 'order' attribute to be set. It is missing in rows "
|
|
380
|
+
f"{row_str}."
|
|
381
|
+
)
|
|
382
|
+
)
|
|
383
|
+
)
|
|
384
|
+
continue
|
|
385
|
+
index.properties = [idx.prop_id for idx in sorted(index_list, key=lambda x: x.order or 999)]
|
|
386
|
+
indices[container_entity][index_id] = index
|
|
387
|
+
return indices
|
|
388
|
+
|
|
389
|
+
def create_constraints(self, read: ReadProperties) -> dict[ParsedEntity, dict[str, Constraint]]:
|
|
390
|
+
constraints: dict[ParsedEntity, dict[str, Constraint]] = defaultdict(dict)
|
|
391
|
+
for (container_entity, constraint_id), constraint_list in read.constraints.items():
|
|
392
|
+
if len(constraint_list) == 0:
|
|
393
|
+
continue
|
|
394
|
+
# Remove duplicates based on prop_id, keeping the first occurrence
|
|
395
|
+
# Note that we have already validated that the constraint definitions are the same
|
|
396
|
+
constraint_list = list(
|
|
397
|
+
{read_constraint.prop_id: read_constraint for read_constraint in constraint_list}.values()
|
|
398
|
+
)
|
|
399
|
+
constraint = constraint_list[0].constraint
|
|
400
|
+
if len(constraint_list) == 1 or not isinstance(constraint, UniquenessConstraintDefinition):
|
|
401
|
+
constraints[container_entity][constraint_id] = constraint
|
|
402
|
+
continue
|
|
403
|
+
if missing_order := [c for c in constraint_list if c.order is None]:
|
|
404
|
+
row_str = humanize_collection(
|
|
405
|
+
[self.source.adjust_row_number(self.Sheets.properties, c.row_no) for c in missing_order]
|
|
406
|
+
)
|
|
407
|
+
self.errors.append(
|
|
408
|
+
ModelSyntaxError(
|
|
409
|
+
message=(
|
|
410
|
+
f"In table {self.Sheets.properties!r} column {self.PropertyColumns.constraint!r}: "
|
|
411
|
+
f"the uniqueness constraint {constraint_id!r} on container {container_entity!s} is defined "
|
|
412
|
+
f"on multiple properties. This requires the 'order' attribute to be set. It is missing in "
|
|
413
|
+
f"rows {row_str}."
|
|
414
|
+
)
|
|
415
|
+
)
|
|
416
|
+
)
|
|
417
|
+
continue
|
|
418
|
+
constraint.properties = [c.prop_id for c in sorted(constraint_list, key=lambda x: x.order or 999)]
|
|
419
|
+
constraints[container_entity][constraint_id] = constraint
|
|
420
|
+
return constraints
|
|
421
|
+
|
|
422
|
+
def _process_view_property(
|
|
423
|
+
self,
|
|
424
|
+
prop: DMSProperty,
|
|
425
|
+
read: ReadProperties,
|
|
426
|
+
row_no: int,
|
|
427
|
+
view_ref_by_entity: dict[ViewReference, ParsedEntity],
|
|
428
|
+
view_entities: set[ParsedEntity],
|
|
429
|
+
) -> None:
|
|
430
|
+
loc = (self.Sheets.properties, row_no)
|
|
431
|
+
data = self.read_view_property(prop, loc)
|
|
432
|
+
view_prop = self._validate_adapter(ViewRequestPropertyAdapter, data, loc)
|
|
433
|
+
if view_prop is None:
|
|
434
|
+
return None
|
|
435
|
+
if prop.view in view_entities:
|
|
436
|
+
read.view[(prop.view, prop.view_property)].append(ReadViewProperty(prop.view_property, row_no, view_prop))
|
|
437
|
+
return None
|
|
438
|
+
# The view entity was not found in the views table. This could either be because the view is missing,
|
|
439
|
+
# or because either the view entity in the Properties table and the View table are specified with/without
|
|
440
|
+
# default space/version inconsistently.
|
|
441
|
+
try:
|
|
442
|
+
view_ref = ViewReference.model_validate(self._create_view_ref(prop.view))
|
|
443
|
+
except ValidationError:
|
|
444
|
+
# Error will be reported when reading the views
|
|
445
|
+
return None
|
|
446
|
+
if view_ref in view_ref_by_entity:
|
|
447
|
+
view_ref_entity = view_ref_by_entity[view_ref]
|
|
448
|
+
read.view[(view_ref_entity, prop.view_property)].append(
|
|
449
|
+
ReadViewProperty(prop.view_property, row_no, view_prop)
|
|
450
|
+
)
|
|
451
|
+
else:
|
|
452
|
+
self.errors.append(
|
|
453
|
+
ModelSyntaxError(
|
|
454
|
+
message=(
|
|
455
|
+
f"In {self.source.location(loc)} the View '{prop.view!s}' "
|
|
456
|
+
f"was not found in the {self.Sheets.views!r} table."
|
|
457
|
+
)
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
return None
|
|
461
|
+
|
|
462
|
+
def _process_container_property(
|
|
463
|
+
self,
|
|
464
|
+
prop: DMSProperty,
|
|
465
|
+
read: ReadProperties,
|
|
466
|
+
enum_collections: dict[str, dict[str, Any]],
|
|
467
|
+
row_no: int,
|
|
468
|
+
container_ref_by_entity: dict[ContainerReference, ParsedEntity],
|
|
469
|
+
container_entities: set[ParsedEntity],
|
|
470
|
+
) -> None:
|
|
471
|
+
loc = (self.Sheets.properties, row_no)
|
|
472
|
+
data = self.read_container_property(prop, enum_collections, loc=loc)
|
|
473
|
+
container_prop = self._validate_obj(ContainerPropertyDefinition, data, loc)
|
|
474
|
+
if container_prop is None:
|
|
475
|
+
return None
|
|
476
|
+
if not (prop.container and prop.container_property):
|
|
477
|
+
return None
|
|
478
|
+
if prop.container in container_entities:
|
|
479
|
+
read.container[(prop.container, prop.container_property)].append(
|
|
480
|
+
ReadContainerProperty(prop.container_property, row_no, container_prop)
|
|
481
|
+
)
|
|
482
|
+
return None
|
|
483
|
+
# The container entity was not found in the containers table. This could either be because the container
|
|
484
|
+
# is missing, or because either the container entity in the Properties table and the Container table are
|
|
485
|
+
# specified with/without default space/version inconsistently.
|
|
486
|
+
try:
|
|
487
|
+
container_ref = ContainerReference.model_validate(self._create_container_ref(prop.container))
|
|
488
|
+
except ValidationError:
|
|
489
|
+
# Error will be reported when reading the containers
|
|
490
|
+
return None
|
|
491
|
+
if container_ref in container_ref_by_entity:
|
|
492
|
+
container_ref_entity = container_ref_by_entity[container_ref]
|
|
493
|
+
read.container[(container_ref_entity, prop.container_property)].append(
|
|
494
|
+
ReadContainerProperty(prop.container_property, row_no, container_prop)
|
|
495
|
+
)
|
|
496
|
+
# Container can be in CDF, this will be reported by a validator later.
|
|
497
|
+
return None
|
|
498
|
+
|
|
499
|
+
def _process_index(
|
|
500
|
+
self,
|
|
501
|
+
prop: DMSProperty,
|
|
502
|
+
read: ReadProperties,
|
|
503
|
+
row_no: int,
|
|
504
|
+
container_ref_by_entity: dict[ContainerReference, ParsedEntity],
|
|
505
|
+
container_entities: set[ParsedEntity],
|
|
506
|
+
) -> None:
|
|
507
|
+
if prop.index is None or prop.container_property is None or prop.container is None:
|
|
508
|
+
return
|
|
509
|
+
|
|
510
|
+
loc = (self.Sheets.properties, row_no, self.PropertyColumns.index)
|
|
511
|
+
for index in prop.index:
|
|
512
|
+
data = self.read_index(index, prop.container_property)
|
|
513
|
+
created = self._validate_adapter(IndexAdapter, data, loc)
|
|
514
|
+
if created is None:
|
|
515
|
+
continue
|
|
516
|
+
order = self._read_order(index.properties, loc)
|
|
517
|
+
|
|
518
|
+
if prop.container in container_entities:
|
|
519
|
+
read.indices[(prop.container, index.suffix)].append(
|
|
520
|
+
ReadIndex(
|
|
521
|
+
prop_id=prop.container_property,
|
|
522
|
+
order=order,
|
|
523
|
+
row_no=row_no,
|
|
524
|
+
index_id=index.suffix,
|
|
525
|
+
index=created,
|
|
526
|
+
)
|
|
527
|
+
)
|
|
528
|
+
continue
|
|
529
|
+
# The container entity was not found in the containers table. This could either be because the
|
|
530
|
+
# container is missing, or because either the container entity in the Properties table and the
|
|
531
|
+
# Container table are specified with/without default space/version inconsistently.
|
|
532
|
+
|
|
533
|
+
try:
|
|
534
|
+
container_ref = ContainerReference.model_validate(self._create_container_ref(prop.container))
|
|
535
|
+
except ValidationError:
|
|
536
|
+
# Error will be reported when reading the containers
|
|
537
|
+
continue
|
|
538
|
+
if container_ref in container_ref_by_entity:
|
|
539
|
+
container_ref_entity = container_ref_by_entity[container_ref]
|
|
540
|
+
read.indices[(container_ref_entity, index.suffix)].append(
|
|
541
|
+
ReadIndex(
|
|
542
|
+
prop_id=prop.container_property,
|
|
543
|
+
order=order,
|
|
544
|
+
row_no=row_no,
|
|
545
|
+
index_id=index.suffix,
|
|
546
|
+
index=created,
|
|
547
|
+
)
|
|
548
|
+
)
|
|
549
|
+
else:
|
|
550
|
+
# Error is reported when reading the property.
|
|
551
|
+
...
|
|
552
|
+
|
|
553
|
+
def _read_order(self, properties: dict[str, Any], loc: tuple[str | int, ...]) -> int | None:
|
|
554
|
+
if "order" not in properties:
|
|
555
|
+
return None
|
|
556
|
+
try:
|
|
557
|
+
return int(properties["order"])
|
|
558
|
+
except ValueError:
|
|
559
|
+
self.errors.append(
|
|
560
|
+
ModelSyntaxError(
|
|
561
|
+
message=f"In {self.source.location(loc)} invalid order value '{properties['order']}'. "
|
|
562
|
+
"Must be an integer."
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
return None
|
|
566
|
+
|
|
567
|
+
@staticmethod
|
|
568
|
+
def read_index(index: ParsedEntity, prop_id: str) -> dict[str, Any]:
|
|
569
|
+
return {
|
|
570
|
+
"indexType": index.prefix,
|
|
571
|
+
"properties": [prop_id],
|
|
572
|
+
**index.properties,
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
def _process_constraint(
|
|
576
|
+
self,
|
|
577
|
+
prop: DMSProperty,
|
|
578
|
+
read: ReadProperties,
|
|
579
|
+
row_no: int,
|
|
580
|
+
container_ref_by_entity: dict[ContainerReference, ParsedEntity],
|
|
581
|
+
container_entities: set[ParsedEntity],
|
|
582
|
+
) -> None:
|
|
583
|
+
if prop.constraint is None or prop.container_property is None or prop.container is None:
|
|
584
|
+
return
|
|
585
|
+
loc = (self.Sheets.properties, row_no, self.PropertyColumns.constraint)
|
|
586
|
+
for constraint in prop.constraint:
|
|
587
|
+
data = self.read_property_constraint(constraint, prop.container_property)
|
|
588
|
+
created = self._validate_adapter(ConstraintAdapter, data, loc)
|
|
589
|
+
if created is None:
|
|
590
|
+
continue
|
|
591
|
+
order = self._read_order(constraint.properties, loc)
|
|
592
|
+
|
|
593
|
+
if prop.container in container_entities:
|
|
594
|
+
read.constraints[(prop.container, constraint.suffix)].append(
|
|
595
|
+
ReadConstraint(
|
|
596
|
+
prop_id=prop.container_property,
|
|
597
|
+
order=order,
|
|
598
|
+
constraint_id=constraint.suffix,
|
|
599
|
+
row_no=row_no,
|
|
600
|
+
constraint=created,
|
|
601
|
+
)
|
|
602
|
+
)
|
|
603
|
+
continue
|
|
604
|
+
# The container entity was not found in the containers table. This could either be because the
|
|
605
|
+
# container is missing, or because either the container entity in the Properties table and the
|
|
606
|
+
# Container table are specified with/without default space/version inconsistently.
|
|
607
|
+
try:
|
|
608
|
+
container_ref = ContainerReference.model_validate(self._create_container_ref(prop.container))
|
|
609
|
+
except ValidationError:
|
|
610
|
+
# Error will be reported when reading the containers
|
|
611
|
+
continue
|
|
612
|
+
if container_ref in container_ref_by_entity:
|
|
613
|
+
container_ref_entity = container_ref_by_entity[container_ref]
|
|
614
|
+
read.constraints[(container_ref_entity, constraint.suffix)].append(
|
|
615
|
+
ReadConstraint(
|
|
616
|
+
prop_id=prop.container_property,
|
|
617
|
+
order=order,
|
|
618
|
+
constraint_id=constraint.suffix,
|
|
619
|
+
row_no=row_no,
|
|
620
|
+
constraint=created,
|
|
621
|
+
)
|
|
622
|
+
)
|
|
623
|
+
else:
|
|
624
|
+
# Error is reported when reading the property.
|
|
625
|
+
...
|
|
626
|
+
|
|
627
|
+
@staticmethod
|
|
628
|
+
def read_property_constraint(constraint: ParsedEntity, prop_id: str) -> dict[str, Any]:
|
|
629
|
+
return {"constraintType": constraint.prefix, "properties": [prop_id], **constraint.properties}
|
|
630
|
+
|
|
631
|
+
def read_view_property(self, prop: DMSProperty, loc: tuple[str | int, ...]) -> dict[str, Any]:
|
|
632
|
+
"""Reads a single view property from a given row in the properties table.
|
|
633
|
+
|
|
634
|
+
The type of property (core, edge, reverse direct relation) is determined based on the connection column
|
|
635
|
+
as follows:
|
|
636
|
+
1. If the connection is empty or 'direct' it is a core property.
|
|
637
|
+
2. If the connection is 'edge' it is an edge property.
|
|
638
|
+
3. If the connection is 'reverse' it is a reverse direct relation property
|
|
639
|
+
4. Otherwise, it is an error.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
prop (DMSProperty): The property row to read.
|
|
643
|
+
loc (tuple[str | int, ...]): The location of the property in the source for error reporting.
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
ViewRequestProperty: The parsed view property.
|
|
647
|
+
"""
|
|
648
|
+
|
|
649
|
+
if prop.connection is None or prop.connection.suffix == "direct":
|
|
650
|
+
return self.read_core_view_property(prop)
|
|
651
|
+
elif prop.connection.suffix == "edge":
|
|
652
|
+
return self.read_edge_view_property(prop, loc)
|
|
653
|
+
elif prop.connection.suffix == "reverse":
|
|
654
|
+
return self.read_reverse_direct_relation_view_property(prop)
|
|
655
|
+
else:
|
|
656
|
+
self.errors.append(
|
|
657
|
+
ModelSyntaxError(
|
|
658
|
+
message=f"In {self.source.location(loc)} invalid connection type '{prop.connection.suffix}'. "
|
|
659
|
+
)
|
|
660
|
+
)
|
|
661
|
+
return {}
|
|
662
|
+
|
|
663
|
+
def read_core_view_property(self, prop: DMSProperty) -> dict[str, Any]:
|
|
664
|
+
source: dict[str, str | None] | None = None
|
|
665
|
+
if prop.connection is not None and prop.value_type.suffix != self.CELL_MISSING:
|
|
666
|
+
source = self._create_view_ref(prop.value_type)
|
|
667
|
+
|
|
668
|
+
return dict(
|
|
669
|
+
connectionType="primary_property",
|
|
670
|
+
name=prop.name,
|
|
671
|
+
description=prop.description,
|
|
672
|
+
container=self._create_container_ref(prop.container),
|
|
673
|
+
containerPropertyIdentifier=prop.container_property,
|
|
674
|
+
source=source,
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
def read_edge_view_property(self, prop: DMSProperty, loc: tuple[str | int, ...]) -> dict[str, Any]:
|
|
678
|
+
if prop.connection is None:
|
|
679
|
+
return {}
|
|
680
|
+
edge_source: dict[str, str | None] | None = None
|
|
681
|
+
if "edgeSource" in prop.connection.properties:
|
|
682
|
+
edge_source = self._create_view_ref_unparsed(
|
|
683
|
+
prop.connection.properties["edgeSource"], (*loc, self.PropertyColumns.connection, "edgeSource")
|
|
684
|
+
)
|
|
685
|
+
return dict(
|
|
686
|
+
connectionType="single_edge_connection" if prop.max_count == 1 else "multi_edge_connection",
|
|
687
|
+
name=prop.name,
|
|
688
|
+
description=prop.description,
|
|
689
|
+
source=self._create_view_ref(prop.value_type),
|
|
690
|
+
type=self._create_node_ref_unparsed(
|
|
691
|
+
prop.connection.properties.get("type"),
|
|
692
|
+
prop.view,
|
|
693
|
+
prop.view_property,
|
|
694
|
+
(*loc, self.PropertyColumns.connection, "type"),
|
|
695
|
+
),
|
|
696
|
+
edgeSource=edge_source,
|
|
697
|
+
direction=prop.connection.properties.get("direction", "outwards"),
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
def read_reverse_direct_relation_view_property(
|
|
701
|
+
self,
|
|
702
|
+
prop: DMSProperty,
|
|
703
|
+
) -> dict[str, Any]:
|
|
704
|
+
if prop.connection is None:
|
|
705
|
+
return {}
|
|
706
|
+
view_ref = self._create_view_ref(prop.value_type)
|
|
707
|
+
return dict(
|
|
708
|
+
connectionType="single_reverse_direct_relation" if prop.max_count == 1 else "multi_reverse_direct_relation",
|
|
709
|
+
name=prop.name,
|
|
710
|
+
description=prop.description,
|
|
711
|
+
source=view_ref,
|
|
712
|
+
through={
|
|
713
|
+
"source": view_ref,
|
|
714
|
+
"identifier": prop.connection.properties.get("property"),
|
|
715
|
+
},
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
def read_container_property(
|
|
719
|
+
self, prop: DMSProperty, enum_collections: dict[str, dict[str, Any]], loc: tuple[str | int, ...]
|
|
720
|
+
) -> dict[str, Any]:
|
|
721
|
+
data_type = self._read_data_type(prop, enum_collections, loc)
|
|
722
|
+
return dict(
|
|
723
|
+
immutable=prop.immutable,
|
|
724
|
+
nullable=prop.min_count == 0 or prop.min_count is None,
|
|
725
|
+
autoIncrement=prop.auto_increment,
|
|
726
|
+
defaultValue=prop.default,
|
|
727
|
+
description=prop.container_property_description,
|
|
728
|
+
name=prop.container_property_name,
|
|
729
|
+
type=data_type,
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
def _read_data_type(
|
|
733
|
+
self, prop: DMSProperty, enum_collections: dict[str, dict[str, Any]], loc: tuple[str | int, ...]
|
|
734
|
+
) -> dict[str, Any]:
|
|
735
|
+
# Implementation to read the container property type from DMSProperty
|
|
736
|
+
is_list = None if prop.max_count is None else prop.max_count > 1
|
|
737
|
+
max_list_size: int | None = None
|
|
738
|
+
if is_list and prop.max_count is not None:
|
|
739
|
+
max_list_size = prop.max_count
|
|
740
|
+
|
|
741
|
+
args: dict[str, Any] = {
|
|
742
|
+
"maxListSize": max_list_size,
|
|
743
|
+
"list": is_list,
|
|
744
|
+
"type": "direct" if prop.connection is not None else prop.value_type.suffix,
|
|
745
|
+
}
|
|
746
|
+
args.update(prop.value_type.properties)
|
|
747
|
+
if "container" in args and prop.connection is not None:
|
|
748
|
+
# Direct relation constraint.
|
|
749
|
+
args["container"] = self._create_container_ref_unparsed(
|
|
750
|
+
prop.connection.properties["container"], (*loc, self.PropertyColumns.connection, "container")
|
|
751
|
+
)
|
|
752
|
+
if args["type"] == "enum" and "collection" in prop.value_type.properties:
|
|
753
|
+
args["values"] = enum_collections.get(prop.value_type.properties["collection"], {})
|
|
754
|
+
return args
|
|
755
|
+
|
|
756
|
+
def read_containers(
|
|
757
|
+
self, containers: list[DMSContainer], properties: ProcessedProperties
|
|
758
|
+
) -> list[ContainerRequest]:
|
|
759
|
+
# Implementation to read containers from DMSContainer list
|
|
760
|
+
containers_requests: list[ContainerRequest] = []
|
|
761
|
+
rows_by_seen: dict[ParsedEntity, list[int]] = defaultdict(list)
|
|
762
|
+
for row_no, container in enumerate(containers):
|
|
763
|
+
property_constraints = properties.constraints.get(container.container, {})
|
|
764
|
+
require_constraints = self.read_container_constraints(container, row_no)
|
|
765
|
+
if conflict := set(property_constraints.keys()).intersection(set(require_constraints.keys())):
|
|
766
|
+
conflict_str = humanize_collection(conflict)
|
|
767
|
+
location_str = self.source.location((self.Sheets.containers, row_no, self.ContainerColumns.constraint))
|
|
768
|
+
self.errors.append(
|
|
769
|
+
ModelSyntaxError(
|
|
770
|
+
message=(
|
|
771
|
+
f"In {location_str} the container '{container.container!s}' has constraints defined "
|
|
772
|
+
f"with the same identifier(s) as the uniqueness constraint defined in the "
|
|
773
|
+
f"{self.Sheets.properties} sheet. Ensure that the identifiers are unique. "
|
|
774
|
+
f"Conflicting identifiers: {conflict_str}. "
|
|
775
|
+
)
|
|
776
|
+
)
|
|
777
|
+
)
|
|
778
|
+
constraints = {**property_constraints, **require_constraints}
|
|
779
|
+
container_request = self._validate_obj(
|
|
780
|
+
ContainerRequest,
|
|
781
|
+
dict(
|
|
782
|
+
**self._create_container_ref(container.container),
|
|
783
|
+
usedFor=container.used_for,
|
|
784
|
+
name=container.name,
|
|
785
|
+
description=container.description,
|
|
786
|
+
properties=properties.container[container.container],
|
|
787
|
+
indexes=properties.indices.get(container.container),
|
|
788
|
+
constraints=constraints or None,
|
|
789
|
+
),
|
|
790
|
+
(self.Sheets.containers, row_no),
|
|
791
|
+
)
|
|
792
|
+
if container_request is None:
|
|
793
|
+
continue
|
|
794
|
+
if container.container in rows_by_seen:
|
|
795
|
+
rows_by_seen[container.container].append(row_no)
|
|
796
|
+
else:
|
|
797
|
+
containers_requests.append(container_request)
|
|
798
|
+
rows_by_seen[container.container] = [row_no]
|
|
799
|
+
for entity, rows in rows_by_seen.items():
|
|
800
|
+
if len(rows) > 1:
|
|
801
|
+
rows_str = humanize_collection([self.source.adjust_row_number(self.Sheets.containers, r) for r in rows])
|
|
802
|
+
self.errors.append(
|
|
803
|
+
ModelSyntaxError(
|
|
804
|
+
message=(
|
|
805
|
+
f"In {self.source.location((self.Sheets.containers,))} the values in "
|
|
806
|
+
f"column {self.ContainerColumns.container!r} must be unique. "
|
|
807
|
+
f"Duplicated entries for container '{entity!s}' found in rows {rows_str}."
|
|
808
|
+
)
|
|
809
|
+
)
|
|
810
|
+
)
|
|
811
|
+
return containers_requests
|
|
812
|
+
|
|
813
|
+
def read_container_constraints(self, container: DMSContainer, row_no: int) -> dict[str, Constraint]:
|
|
814
|
+
constraints: dict[str, Constraint] = {}
|
|
815
|
+
if not container.constraint:
|
|
816
|
+
return constraints
|
|
817
|
+
for entity in container.constraint:
|
|
818
|
+
loc = self.Sheets.containers, row_no, self.ContainerColumns.constraint
|
|
819
|
+
if entity.prefix != "requires":
|
|
820
|
+
self.errors.append(
|
|
821
|
+
ModelSyntaxError(
|
|
822
|
+
message=(
|
|
823
|
+
f"In {self.source.location(loc)} the constraint '{entity.suffix}' on container "
|
|
824
|
+
f"'{container.container!s}' has an invalid type '{entity.prefix}'. Only 'requires' "
|
|
825
|
+
f"constraints are supported at the container level."
|
|
826
|
+
)
|
|
827
|
+
)
|
|
828
|
+
)
|
|
829
|
+
continue
|
|
830
|
+
|
|
831
|
+
if "require" not in entity.properties:
|
|
832
|
+
self.errors.append(
|
|
833
|
+
ModelSyntaxError(
|
|
834
|
+
message=(
|
|
835
|
+
f"In {self.source.location(loc)} the constraint '{entity.suffix}' on container "
|
|
836
|
+
f"'{container.container!s}' is missing the "
|
|
837
|
+
f"'require' property which is required for container level constraints."
|
|
838
|
+
)
|
|
839
|
+
)
|
|
840
|
+
)
|
|
841
|
+
continue
|
|
842
|
+
data = {
|
|
843
|
+
"constraintType": entity.prefix,
|
|
844
|
+
"require": self._create_container_ref_unparsed(entity.properties["require"], loc),
|
|
845
|
+
}
|
|
846
|
+
created = self._validate_adapter(ConstraintAdapter, data, loc)
|
|
847
|
+
if created is None:
|
|
848
|
+
continue
|
|
849
|
+
constraints[entity.suffix] = created
|
|
850
|
+
return constraints
|
|
851
|
+
|
|
852
|
+
def read_views(
|
|
853
|
+
self,
|
|
854
|
+
views: list[DMSView],
|
|
855
|
+
properties: dict[ParsedEntity, dict[str, ViewRequestProperty]],
|
|
856
|
+
) -> tuple[list[ViewRequest], set[ParsedEntity]]:
|
|
857
|
+
views_requests: list[ViewRequest] = []
|
|
858
|
+
rows_by_seen: dict[ParsedEntity, list[int]] = defaultdict(list)
|
|
859
|
+
for row_no, view in enumerate(views):
|
|
860
|
+
view_request = self._validate_obj(
|
|
861
|
+
ViewRequest,
|
|
862
|
+
dict(
|
|
863
|
+
**self._create_view_ref(view.view),
|
|
864
|
+
name=view.name,
|
|
865
|
+
description=view.description,
|
|
866
|
+
implements=[self._create_view_ref(impl) for impl in view.implements] if view.implements else None,
|
|
867
|
+
filter=self._create_filter_dict(view.filter, row_no) if view.filter else None,
|
|
868
|
+
properties=properties.get(view.view, {}),
|
|
869
|
+
),
|
|
870
|
+
(self.Sheets.views, row_no),
|
|
871
|
+
)
|
|
872
|
+
if view_request is None:
|
|
873
|
+
continue
|
|
874
|
+
if view.view in rows_by_seen:
|
|
875
|
+
rows_by_seen[view.view].append(row_no)
|
|
876
|
+
else:
|
|
877
|
+
views_requests.append(view_request)
|
|
878
|
+
rows_by_seen[view.view] = [row_no]
|
|
879
|
+
for entity, rows in rows_by_seen.items():
|
|
880
|
+
if len(rows) > 1:
|
|
881
|
+
rows_str = humanize_collection([self.source.adjust_row_number(self.Sheets.views, r) for r in rows])
|
|
882
|
+
self.errors.append(
|
|
883
|
+
ModelSyntaxError(
|
|
884
|
+
message=(
|
|
885
|
+
f"In {self.source.location((self.Sheets.views,))} the values in "
|
|
886
|
+
f"column {self.ViewColumns.view!r} must be unique. "
|
|
887
|
+
f"Duplicated entries for view '{entity!s}' found in rows {rows_str}."
|
|
888
|
+
)
|
|
889
|
+
)
|
|
890
|
+
)
|
|
891
|
+
return views_requests, set(rows_by_seen.keys())
|
|
892
|
+
|
|
893
|
+
def _create_filter_dict(self, filter: TableViewFilter, row_no: int) -> dict[str, Any] | None:
|
|
894
|
+
if isinstance(filter, RAWFilterTableFilter):
|
|
895
|
+
try:
|
|
896
|
+
return json.loads(filter.filter)
|
|
897
|
+
except ValueError as e:
|
|
898
|
+
self.errors.append(
|
|
899
|
+
ModelSyntaxError(
|
|
900
|
+
message=(
|
|
901
|
+
f"In {self.source.location((self.Sheets.views, row_no, self.ViewColumns.filter))} "
|
|
902
|
+
f"must be valid json. Got error {e!s}"
|
|
903
|
+
)
|
|
904
|
+
)
|
|
905
|
+
)
|
|
906
|
+
return None
|
|
907
|
+
elif isinstance(filter, EntityTableFilter):
|
|
908
|
+
return self._create_entity_filter_dict(filter)
|
|
909
|
+
else:
|
|
910
|
+
# This is unreachable due to validation of the TableViewFilter model.
|
|
911
|
+
raise RuntimeError(f"Unknown filter type {filter.__class__.__name__}")
|
|
912
|
+
|
|
913
|
+
def _create_entity_filter_dict(self, filter: EntityTableFilter) -> dict[str, Any]:
|
|
914
|
+
"""Creates the filter dictionary from an EntityTableFilter."""
|
|
915
|
+
if filter.type == "hasData":
|
|
916
|
+
return {
|
|
917
|
+
"hasData": [{**self._create_container_ref(entity), "type": "container"} for entity in filter.entities]
|
|
918
|
+
}
|
|
919
|
+
elif filter.type == "nodeType":
|
|
920
|
+
if len(filter.entities) == 1:
|
|
921
|
+
return {"equals": {"property": ["node", "type"], "value": self._create_node_ref(filter.entities[0])}}
|
|
922
|
+
else:
|
|
923
|
+
return {
|
|
924
|
+
"in": {
|
|
925
|
+
"property": ["node", "type"],
|
|
926
|
+
"values": [self._create_node_ref(entity) for entity in filter.entities],
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
else:
|
|
930
|
+
# This is unreachable due to validation of the EntityTableFilter model.
|
|
931
|
+
raise RuntimeError(f"Unknown filter type {filter.__class__.__name__}")
|
|
932
|
+
|
|
933
|
+
def read_data_model(self, tables: TableDMS, valid_view_entities: set[ParsedEntity]) -> DataModelRequest:
|
|
934
|
+
data: dict[str, Any] = {
|
|
935
|
+
**{meta.key: meta.value for meta in tables.metadata},
|
|
936
|
+
"views": [self._create_view_ref(view.view) for view in tables.views if view.view in valid_view_entities],
|
|
937
|
+
}
|
|
938
|
+
if description := self._create_description_field(data):
|
|
939
|
+
data["description"] = description
|
|
940
|
+
model = self._validate_obj(DataModelRequest, data, (self.Sheets.metadata,), field_name="value")
|
|
941
|
+
if model is None:
|
|
942
|
+
# This is the last step, so we can raise the error here.
|
|
943
|
+
raise DataModelImportException(self.errors) from None
|
|
944
|
+
return model
|
|
945
|
+
|
|
946
|
+
def _create_description_field(self, data: dict[str, Any]) -> str | None:
|
|
947
|
+
"""DataModelRequest does not have a 'creator' field, this is a special addition that the Neat tables
|
|
948
|
+
format supports (and recommends using). To keep it, Neat adds it to the suffix of the description field.
|
|
949
|
+
"""
|
|
950
|
+
if CREATOR_KEY not in data and CREATOR_KEY.title() not in data:
|
|
951
|
+
return None
|
|
952
|
+
creator_val = data.pop(CREATOR_KEY, data.pop(CREATOR_KEY.title(), None))
|
|
953
|
+
|
|
954
|
+
if not creator_val:
|
|
955
|
+
return None
|
|
956
|
+
|
|
957
|
+
creator = str(creator_val)
|
|
958
|
+
# We do a split/join to clean up any spaces around commas. Ensuring that we have a consistent
|
|
959
|
+
# canonical format.
|
|
960
|
+
cleaned_creator = ", ".join(item.strip() for item in creator.split(","))
|
|
961
|
+
if not cleaned_creator:
|
|
962
|
+
return None
|
|
963
|
+
suffix = f"{CREATOR_MARKER}{cleaned_creator}"
|
|
964
|
+
description = data.get("description", "")
|
|
965
|
+
if len(description) + len(suffix) > DATA_MODEL_DESCRIPTION_MAX_LENGTH:
|
|
966
|
+
description = description[: DATA_MODEL_DESCRIPTION_MAX_LENGTH - len(suffix) - 4] + "..."
|
|
967
|
+
if description:
|
|
968
|
+
description = f"{description} {suffix}"
|
|
969
|
+
else:
|
|
970
|
+
description = suffix
|
|
971
|
+
return description
|
|
972
|
+
|
|
973
|
+
def _parse_entity(self, entity: str, loc: tuple[str | int, ...]) -> ParsedEntity | None:
|
|
974
|
+
try:
|
|
975
|
+
parsed = parse_entity(entity)
|
|
976
|
+
except ValueError as e:
|
|
977
|
+
self.errors.append(
|
|
978
|
+
ModelSyntaxError(message=f"In {self.source.location(loc)} failed to parse entity '{entity}': {e!s}")
|
|
979
|
+
)
|
|
980
|
+
return None
|
|
981
|
+
return parsed
|
|
982
|
+
|
|
983
|
+
def _create_view_ref_unparsed(self, entity: str, loc: tuple[str | int, ...]) -> dict[str, str | None]:
|
|
984
|
+
parsed = self._parse_entity(entity, loc)
|
|
985
|
+
if parsed is None:
|
|
986
|
+
return dict()
|
|
987
|
+
return self._create_view_ref(parsed)
|
|
988
|
+
|
|
989
|
+
def _create_view_ref(self, entity: ParsedEntity | None) -> dict[str, str | None]:
|
|
990
|
+
if entity is None or entity.suffix == "":
|
|
991
|
+
# If no suffix is given, we cannot create a valid reference.
|
|
992
|
+
return dict()
|
|
993
|
+
space, version = entity.prefix, entity.properties.get("version")
|
|
994
|
+
if space == "":
|
|
995
|
+
space = self.default_space
|
|
996
|
+
# Only if default space is used, we can use default version.
|
|
997
|
+
if version is None:
|
|
998
|
+
version = self.default_version
|
|
999
|
+
return {
|
|
1000
|
+
"space": space,
|
|
1001
|
+
"externalId": entity.suffix,
|
|
1002
|
+
"version": version,
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
def _create_container_ref_unparsed(self, entity: str, loc: tuple[str | int, ...]) -> dict[str, str]:
|
|
1006
|
+
parsed = self._parse_entity(entity, loc)
|
|
1007
|
+
if parsed is None:
|
|
1008
|
+
return dict()
|
|
1009
|
+
return self._create_container_ref(parsed)
|
|
1010
|
+
|
|
1011
|
+
def _create_container_ref(self, entity: ParsedEntity | None) -> dict[str, str]:
|
|
1012
|
+
if entity is None or entity.suffix == "":
|
|
1013
|
+
# If no suffix is given, we cannot create a valid reference.
|
|
1014
|
+
return dict()
|
|
1015
|
+
return {
|
|
1016
|
+
"space": entity.prefix or self.default_space,
|
|
1017
|
+
"externalId": entity.suffix,
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
def _create_node_ref_unparsed(
|
|
1021
|
+
self, entity: str | None, view: ParsedEntity, view_prop: str, loc: tuple[str | int, ...]
|
|
1022
|
+
) -> dict[str, str | None]:
|
|
1023
|
+
if entity is None:
|
|
1024
|
+
# Use default
|
|
1025
|
+
return self._create_node_ref(None, view, view_prop)
|
|
1026
|
+
parsed = self._parse_entity(entity, loc)
|
|
1027
|
+
if parsed is None:
|
|
1028
|
+
return dict()
|
|
1029
|
+
return self._create_node_ref(parsed, view, view_prop)
|
|
1030
|
+
|
|
1031
|
+
@overload
|
|
1032
|
+
def _create_node_ref(
|
|
1033
|
+
self, entity: ParsedEntity, *, view: None = None, view_prop: None = None
|
|
1034
|
+
) -> dict[str, str | None]: ...
|
|
1035
|
+
|
|
1036
|
+
@overload
|
|
1037
|
+
def _create_node_ref(
|
|
1038
|
+
self, entity: ParsedEntity | None, view: ParsedEntity, view_prop: str
|
|
1039
|
+
) -> dict[str, str | None]: ...
|
|
1040
|
+
|
|
1041
|
+
def _create_node_ref(
|
|
1042
|
+
self, entity: ParsedEntity | None, view: ParsedEntity | None = None, view_prop: str | None = None
|
|
1043
|
+
) -> dict[str, str | None]:
|
|
1044
|
+
if entity is None or entity.suffix == "":
|
|
1045
|
+
if view is None or view_prop is None:
|
|
1046
|
+
return dict()
|
|
1047
|
+
# If no suffix is given, we fallback to the view's property
|
|
1048
|
+
return {
|
|
1049
|
+
"space": view.prefix or self.default_space,
|
|
1050
|
+
"externalId": f"{view.suffix}.{view_prop}",
|
|
1051
|
+
}
|
|
1052
|
+
return {
|
|
1053
|
+
"space": entity.prefix or self.default_space,
|
|
1054
|
+
"externalId": entity.suffix,
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
def _validate_obj(
|
|
1058
|
+
self,
|
|
1059
|
+
obj: type[T_BaseModel],
|
|
1060
|
+
data: dict,
|
|
1061
|
+
parent_loc: tuple[str | int, ...],
|
|
1062
|
+
field_name: Literal["field", "column", "value"] = "column",
|
|
1063
|
+
) -> T_BaseModel | None:
|
|
1064
|
+
try:
|
|
1065
|
+
return obj.model_validate(data)
|
|
1066
|
+
except ValidationError as e:
|
|
1067
|
+
self._add_error_messages(e, parent_loc, field_name=field_name)
|
|
1068
|
+
return None
|
|
1069
|
+
|
|
1070
|
+
def _validate_adapter(
|
|
1071
|
+
self, adapter: TypeAdapter[T_BaseModel], data: dict[str, Any], parent_loc: tuple[str | int, ...]
|
|
1072
|
+
) -> T_BaseModel | None:
|
|
1073
|
+
try:
|
|
1074
|
+
return adapter.validate_python(data, strict=True)
|
|
1075
|
+
except ValidationError as e:
|
|
1076
|
+
self._add_error_messages(e, parent_loc, field_name="column")
|
|
1077
|
+
return None
|
|
1078
|
+
|
|
1079
|
+
def _add_error_messages(
|
|
1080
|
+
self,
|
|
1081
|
+
error: ValidationError,
|
|
1082
|
+
parent_loc: tuple[str | int, ...],
|
|
1083
|
+
field_name: Literal["field", "column", "value"] = "column",
|
|
1084
|
+
) -> None:
|
|
1085
|
+
context = ValidationContext(
|
|
1086
|
+
parent_loc=parent_loc,
|
|
1087
|
+
humanize_location=self.source.location,
|
|
1088
|
+
field_name=field_name,
|
|
1089
|
+
missing_required_descriptor="empty" if field_name == "column" else "missing",
|
|
1090
|
+
)
|
|
1091
|
+
|
|
1092
|
+
if field_renaming := self.source.field_mapping(parent_loc[0]):
|
|
1093
|
+
context.field_renaming = field_renaming
|
|
1094
|
+
|
|
1095
|
+
seen: set[str] = set()
|
|
1096
|
+
for error_details in error.errors(include_input=True, include_url=False):
|
|
1097
|
+
message = humanize_validation_error(error_details, context)
|
|
1098
|
+
if message in seen:
|
|
1099
|
+
continue
|
|
1100
|
+
|
|
1101
|
+
seen.add(message)
|
|
1102
|
+
self.errors.append(ModelSyntaxError(message=message))
|