cognite-neat 0.123.2__py3-none-any.whl → 0.127.30__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 +2 -2
- cognite/neat/_client/__init__.py +4 -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 +125 -0
- cognite/neat/_client/data_classes.py +44 -0
- cognite/neat/_client/data_model_api.py +115 -0
- cognite/neat/_client/spaces_api.py +115 -0
- cognite/neat/_client/statistics_api.py +24 -0
- cognite/neat/_client/views_api.py +129 -0
- cognite/neat/_config.py +185 -0
- cognite/neat/_data_model/_analysis.py +196 -0
- cognite/neat/_data_model/_constants.py +67 -0
- cognite/neat/_data_model/_identifiers.py +61 -0
- cognite/neat/_data_model/_shared.py +41 -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 +529 -0
- cognite/neat/_data_model/deployer/deployer.py +401 -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 +421 -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 +295 -0
- cognite/neat/_data_model/importers/_table_importer/importer.py +192 -0
- cognite/neat/_data_model/importers/_table_importer/reader.py +1063 -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 +135 -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 +282 -0
- cognite/neat/_data_model/models/dms/_view_property.py +235 -0
- cognite/neat/_data_model/models/dms/_views.py +210 -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 +364 -0
- cognite/neat/_data_model/validation/dms/_base.py +307 -0
- cognite/neat/_data_model/validation/dms/_connections.py +638 -0
- cognite/neat/_data_model/validation/dms/_consistency.py +57 -0
- cognite/neat/_data_model/validation/dms/_containers.py +174 -0
- cognite/neat/_data_model/validation/dms/_limits.py +420 -0
- cognite/neat/_data_model/validation/dms/_orchestrator.py +222 -0
- cognite/neat/_data_model/validation/dms/_views.py +103 -0
- cognite/neat/_exceptions.py +56 -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 +303 -0
- cognite/neat/_session/_html/static/deployment.js +150 -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 +75 -0
- cognite/neat/_session/_html/templates/issues.html +45 -0
- cognite/neat/_session/_issues.py +81 -0
- cognite/neat/_session/_opt.py +35 -0
- cognite/neat/_session/_physical.py +261 -0
- cognite/neat/_session/_result.py +236 -0
- cognite/neat/_session/_session.py +88 -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 +82 -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 +81 -0
- cognite/neat/_store/_store.py +156 -0
- cognite/neat/_utils/__init__.py +0 -0
- cognite/neat/_utils/_reader.py +194 -0
- cognite/neat/_utils/auxiliary.py +39 -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/text.py +71 -0
- cognite/neat/_utils/useful_types.py +37 -0
- cognite/neat/_utils/validation.py +154 -0
- cognite/neat/_version.py +1 -1
- 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 +86 -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 +10 -3
- cognite/neat/v0/core/_data_model/__init__.py +0 -0
- cognite/neat/{core → v0/core}/_data_model/_constants.py +9 -6
- cognite/neat/{core → v0/core}/_data_model/_shared.py +5 -5
- cognite/neat/{core → v0/core}/_data_model/analysis/_base.py +12 -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 +13 -13
- 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 -133
- cognite/neat/{core → v0/core}/_data_model/exporters/_data_model2yaml.py +1 -1
- cognite/neat/{core → v0/core}/_data_model/importers/__init__.py +4 -6
- 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 +6 -6
- cognite/neat/{core → v0/core}/_data_model/importers/_dms2data_model.py +19 -16
- cognite/neat/v0/core/_data_model/importers/_graph2data_model.py +299 -0
- cognite/neat/v0/core/_data_model/importers/_rdf/__init__.py +4 -0
- cognite/neat/{core → v0/core}/_data_model/importers/_rdf/_base.py +13 -13
- 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/v0/core/_data_model/importers/_rdf/_shared.py +255 -0
- cognite/neat/{core → v0/core}/_data_model/importers/_spreadsheet2data_model.py +94 -13
- 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/v0/core/_data_model/models/_import_contexts.py +82 -0
- cognite/neat/{core → v0/core}/_data_model/models/_types.py +5 -5
- cognite/neat/{core → v0/core}/_data_model/models/conceptual/_unverified.py +18 -12
- cognite/neat/v0/core/_data_model/models/conceptual/_validation.py +308 -0
- cognite/neat/{core → v0/core}/_data_model/models/conceptual/_verified.py +13 -11
- 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 +28 -21
- cognite/neat/{core → v0/core}/_data_model/models/physical/_unverified.py +141 -38
- cognite/neat/{core → v0/core}/_data_model/models/physical/_validation.py +190 -24
- cognite/neat/{core → v0/core}/_data_model/models/physical/_verified.py +135 -15
- cognite/neat/{core → v0/core}/_data_model/transformers/__init__.py +2 -0
- cognite/neat/{core → v0/core}/_data_model/transformers/_base.py +4 -4
- cognite/neat/{core → v0/core}/_data_model/transformers/_converters.py +39 -32
- cognite/neat/{core → v0/core}/_data_model/transformers/_mapping.py +7 -7
- cognite/neat/v0/core/_data_model/transformers/_union_conceptual.py +208 -0
- 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 +1 -1
- 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 +11 -6
- cognite/neat/{core → v0/core}/_issues/_contextmanagers.py +8 -6
- cognite/neat/{core → v0/core}/_issues/_factory.py +11 -8
- cognite/neat/{core → v0/core}/_issues/errors/__init__.py +3 -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 +12 -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 +5 -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 +39 -4
- cognite/neat/{core → v0/core}/_issues/warnings/_properties.py +13 -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 +13 -12
- cognite/neat/{core → v0/core}/_store/_instance.py +45 -12
- 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 +1 -1
- cognite/neat/{core → v0/core}/_utils/auxiliary.py +7 -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 +38 -14
- cognite/neat/{core → v0/core}/_utils/reader/_base.py +1 -1
- cognite/neat/{core → v0/core}/_utils/spreadsheet.py +22 -4
- cognite/neat/v0/core/_utils/tarjan.py +44 -0
- cognite/neat/{core → v0/core}/_utils/text.py +1 -1
- cognite/neat/{core → v0/core}/_utils/upload.py +3 -3
- 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 -10
- 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 +33 -43
- 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 +22 -8
- 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}/engine/_load.py +1 -1
- cognite/neat/{session → v0/session}/exceptions.py +5 -5
- cognite/neat/v1.py +3 -0
- {cognite_neat-0.123.2.dist-info → cognite_neat-0.127.30.dist-info}/METADATA +9 -8
- cognite_neat-0.127.30.dist-info/RECORD +319 -0
- {cognite_neat-0.123.2.dist-info → cognite_neat-0.127.30.dist-info}/WHEEL +1 -1
- cognite/neat/core/_data_model/importers/_dtdl2data_model/__init__.py +0 -3
- cognite/neat/core/_data_model/importers/_dtdl2data_model/_unit_lookup.py +0 -224
- cognite/neat/core/_data_model/importers/_dtdl2data_model/dtdl_converter.py +0 -320
- cognite/neat/core/_data_model/importers/_dtdl2data_model/dtdl_importer.py +0 -155
- cognite/neat/core/_data_model/importers/_dtdl2data_model/spec.py +0 -363
- cognite/neat/core/_data_model/importers/_rdf/__init__.py +0 -5
- cognite/neat/core/_data_model/importers/_rdf/_imf2data_model.py +0 -98
- cognite/neat/core/_data_model/importers/_rdf/_owl2data_model.py +0 -87
- cognite/neat/core/_data_model/importers/_rdf/_shared.py +0 -168
- cognite/neat/core/_data_model/models/conceptual/_validation.py +0 -294
- 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-0.123.2.dist-info/RECORD +0 -197
- /cognite/neat/{core → _data_model}/__init__.py +0 -0
- /cognite/neat/{core/_client/_api → _data_model/deployer}/__init__.py +0 -0
- /cognite/neat/{core/_client/data_classes → _data_model/exporters/_table_exporter}/__init__.py +0 -0
- /cognite/neat/{core/_data_model → _data_model/importers/_table_importer}/__init__.py +0 -0
- /cognite/neat/{core/_instances → _data_model/models}/__init__.py +0 -0
- /cognite/neat/{core/_instances/extractors/_classic_cdf → _data_model/models/conceptual}/__init__.py +0 -0
- /cognite/neat/{core/_utils → _data_model/validation}/__init__.py +0 -0
- /cognite/neat/{plugins/data_model → _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/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}/_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/time_.py +0 -0
- /cognite/neat/{core → v0/core}/_utils/xml_.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
- /cognite/neat/{session → v0/session}/engine/__init__.py +0 -0
- /cognite/neat/{session → v0/session}/engine/_import.py +0 -0
- /cognite/neat/{session → v0/session}/engine/_interface.py +0 -0
- {cognite_neat-0.123.2.dist-info → cognite_neat-0.127.30.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import platform
|
|
3
|
+
|
|
4
|
+
from cognite.neat._utils.auxiliary import get_current_neat_version
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@functools.lru_cache(maxsize=1)
|
|
8
|
+
def get_user_agent() -> str:
|
|
9
|
+
neat_version = f"CogniteNeat/{get_current_neat_version()}"
|
|
10
|
+
python_version = (
|
|
11
|
+
f"{platform.python_implementation()}/{platform.python_version()} "
|
|
12
|
+
f"({platform.python_build()};{platform.python_compiler()})"
|
|
13
|
+
)
|
|
14
|
+
os_version_info = [platform.release(), platform.machine(), platform.architecture()[0]]
|
|
15
|
+
os_version_info = [s for s in os_version_info if s] # Ignore empty strings
|
|
16
|
+
os_version_info_str = "-".join(os_version_info)
|
|
17
|
+
operating_system = f"{platform.system()}/{os_version_info_str}"
|
|
18
|
+
|
|
19
|
+
return f"{neat_version} {python_version} {operating_system}"
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from collections import UserList
|
|
4
|
+
from collections.abc import MutableSequence, Sequence
|
|
5
|
+
from typing import Generic, Literal, TypeAlias, TypeVar
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field, JsonValue, ValidationError, model_serializer
|
|
9
|
+
|
|
10
|
+
from cognite.neat._exceptions import CDFAPIException
|
|
11
|
+
from cognite.neat._utils.http_client._tracker import ItemsRequestTracker
|
|
12
|
+
from cognite.neat._utils.useful_types import PrimaryTypes, ReferenceObject, T_Reference
|
|
13
|
+
|
|
14
|
+
if sys.version_info >= (3, 11):
|
|
15
|
+
from typing import Self
|
|
16
|
+
else:
|
|
17
|
+
from typing_extensions import Self
|
|
18
|
+
|
|
19
|
+
StatusCode: TypeAlias = int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HTTPMessage(BaseModel):
|
|
23
|
+
"""Base class for HTTP messages (requests and responses)"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FailedRequestMessage(HTTPMessage):
|
|
27
|
+
message: str
|
|
28
|
+
|
|
29
|
+
def __str__(self) -> str:
|
|
30
|
+
return self.message
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ResponseMessage(HTTPMessage):
|
|
34
|
+
code: StatusCode
|
|
35
|
+
body: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SuccessResponse(ResponseMessage): ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ErrorDetails(BaseModel):
|
|
42
|
+
"""This is the structure of failure responses from CDF APIs"""
|
|
43
|
+
|
|
44
|
+
code: StatusCode
|
|
45
|
+
message: str
|
|
46
|
+
missing: list[JsonValue] | None = None
|
|
47
|
+
duplicated: list[JsonValue] | None = None
|
|
48
|
+
is_auto_retryable: bool | None = Field(None, alias="isAutoRetryable")
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_response(cls, response: httpx.Response) -> "ErrorDetails":
|
|
52
|
+
try:
|
|
53
|
+
return _ErrorResponse.model_validate_json(response.text).error
|
|
54
|
+
except ValidationError:
|
|
55
|
+
return cls(code=response.status_code, message=response.text)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _ErrorResponse(BaseModel):
|
|
59
|
+
error: ErrorDetails
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class FailedResponse(ResponseMessage):
|
|
63
|
+
error: ErrorDetails
|
|
64
|
+
|
|
65
|
+
def __str__(self) -> str:
|
|
66
|
+
return f"HTTP {self.code} | {self.error.message}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class RequestMessage(HTTPMessage, ABC):
|
|
70
|
+
"""Base class for HTTP request messages"""
|
|
71
|
+
|
|
72
|
+
endpoint_url: str
|
|
73
|
+
method: Literal["GET", "POST", "PATCH", "DELETE"]
|
|
74
|
+
connect_attempt: int = 0
|
|
75
|
+
read_attempt: int = 0
|
|
76
|
+
status_attempt: int = 0
|
|
77
|
+
api_version: str | None = None
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def total_attempts(self) -> int:
|
|
81
|
+
return self.connect_attempt + self.read_attempt + self.status_attempt
|
|
82
|
+
|
|
83
|
+
@abstractmethod
|
|
84
|
+
def create_success_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
|
|
85
|
+
raise NotImplementedError()
|
|
86
|
+
|
|
87
|
+
@abstractmethod
|
|
88
|
+
def create_failure_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
|
|
89
|
+
raise NotImplementedError()
|
|
90
|
+
|
|
91
|
+
@abstractmethod
|
|
92
|
+
def create_failed_request(self, error_message: str) -> Sequence[HTTPMessage]:
|
|
93
|
+
raise NotImplementedError()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SimpleRequest(RequestMessage):
|
|
97
|
+
"""Base class for requests with a simple success/fail response structure"""
|
|
98
|
+
|
|
99
|
+
def create_success_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
|
|
100
|
+
return [SuccessResponse(code=response.status_code, body=response.text)]
|
|
101
|
+
|
|
102
|
+
def create_failure_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
|
|
103
|
+
return [
|
|
104
|
+
FailedResponse(code=response.status_code, body=response.text, error=ErrorDetails.from_response(response))
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
def create_failed_request(self, error_message: str) -> Sequence[HTTPMessage]:
|
|
108
|
+
return [FailedRequestMessage(message=error_message)]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ParametersRequest(SimpleRequest):
|
|
112
|
+
"""Base class for HTTP request messages with query parameters"""
|
|
113
|
+
|
|
114
|
+
parameters: dict[str, PrimaryTypes] | None = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class BodyRequest(ParametersRequest, ABC):
|
|
118
|
+
"""Base class for HTTP request messages with a body"""
|
|
119
|
+
|
|
120
|
+
@abstractmethod
|
|
121
|
+
def data(self) -> str:
|
|
122
|
+
raise NotImplementedError()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class SimpleBodyRequest(BodyRequest):
|
|
126
|
+
body: str
|
|
127
|
+
|
|
128
|
+
def data(self) -> str:
|
|
129
|
+
return self.body
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ItemMessage(BaseModel, Generic[T_Reference], ABC):
|
|
133
|
+
"""Base class for message related to a specific item"""
|
|
134
|
+
|
|
135
|
+
ids: Sequence[T_Reference]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class SuccessResponseItems(ItemMessage[T_Reference], SuccessResponse): ...
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class FailedResponseItems(ItemMessage[T_Reference], FailedResponse): ...
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class FailedRequestItems(ItemMessage[T_Reference], FailedRequestMessage): ...
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
T_BaseModel = TypeVar("T_BaseModel", bound=BaseModel)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ItemBody(BaseModel, Generic[T_Reference, T_BaseModel], ABC):
|
|
151
|
+
items: Sequence[T_BaseModel]
|
|
152
|
+
extra_args: dict[str, JsonValue] | None = None
|
|
153
|
+
|
|
154
|
+
@model_serializer(mode="plain", return_type=dict)
|
|
155
|
+
def serialize(self) -> dict[str, JsonValue]:
|
|
156
|
+
data: dict[str, JsonValue] = {
|
|
157
|
+
"items": [item.model_dump(exclude_unset=False, by_alias=True, exclude_none=False) for item in self.items]
|
|
158
|
+
}
|
|
159
|
+
if isinstance(self.extra_args, dict):
|
|
160
|
+
data.update(self.extra_args)
|
|
161
|
+
return data
|
|
162
|
+
|
|
163
|
+
@abstractmethod
|
|
164
|
+
def as_ids(self) -> list[T_Reference]:
|
|
165
|
+
"""Returns the list of item identifiers for the items in the body."""
|
|
166
|
+
raise NotImplementedError()
|
|
167
|
+
|
|
168
|
+
def split(self, mid: int) -> tuple[Self, Self]:
|
|
169
|
+
"""Splits the body into two smaller bodies.
|
|
170
|
+
|
|
171
|
+
This is useful for retrying requests that fail due to size limits or timeouts.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
mid: The index at which to split the items.
|
|
175
|
+
Returns:
|
|
176
|
+
A tuple containing two new ItemBody instances, each with half of the original items.
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
type_ = type(self)
|
|
181
|
+
return type_(items=self.items[:mid], extra_args=self.extra_args), type_(
|
|
182
|
+
items=self.items[mid:], extra_args=self.extra_args
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class ItemIDBody(ItemBody[ReferenceObject, ReferenceObject]):
|
|
187
|
+
def as_ids(self) -> list[ReferenceObject]:
|
|
188
|
+
return list(self.items)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ItemsRequest(BodyRequest, Generic[T_Reference, T_BaseModel]):
|
|
192
|
+
"""Requests message for endpoints that accept multiple items in a single request.
|
|
193
|
+
|
|
194
|
+
This class provides functionality to split large requests into smaller ones, handle responses for each item,
|
|
195
|
+
and manage errors effectively.
|
|
196
|
+
|
|
197
|
+
Attributes:
|
|
198
|
+
body (ItemBody): The body of the request containing the items to be processed.
|
|
199
|
+
max_failures_before_abort (int): The maximum number of failed split requests before aborting further splits.
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
204
|
+
body: ItemBody[T_Reference, T_BaseModel]
|
|
205
|
+
max_failures_before_abort: int = 50
|
|
206
|
+
tracker: ItemsRequestTracker | None = None
|
|
207
|
+
|
|
208
|
+
def data(self) -> str:
|
|
209
|
+
return self.body.model_dump_json(exclude_unset=True, by_alias=True)
|
|
210
|
+
|
|
211
|
+
def split(self, status_attempts: int) -> "list[ItemsRequest]":
|
|
212
|
+
"""Splits the request into two smaller requests.
|
|
213
|
+
|
|
214
|
+
This is useful for retrying requests that fail due to size limits or timeouts.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
status_attempts: The number of status attempts to set for the new requests. This is used when the
|
|
218
|
+
request failed with a 5xx status code and we want to track the number of attempts. For 4xx errors,
|
|
219
|
+
there is at least one item causing the error, so we do not increment the status attempts, but
|
|
220
|
+
instead essentially do a binary search to find the problematic item(s).
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
A list containing two new ItemsRequest instances, each with half of the original items.
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
mid = len(self.body.items) // 2
|
|
227
|
+
if mid == 0:
|
|
228
|
+
return [self]
|
|
229
|
+
tracker = self.tracker or ItemsRequestTracker(self.max_failures_before_abort)
|
|
230
|
+
tracker.register_failure()
|
|
231
|
+
messages: list[ItemsRequest] = []
|
|
232
|
+
for body in self.body.split(mid):
|
|
233
|
+
item_request = ItemsRequest(
|
|
234
|
+
endpoint_url=self.endpoint_url,
|
|
235
|
+
method=self.method,
|
|
236
|
+
body=body,
|
|
237
|
+
connect_attempt=self.connect_attempt,
|
|
238
|
+
read_attempt=self.read_attempt,
|
|
239
|
+
status_attempt=status_attempts,
|
|
240
|
+
)
|
|
241
|
+
item_request.tracker = tracker
|
|
242
|
+
messages.append(item_request)
|
|
243
|
+
return messages
|
|
244
|
+
|
|
245
|
+
def create_success_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
|
|
246
|
+
return [SuccessResponseItems(code=response.status_code, body=response.text, ids=self.body.as_ids())]
|
|
247
|
+
|
|
248
|
+
def create_failure_response(self, response: httpx.Response) -> Sequence[HTTPMessage]:
|
|
249
|
+
"""Creates response messages based on the HTTP response and the original request.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
response: The HTTP response received from the server.
|
|
253
|
+
Returns:
|
|
254
|
+
A sequence of HTTPMessage instances representing the outcome for each item in the request.
|
|
255
|
+
"""
|
|
256
|
+
return [
|
|
257
|
+
FailedResponseItems(
|
|
258
|
+
code=response.status_code,
|
|
259
|
+
body=response.text,
|
|
260
|
+
error=ErrorDetails.from_response(response),
|
|
261
|
+
ids=self.body.as_ids(),
|
|
262
|
+
)
|
|
263
|
+
]
|
|
264
|
+
|
|
265
|
+
def create_failed_request(self, error_message: str) -> Sequence[HTTPMessage]:
|
|
266
|
+
"""Creates failed request messages for each item in the request.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
error_message: The error message to include in the failed request messages.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
A sequence of HTTPMessage instances representing the failed request for each item.
|
|
273
|
+
"""
|
|
274
|
+
return [FailedRequestItems(message=error_message, ids=self.body.as_ids())]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class APIResponse(UserList, MutableSequence[ResponseMessage | FailedRequestMessage]):
|
|
278
|
+
def __init__(self, collection: Sequence[ResponseMessage | FailedRequestMessage] | None = None):
|
|
279
|
+
super().__init__(collection or [])
|
|
280
|
+
|
|
281
|
+
def raise_for_status(self) -> None:
|
|
282
|
+
error_messages = [message for message in self.data if not isinstance(message, SuccessResponse)]
|
|
283
|
+
if error_messages:
|
|
284
|
+
raise CDFAPIException(error_messages)
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def success_response(self) -> SuccessResponse:
|
|
288
|
+
success = [msg for msg in self.data if isinstance(msg, SuccessResponse)]
|
|
289
|
+
if len(success) == 1:
|
|
290
|
+
return success[0]
|
|
291
|
+
elif success:
|
|
292
|
+
raise ValueError("Multiple successful HTTP responses found in the messages.")
|
|
293
|
+
else:
|
|
294
|
+
raise ValueError("No successful HTTP response found in the messages.")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class ItemsRequestTracker:
|
|
7
|
+
"""Tracks the state of requests split from an original request.
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
max_failures_before_abort (int): Maximum number of allowed failed split requests before aborting
|
|
11
|
+
the entire operation. A value of -1 indicates no early abort.
|
|
12
|
+
lock (threading.Lock): A lock to ensure thread-safe updates to the failure count.
|
|
13
|
+
failed_split_count (int): The current count of failed split requests.
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
max_failures_before_abort: int = -1 # -1 means no early abort
|
|
18
|
+
lock: threading.Lock = field(default_factory=threading.Lock, init=False)
|
|
19
|
+
failed_split_count: int = field(default=0, init=False)
|
|
20
|
+
|
|
21
|
+
def register_failure(self) -> None:
|
|
22
|
+
"""Register a failed split request and return whether to continue splitting."""
|
|
23
|
+
with self.lock:
|
|
24
|
+
self.failed_split_count += 1
|
|
25
|
+
|
|
26
|
+
def limit_reached(self) -> bool:
|
|
27
|
+
"""Check if the failure limit has been reached."""
|
|
28
|
+
with self.lock:
|
|
29
|
+
if self.max_failures_before_abort < 0:
|
|
30
|
+
return False
|
|
31
|
+
return self.failed_split_count >= self.max_failures_before_abort
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from collections.abc import Collection
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
NEWLINE = "\n"
|
|
6
|
+
TAB = "\t"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def humanize_collection(collection: Collection[Any], /, *, sort: bool = True, bind_word: str = "and") -> str:
|
|
10
|
+
"""Convert a collection of items to a human-readable string.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
collection: The collection of items to convert.
|
|
14
|
+
sort: Whether to sort the collection before converting. Default is True.
|
|
15
|
+
bind_word: The word to use to bind the last two items. Default is "and".
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
A human-readable string of the collection.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> humanize_collection(["b", "c", "a"])
|
|
22
|
+
'a, b and c'
|
|
23
|
+
>>> humanize_collection(["b", "c", "a"], sort=False)
|
|
24
|
+
'b, c and a'
|
|
25
|
+
>>> humanize_collection(["a", "b"])
|
|
26
|
+
'a and b'
|
|
27
|
+
>>> humanize_collection(["a"])
|
|
28
|
+
'a'
|
|
29
|
+
>>> humanize_collection([])
|
|
30
|
+
''
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
if not collection:
|
|
34
|
+
return ""
|
|
35
|
+
elif len(collection) == 1:
|
|
36
|
+
return str(next(iter(collection)))
|
|
37
|
+
|
|
38
|
+
strings = (str(item) for item in collection)
|
|
39
|
+
if sort:
|
|
40
|
+
sequence = sorted(strings)
|
|
41
|
+
else:
|
|
42
|
+
sequence = list(strings)
|
|
43
|
+
|
|
44
|
+
return f"{', '.join(sequence[:-1])} {bind_word} {sequence[-1]}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def title_case(s: str) -> str:
|
|
48
|
+
"""Convert a string to title case, handling underscores and hyphens.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
s: The string to convert.
|
|
52
|
+
Returns:
|
|
53
|
+
The title-cased string.
|
|
54
|
+
Examples:
|
|
55
|
+
>>> title_case("hello world")
|
|
56
|
+
'Hello World'
|
|
57
|
+
>>> title_case("hello_world")
|
|
58
|
+
'Hello World'
|
|
59
|
+
>>> title_case("hello-world")
|
|
60
|
+
'Hello World'
|
|
61
|
+
>>> title_case("hello_world-and-universe")
|
|
62
|
+
'Hello World And Universe'
|
|
63
|
+
>>> title_case("HELLO WORLD")
|
|
64
|
+
'Hello World'
|
|
65
|
+
"""
|
|
66
|
+
return " ".join(word.capitalize() for word in s.replace("_", " ").replace("-", " ").split())
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def split_on_capitals(text: str) -> list[str]:
|
|
70
|
+
"""Split a string at capital letters."""
|
|
71
|
+
return re.findall(r"[A-Z][a-z]*", text)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from collections.abc import Hashable
|
|
2
|
+
from datetime import date, datetime, time, timedelta
|
|
3
|
+
from typing import Literal, TypeAlias, TypeVar
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
from pydantic.alias_generators import to_camel
|
|
7
|
+
|
|
8
|
+
JsonVal: TypeAlias = None | str | int | float | bool | dict[str, "JsonVal"] | list["JsonVal"]
|
|
9
|
+
PrimaryTypes: TypeAlias = str | int | float | bool
|
|
10
|
+
|
|
11
|
+
T_ID = TypeVar("T_ID", bound=Hashable)
|
|
12
|
+
# These are the types that openpyxl supports in cells
|
|
13
|
+
CellValueType: TypeAlias = str | int | float | bool | datetime | date | time | timedelta | None
|
|
14
|
+
|
|
15
|
+
# The format expected for excel sheets representing a data model
|
|
16
|
+
DataModelTableType: TypeAlias = dict[str, list[dict[str, CellValueType]]]
|
|
17
|
+
PrimitiveType: TypeAlias = str | int | float | bool
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseModelObject(BaseModel, alias_generator=to_camel, extra="ignore"):
|
|
21
|
+
"""Base class for all object. This includes resources and nested objects."""
|
|
22
|
+
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
T_Item = TypeVar("T_Item", bound=BaseModelObject)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ReferenceObject(BaseModelObject, frozen=True, populate_by_name=True):
|
|
30
|
+
"""Base class for all reference objects - these are identifiers."""
|
|
31
|
+
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
T_Reference = TypeVar("T_Reference", bound=ReferenceObject, covariant=True)
|
|
36
|
+
|
|
37
|
+
ModusOperandi: TypeAlias = Literal["rebuild", "additive"]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from collections.abc import Callable, Mapping
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic_core import ErrorDetails
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def as_json_path(loc: tuple[str | int, ...]) -> str:
|
|
9
|
+
"""Converts a location tuple to a JSON path.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
loc: The location tuple to convert.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
A JSON path string.
|
|
16
|
+
"""
|
|
17
|
+
if not loc:
|
|
18
|
+
return ""
|
|
19
|
+
# +1 to convert from 0-based to 1-based indexing
|
|
20
|
+
prefix = ""
|
|
21
|
+
if isinstance(loc[0], int):
|
|
22
|
+
prefix = "item"
|
|
23
|
+
|
|
24
|
+
suffix = ".".join([str(x) if isinstance(x, str) else f"[{x + 1}]" for x in loc]).replace(".[", "[")
|
|
25
|
+
return f"{prefix}{suffix}"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ValidationContext:
|
|
30
|
+
"""
|
|
31
|
+
Context for validation errors providing configuration for error message formatting.
|
|
32
|
+
|
|
33
|
+
This class configures how validation errors are reported, including location formatting,
|
|
34
|
+
field naming conventions, and how to present missing required fields.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
parent_loc: Optional location tuple to prepend to each error location.
|
|
38
|
+
This is useful when the error is for a nested model and you want to include the location
|
|
39
|
+
of the parent model.
|
|
40
|
+
humanize_location: A function that converts a location tuple to a human-readable string.
|
|
41
|
+
The default is `as_json_path`, which converts the location to a JSON path.
|
|
42
|
+
This can for example be replaced when the location comes from an Excel table.
|
|
43
|
+
field_name: The name use for "field" in error messages. Default is "field". This can be changed to
|
|
44
|
+
"column" or "value" to better fit the context.
|
|
45
|
+
field_renaming: Optional mapping of field names to source names.
|
|
46
|
+
This is useful when the field names in the model are different from the names in the source.
|
|
47
|
+
For example, if the model field is "asset_id" but the source column is "Asset ID",
|
|
48
|
+
you can provide a mapping {"asset_id": "Asset ID"} to have the error messages use the source names.
|
|
49
|
+
missing_required_descriptor: How to describe missing required fields. Default is "missing".
|
|
50
|
+
Other option is "empty" which can be more suitable for table data.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
parent_loc: tuple[int | str, ...] = field(default_factory=tuple)
|
|
54
|
+
humanize_location: Callable[[tuple[int | str, ...]], str] = as_json_path
|
|
55
|
+
field_name: Literal["field", "column", "value"] = "field"
|
|
56
|
+
field_renaming: Mapping[str, str] = field(default_factory=dict)
|
|
57
|
+
missing_required_descriptor: Literal["empty", "missing"] = "missing"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def humanize_validation_error(
|
|
61
|
+
error: ErrorDetails,
|
|
62
|
+
context: ValidationContext | None = None,
|
|
63
|
+
) -> str:
|
|
64
|
+
"""Converts a pydantic ErrorDetails object to a human-readable format.
|
|
65
|
+
This overwrites the default error messages from Pydantic to be better suited for NEAT users.
|
|
66
|
+
Args:
|
|
67
|
+
error: The ErrorDetails object to convert.
|
|
68
|
+
context: The context for humanizing the error.
|
|
69
|
+
Returns:
|
|
70
|
+
A human-readable error message.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
context = context or ValidationContext()
|
|
74
|
+
|
|
75
|
+
loc = (*context.parent_loc, *error["loc"])
|
|
76
|
+
type_ = error["type"]
|
|
77
|
+
|
|
78
|
+
if type_ == "missing":
|
|
79
|
+
msg = f"Missing required {context.field_name}: {loc[-1]!r}"
|
|
80
|
+
elif type_ == "extra_forbidden":
|
|
81
|
+
msg = f"Unused {context.field_name}: {loc[-1]!r}"
|
|
82
|
+
elif type_ == "value_error":
|
|
83
|
+
msg = str(error["ctx"]["error"])
|
|
84
|
+
elif type_ == "literal_error":
|
|
85
|
+
msg = f"{error['msg']}. Got {error['input']!r}."
|
|
86
|
+
elif type_ == "string_type":
|
|
87
|
+
msg = f"{error['msg']}. Got {error['input']!r} of type {type(error['input']).__name__}. "
|
|
88
|
+
elif type_ == "model_type":
|
|
89
|
+
model_name = error["ctx"].get("class_name", "unknown")
|
|
90
|
+
msg = (
|
|
91
|
+
f"Input must be an object of type {model_name}. Got {error['input']!r} of "
|
|
92
|
+
f"type {type(error['input']).__name__}."
|
|
93
|
+
)
|
|
94
|
+
elif type_ == "union_tag_invalid":
|
|
95
|
+
msg = error["msg"].replace(", 'direct'", "").replace("found using 'type' ", "").replace("tag", "value")
|
|
96
|
+
elif type_ == "string_pattern_mismatch":
|
|
97
|
+
msg = f"string '{error['input']}' does not match the required pattern: '{error['ctx']['pattern']}'."
|
|
98
|
+
|
|
99
|
+
elif type_.endswith("_type"):
|
|
100
|
+
msg = f"{error['msg']}. Got {error['input']!r} of type {type(error['input']).__name__}."
|
|
101
|
+
else:
|
|
102
|
+
# Default to the Pydantic error message
|
|
103
|
+
msg = error["msg"]
|
|
104
|
+
|
|
105
|
+
if type_.endswith("dict_type") and len(loc) > 1:
|
|
106
|
+
# If this is a dict_type error for a JSON field, the location will be:
|
|
107
|
+
# dict[str,json-or-python[json=any,python=tagged-union[list[...],dict[str,...],str,bool,int,float,none]]]
|
|
108
|
+
# This is hard to read, so we simplify it to just the field name.
|
|
109
|
+
loc = tuple(["dict" if isinstance(x, str) and "json-or-python" in x else x for x in loc])
|
|
110
|
+
|
|
111
|
+
error_suffix = f"{msg[:1].casefold()}{msg[1:]}"
|
|
112
|
+
|
|
113
|
+
if len(loc) >= 3 and context.field_name == "column" and loc[-3:] == ("type", "enum", "values"):
|
|
114
|
+
# Special handling for enum errors in table columns
|
|
115
|
+
msg = _enum_message(type_, loc, context)
|
|
116
|
+
elif len(loc) > 1 and type_ in {"extra_forbidden", "missing"}:
|
|
117
|
+
if context.missing_required_descriptor == "empty" and type_ == "missing":
|
|
118
|
+
# This is a table so we modify the error message.
|
|
119
|
+
msg = (
|
|
120
|
+
f"In {context.humanize_location(loc[:-1])} the {context.field_name}"
|
|
121
|
+
f" {context.field_renaming.get(str(loc[-1]), loc[-1])!r} "
|
|
122
|
+
"cannot be empty."
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
# We skip the last element as this is in the message already
|
|
126
|
+
msg = f"In {context.humanize_location(loc[:-1])} {error_suffix.replace('field', context.field_name)}"
|
|
127
|
+
elif len(loc) > 1:
|
|
128
|
+
if context.parent_loc == ("Metadata",) and len(loc) == 2:
|
|
129
|
+
msg = f"In table '{loc[0]}' '{loc[1]}' {error_suffix}"
|
|
130
|
+
else:
|
|
131
|
+
msg = f"In {context.humanize_location(loc)} {error_suffix}"
|
|
132
|
+
elif len(loc) == 1 and isinstance(loc[0], str) and type_ not in {"extra_forbidden", "missing"}:
|
|
133
|
+
msg = f"In {context.field_name} {loc[0]!r}, {error_suffix}"
|
|
134
|
+
|
|
135
|
+
msg = msg.strip()
|
|
136
|
+
if not msg.endswith("."):
|
|
137
|
+
msg += "."
|
|
138
|
+
return msg
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _enum_message(type_: str, loc: tuple[int | str, ...], context: ValidationContext) -> str:
|
|
142
|
+
"""Special handling of enum errors in table columns."""
|
|
143
|
+
|
|
144
|
+
if loc[-1] != "values":
|
|
145
|
+
raise RuntimeError("This is a neat bug, report to the team.")
|
|
146
|
+
if type_ == "missing":
|
|
147
|
+
return (
|
|
148
|
+
f"In {context.humanize_location(loc[:-1])} definition should include "
|
|
149
|
+
"a reference to a collection in the 'Enum' sheet (e.g., collection='MyEnumCollection')."
|
|
150
|
+
)
|
|
151
|
+
elif type_ == "too_short":
|
|
152
|
+
return f"In {context.humanize_location(loc[:-1])} collection is not defined in the 'Enum' sheet"
|
|
153
|
+
else:
|
|
154
|
+
raise RuntimeError("This is a neat bug, report to the team.")
|
cognite/neat/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.127.30"
|
|
2
2
|
__engine__ = "^2.0.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|