cognite-neat 0.70.1__py3-none-any.whl → 0.127.19__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 -1
- 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/_data_model/_analysis.py +186 -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 +399 -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 +291 -0
- cognite/neat/_data_model/importers/_table_importer/importer.py +192 -0
- cognite/neat/_data_model/importers/_table_importer/reader.py +875 -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 +47 -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 +94 -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 +57 -0
- cognite/neat/_data_model/validation/dms/_ai_readiness.py +167 -0
- cognite/neat/_data_model/validation/dms/_base.py +304 -0
- cognite/neat/_data_model/validation/dms/_connections.py +627 -0
- cognite/neat/_data_model/validation/dms/_consistency.py +56 -0
- cognite/neat/_data_model/validation/dms/_containers.py +135 -0
- cognite/neat/_data_model/validation/dms/_limits.py +414 -0
- cognite/neat/_data_model/validation/dms/_orchestrator.py +202 -0
- cognite/neat/_data_model/validation/dms/_views.py +101 -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 +149 -0
- cognite/neat/_session/_html/static/issues.css +211 -0
- cognite/neat/_session/_html/static/issues.js +167 -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 +74 -0
- cognite/neat/_session/_html/templates/issues.html +44 -0
- cognite/neat/_session/_issues.py +76 -0
- cognite/neat/_session/_opt.py +35 -0
- cognite/neat/_session/_physical.py +240 -0
- cognite/neat/_session/_result.py +231 -0
- cognite/neat/_session/_session.py +69 -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 +71 -0
- cognite/neat/_store/_store.py +144 -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 +149 -0
- cognite/neat/_version.py +2 -1
- cognite/neat/v0/core/__init__.py +0 -0
- cognite/neat/v0/core/_client/__init__.py +4 -0
- cognite/neat/v0/core/_client/_api/__init__.py +0 -0
- cognite/neat/v0/core/_client/_api/data_modeling_loaders.py +1032 -0
- cognite/neat/v0/core/_client/_api/neat_instances.py +101 -0
- cognite/neat/v0/core/_client/_api/schema.py +158 -0
- cognite/neat/v0/core/_client/_api/statistics.py +91 -0
- cognite/neat/v0/core/_client/_api_client.py +21 -0
- cognite/neat/v0/core/_client/data_classes/__init__.py +0 -0
- cognite/neat/v0/core/_client/data_classes/data_modeling.py +198 -0
- cognite/neat/v0/core/_client/data_classes/neat_sequence.py +261 -0
- cognite/neat/v0/core/_client/data_classes/schema.py +540 -0
- cognite/neat/v0/core/_client/data_classes/statistics.py +125 -0
- cognite/neat/v0/core/_client/testing.py +39 -0
- cognite/neat/v0/core/_config.py +11 -0
- cognite/neat/v0/core/_constants.py +278 -0
- cognite/neat/v0/core/_data_model/__init__.py +0 -0
- cognite/neat/v0/core/_data_model/_constants.py +210 -0
- cognite/neat/v0/core/_data_model/_shared.py +59 -0
- cognite/neat/v0/core/_data_model/analysis/__init__.py +3 -0
- cognite/neat/v0/core/_data_model/analysis/_base.py +578 -0
- cognite/neat/v0/core/_data_model/catalog/__init__.py +8 -0
- cognite/neat/v0/core/_data_model/catalog/classic_model.xlsx +0 -0
- cognite/neat/v0/core/_data_model/catalog/conceptual-imf-data-model.xlsx +0 -0
- cognite/neat/v0/core/_data_model/catalog/hello_world_pump.xlsx +0 -0
- cognite/neat/v0/core/_data_model/exporters/__init__.py +38 -0
- cognite/neat/v0/core/_data_model/exporters/_base.py +67 -0
- cognite/neat/v0/core/_data_model/exporters/_data_model2dms.py +428 -0
- cognite/neat/v0/core/_data_model/exporters/_data_model2excel.py +612 -0
- cognite/neat/v0/core/_data_model/exporters/_data_model2instance_template.py +158 -0
- cognite/neat/v0/core/_data_model/exporters/_data_model2semantic_model.py +646 -0
- cognite/neat/v0/core/_data_model/exporters/_data_model2yaml.py +84 -0
- cognite/neat/v0/core/_data_model/importers/__init__.py +49 -0
- cognite/neat/v0/core/_data_model/importers/_base.py +64 -0
- cognite/neat/v0/core/_data_model/importers/_base_file_reader.py +56 -0
- cognite/neat/v0/core/_data_model/importers/_dict2data_model.py +131 -0
- cognite/neat/v0/core/_data_model/importers/_dms2data_model.py +705 -0
- 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/v0/core/_data_model/importers/_rdf/_base.py +161 -0
- cognite/neat/v0/core/_data_model/importers/_rdf/_inference2rdata_model.py +618 -0
- cognite/neat/v0/core/_data_model/importers/_rdf/_owl2data_model.py +124 -0
- cognite/neat/v0/core/_data_model/importers/_rdf/_shared.py +255 -0
- cognite/neat/v0/core/_data_model/importers/_spreadsheet2data_model.py +424 -0
- cognite/neat/v0/core/_data_model/models/__init__.py +38 -0
- cognite/neat/v0/core/_data_model/models/_base_unverified.py +181 -0
- cognite/neat/v0/core/_data_model/models/_base_verified.py +449 -0
- cognite/neat/v0/core/_data_model/models/_import_contexts.py +82 -0
- cognite/neat/v0/core/_data_model/models/_types.py +171 -0
- cognite/neat/v0/core/_data_model/models/conceptual/__init__.py +25 -0
- cognite/neat/v0/core/_data_model/models/conceptual/_unverified.py +193 -0
- cognite/neat/v0/core/_data_model/models/conceptual/_validation.py +308 -0
- cognite/neat/v0/core/_data_model/models/conceptual/_verified.py +327 -0
- cognite/neat/v0/core/_data_model/models/data_types.py +422 -0
- cognite/neat/v0/core/_data_model/models/entities/__init__.py +70 -0
- cognite/neat/v0/core/_data_model/models/entities/_constants.py +18 -0
- cognite/neat/v0/core/_data_model/models/entities/_loaders.py +155 -0
- cognite/neat/v0/core/_data_model/models/entities/_multi_value.py +83 -0
- cognite/neat/v0/core/_data_model/models/entities/_restrictions.py +230 -0
- cognite/neat/v0/core/_data_model/models/entities/_single_value.py +676 -0
- cognite/neat/v0/core/_data_model/models/entities/_types.py +103 -0
- cognite/neat/v0/core/_data_model/models/entities/_wrapped.py +217 -0
- cognite/neat/v0/core/_data_model/models/mapping/__init__.py +3 -0
- cognite/neat/v0/core/_data_model/models/mapping/_classic2core.py +39 -0
- cognite/neat/v0/core/_data_model/models/mapping/_classic2core.yaml +608 -0
- cognite/neat/v0/core/_data_model/models/physical/__init__.py +40 -0
- cognite/neat/v0/core/_data_model/models/physical/_exporter.py +658 -0
- cognite/neat/v0/core/_data_model/models/physical/_unverified.py +557 -0
- cognite/neat/v0/core/_data_model/models/physical/_validation.py +887 -0
- cognite/neat/v0/core/_data_model/models/physical/_verified.py +667 -0
- cognite/neat/v0/core/_data_model/transformers/__init__.py +70 -0
- cognite/neat/v0/core/_data_model/transformers/_base.py +79 -0
- cognite/neat/v0/core/_data_model/transformers/_converters.py +2485 -0
- cognite/neat/v0/core/_data_model/transformers/_mapping.py +390 -0
- cognite/neat/v0/core/_data_model/transformers/_union_conceptual.py +208 -0
- cognite/neat/v0/core/_data_model/transformers/_verification.py +120 -0
- cognite/neat/v0/core/_instances/__init__.py +0 -0
- cognite/neat/v0/core/_instances/_shared.py +37 -0
- cognite/neat/v0/core/_instances/_tracking/__init__.py +4 -0
- cognite/neat/v0/core/_instances/_tracking/base.py +30 -0
- cognite/neat/v0/core/_instances/_tracking/log.py +27 -0
- cognite/neat/v0/core/_instances/extractors/__init__.py +76 -0
- cognite/neat/v0/core/_instances/extractors/_base.py +58 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/__init__.py +0 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_assets.py +37 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_base.py +443 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_classic.py +479 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_data_sets.py +35 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_events.py +33 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_files.py +45 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_labels.py +54 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_relationships.py +122 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_sequences.py +280 -0
- cognite/neat/v0/core/_instances/extractors/_classic_cdf/_timeseries.py +48 -0
- cognite/neat/v0/core/_instances/extractors/_dict.py +105 -0
- cognite/neat/v0/core/_instances/extractors/_dms.py +315 -0
- cognite/neat/v0/core/_instances/extractors/_dms_graph.py +238 -0
- cognite/neat/v0/core/_instances/extractors/_mock_graph_generator.py +404 -0
- cognite/neat/v0/core/_instances/extractors/_raw.py +67 -0
- cognite/neat/v0/core/_instances/extractors/_rdf_file.py +80 -0
- cognite/neat/v0/core/_instances/loaders/__init__.py +24 -0
- cognite/neat/v0/core/_instances/loaders/_base.py +116 -0
- cognite/neat/v0/core/_instances/loaders/_rdf2dms.py +649 -0
- cognite/neat/v0/core/_instances/loaders/_rdf_to_instance_space.py +249 -0
- cognite/neat/v0/core/_instances/queries/__init__.py +3 -0
- cognite/neat/v0/core/_instances/queries/_base.py +16 -0
- cognite/neat/v0/core/_instances/queries/_queries.py +16 -0
- cognite/neat/v0/core/_instances/queries/_select.py +478 -0
- cognite/neat/v0/core/_instances/queries/_update.py +37 -0
- cognite/neat/v0/core/_instances/transformers/__init__.py +65 -0
- cognite/neat/v0/core/_instances/transformers/_base.py +133 -0
- cognite/neat/v0/core/_instances/transformers/_classic_cdf.py +589 -0
- cognite/neat/v0/core/_instances/transformers/_prune_graph.py +315 -0
- cognite/neat/v0/core/_instances/transformers/_rdfpath.py +84 -0
- cognite/neat/v0/core/_instances/transformers/_value_type.py +366 -0
- cognite/neat/v0/core/_issues/__init__.py +21 -0
- cognite/neat/v0/core/_issues/_base.py +348 -0
- cognite/neat/v0/core/_issues/_contextmanagers.py +50 -0
- cognite/neat/v0/core/_issues/_factory.py +69 -0
- cognite/neat/v0/core/_issues/errors/__init__.py +90 -0
- cognite/neat/v0/core/_issues/errors/_external.py +91 -0
- cognite/neat/v0/core/_issues/errors/_general.py +51 -0
- cognite/neat/v0/core/_issues/errors/_properties.py +80 -0
- cognite/neat/v0/core/_issues/errors/_resources.py +116 -0
- cognite/neat/v0/core/_issues/errors/_wrapper.py +93 -0
- cognite/neat/{rules/issues → v0/core/_issues}/formatters.py +12 -10
- cognite/neat/v0/core/_issues/warnings/__init__.py +99 -0
- cognite/neat/v0/core/_issues/warnings/_external.py +56 -0
- cognite/neat/v0/core/_issues/warnings/_general.py +46 -0
- cognite/neat/v0/core/_issues/warnings/_models.py +165 -0
- cognite/neat/v0/core/_issues/warnings/_properties.py +108 -0
- cognite/neat/v0/core/_issues/warnings/_resources.py +110 -0
- cognite/neat/v0/core/_issues/warnings/user_modeling.py +125 -0
- cognite/neat/v0/core/_shared.py +77 -0
- cognite/neat/v0/core/_store/__init__.py +4 -0
- cognite/neat/v0/core/_store/_data_model.py +487 -0
- cognite/neat/v0/core/_store/_instance.py +485 -0
- cognite/neat/v0/core/_store/_provenance.py +217 -0
- cognite/neat/v0/core/_store/exceptions.py +59 -0
- cognite/neat/v0/core/_utils/__init__.py +0 -0
- cognite/neat/v0/core/_utils/auth.py +364 -0
- cognite/neat/v0/core/_utils/auxiliary.py +169 -0
- cognite/neat/v0/core/_utils/collection_.py +66 -0
- cognite/neat/v0/core/_utils/graph_transformations_report.py +36 -0
- cognite/neat/v0/core/_utils/io_.py +11 -0
- cognite/neat/v0/core/_utils/rdf_.py +336 -0
- cognite/neat/v0/core/_utils/reader/__init__.py +3 -0
- cognite/neat/v0/core/_utils/reader/_base.py +194 -0
- cognite/neat/v0/core/_utils/spreadsheet.py +182 -0
- cognite/neat/v0/core/_utils/tarjan.py +44 -0
- cognite/neat/v0/core/_utils/text.py +277 -0
- cognite/neat/v0/core/_utils/time_.py +17 -0
- cognite/neat/v0/core/_utils/upload.py +134 -0
- cognite/neat/v0/core/_utils/xml_.py +52 -0
- 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/v0/plugins/_issues.py +35 -0
- cognite/neat/v0/plugins/_manager.py +94 -0
- cognite/neat/v0/session/__init__.py +3 -0
- cognite/neat/v0/session/_base.py +332 -0
- cognite/neat/v0/session/_collector.py +131 -0
- cognite/neat/v0/session/_diff.py +51 -0
- cognite/neat/v0/session/_drop.py +103 -0
- cognite/neat/v0/session/_experimental.py +26 -0
- cognite/neat/v0/session/_explore.py +39 -0
- cognite/neat/v0/session/_fix.py +28 -0
- cognite/neat/v0/session/_inspect.py +285 -0
- cognite/neat/v0/session/_mapping.py +71 -0
- cognite/neat/v0/session/_plugin.py +66 -0
- cognite/neat/v0/session/_prepare.py +286 -0
- cognite/neat/v0/session/_read.py +923 -0
- cognite/neat/v0/session/_set.py +101 -0
- cognite/neat/v0/session/_show.py +298 -0
- cognite/neat/v0/session/_state/README.md +23 -0
- cognite/neat/v0/session/_state.py +161 -0
- cognite/neat/v0/session/_subset.py +88 -0
- cognite/neat/v0/session/_template.py +208 -0
- cognite/neat/v0/session/_to.py +454 -0
- cognite/neat/v0/session/_wizard.py +44 -0
- cognite/neat/v0/session/engine/__init__.py +4 -0
- cognite/neat/v0/session/engine/_import.py +7 -0
- cognite/neat/v0/session/engine/_interface.py +25 -0
- cognite/neat/v0/session/engine/_load.py +131 -0
- cognite/neat/v0/session/exceptions.py +103 -0
- cognite/neat/v1.py +3 -0
- cognite_neat-0.127.19.dist-info/METADATA +145 -0
- cognite_neat-0.127.19.dist-info/RECORD +318 -0
- {cognite_neat-0.70.1.dist-info → cognite_neat-0.127.19.dist-info}/WHEEL +1 -1
- cognite/neat/app/api/asgi/metrics.py +0 -4
- cognite/neat/app/api/configuration.py +0 -110
- cognite/neat/app/api/context_manager/__init__.py +0 -3
- cognite/neat/app/api/context_manager/manager.py +0 -16
- cognite/neat/app/api/data_classes/configuration.py +0 -121
- cognite/neat/app/api/data_classes/rest.py +0 -78
- cognite/neat/app/api/explorer.py +0 -64
- cognite/neat/app/api/routers/configuration.py +0 -24
- cognite/neat/app/api/routers/core.py +0 -51
- cognite/neat/app/api/routers/crud.py +0 -112
- cognite/neat/app/api/routers/data_exploration.py +0 -334
- cognite/neat/app/api/routers/metrics.py +0 -10
- cognite/neat/app/api/routers/rules.py +0 -169
- cognite/neat/app/api/routers/workflows.py +0 -274
- cognite/neat/app/api/utils/data_mapping.py +0 -17
- cognite/neat/app/api/utils/logging.py +0 -26
- cognite/neat/app/api/utils/query_templates.py +0 -92
- cognite/neat/app/main.py +0 -17
- cognite/neat/app/monitoring/metrics.py +0 -69
- cognite/neat/app/ui/index.html +0 -1
- cognite/neat/app/ui/neat-app/.gitignore +0 -23
- cognite/neat/app/ui/neat-app/README.md +0 -70
- cognite/neat/app/ui/neat-app/build/asset-manifest.json +0 -14
- cognite/neat/app/ui/neat-app/build/favicon.ico +0 -0
- cognite/neat/app/ui/neat-app/build/index.html +0 -1
- cognite/neat/app/ui/neat-app/build/logo192.png +0 -0
- cognite/neat/app/ui/neat-app/build/manifest.json +0 -25
- cognite/neat/app/ui/neat-app/build/robots.txt +0 -3
- cognite/neat/app/ui/neat-app/build/static/css/main.38a62222.css +0 -2
- cognite/neat/app/ui/neat-app/build/static/css/main.38a62222.css.map +0 -1
- cognite/neat/app/ui/neat-app/build/static/js/main.ed960141.js +0 -3
- cognite/neat/app/ui/neat-app/build/static/js/main.ed960141.js.LICENSE.txt +0 -97
- cognite/neat/app/ui/neat-app/build/static/js/main.ed960141.js.map +0 -1
- cognite/neat/app/ui/neat-app/build/static/media/logo.8093b84df9ed36a174c629d6fe0b730d.svg +0 -1
- cognite/neat/config.py +0 -46
- cognite/neat/constants.py +0 -36
- cognite/neat/exceptions.py +0 -139
- cognite/neat/graph/__init__.py +0 -3
- cognite/neat/graph/exceptions.py +0 -91
- cognite/neat/graph/extractor/__init__.py +0 -6
- cognite/neat/graph/extractor/_base.py +0 -14
- cognite/neat/graph/extractor/_dexpi.py +0 -306
- cognite/neat/graph/extractor/_graph_capturing_sheet.py +0 -401
- cognite/neat/graph/extractor/_mock_graph_generator.py +0 -359
- cognite/neat/graph/extractors/_base.py +0 -14
- cognite/neat/graph/extractors/_mock_graph_generator.py +0 -359
- cognite/neat/graph/loader/__init__.py +0 -23
- cognite/neat/graph/loader/_asset_loader.py +0 -516
- cognite/neat/graph/loader/_base.py +0 -69
- cognite/neat/graph/loader/_exceptions.py +0 -87
- cognite/neat/graph/loader/core/labels.py +0 -58
- cognite/neat/graph/loader/core/models.py +0 -136
- cognite/neat/graph/loader/core/rdf_to_assets.py +0 -1047
- cognite/neat/graph/loader/core/rdf_to_relationships.py +0 -558
- cognite/neat/graph/loader/rdf_to_dms.py +0 -309
- cognite/neat/graph/loader/validator.py +0 -87
- cognite/neat/graph/models.py +0 -6
- cognite/neat/graph/stores/__init__.py +0 -13
- cognite/neat/graph/stores/_base.py +0 -384
- cognite/neat/graph/stores/_graphdb_store.py +0 -51
- cognite/neat/graph/stores/_memory_store.py +0 -43
- cognite/neat/graph/stores/_oxigraph_store.py +0 -145
- cognite/neat/graph/stores/_oxrdflib.py +0 -247
- cognite/neat/graph/stores/_rdf_to_graph.py +0 -40
- cognite/neat/graph/transformation/entity_matcher.py +0 -101
- cognite/neat/graph/transformation/query_generator/__init__.py +0 -3
- cognite/neat/graph/transformation/query_generator/sparql.py +0 -540
- cognite/neat/graph/transformation/transformer.py +0 -316
- cognite/neat/rules/_analysis/_base.py +0 -25
- cognite/neat/rules/_analysis/_information_rules.py +0 -414
- cognite/neat/rules/_shared.py +0 -5
- cognite/neat/rules/analysis.py +0 -231
- cognite/neat/rules/examples/Rules-Nordic44-to-TNT.xlsx +0 -0
- cognite/neat/rules/examples/Rules-Nordic44-to-graphql.xlsx +0 -0
- cognite/neat/rules/examples/__init__.py +0 -18
- cognite/neat/rules/examples/power-grid-containers.yaml +0 -113
- cognite/neat/rules/examples/power-grid-example.xlsx +0 -0
- cognite/neat/rules/examples/power-grid-model.yaml +0 -213
- cognite/neat/rules/examples/rules-template.xlsx +0 -0
- cognite/neat/rules/examples/sheet2cdf-transformation-rules.xlsx +0 -0
- cognite/neat/rules/examples/skos-rules.xlsx +0 -0
- cognite/neat/rules/examples/source-to-solution-mapping-rules.xlsx +0 -0
- cognite/neat/rules/examples/wind-energy.owl +0 -1511
- cognite/neat/rules/exceptions.py +0 -2972
- cognite/neat/rules/exporter/__init__.py +0 -20
- cognite/neat/rules/exporter/_base.py +0 -45
- cognite/neat/rules/exporter/_core/__init__.py +0 -5
- cognite/neat/rules/exporter/_core/rules2labels.py +0 -24
- cognite/neat/rules/exporter/_rules2dms.py +0 -888
- cognite/neat/rules/exporter/_rules2excel.py +0 -212
- cognite/neat/rules/exporter/_rules2graphql.py +0 -183
- cognite/neat/rules/exporter/_rules2ontology.py +0 -523
- cognite/neat/rules/exporter/_rules2pydantic_models.py +0 -748
- cognite/neat/rules/exporter/_rules2rules.py +0 -104
- cognite/neat/rules/exporter/_rules2triples.py +0 -37
- cognite/neat/rules/exporter/_validation.py +0 -150
- cognite/neat/rules/exporters/__init__.py +0 -15
- cognite/neat/rules/exporters/_base.py +0 -42
- cognite/neat/rules/exporters/_models.py +0 -58
- cognite/neat/rules/exporters/_rules2dms.py +0 -259
- cognite/neat/rules/exporters/_rules2excel.py +0 -196
- cognite/neat/rules/exporters/_rules2ontology.py +0 -561
- cognite/neat/rules/exporters/_rules2yaml.py +0 -78
- cognite/neat/rules/exporters/_validation.py +0 -107
- cognite/neat/rules/importer/__init__.py +0 -22
- cognite/neat/rules/importer/_base.py +0 -70
- cognite/neat/rules/importer/_dict2rules.py +0 -158
- cognite/neat/rules/importer/_dms2rules.py +0 -196
- cognite/neat/rules/importer/_graph2rules.py +0 -304
- cognite/neat/rules/importer/_json2rules.py +0 -39
- cognite/neat/rules/importer/_owl2rules/__init__.py +0 -3
- cognite/neat/rules/importer/_owl2rules/_owl2classes.py +0 -239
- cognite/neat/rules/importer/_owl2rules/_owl2metadata.py +0 -255
- cognite/neat/rules/importer/_owl2rules/_owl2properties.py +0 -217
- cognite/neat/rules/importer/_owl2rules/_owl2rules.py +0 -290
- cognite/neat/rules/importer/_spreadsheet2rules.py +0 -45
- cognite/neat/rules/importer/_xsd2rules.py +0 -20
- cognite/neat/rules/importer/_yaml2rules.py +0 -39
- cognite/neat/rules/importers/__init__.py +0 -16
- cognite/neat/rules/importers/_base.py +0 -124
- cognite/neat/rules/importers/_dms2rules.py +0 -191
- cognite/neat/rules/importers/_dtdl2rules/__init__.py +0 -3
- cognite/neat/rules/importers/_dtdl2rules/_unit_lookup.py +0 -224
- cognite/neat/rules/importers/_dtdl2rules/dtdl_converter.py +0 -318
- cognite/neat/rules/importers/_dtdl2rules/dtdl_importer.py +0 -164
- cognite/neat/rules/importers/_dtdl2rules/spec.py +0 -359
- cognite/neat/rules/importers/_owl2rules/__init__.py +0 -3
- cognite/neat/rules/importers/_owl2rules/_owl2classes.py +0 -219
- cognite/neat/rules/importers/_owl2rules/_owl2metadata.py +0 -217
- cognite/neat/rules/importers/_owl2rules/_owl2properties.py +0 -203
- cognite/neat/rules/importers/_owl2rules/_owl2rules.py +0 -148
- cognite/neat/rules/importers/_spreadsheet2rules.py +0 -266
- cognite/neat/rules/importers/_yaml2rules.py +0 -110
- cognite/neat/rules/issues/__init__.py +0 -27
- cognite/neat/rules/issues/base.py +0 -190
- cognite/neat/rules/issues/dms.py +0 -331
- cognite/neat/rules/issues/fileread.py +0 -156
- cognite/neat/rules/issues/importing.py +0 -220
- cognite/neat/rules/issues/spreadsheet.py +0 -381
- cognite/neat/rules/issues/spreadsheet_file.py +0 -151
- cognite/neat/rules/models/__init__.py +0 -5
- cognite/neat/rules/models/_base.py +0 -151
- cognite/neat/rules/models/_rules/__init__.py +0 -14
- cognite/neat/rules/models/_rules/_types/__init__.py +0 -66
- cognite/neat/rules/models/_rules/_types/_base.py +0 -450
- cognite/neat/rules/models/_rules/_types/_field.py +0 -339
- cognite/neat/rules/models/_rules/_types/_value.py +0 -156
- cognite/neat/rules/models/_rules/base.py +0 -320
- cognite/neat/rules/models/_rules/dms_architect_rules.py +0 -1034
- cognite/neat/rules/models/_rules/dms_schema.py +0 -641
- cognite/neat/rules/models/_rules/domain_rules.py +0 -55
- cognite/neat/rules/models/_rules/information_rules.py +0 -525
- cognite/neat/rules/models/raw_rules.py +0 -304
- cognite/neat/rules/models/rdfpath.py +0 -238
- cognite/neat/rules/models/rules.py +0 -1269
- cognite/neat/rules/models/tables.py +0 -9
- cognite/neat/rules/models/value_types.py +0 -117
- cognite/neat/utils/__init__.py +0 -3
- cognite/neat/utils/auxiliary.py +0 -11
- cognite/neat/utils/cdf.py +0 -24
- cognite/neat/utils/cdf_loaders/__init__.py +0 -25
- cognite/neat/utils/cdf_loaders/_base.py +0 -62
- cognite/neat/utils/cdf_loaders/_data_modeling.py +0 -275
- cognite/neat/utils/cdf_loaders/_ingestion.py +0 -152
- cognite/neat/utils/cdf_loaders/data_classes.py +0 -121
- cognite/neat/utils/exceptions.py +0 -41
- cognite/neat/utils/spreadsheet.py +0 -84
- cognite/neat/utils/text.py +0 -104
- cognite/neat/utils/utils.py +0 -354
- cognite/neat/utils/xml.py +0 -37
- cognite/neat/workflows/__init__.py +0 -12
- cognite/neat/workflows/_exceptions.py +0 -41
- cognite/neat/workflows/base.py +0 -574
- cognite/neat/workflows/cdf_store.py +0 -393
- cognite/neat/workflows/examples/Export DMS/workflow.yaml +0 -89
- cognite/neat/workflows/examples/Export Rules to Ontology/workflow.yaml +0 -152
- cognite/neat/workflows/examples/Extract DEXPI Graph and Export Rules/workflow.yaml +0 -139
- cognite/neat/workflows/examples/Extract RDF Graph and Generate Assets/workflow.yaml +0 -270
- cognite/neat/workflows/examples/Import DMS/workflow.yaml +0 -65
- cognite/neat/workflows/examples/Ontology to Data Model/workflow.yaml +0 -116
- cognite/neat/workflows/examples/Validate Rules/workflow.yaml +0 -67
- cognite/neat/workflows/examples/Validate Solution Model/workflow.yaml +0 -64
- cognite/neat/workflows/examples/Visualize Data Model Using Mock Graph/workflow.yaml +0 -95
- cognite/neat/workflows/examples/Visualize Semantic Data Model/workflow.yaml +0 -111
- cognite/neat/workflows/manager.py +0 -309
- cognite/neat/workflows/migration/steps.py +0 -93
- cognite/neat/workflows/migration/wf_manifests.py +0 -33
- cognite/neat/workflows/model.py +0 -202
- cognite/neat/workflows/steps/data_contracts.py +0 -140
- cognite/neat/workflows/steps/lib/__init__.py +0 -7
- cognite/neat/workflows/steps/lib/graph_extractor.py +0 -123
- cognite/neat/workflows/steps/lib/graph_loader.py +0 -68
- cognite/neat/workflows/steps/lib/graph_store.py +0 -139
- cognite/neat/workflows/steps/lib/io_steps.py +0 -393
- cognite/neat/workflows/steps/lib/rules_exporter.py +0 -453
- cognite/neat/workflows/steps/lib/rules_importer.py +0 -171
- cognite/neat/workflows/steps/lib/rules_validator.py +0 -102
- cognite/neat/workflows/steps/lib/v1/__init__.py +0 -7
- cognite/neat/workflows/steps/lib/v1/graph_contextualization.py +0 -82
- cognite/neat/workflows/steps/lib/v1/graph_extractor.py +0 -644
- cognite/neat/workflows/steps/lib/v1/graph_loader.py +0 -606
- cognite/neat/workflows/steps/lib/v1/graph_store.py +0 -278
- cognite/neat/workflows/steps/lib/v1/graph_transformer.py +0 -58
- cognite/neat/workflows/steps/lib/v1/rules_exporter.py +0 -513
- cognite/neat/workflows/steps/lib/v1/rules_importer.py +0 -612
- cognite/neat/workflows/steps/step_model.py +0 -83
- cognite/neat/workflows/steps_registry.py +0 -212
- cognite/neat/workflows/tasks.py +0 -18
- cognite/neat/workflows/triggers.py +0 -169
- cognite/neat/workflows/utils.py +0 -19
- cognite_neat-0.70.1.dist-info/METADATA +0 -212
- cognite_neat-0.70.1.dist-info/RECORD +0 -234
- cognite_neat-0.70.1.dist-info/entry_points.txt +0 -3
- /cognite/neat/{app/api → _data_model}/__init__.py +0 -0
- /cognite/neat/{app/api/data_classes → _data_model/deployer}/__init__.py +0 -0
- /cognite/neat/{app/api/utils → _data_model/exporters/_table_exporter}/__init__.py +0 -0
- /cognite/neat/{app/monitoring → _data_model/importers/_table_importer}/__init__.py +0 -0
- /cognite/neat/{graph/extractors → _data_model/models}/__init__.py +0 -0
- /cognite/neat/{graph/loader/core → _data_model/models/conceptual}/__init__.py +0 -0
- /cognite/neat/{graph/transformation → _data_model/validation}/__init__.py +0 -0
- /cognite/neat/{rules → _session/_html}/__init__.py +0 -0
- /cognite/neat/{rules/_analysis → _session/_usage_analytics}/__init__.py +0 -0
- /cognite/neat/{workflows/migration → _utils}/__init__.py +0 -0
- /cognite/neat/{workflows/steps → v0}/__init__.py +0 -0
- /cognite/neat/{graph → v0/core/_instances}/examples/Knowledge-Graph-Nordic44-dirty.xml +0 -0
- /cognite/neat/{graph → v0/core/_instances}/examples/Knowledge-Graph-Nordic44.xml +0 -0
- /cognite/neat/{graph → v0/core/_instances}/examples/__init__.py +0 -0
- /cognite/neat/{graph → v0/core/_instances}/examples/skos-capturing-sheet-wind-topics.xlsx +0 -0
- {cognite_neat-0.70.1.dist-info → cognite_neat-0.127.19.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, Literal, cast, overload
|
|
4
|
+
from warnings import catch_warnings
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from openpyxl import load_workbook
|
|
8
|
+
from openpyxl.worksheet.datavalidation import DataValidation
|
|
9
|
+
from openpyxl.worksheet.worksheet import Worksheet
|
|
10
|
+
|
|
11
|
+
from cognite.neat.v0.core._data_model._constants import get_internal_properties
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class SpreadsheetRead:
|
|
16
|
+
"""This class is used to store information about the source spreadsheet.
|
|
17
|
+
|
|
18
|
+
It is used to adjust row numbers to account for header rows and empty rows
|
|
19
|
+
such that the error/warning messages are accurate.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
header_row: int = 1
|
|
23
|
+
empty_rows: list[int] = field(default_factory=list)
|
|
24
|
+
skipped_rows: list[int] = field(default_factory=list)
|
|
25
|
+
is_one_indexed: bool = True
|
|
26
|
+
|
|
27
|
+
def __post_init__(self) -> None:
|
|
28
|
+
self.empty_rows = sorted(self.empty_rows)
|
|
29
|
+
|
|
30
|
+
def adjusted_row_number(self, row_no: int) -> int:
|
|
31
|
+
output = row_no
|
|
32
|
+
for empty_row in self.empty_rows:
|
|
33
|
+
if empty_row <= output:
|
|
34
|
+
output += 1
|
|
35
|
+
else:
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
for skipped_rows in self.skipped_rows:
|
|
39
|
+
if skipped_rows <= output:
|
|
40
|
+
output += 1
|
|
41
|
+
else:
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
return output + self.header_row + (1 if self.is_one_indexed else 0)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@overload
|
|
48
|
+
def read_individual_sheet(
|
|
49
|
+
excel_file: pd.ExcelFile,
|
|
50
|
+
sheet_name: str,
|
|
51
|
+
return_read_info: Literal[True],
|
|
52
|
+
expected_headers: list[str] | None = None,
|
|
53
|
+
) -> tuple[list[dict], SpreadsheetRead]: ...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@overload
|
|
57
|
+
def read_individual_sheet(
|
|
58
|
+
excel_file: pd.ExcelFile,
|
|
59
|
+
sheet_name: str,
|
|
60
|
+
return_read_info: Literal[False] = False,
|
|
61
|
+
expected_headers: list[str] | None = None,
|
|
62
|
+
) -> list[dict]: ...
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def read_individual_sheet(
|
|
66
|
+
excel_file: pd.ExcelFile,
|
|
67
|
+
sheet_name: str,
|
|
68
|
+
return_read_info: bool = False,
|
|
69
|
+
expected_headers: list[str] | None = None,
|
|
70
|
+
) -> tuple[list[dict], SpreadsheetRead] | list[dict]:
|
|
71
|
+
if expected_headers:
|
|
72
|
+
with catch_warnings():
|
|
73
|
+
# When reading spreadsheets produced by neat, they contain dropdowns. These
|
|
74
|
+
# are not supported by openpyxl and will raise a warning as openpyxl cannot validate these.
|
|
75
|
+
# We ignore these warnings as Neat will do the same checks.
|
|
76
|
+
warnings.simplefilter("ignore")
|
|
77
|
+
target_row = _get_row_number(cast(Worksheet, load_workbook(excel_file)[sheet_name]), expected_headers)
|
|
78
|
+
skiprows = target_row - 1 if target_row is not None else 0
|
|
79
|
+
else:
|
|
80
|
+
skiprows = 0
|
|
81
|
+
|
|
82
|
+
with catch_warnings():
|
|
83
|
+
# When reading spreadsheets produced by neat, they contain dropdowns. These
|
|
84
|
+
# are not supported by openpyxl and will raise a warning as openpyxl cannot validate these.
|
|
85
|
+
# We ignore these warnings as Neat will do the same checks.
|
|
86
|
+
warnings.simplefilter("ignore")
|
|
87
|
+
raw = pd.read_excel(excel_file, sheet_name, skiprows=skiprows)
|
|
88
|
+
is_na = raw.isnull().all(axis=1)
|
|
89
|
+
skip_rows = _find_rows_to_skip(raw)
|
|
90
|
+
empty_rows = is_na[is_na].index.tolist()
|
|
91
|
+
|
|
92
|
+
if skip_rows:
|
|
93
|
+
raw = raw.drop(skip_rows)
|
|
94
|
+
|
|
95
|
+
raw.dropna(axis=0, how="all", inplace=True)
|
|
96
|
+
|
|
97
|
+
if "Value Type" in raw.columns:
|
|
98
|
+
# Special handling for Value Type column, #N/A is treated specially by NEAT it means Unknown
|
|
99
|
+
raw["Value Type"] = raw["Value Type"].replace(float("nan"), "#N/A")
|
|
100
|
+
|
|
101
|
+
if "Concept" in raw.columns:
|
|
102
|
+
# Special handling for Concept column, #N/A is treated specially by NEAT it means Unknown
|
|
103
|
+
raw["Concept"] = raw["Concept"].replace(float("nan"), "#N/A")
|
|
104
|
+
|
|
105
|
+
output = raw.replace(float("nan"), None).to_dict(orient="records")
|
|
106
|
+
if return_read_info:
|
|
107
|
+
# If no rows are skipped, row 1 is the header row.
|
|
108
|
+
return output, SpreadsheetRead(
|
|
109
|
+
header_row=skiprows + 1,
|
|
110
|
+
empty_rows=empty_rows,
|
|
111
|
+
is_one_indexed=True,
|
|
112
|
+
skipped_rows=skip_rows,
|
|
113
|
+
)
|
|
114
|
+
return output
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _find_rows_to_skip(
|
|
118
|
+
df: pd.DataFrame,
|
|
119
|
+
) -> list:
|
|
120
|
+
"""Find rows which are having all values as None except for internal properties."""
|
|
121
|
+
rows_to_skip = []
|
|
122
|
+
|
|
123
|
+
internal_cols = {val.lower() for val in get_internal_properties()}
|
|
124
|
+
for i, row in df.iterrows():
|
|
125
|
+
user_cols_state = []
|
|
126
|
+
internal_cols_state = []
|
|
127
|
+
for col in df.columns:
|
|
128
|
+
if col.lower() not in internal_cols:
|
|
129
|
+
user_cols_state.append(row[col] == "#N/A" or row[col].__str__().lower() in ["none", "nan"])
|
|
130
|
+
else:
|
|
131
|
+
internal_cols_state.append(row[col] is not None)
|
|
132
|
+
|
|
133
|
+
if all(user_cols_state) and any(internal_cols_state):
|
|
134
|
+
rows_to_skip.append(i)
|
|
135
|
+
|
|
136
|
+
return rows_to_skip
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _get_row_number(sheet: Worksheet, values_to_find: list[str]) -> int | None:
|
|
140
|
+
for row_number, row in enumerate(sheet.iter_rows(values_only=True), start=1):
|
|
141
|
+
if any(value in row for value in values_to_find):
|
|
142
|
+
return row_number
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@overload
|
|
147
|
+
def find_column_and_row_with_value(
|
|
148
|
+
sheet: Worksheet, value: Any, column_letter: Literal[True] = True
|
|
149
|
+
) -> tuple[str, int] | tuple[None, None]: ...
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@overload
|
|
153
|
+
def find_column_and_row_with_value(
|
|
154
|
+
sheet: Worksheet, value: Any, column_letter: Literal[False]
|
|
155
|
+
) -> tuple[int, int] | tuple[None, None]: ...
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def find_column_and_row_with_value(
|
|
159
|
+
sheet: Worksheet, value: Any, column_letter: bool = True
|
|
160
|
+
) -> tuple[int, int] | tuple[str, int] | tuple[None, None]:
|
|
161
|
+
for row in sheet.iter_rows():
|
|
162
|
+
for cell in row:
|
|
163
|
+
if cell.value and isinstance(cell.value, str) and cell.value.lower() == value.lower():
|
|
164
|
+
return (cell.column_letter, cell.row) if column_letter else (cell.column, cell.row)
|
|
165
|
+
|
|
166
|
+
return None, None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def generate_data_validation(sheet: str, column: str, total_header_rows: int, validation_range: int) -> DataValidation:
|
|
170
|
+
"""Creates openpyxl data validation object for a cell in a sheet
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
sheet: The name of the sheet where the data validation is applied.
|
|
174
|
+
column: The column letter where the data validation is applied.
|
|
175
|
+
total_header_rows: The number of header rows in the sheet.
|
|
176
|
+
validation_range: The total number of validation values in the column.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
return DataValidation(
|
|
180
|
+
type="list",
|
|
181
|
+
formula1=f"={sheet}!{column}${total_header_rows + 1}:{column}${validation_range + total_header_rows + 1}",
|
|
182
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from typing import TypeVar
|
|
2
|
+
|
|
3
|
+
T = TypeVar("T")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def tarjan(dependencies_by_id: dict[T, set[T]]) -> list[set[T]]:
|
|
7
|
+
"""Returns the strongly connected components of the dependency graph
|
|
8
|
+
in topological order.
|
|
9
|
+
Args:
|
|
10
|
+
dependencies_by_id: A dictionary where the keys are ids and the values are sets of ids that the key depends on.
|
|
11
|
+
Returns:
|
|
12
|
+
A list of sets of ids that are strongly connected components in the dependency graph.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
stack = []
|
|
16
|
+
stack_set = set()
|
|
17
|
+
index: dict[T, int] = {}
|
|
18
|
+
lowlink = {}
|
|
19
|
+
result = []
|
|
20
|
+
|
|
21
|
+
def visit(v: T) -> None:
|
|
22
|
+
index[v] = len(index)
|
|
23
|
+
lowlink[v] = index[v]
|
|
24
|
+
stack.append(v)
|
|
25
|
+
stack_set.add(v)
|
|
26
|
+
for w in dependencies_by_id.get(v, []):
|
|
27
|
+
if w not in index:
|
|
28
|
+
visit(w)
|
|
29
|
+
lowlink[v] = min(lowlink[w], lowlink[v])
|
|
30
|
+
elif w in stack_set:
|
|
31
|
+
lowlink[v] = min(lowlink[v], index[w])
|
|
32
|
+
if lowlink[v] == index[v]:
|
|
33
|
+
scc = set()
|
|
34
|
+
dependency: T | None = None
|
|
35
|
+
while v != dependency:
|
|
36
|
+
dependency = stack.pop()
|
|
37
|
+
scc.add(dependency)
|
|
38
|
+
stack_set.remove(dependency)
|
|
39
|
+
result.append(scc)
|
|
40
|
+
|
|
41
|
+
for view_id in dependencies_by_id.keys():
|
|
42
|
+
if view_id not in index:
|
|
43
|
+
visit(view_id)
|
|
44
|
+
return result
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import urllib.parse
|
|
3
|
+
from collections.abc import Collection, Set
|
|
4
|
+
from re import Pattern
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from cognite.neat.v0.core._data_model._constants import get_reserved_words
|
|
8
|
+
|
|
9
|
+
PREPOSITIONS = frozenset(
|
|
10
|
+
{
|
|
11
|
+
"in",
|
|
12
|
+
"on",
|
|
13
|
+
"at",
|
|
14
|
+
"by",
|
|
15
|
+
"for",
|
|
16
|
+
"with",
|
|
17
|
+
"about",
|
|
18
|
+
"against",
|
|
19
|
+
"between",
|
|
20
|
+
"into",
|
|
21
|
+
"through",
|
|
22
|
+
"during",
|
|
23
|
+
"before",
|
|
24
|
+
"after",
|
|
25
|
+
"above",
|
|
26
|
+
"below",
|
|
27
|
+
"to",
|
|
28
|
+
"from",
|
|
29
|
+
"up",
|
|
30
|
+
"down",
|
|
31
|
+
"out",
|
|
32
|
+
"off",
|
|
33
|
+
"over",
|
|
34
|
+
"under",
|
|
35
|
+
"again",
|
|
36
|
+
"further",
|
|
37
|
+
"then",
|
|
38
|
+
"once",
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def to_camel_case(string: str) -> str:
|
|
44
|
+
"""Convert snake_case_name to camelCaseName.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
string: The string to convert.
|
|
48
|
+
Returns:
|
|
49
|
+
camelCase of the input string.
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
>>> to_camel_case("a_b")
|
|
53
|
+
'aB'
|
|
54
|
+
>>> to_camel_case("ScenarioInstance_priceForecast")
|
|
55
|
+
'scenarioInstancePriceForecast'
|
|
56
|
+
"""
|
|
57
|
+
string = re.sub(r"[^a-zA-Z0-9_]", "_", string)
|
|
58
|
+
string = re.sub("_+", "_", string)
|
|
59
|
+
is_all_upper = string.upper() == string
|
|
60
|
+
is_first_upper = (
|
|
61
|
+
len(string) >= 2 and string[:2].upper() == string[:2] and "_" not in string[:2] and not is_all_upper
|
|
62
|
+
)
|
|
63
|
+
return _to_camel_case(string, is_all_upper, is_first_upper)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _to_camel_case(string: str, is_all_upper: bool, is_first_upper: bool) -> str:
|
|
67
|
+
if "_" in string:
|
|
68
|
+
pascal_splits = [
|
|
69
|
+
_to_pascal_case(part, is_all_upper, is_first_upper and no == 0)
|
|
70
|
+
for no, part in enumerate(string.split("_"), 0)
|
|
71
|
+
]
|
|
72
|
+
else:
|
|
73
|
+
# Ensure pascal
|
|
74
|
+
if string:
|
|
75
|
+
string = string[0].upper() + string[1:]
|
|
76
|
+
pascal_splits = [string]
|
|
77
|
+
cleaned: list[str] = []
|
|
78
|
+
for part in pascal_splits:
|
|
79
|
+
if part.upper() == part and is_all_upper:
|
|
80
|
+
cleaned.append(part.capitalize())
|
|
81
|
+
else:
|
|
82
|
+
cleaned.append(part)
|
|
83
|
+
|
|
84
|
+
string_split = []
|
|
85
|
+
for part in cleaned:
|
|
86
|
+
string_split.extend(re.findall(r"[A-Z][a-z0-9]*", part))
|
|
87
|
+
if not string_split:
|
|
88
|
+
string_split = [string]
|
|
89
|
+
if len(string_split) == 0:
|
|
90
|
+
return ""
|
|
91
|
+
# The first word is a single letter, keep the original case
|
|
92
|
+
if is_first_upper:
|
|
93
|
+
return "".join(word for word in string_split)
|
|
94
|
+
else:
|
|
95
|
+
return string_split[0].casefold() + "".join(word for word in string_split[1:])
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def to_pascal_case(string: str) -> str:
|
|
99
|
+
"""Convert string to PascalCaseName.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
string: The string to convert.
|
|
103
|
+
Returns:
|
|
104
|
+
PascalCase of the input string.
|
|
105
|
+
|
|
106
|
+
Examples:
|
|
107
|
+
>>> to_pascal_case("a_b")
|
|
108
|
+
'AB'
|
|
109
|
+
>>> to_pascal_case('camel_case')
|
|
110
|
+
'CamelCase'
|
|
111
|
+
"""
|
|
112
|
+
return _to_pascal_case(string, string == string.upper(), True)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _to_pascal_case(string: str, is_all_upper: bool, is_first_upper: bool) -> str:
|
|
116
|
+
camel = _to_camel_case(string, is_all_upper, is_first_upper)
|
|
117
|
+
return f"{camel[0].upper()}{camel[1:]}" if camel else ""
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def to_snake_case(string: str) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Convert input string to snake_case
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
string: The string to convert.
|
|
126
|
+
Returns:
|
|
127
|
+
snake_case of the input string.
|
|
128
|
+
|
|
129
|
+
Examples:
|
|
130
|
+
>>> to_snake_case("aB")
|
|
131
|
+
'a_b'
|
|
132
|
+
>>> to_snake_case('CamelCase')
|
|
133
|
+
'camel_case'
|
|
134
|
+
>>> to_snake_case('camelCamelCase')
|
|
135
|
+
'camel_camel_case'
|
|
136
|
+
>>> to_snake_case('Camel2Camel2Case')
|
|
137
|
+
'camel_2_camel_2_case'
|
|
138
|
+
>>> to_snake_case('getHTTPResponseCode')
|
|
139
|
+
'get_http_response_code'
|
|
140
|
+
>>> to_snake_case('get200HTTPResponseCode')
|
|
141
|
+
'get_200_http_response_code'
|
|
142
|
+
>>> to_snake_case('getHTTP200ResponseCode')
|
|
143
|
+
'get_http_200_response_code'
|
|
144
|
+
>>> to_snake_case('HTTPResponseCode')
|
|
145
|
+
'http_response_code'
|
|
146
|
+
>>> to_snake_case('ResponseHTTP')
|
|
147
|
+
'response_http'
|
|
148
|
+
>>> to_snake_case('ResponseHTTP2')
|
|
149
|
+
'response_http_2'
|
|
150
|
+
>>> to_snake_case('Fun?!awesome')
|
|
151
|
+
'fun_awesome'
|
|
152
|
+
>>> to_snake_case('Fun?!Awesome')
|
|
153
|
+
'fun_awesome'
|
|
154
|
+
>>> to_snake_case('10CoolDudes')
|
|
155
|
+
'10_cool_dudes'
|
|
156
|
+
>>> to_snake_case('20coolDudes')
|
|
157
|
+
'20_cool_dudes'
|
|
158
|
+
"""
|
|
159
|
+
pattern = re.compile(r"[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\d|\W|$)|\d+")
|
|
160
|
+
if "_" in string:
|
|
161
|
+
words = [word for section in string.split("_") for word in pattern.findall(section)]
|
|
162
|
+
else:
|
|
163
|
+
words = pattern.findall(string)
|
|
164
|
+
return "_".join(map(str.lower, words))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def to_words(string: str) -> str:
|
|
168
|
+
"""Converts snake_case camelCase or PascalCase to words."""
|
|
169
|
+
return to_snake_case(string).replace("_", " ")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def title(text: str, skip_words: Set[str] = PREPOSITIONS) -> str:
|
|
173
|
+
"""Converts text to title case, skipping prepositions."""
|
|
174
|
+
words = (word.lower() for word in text.split())
|
|
175
|
+
titled_words = (word.capitalize() if word not in skip_words else word for word in words)
|
|
176
|
+
return " ".join(titled_words)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def sentence_or_string_to_camel(string: str) -> str:
|
|
180
|
+
# Could be a combination of kebab and pascal/camel case
|
|
181
|
+
if " " in string:
|
|
182
|
+
parts = string.split(" ")
|
|
183
|
+
try:
|
|
184
|
+
return parts[0].casefold() + "".join(word.capitalize() for word in parts[1:])
|
|
185
|
+
except IndexError:
|
|
186
|
+
return ""
|
|
187
|
+
else:
|
|
188
|
+
return to_camel_case(string)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def replace_non_alphanumeric_with_underscore(text: str) -> str:
|
|
192
|
+
return re.sub(r"\W+", "_", text)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def humanize_collection(collection: Collection[Any], /, *, sort: bool = True, bind_word: str = "and") -> str:
|
|
196
|
+
"""Convert a collection of items to a human-readable string.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
collection: The collection of items to convert.
|
|
200
|
+
sort: Whether to sort the collection before converting. Default is True.
|
|
201
|
+
bind_word: The word to use to bind the last two items. Default is "and".
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
A human-readable string of the collection.
|
|
205
|
+
|
|
206
|
+
Examples:
|
|
207
|
+
>>> humanize_collection(["b", "c", "a"])
|
|
208
|
+
'a, b and c'
|
|
209
|
+
>>> humanize_collection(["b", "c", "a"], sort=False)
|
|
210
|
+
'b, c and a'
|
|
211
|
+
>>> humanize_collection(["a", "b"])
|
|
212
|
+
'a and b'
|
|
213
|
+
>>> humanize_collection(["a"])
|
|
214
|
+
'a'
|
|
215
|
+
>>> humanize_collection([])
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
if not collection:
|
|
219
|
+
return ""
|
|
220
|
+
elif len(collection) == 1:
|
|
221
|
+
return str(next(iter(collection)))
|
|
222
|
+
|
|
223
|
+
strings = (str(item) for item in collection)
|
|
224
|
+
if sort:
|
|
225
|
+
sequence = sorted(strings)
|
|
226
|
+
else:
|
|
227
|
+
sequence = list(strings)
|
|
228
|
+
|
|
229
|
+
return f"{', '.join(sequence[:-1])} {bind_word} {sequence[-1]}"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class NamingStandardization:
|
|
233
|
+
_letter_number_underscore = re.compile(r"[^a-zA-Z0-9_]+")
|
|
234
|
+
_letter_number_underscore_hyphen = re.compile(r"[^a-zA-Z0-9_-]+")
|
|
235
|
+
_multi_underscore_pattern = re.compile(r"_+")
|
|
236
|
+
_start_letter_pattern = re.compile(r"^[a-zA-Z]")
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
def standardize_concept_str(cls, raw: str) -> str:
|
|
240
|
+
clean = cls._clean_string(raw)
|
|
241
|
+
if not cls._start_letter_pattern.match(clean):
|
|
242
|
+
# Underscore ensure that 'Class' it treated as a separate word
|
|
243
|
+
# in the to_pascale function
|
|
244
|
+
clean = f"Class_{clean}"
|
|
245
|
+
return to_pascal_case(clean)
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def standardize_property_str(cls, raw: str) -> str:
|
|
249
|
+
clean = cls._clean_string(raw)
|
|
250
|
+
if not cls._start_letter_pattern.match(clean):
|
|
251
|
+
# Underscore ensure that 'property' it treated as a separate word
|
|
252
|
+
# in the to_camel function
|
|
253
|
+
clean = f"property_{clean}"
|
|
254
|
+
return to_camel_case(clean)
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
def standardize_space_str(cls, raw: str) -> str:
|
|
258
|
+
clean = cls._clean_string(raw, cls._letter_number_underscore_hyphen)
|
|
259
|
+
if not cls._start_letter_pattern.match(clean):
|
|
260
|
+
clean = f"sp_{clean}"
|
|
261
|
+
if clean in set(get_reserved_words("space")):
|
|
262
|
+
clean = f"my_{clean}"
|
|
263
|
+
if len(clean) > 43:
|
|
264
|
+
clean = clean[:43]
|
|
265
|
+
if not (clean[-1].isalnum()) and len(clean) == 43:
|
|
266
|
+
clean = f"{clean[:-1]}x"
|
|
267
|
+
elif not clean[-1].isalnum():
|
|
268
|
+
clean = f"{clean}x"
|
|
269
|
+
if not clean:
|
|
270
|
+
raise ValueError("Space name must contain at least one letter.")
|
|
271
|
+
return to_snake_case(clean)
|
|
272
|
+
|
|
273
|
+
@classmethod
|
|
274
|
+
def _clean_string(cls, raw: str, clean_pattern: Pattern[str] = _letter_number_underscore) -> str:
|
|
275
|
+
raw = urllib.parse.unquote(raw)
|
|
276
|
+
raw = clean_pattern.sub("_", raw)
|
|
277
|
+
return cls._multi_underscore_pattern.sub("_", raw)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
if sys.version_info >= (3, 11):
|
|
5
|
+
from datetime import UTC
|
|
6
|
+
else:
|
|
7
|
+
from datetime import timezone
|
|
8
|
+
|
|
9
|
+
UTC = timezone.utc
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def epoch_now_ms() -> int:
|
|
13
|
+
return int((datetime.now(UTC) - datetime(1970, 1, 1, tzinfo=UTC)).total_seconds() * 1000)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def datetime_utc_now() -> datetime:
|
|
17
|
+
return datetime.now(UTC)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from functools import total_ordering
|
|
4
|
+
from typing import Any, Generic
|
|
5
|
+
|
|
6
|
+
from cognite.neat.v0.core._issues import IssueList
|
|
7
|
+
from cognite.neat.v0.core._issues.errors import NeatValueError
|
|
8
|
+
from cognite.neat.v0.core._shared import T_ID, NeatList, NeatObject
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@total_ordering
|
|
12
|
+
@dataclass
|
|
13
|
+
class UploadResultCore(NeatObject, ABC):
|
|
14
|
+
name: str
|
|
15
|
+
error_messages: list[str] = field(default_factory=list)
|
|
16
|
+
issues: IssueList = field(default_factory=IssueList)
|
|
17
|
+
|
|
18
|
+
def __lt__(self, other: object) -> bool:
|
|
19
|
+
if isinstance(other, UploadResultCore):
|
|
20
|
+
return self.name < other.name
|
|
21
|
+
else:
|
|
22
|
+
return NotImplemented
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other: object) -> bool:
|
|
25
|
+
if isinstance(other, UploadResultCore):
|
|
26
|
+
return self.name == other.name
|
|
27
|
+
else:
|
|
28
|
+
return NotImplemented
|
|
29
|
+
|
|
30
|
+
def dump(self, aggregate: bool = True) -> dict[str, Any]:
|
|
31
|
+
output: dict[str, Any] = {"name": self.name}
|
|
32
|
+
if self.error_messages:
|
|
33
|
+
output["error_messages"] = len(self.error_messages) if aggregate else self.error_messages
|
|
34
|
+
if self.issues:
|
|
35
|
+
output["issues"] = len(self.issues) if aggregate else [issue.dump() for issue in self.issues]
|
|
36
|
+
return output
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class UploadResultList(NeatList[UploadResultCore]):
|
|
40
|
+
def _repr_html_(self) -> str:
|
|
41
|
+
df = self.to_pandas().fillna(0)
|
|
42
|
+
df = df.style.format({column: "{:,.0f}".format for column in df.select_dtypes(include="number").columns})
|
|
43
|
+
return df._repr_html_() # type: ignore[attr-defined]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class UploadResult(UploadResultCore, Generic[T_ID]):
|
|
48
|
+
created: set[T_ID] = field(default_factory=set)
|
|
49
|
+
upserted: set[T_ID] = field(default_factory=set)
|
|
50
|
+
deleted: set[T_ID] = field(default_factory=set)
|
|
51
|
+
changed: set[T_ID] = field(default_factory=set)
|
|
52
|
+
unchanged: set[T_ID] = field(default_factory=set)
|
|
53
|
+
skipped: set[T_ID] = field(default_factory=set)
|
|
54
|
+
failed_created: set[T_ID] = field(default_factory=set)
|
|
55
|
+
failed_upserted: set[T_ID] = field(default_factory=set)
|
|
56
|
+
failed_changed: set[T_ID] = field(default_factory=set)
|
|
57
|
+
failed_deleted: set[T_ID] = field(default_factory=set)
|
|
58
|
+
failed_items: list = field(default_factory=list)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def failed(self) -> int:
|
|
62
|
+
return (
|
|
63
|
+
len(self.failed_created) + len(self.failed_changed) + len(self.failed_deleted) + len(self.failed_upserted)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def success(self) -> int:
|
|
68
|
+
return (
|
|
69
|
+
len(self.created)
|
|
70
|
+
+ len(self.deleted)
|
|
71
|
+
+ len(self.changed)
|
|
72
|
+
+ len(self.upserted)
|
|
73
|
+
+ len(self.unchanged)
|
|
74
|
+
+ len(self.skipped)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def dump(self, aggregate: bool = True) -> dict[str, Any]:
|
|
78
|
+
output = super().dump(aggregate)
|
|
79
|
+
if self.created:
|
|
80
|
+
output["created"] = len(self.created) if aggregate else list(self.created)
|
|
81
|
+
if self.upserted:
|
|
82
|
+
output["upserted"] = len(self.upserted) if aggregate else list(self.upserted)
|
|
83
|
+
if self.deleted:
|
|
84
|
+
output["deleted"] = len(self.deleted) if aggregate else list(self.deleted)
|
|
85
|
+
if self.changed:
|
|
86
|
+
output["changed"] = len(self.changed) if aggregate else list(self.changed)
|
|
87
|
+
if self.unchanged:
|
|
88
|
+
output["unchanged"] = len(self.unchanged) if aggregate else list(self.unchanged)
|
|
89
|
+
if self.skipped:
|
|
90
|
+
output["skipped"] = len(self.skipped) if aggregate else list(self.skipped)
|
|
91
|
+
if self.failed_created:
|
|
92
|
+
output["failed_created"] = len(self.failed_created) if aggregate else list(self.failed_created)
|
|
93
|
+
if self.failed_upserted:
|
|
94
|
+
output["failed_upserted"] = len(self.failed_upserted) if aggregate else list(self.failed_upserted)
|
|
95
|
+
if self.failed_changed:
|
|
96
|
+
output["failed_changed"] = len(self.failed_changed) if aggregate else list(self.failed_changed)
|
|
97
|
+
if self.failed_deleted:
|
|
98
|
+
output["failed_deleted"] = len(self.failed_deleted) if aggregate else list(self.failed_deleted)
|
|
99
|
+
if "error_messages" in output:
|
|
100
|
+
# Trick to move error_messages to the end of the dict
|
|
101
|
+
output["error_messages"] = output.pop("error_messages")
|
|
102
|
+
if "issues" in output:
|
|
103
|
+
# Trick to move issues to the end of the dict
|
|
104
|
+
output["issues"] = output.pop("issues")
|
|
105
|
+
return output
|
|
106
|
+
|
|
107
|
+
def __str__(self) -> str:
|
|
108
|
+
dumped = self.dump(aggregate=True)
|
|
109
|
+
lines: list[str] = []
|
|
110
|
+
for key, value in dumped.items():
|
|
111
|
+
if key in ["name", "error_messages", "issues"]:
|
|
112
|
+
continue
|
|
113
|
+
lines.append(f"{key}: {value}")
|
|
114
|
+
return f"{self.name.title()}: {', '.join(lines)}"
|
|
115
|
+
|
|
116
|
+
def merge(self, other: "UploadResult[T_ID]") -> "UploadResult[T_ID]":
|
|
117
|
+
if self.name != other.name:
|
|
118
|
+
raise NeatValueError(f"Cannot merge UploadResults with different names: {self.name} and {other.name}")
|
|
119
|
+
return UploadResult(
|
|
120
|
+
name=self.name,
|
|
121
|
+
error_messages=self.error_messages + other.error_messages,
|
|
122
|
+
issues=IssueList(self.issues + other.issues),
|
|
123
|
+
created=self.created.union(other.created),
|
|
124
|
+
upserted=self.upserted.union(other.upserted),
|
|
125
|
+
deleted=self.deleted.union(other.deleted),
|
|
126
|
+
changed=self.changed.union(other.changed),
|
|
127
|
+
unchanged=self.unchanged.union(other.unchanged),
|
|
128
|
+
skipped=self.skipped.union(other.skipped),
|
|
129
|
+
failed_created=self.failed_created.union(other.failed_created),
|
|
130
|
+
failed_upserted=self.failed_upserted.union(other.failed_upserted),
|
|
131
|
+
failed_changed=self.failed_changed.union(other.failed_changed),
|
|
132
|
+
failed_deleted=self.failed_deleted.union(other.failed_deleted),
|
|
133
|
+
failed_items=self.failed_items + other.failed_items,
|
|
134
|
+
)
|