simstack 2.0.1__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.
- simstack/__init__.py +0 -0
- simstack/_version.py +24 -0
- simstack/core/__init__.py +0 -0
- simstack/core/artifacts.py +406 -0
- simstack/core/asnyc_helper.py +14 -0
- simstack/core/config_file.py +60 -0
- simstack/core/context.py +283 -0
- simstack/core/definitions.py +22 -0
- simstack/core/engine.py +115 -0
- simstack/core/find_simstack_modules.py +41 -0
- simstack/core/hash.py +179 -0
- simstack/core/node.py +1137 -0
- simstack/core/node_claim.py +31 -0
- simstack/core/node_runner.py +362 -0
- simstack/core/process_results.py +102 -0
- simstack/core/recompute_artifacts.py +117 -0
- simstack/core/reset_db.py +12 -0
- simstack/core/resource_assignment.py +222 -0
- simstack/core/resources.py +90 -0
- simstack/core/route_table.py +24 -0
- simstack/core/run_docker.py +109 -0
- simstack/core/run_node.py +71 -0
- simstack/core/runner.py +169 -0
- simstack/core/services/__init__.py +11 -0
- simstack/core/services/base_service.py +191 -0
- simstack/core/services/file_transfer_service.py +117 -0
- simstack/core/services/git_restart_service.py +12 -0
- simstack/core/services/git_uv_update_service.py +105 -0
- simstack/core/services/node_execution_service.py +182 -0
- simstack/core/services/resource_branch_monitor_service.py +71 -0
- simstack/core/services/runner_cleanup_service.py +149 -0
- simstack/core/services/runner_manager.py +163 -0
- simstack/core/services/runner_status_service.py +10 -0
- simstack/core/services/slurm_status_service.py +67 -0
- simstack/core/services/stop_check_service.py +27 -0
- simstack/core/services/timeout_restart_service.py +26 -0
- simstack/core/simstack_result.py +39 -0
- simstack/core/submit_node.py +217 -0
- simstack/core/task_id.py +22 -0
- simstack/methods/__init__.py +0 -0
- simstack/methods/archive_files.py +292 -0
- simstack/methods/fire_and_forget_runner.py +113 -0
- simstack/methods/generate_test.py +125 -0
- simstack/methods/get_file.py +26 -0
- simstack/methods/mass_runner.py +154 -0
- simstack/methods/multiple_executor.py +307 -0
- simstack/methods/switch_git.py +16 -0
- simstack/methods/upload_helpers.py +81 -0
- simstack/models/__init__.py +74 -0
- simstack/models/array_list.py +21 -0
- simstack/models/array_storage.py +83 -0
- simstack/models/artifact_models.py +127 -0
- simstack/models/base_lists.py +51 -0
- simstack/models/base_types.py +209 -0
- simstack/models/charts_artifact.py +690 -0
- simstack/models/dataset.py +451 -0
- simstack/models/dataset_metadata.py +390 -0
- simstack/models/datasettuple.py +587 -0
- simstack/models/file_instance.py +150 -0
- simstack/models/file_list.py +45 -0
- simstack/models/files.py +424 -0
- simstack/models/fire_and_forget_result.py +19 -0
- simstack/models/images2d.py +113 -0
- simstack/models/log_entry_model.py +37 -0
- simstack/models/models.py +52 -0
- simstack/models/named_data_reference.py +36 -0
- simstack/models/node_registry.py +96 -0
- simstack/models/pandas_model.py +239 -0
- simstack/models/parameters.py +402 -0
- simstack/models/pickle_models.py +300 -0
- simstack/models/project.py +13 -0
- simstack/models/resource_assignment.py +170 -0
- simstack/models/resource_definition.py +125 -0
- simstack/models/runner_model.py +39 -0
- simstack/models/simple_table.py +79 -0
- simstack/models/simstack_model.py +97 -0
- simstack/models/slurm_info.py +17 -0
- simstack/models/table_artifact.py +87 -0
- simstack/models/tag.py +12 -0
- simstack/models/unused_test_file.py +5 -0
- simstack/simstack_marker.com +1 -0
- simstack/static/docs/.buildinfo +4 -0
- simstack/static/docs/_images/GUI Concept.webp +0 -0
- simstack/static/docs/_modules/index.html +434 -0
- simstack/static/docs/_modules/simstack/core/artifacts.html +743 -0
- simstack/static/docs/_modules/simstack/core/asnyc_helper.html +330 -0
- simstack/static/docs/_modules/simstack/core/config_file.html +379 -0
- simstack/static/docs/_modules/simstack/core/definitions.html +341 -0
- simstack/static/docs/_modules/simstack/core/engine.html +437 -0
- simstack/static/docs/_modules/simstack/core/find_simstack_modules.html +360 -0
- simstack/static/docs/_modules/simstack/core/hash.html +528 -0
- simstack/static/docs/_modules/simstack/core/node.html +1392 -0
- simstack/static/docs/_modules/simstack/core/node_claim.html +347 -0
- simstack/static/docs/_modules/simstack/core/node_runner.html +711 -0
- simstack/static/docs/_modules/simstack/core/process_results.html +418 -0
- simstack/static/docs/_modules/simstack/core/recompute_artifacts.html +433 -0
- simstack/static/docs/_modules/simstack/core/reset_db.html +328 -0
- simstack/static/docs/_modules/simstack/core/resource_assignment.html +553 -0
- simstack/static/docs/_modules/simstack/core/resources.html +424 -0
- simstack/static/docs/_modules/simstack/core/route_table.html +349 -0
- simstack/static/docs/_modules/simstack/core/run_docker.html +425 -0
- simstack/static/docs/_modules/simstack/core/run_node.html +390 -0
- simstack/static/docs/_modules/simstack/core/runner.html +491 -0
- simstack/static/docs/_modules/simstack/core/services/base_service.html +522 -0
- simstack/static/docs/_modules/simstack/core/services/file_transfer_service.html +436 -0
- simstack/static/docs/_modules/simstack/core/services/git_restart_service.html +331 -0
- simstack/static/docs/_modules/simstack/core/services/git_uv_update_service.html +427 -0
- simstack/static/docs/_modules/simstack/core/services/node_execution_service.html +507 -0
- simstack/static/docs/_modules/simstack/core/services/resource_branch_monitor_service.html +390 -0
- simstack/static/docs/_modules/simstack/core/services/runner_cleanup_service.html +468 -0
- simstack/static/docs/_modules/simstack/core/services/runner_manager.html +485 -0
- simstack/static/docs/_modules/simstack/core/services/runner_status_service.html +329 -0
- simstack/static/docs/_modules/simstack/core/services/slurm_status_service.html +386 -0
- simstack/static/docs/_modules/simstack/core/services/stop_check_service.html +346 -0
- simstack/static/docs/_modules/simstack/core/services/timeout_restart_service.html +345 -0
- simstack/static/docs/_modules/simstack/core/simstack_result.html +355 -0
- simstack/static/docs/_modules/simstack/core/submit_node.html +536 -0
- simstack/static/docs/_modules/simstack/core/task_id.html +344 -0
- simstack/static/docs/_modules/simstack/methods/archive_files.html +598 -0
- simstack/static/docs/_modules/simstack/methods/fire_and_forget_runner.html +432 -0
- simstack/static/docs/_modules/simstack/methods/generate_test.html +443 -0
- simstack/static/docs/_modules/simstack/methods/get_file.html +342 -0
- simstack/static/docs/_modules/simstack/methods/mass_runner.html +476 -0
- simstack/static/docs/_modules/simstack/methods/multiple_executor.html +674 -0
- simstack/static/docs/_modules/simstack/methods/switch_git.html +332 -0
- simstack/static/docs/_modules/simstack/methods/upload_helpers.html +403 -0
- simstack/static/docs/_modules/simstack/models/array_list.html +337 -0
- simstack/static/docs/_modules/simstack/models/array_storage.html +417 -0
- simstack/static/docs/_modules/simstack/models/base_lists.html +374 -0
- simstack/static/docs/_modules/simstack/models/base_types.html +588 -0
- simstack/static/docs/_modules/simstack/models/charts_artifact.html +1035 -0
- simstack/static/docs/_modules/simstack/models/dataset.html +866 -0
- simstack/static/docs/_modules/simstack/models/dataset_metadata.html +711 -0
- simstack/static/docs/_modules/simstack/models/datasettuple.html +1005 -0
- simstack/static/docs/_modules/simstack/models/file_instance.html +472 -0
- simstack/static/docs/_modules/simstack/models/file_list.html +367 -0
- simstack/static/docs/_modules/simstack/models/files.html +773 -0
- simstack/static/docs/_modules/simstack/models/fire_and_forget_result.html +335 -0
- simstack/static/docs/_modules/simstack/models/images2d.html +447 -0
- simstack/static/docs/_modules/simstack/models/log_entry_model.html +356 -0
- simstack/static/docs/_modules/simstack/models/models.html +374 -0
- simstack/static/docs/_modules/simstack/models/named_data_reference.html +355 -0
- simstack/static/docs/_modules/simstack/models/node_registry.html +415 -0
- simstack/static/docs/_modules/simstack/models/pandas_model.html +570 -0
- simstack/static/docs/_modules/simstack/models/parameters.html +739 -0
- simstack/static/docs/_modules/simstack/models/pickle_models.html +631 -0
- simstack/static/docs/_modules/simstack/models/project.html +329 -0
- simstack/static/docs/_modules/simstack/models/resource_assignment.html +501 -0
- simstack/static/docs/_modules/simstack/models/resource_definition.html +477 -0
- simstack/static/docs/_modules/simstack/models/runner_model.html +361 -0
- simstack/static/docs/_modules/simstack/models/simple_table.html +413 -0
- simstack/static/docs/_modules/simstack/models/simstack_model.html +416 -0
- simstack/static/docs/_modules/simstack/models/slurm_info.html +333 -0
- simstack/static/docs/_modules/simstack/models/table_artifact.html +406 -0
- simstack/static/docs/_modules/simstack/models/tag.html +328 -0
- simstack/static/docs/_modules/simstack/tables/model_table.html +519 -0
- simstack/static/docs/_modules/simstack/tables/node_children.html +538 -0
- simstack/static/docs/_modules/simstack/tables/node_table.html +854 -0
- simstack/static/docs/_modules/simstack/tables/table_builder.html +642 -0
- simstack/static/docs/_modules/simstack/util/async_zip_utils.html +337 -0
- simstack/static/docs/_modules/simstack/util/b64mixin.html +339 -0
- simstack/static/docs/_modules/simstack/util/cleaned_json_schema.html +336 -0
- simstack/static/docs/_modules/simstack/util/config_reader.html +498 -0
- simstack/static/docs/_modules/simstack/util/custom_model_dump.html +391 -0
- simstack/static/docs/_modules/simstack/util/database_information.html +488 -0
- simstack/static/docs/_modules/simstack/util/db.html +1392 -0
- simstack/static/docs/_modules/simstack/util/db_logger.html +448 -0
- simstack/static/docs/_modules/simstack/util/default_from_dict.html +376 -0
- simstack/static/docs/_modules/simstack/util/directory_iterator.html +553 -0
- simstack/static/docs/_modules/simstack/util/docstring_parser.html +526 -0
- simstack/static/docs/_modules/simstack/util/file_hashing.html +394 -0
- simstack/static/docs/_modules/simstack/util/file_transfer_client.html +791 -0
- simstack/static/docs/_modules/simstack/util/files.html +733 -0
- simstack/static/docs/_modules/simstack/util/find_python_files.html +349 -0
- simstack/static/docs/_modules/simstack/util/generate_ui_schema.html +396 -0
- simstack/static/docs/_modules/simstack/util/generic_list_mixin.html +487 -0
- simstack/static/docs/_modules/simstack/util/get_module_path.html +336 -0
- simstack/static/docs/_modules/simstack/util/git_pull.html +381 -0
- simstack/static/docs/_modules/simstack/util/git_repository_status.html +411 -0
- simstack/static/docs/_modules/simstack/util/import_module.html +377 -0
- simstack/static/docs/_modules/simstack/util/importer.html +637 -0
- simstack/static/docs/_modules/simstack/util/init_data_source.html +357 -0
- simstack/static/docs/_modules/simstack/util/make_graph.html +423 -0
- simstack/static/docs/_modules/simstack/util/make_table.html +1171 -0
- simstack/static/docs/_modules/simstack/util/mappings.html +378 -0
- simstack/static/docs/_modules/simstack/util/minimal_route_finder.html +388 -0
- simstack/static/docs/_modules/simstack/util/mock_db.html +472 -0
- simstack/static/docs/_modules/simstack/util/module_path_checker.html +354 -0
- simstack/static/docs/_modules/simstack/util/object_list_mixin.html +649 -0
- simstack/static/docs/_modules/simstack/util/path_manager.html +566 -0
- simstack/static/docs/_modules/simstack/util/project_root_finder.html +357 -0
- simstack/static/docs/_modules/simstack/util/queue_watcher.html +593 -0
- simstack/static/docs/_modules/simstack/util/resource_config.html +532 -0
- simstack/static/docs/_modules/simstack/util/route_finder.html +390 -0
- simstack/static/docs/_modules/simstack/util/runner_util.html +487 -0
- simstack/static/docs/_modules/simstack/util/runner_utils.html +468 -0
- simstack/static/docs/_modules/simstack/util/safe_code_executor.html +510 -0
- simstack/static/docs/_modules/simstack/util/sample_config.html +414 -0
- simstack/static/docs/_modules/simstack/util/sensitive_content_filter.html +364 -0
- simstack/static/docs/_modules/simstack/util/setup_logging.html +365 -0
- simstack/static/docs/_modules/simstack/util/submit_to_watchdog.html +425 -0
- simstack/static/docs/_modules/simstack/util/toml_reader.html +482 -0
- simstack/static/docs/_modules/simstack/util/transform_file_name.html +357 -0
- simstack/static/docs/_modules/simstack/util/ui_tools.html +541 -0
- simstack/static/docs/_sources/architecture.rst.txt +25 -0
- simstack/static/docs/_sources/array_lists.rst.txt +60 -0
- simstack/static/docs/_sources/ci_pipeline.rst.txt +34 -0
- simstack/static/docs/_sources/data.rst.txt +175 -0
- simstack/static/docs/_sources/database_migrations.rst.txt +310 -0
- simstack/static/docs/_sources/datasets.rst.txt +93 -0
- simstack/static/docs/_sources/developer_guide.rst.txt +25 -0
- simstack/static/docs/_sources/docker_nodes.rst.txt +149 -0
- simstack/static/docs/_sources/files.rst.txt +159 -0
- simstack/static/docs/_sources/fire_and_forget_runner.rst.txt +67 -0
- simstack/static/docs/_sources/index.rst.txt +25 -0
- simstack/static/docs/_sources/installation.rst.txt +213 -0
- simstack/static/docs/_sources/lists.rst.txt +111 -0
- simstack/static/docs/_sources/mass_runner.rst.txt +91 -0
- simstack/static/docs/_sources/misc.rst.txt +106 -0
- simstack/static/docs/_sources/overview.rst.txt +91 -0
- simstack/static/docs/_sources/recovery.rst.txt +38 -0
- simstack/static/docs/_sources/resource_config.rst.txt +141 -0
- simstack/static/docs/_sources/simstack/modules/modules.rst.txt +7 -0
- simstack/static/docs/_sources/simstack/modules/simstack.core.rst.txt +205 -0
- simstack/static/docs/_sources/simstack/modules/simstack.core.services.rst.txt +109 -0
- simstack/static/docs/_sources/simstack/modules/simstack.methods.rst.txt +77 -0
- simstack/static/docs/_sources/simstack/modules/simstack.models.rst.txt +261 -0
- simstack/static/docs/_sources/simstack/modules/simstack.rst.txt +22 -0
- simstack/static/docs/_sources/simstack/modules/simstack.tables.rst.txt +45 -0
- simstack/static/docs/_sources/simstack/modules/simstack.util.rst.txt +381 -0
- simstack/static/docs/_sources/submit.rst.txt +141 -0
- simstack/static/docs/_sources/tables.rst.txt +50 -0
- simstack/static/docs/_sources/testing_complex_nodes.rst.txt +92 -0
- simstack/static/docs/_sources/ui_documentation.rst.txt +272 -0
- simstack/static/docs/_sources/workflows.rst.txt +446 -0
- simstack/static/docs/_static/base-stemmer.js +476 -0
- simstack/static/docs/_static/basic.css +906 -0
- simstack/static/docs/_static/cobalt2-theme.css +1 -0
- simstack/static/docs/_static/debug.css +69 -0
- simstack/static/docs/_static/docs-build-badge.css +51 -0
- simstack/static/docs/_static/docs-build-badge.js +51 -0
- simstack/static/docs/_static/doctools.js +150 -0
- simstack/static/docs/_static/documentation_options.js +13 -0
- simstack/static/docs/_static/english-stemmer.js +1066 -0
- simstack/static/docs/_static/file.png +0 -0
- simstack/static/docs/_static/language_data.js +13 -0
- simstack/static/docs/_static/minus.png +0 -0
- simstack/static/docs/_static/plus.png +0 -0
- simstack/static/docs/_static/pygments.css +250 -0
- simstack/static/docs/_static/scripts/furo-extensions.js +0 -0
- simstack/static/docs/_static/scripts/furo.js +3 -0
- simstack/static/docs/_static/scripts/furo.js.LICENSE.txt +7 -0
- simstack/static/docs/_static/scripts/furo.js.map +1 -0
- simstack/static/docs/_static/searchtools.js +693 -0
- simstack/static/docs/_static/skeleton.css +296 -0
- simstack/static/docs/_static/sphinx_highlight.js +159 -0
- simstack/static/docs/_static/styles/furo-extensions.css +2 -0
- simstack/static/docs/_static/styles/furo-extensions.css.map +1 -0
- simstack/static/docs/_static/styles/furo.css +2 -0
- simstack/static/docs/_static/styles/furo.css.map +1 -0
- simstack/static/docs/architecture.html +340 -0
- simstack/static/docs/array_lists.html +678 -0
- simstack/static/docs/ci_pipeline.html +385 -0
- simstack/static/docs/data.html +545 -0
- simstack/static/docs/database_migrations.html +652 -0
- simstack/static/docs/datasets.html +1268 -0
- simstack/static/docs/developer_guide.html +359 -0
- simstack/static/docs/docker_nodes.html +503 -0
- simstack/static/docs/files.html +718 -0
- simstack/static/docs/fire_and_forget_runner.html +641 -0
- simstack/static/docs/genindex.html +6390 -0
- simstack/static/docs/index.html +516 -0
- simstack/static/docs/installation.html +553 -0
- simstack/static/docs/lists.html +478 -0
- simstack/static/docs/mass_runner.html +481 -0
- simstack/static/docs/misc.html +454 -0
- simstack/static/docs/objects.inv +0 -0
- simstack/static/docs/overview.html +469 -0
- simstack/static/docs/py-modindex.html +1111 -0
- simstack/static/docs/recovery.html +371 -0
- simstack/static/docs/resource_config.html +590 -0
- simstack/static/docs/search.html +327 -0
- simstack/static/docs/searchindex.js +1 -0
- simstack/static/docs/simstack/modules/modules.html +489 -0
- simstack/static/docs/simstack/modules/simstack.core.html +1883 -0
- simstack/static/docs/simstack/modules/simstack.core.services.html +698 -0
- simstack/static/docs/simstack/modules/simstack.html +3060 -0
- simstack/static/docs/simstack/modules/simstack.methods.html +1150 -0
- simstack/static/docs/simstack/modules/simstack.models.html +13113 -0
- simstack/static/docs/simstack/modules/simstack.tables.html +635 -0
- simstack/static/docs/simstack/modules/simstack.util.html +3320 -0
- simstack/static/docs/submit.html +487 -0
- simstack/static/docs/tables.html +539 -0
- simstack/static/docs/testing_complex_nodes.html +458 -0
- simstack/static/docs/ui_documentation.html +788 -0
- simstack/static/docs/workflows.html +766 -0
- simstack/tables/__init__.py +0 -0
- simstack/tables/model_table.py +191 -0
- simstack/tables/node_children.py +219 -0
- simstack/tables/node_table.py +517 -0
- simstack/tables/table_builder.py +314 -0
- simstack/util/__init__.py +0 -0
- simstack/util/async_zip_utils.py +18 -0
- simstack/util/b64mixin.py +23 -0
- simstack/util/cleaned_json_schema.py +20 -0
- simstack/util/config_reader.py +179 -0
- simstack/util/custom_model_dump.py +75 -0
- simstack/util/database_information.py +160 -0
- simstack/util/db.py +1013 -0
- simstack/util/db_logger.py +117 -0
- simstack/util/default_from_dict.py +57 -0
- simstack/util/directory_iterator.py +216 -0
- simstack/util/docstring_parser.py +186 -0
- simstack/util/file_hashing.py +72 -0
- simstack/util/file_transfer_client.py +427 -0
- simstack/util/files.py +396 -0
- simstack/util/find_python_files.py +33 -0
- simstack/util/generate_ui_schema.py +80 -0
- simstack/util/generic_list_mixin.py +120 -0
- simstack/util/geometry_utils.py +118 -0
- simstack/util/get_module_path.py +20 -0
- simstack/util/git_pull.py +65 -0
- simstack/util/git_repository_status.py +95 -0
- simstack/util/import_module.py +63 -0
- simstack/util/importer.py +312 -0
- simstack/util/init_data_source.py +41 -0
- simstack/util/make_graph.py +107 -0
- simstack/util/make_table.py +837 -0
- simstack/util/mappings.py +41 -0
- simstack/util/minimal_route_finder.py +72 -0
- simstack/util/mock_db.py +144 -0
- simstack/util/module_path_checker.py +38 -0
- simstack/util/object_list_mixin.py +282 -0
- simstack/util/path_manager.py +223 -0
- simstack/util/project_root_finder.py +41 -0
- simstack/util/queue_watcher.py +262 -0
- simstack/util/resource_config.py +198 -0
- simstack/util/route_finder.py +74 -0
- simstack/util/runner_util.py +166 -0
- simstack/util/runner_utils.py +143 -0
- simstack/util/safe_code_executor.py +194 -0
- simstack/util/sample_config.py +98 -0
- simstack/util/sensitive_content_filter.py +48 -0
- simstack/util/setup_logging.py +49 -0
- simstack/util/submit_to_watchdog.py +106 -0
- simstack/util/toml_reader.py +142 -0
- simstack/util/transform_file_name.py +41 -0
- simstack/util/ui_tools.py +213 -0
- simstack-2.0.1.dist-info/METADATA +454 -0
- simstack-2.0.1.dist-info/RECORD +354 -0
- simstack-2.0.1.dist-info/WHEEL +5 -0
- simstack-2.0.1.dist-info/entry_points.txt +12 -0
- simstack-2.0.1.dist-info/licenses/LICENSE +21 -0
- simstack-2.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Dict, Any, Union, List
|
|
3
|
+
|
|
4
|
+
from odmantic import EmbeddedModel, Field, Model
|
|
5
|
+
|
|
6
|
+
from simstack.core.asnyc_helper import async_helper
|
|
7
|
+
from simstack.models import simstack_model
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _get_json_schema(data: Dict) -> dict:
|
|
11
|
+
"""
|
|
12
|
+
Inspects the current data dict and returns a JSON schema for that dict.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
dict: JSON schema describing the structure and types of the current data dict
|
|
16
|
+
"""
|
|
17
|
+
if not data:
|
|
18
|
+
return {"type": "object", "properties": {}, "additionalProperties": False}
|
|
19
|
+
|
|
20
|
+
properties = {}
|
|
21
|
+
|
|
22
|
+
for key, value in data.items():
|
|
23
|
+
if isinstance(value, str):
|
|
24
|
+
properties[key] = {"type": "string"}
|
|
25
|
+
elif isinstance(value, bool):
|
|
26
|
+
# Check bool before int/float since bool is a subclass of int in Python
|
|
27
|
+
properties[key] = {"type": "boolean"}
|
|
28
|
+
elif isinstance(value, int):
|
|
29
|
+
properties[key] = {"type": "integer"}
|
|
30
|
+
elif isinstance(value, float):
|
|
31
|
+
properties[key] = {"type": "number"}
|
|
32
|
+
elif isinstance(value, datetime):
|
|
33
|
+
properties[key] = {"type": "string", "format": "date-time"}
|
|
34
|
+
else:
|
|
35
|
+
# Fallback for any unexpected types
|
|
36
|
+
properties[key] = {"type": "string"}
|
|
37
|
+
|
|
38
|
+
schema = {"type": "object", "properties": properties, "additionalProperties": False}
|
|
39
|
+
|
|
40
|
+
return schema
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DataSetMetadataTemplate(Model):
|
|
44
|
+
dataset_type: str
|
|
45
|
+
model_json: dict[str, Any]
|
|
46
|
+
structure: Dict[str, Dict[str, str]] = Field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@simstack_model
|
|
50
|
+
class DataSetMetadata(EmbeddedModel):
|
|
51
|
+
field_name: str = Field(unique=True)
|
|
52
|
+
data: Dict[str, Union[str, int, float, bool, datetime]] = Field(
|
|
53
|
+
default_factory=dict
|
|
54
|
+
)
|
|
55
|
+
is_validated: bool = False
|
|
56
|
+
structure: Dict[str, Dict[str, str]] = Field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
def get_json_schema(self):
|
|
59
|
+
return _get_json_schema(self.data)
|
|
60
|
+
|
|
61
|
+
async def validate_dict(self, new_structure: Dict[str, Dict[str, str]], dataset_tuple: bool = False) -> bool:
|
|
62
|
+
"""
|
|
63
|
+
Asynchronously validates and updates the structure of a dataset metadata template based on a provided dictionary.
|
|
64
|
+
|
|
65
|
+
This function ensures that the structure of the provided `new_structure` aligns
|
|
66
|
+
with the pre-existing metadata template stored in the database. If the metadata
|
|
67
|
+
template does not exist, it initializes a new one. Schema properties and section
|
|
68
|
+
contents are rigorously compared for consistency, and any mismatches result in
|
|
69
|
+
an exception being raised. The function also handles updates to the template when
|
|
70
|
+
new sections or keys are introduced.
|
|
71
|
+
|
|
72
|
+
Parameters:
|
|
73
|
+
new_structure (Dict[str, Dict[str, str]]): A dictionary describing the sections
|
|
74
|
+
and their respective key-type mappings for the new dataset structure.
|
|
75
|
+
dataset_tuple (bool, optional): A flag indicating whether the provided structure
|
|
76
|
+
should be treated as a tuple. Defaults to False.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
bool: True if the structure is successfully validated or updated.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If there is a mismatch between the existing and provided schema
|
|
83
|
+
properties or section content.
|
|
84
|
+
"""
|
|
85
|
+
from simstack.core.context import context
|
|
86
|
+
reference_metadata = await context.db.find_one(
|
|
87
|
+
DataSetMetadataTemplate,
|
|
88
|
+
DataSetMetadataTemplate.dataset_type == self.field_name,
|
|
89
|
+
)
|
|
90
|
+
# remove empty sections without mutating the dict during iteration
|
|
91
|
+
new_structure = {
|
|
92
|
+
key: value for key, value in new_structure.items() if value
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if reference_metadata is None:
|
|
96
|
+
metadata_template = DataSetMetadataTemplate(
|
|
97
|
+
dataset_type=self.field_name,
|
|
98
|
+
model_json=_get_json_schema(self.data),
|
|
99
|
+
structure=new_structure,
|
|
100
|
+
)
|
|
101
|
+
await context.db.save(metadata_template)
|
|
102
|
+
self.structure = new_structure
|
|
103
|
+
return True # first model of this type
|
|
104
|
+
|
|
105
|
+
new_data_json = _get_json_schema(self.data)
|
|
106
|
+
|
|
107
|
+
# Compare schemas element by element
|
|
108
|
+
# this section compares whether the data in the metadata is the same
|
|
109
|
+
ref_props = reference_metadata.model_json.get("properties", {})
|
|
110
|
+
new_props = new_data_json.get("properties", {})
|
|
111
|
+
|
|
112
|
+
# Check if property keys match
|
|
113
|
+
if set(ref_props.keys()) != set(new_props.keys()):
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Data schema properties mismatch. Reference keys: {set(ref_props.keys())}, Current keys: {set(new_props.keys())}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Check each property type, allowing string format differences
|
|
119
|
+
for key in ref_props.keys():
|
|
120
|
+
ref_prop = ref_props[key]
|
|
121
|
+
new_prop = new_props[key]
|
|
122
|
+
|
|
123
|
+
ref_type = ref_prop.get("type")
|
|
124
|
+
new_type = new_prop.get("type")
|
|
125
|
+
|
|
126
|
+
if ref_type != new_type:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"Property '{key}' type mismatch. Reference: {ref_type}, Current: {new_type}"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# For string types, allow the format field to differ or be missing
|
|
132
|
+
if ref_type == "string":
|
|
133
|
+
# Compare all fields except 'format'
|
|
134
|
+
ref_without_format = {k: v for k, v in ref_prop.items() if k != "format"}
|
|
135
|
+
new_without_format = {k: v for k, v in new_prop.items() if k != "format"}
|
|
136
|
+
if ref_without_format != new_without_format:
|
|
137
|
+
raise ValueError(
|
|
138
|
+
f"Property '{key}' schema mismatch (excluding format). Reference: {ref_without_format}, Current: {new_without_format}"
|
|
139
|
+
)
|
|
140
|
+
else:
|
|
141
|
+
# For non-string types, require exact match
|
|
142
|
+
if ref_prop != new_prop:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"Property '{key}' schema mismatch. Reference: {ref_prop}, Current: {new_prop}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Check if model types in existing sections match, and add new keys
|
|
148
|
+
save_template = False
|
|
149
|
+
updated_structure = reference_metadata.structure.copy()
|
|
150
|
+
|
|
151
|
+
for section_name, new_section_content in new_structure.items():
|
|
152
|
+
if section_name in updated_structure:
|
|
153
|
+
ref_section_content = updated_structure[section_name]
|
|
154
|
+
# Check if all elements in new_section_content match ref_section_content exactly
|
|
155
|
+
|
|
156
|
+
if dataset_tuple: # for tuples the sections must be exactly the same
|
|
157
|
+
if ref_section_content != new_section_content:
|
|
158
|
+
raise ValueError(
|
|
159
|
+
f"Section '{section_name}' content mismatch. Reference: {ref_section_content}, Current: {new_section_content}"
|
|
160
|
+
)
|
|
161
|
+
continue
|
|
162
|
+
else:
|
|
163
|
+
ref_keys = set(ref_section_content.keys())
|
|
164
|
+
new_keys = set(new_section_content.keys())
|
|
165
|
+
|
|
166
|
+
extra_keys = new_keys - ref_keys
|
|
167
|
+
|
|
168
|
+
# Check for type mismatches in common keys
|
|
169
|
+
common_keys = ref_keys.intersection(new_keys)
|
|
170
|
+
for key in common_keys:
|
|
171
|
+
if ref_section_content[key] != new_section_content[key]:
|
|
172
|
+
error_msg = f"MetaData Validation failed: Section '{section_name}' has different value for key '{key}'.\n"
|
|
173
|
+
error_msg += f"Reference: {ref_section_content[key]}, Current: {new_section_content[key]}\n"
|
|
174
|
+
error_msg += f"Reference keys: {ref_keys}\n"
|
|
175
|
+
error_msg += f"Current keys: {new_keys}"
|
|
176
|
+
raise ValueError(error_msg)
|
|
177
|
+
|
|
178
|
+
if len(extra_keys) > 0:
|
|
179
|
+
for key in extra_keys:
|
|
180
|
+
updated_structure[section_name][key] = new_section_content[key]
|
|
181
|
+
save_template = True
|
|
182
|
+
else:
|
|
183
|
+
# Completely new section
|
|
184
|
+
updated_structure[section_name] = new_section_content
|
|
185
|
+
save_template = True
|
|
186
|
+
|
|
187
|
+
if save_template:
|
|
188
|
+
reference_metadata.structure = updated_structure
|
|
189
|
+
await context.db.save(reference_metadata)
|
|
190
|
+
self.structure = updated_structure
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
@async_helper
|
|
194
|
+
async def freeze(self, new_structure: Dict[str, Dict[str, str]]) -> bool:
|
|
195
|
+
from simstack.core.context import context
|
|
196
|
+
db = context.db
|
|
197
|
+
reference_metadata = await db.find_one(
|
|
198
|
+
DataSetMetadataTemplate,
|
|
199
|
+
DataSetMetadataTemplate.dataset_type == self.field_name,
|
|
200
|
+
)
|
|
201
|
+
if not reference_metadata:
|
|
202
|
+
raise ValueError("Metadata does not exist")
|
|
203
|
+
if self.structure != reference_metadata.structure:
|
|
204
|
+
raise ValueError("Metadata structure has changed in the database")
|
|
205
|
+
if self.structure == {}:
|
|
206
|
+
self.structure = new_structure
|
|
207
|
+
reference_metadata.structure = new_structure
|
|
208
|
+
await db.save(reference_metadata)
|
|
209
|
+
# some structure exists already
|
|
210
|
+
return new_structure == self.structure
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def initialized(self) -> bool:
|
|
214
|
+
"""Check if the model has been fully constructed."""
|
|
215
|
+
# A simple heuristic: if we have an ID or if type is set, we're initialized
|
|
216
|
+
return hasattr(self, "dataset_type") and self.field_name is not None
|
|
217
|
+
|
|
218
|
+
# Dict-like behavior methods
|
|
219
|
+
def __getitem__(self, key: str):
|
|
220
|
+
"""Get item from data dict."""
|
|
221
|
+
return self.data[key]
|
|
222
|
+
|
|
223
|
+
def __setitem__(self, key: str, value: Union[str, int, float, bool, datetime]):
|
|
224
|
+
"""Set item in data dict with validation."""
|
|
225
|
+
# Validate value type
|
|
226
|
+
if not isinstance(value, (str, int, float, bool, datetime)):
|
|
227
|
+
raise TypeError(
|
|
228
|
+
f"Value must be str, int, float, bool, or datetime, got {type(value).__name__}"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Only check for structural changes after initialization
|
|
232
|
+
if self.initialized and key not in self.data:
|
|
233
|
+
raise KeyError(
|
|
234
|
+
f"Cannot add new key '{key}' after initialization. Existing keys: {list(self.data.keys())}"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Only check for type changes after initialization
|
|
238
|
+
if self.initialized and key in self.data:
|
|
239
|
+
existing_value = self.data[key]
|
|
240
|
+
if type(existing_value) != type(value):
|
|
241
|
+
raise TypeError(
|
|
242
|
+
f"Cannot change type of key '{key}' from {type(existing_value).__name__} "
|
|
243
|
+
f"to {type(value).__name__}"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
self.data[key] = value
|
|
247
|
+
|
|
248
|
+
def __delitem__(self, key: str):
|
|
249
|
+
"""Delete item from data dict."""
|
|
250
|
+
if self.initialized:
|
|
251
|
+
raise KeyError(f"Cannot delete key '{key}' after initialization")
|
|
252
|
+
del self.data[key]
|
|
253
|
+
|
|
254
|
+
def __contains__(self, key: str) -> bool:
|
|
255
|
+
"""Check if key exists in data dict."""
|
|
256
|
+
return key in self.data
|
|
257
|
+
|
|
258
|
+
def __iter__(self):
|
|
259
|
+
"""Iterate over keys in data dict."""
|
|
260
|
+
return iter(self.data)
|
|
261
|
+
|
|
262
|
+
def __len__(self) -> int:
|
|
263
|
+
"""Get number of items in data dict."""
|
|
264
|
+
return len(self.data)
|
|
265
|
+
|
|
266
|
+
def keys(self):
|
|
267
|
+
"""Get keys from data dict."""
|
|
268
|
+
return self.data.keys()
|
|
269
|
+
|
|
270
|
+
def values(self):
|
|
271
|
+
"""Get values from data dict."""
|
|
272
|
+
return self.data.values()
|
|
273
|
+
|
|
274
|
+
def items(self):
|
|
275
|
+
"""Get items from data dict."""
|
|
276
|
+
return self.data.items()
|
|
277
|
+
|
|
278
|
+
def get(self, key: str, default=None):
|
|
279
|
+
"""Get item from data dict with default."""
|
|
280
|
+
return self.data.get(key, default)
|
|
281
|
+
|
|
282
|
+
def pop(self, key: str, *args):
|
|
283
|
+
"""Pop item from data dict."""
|
|
284
|
+
if self.initialized:
|
|
285
|
+
raise KeyError(f"Cannot pop key '{key}' after initialization")
|
|
286
|
+
return self.data.pop(key, *args)
|
|
287
|
+
|
|
288
|
+
def popitem(self):
|
|
289
|
+
"""Pop item from data dict."""
|
|
290
|
+
if self.initialized:
|
|
291
|
+
raise KeyError("Cannot pop items after initialization")
|
|
292
|
+
return self.data.popitem()
|
|
293
|
+
|
|
294
|
+
def clear(self):
|
|
295
|
+
"""Clear data dict."""
|
|
296
|
+
if self.initialized:
|
|
297
|
+
raise RuntimeError("Cannot clear data after initialization")
|
|
298
|
+
self.data.clear()
|
|
299
|
+
|
|
300
|
+
def update(self, *args, **kwargs):
|
|
301
|
+
"""Update data dict with validation."""
|
|
302
|
+
# Handle different update signatures
|
|
303
|
+
if args:
|
|
304
|
+
other = args[0]
|
|
305
|
+
if hasattr(other, "items"):
|
|
306
|
+
items_to_update = other.items()
|
|
307
|
+
else:
|
|
308
|
+
items_to_update = other
|
|
309
|
+
else:
|
|
310
|
+
items_to_update = []
|
|
311
|
+
|
|
312
|
+
# Combine with kwargs
|
|
313
|
+
all_items = list(items_to_update) + list(kwargs.items())
|
|
314
|
+
|
|
315
|
+
# Validate all items before updating
|
|
316
|
+
for key, value in all_items:
|
|
317
|
+
# Type validation (always required)
|
|
318
|
+
if not isinstance(value, (str, int, float, bool, datetime)):
|
|
319
|
+
raise TypeError(
|
|
320
|
+
f"Value for key '{key}' must be str, int, float, bool, or datetime, got {type(value).__name__}"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Structural validation (only after initialization)
|
|
324
|
+
if self.initialized:
|
|
325
|
+
if key not in self.data:
|
|
326
|
+
raise KeyError(f"Cannot add new key '{key}' after initialization")
|
|
327
|
+
|
|
328
|
+
existing_value = self.data[key]
|
|
329
|
+
if type(existing_value) != type(value):
|
|
330
|
+
raise TypeError(
|
|
331
|
+
f"Cannot change type of key '{key}' from {type(existing_value).__name__} "
|
|
332
|
+
f"to {type(value).__name__}"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# If all validations pass, update the data
|
|
336
|
+
for key, value in all_items:
|
|
337
|
+
self.data[key] = value
|
|
338
|
+
|
|
339
|
+
def setdefault(self, key: str, default=None):
|
|
340
|
+
"""Set default value for key if not exists."""
|
|
341
|
+
if key not in self.data:
|
|
342
|
+
if self.initialized:
|
|
343
|
+
raise KeyError(f"Cannot add new key '{key}' after initialization")
|
|
344
|
+
|
|
345
|
+
if default is not None and not isinstance(
|
|
346
|
+
default, (str, int, float, bool, datetime)
|
|
347
|
+
):
|
|
348
|
+
raise TypeError(
|
|
349
|
+
f"Default value must be str, int, float, bool, or datetime, got {type(default).__name__}"
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
self.data[key] = default
|
|
353
|
+
|
|
354
|
+
return self.data[key]
|
|
355
|
+
|
|
356
|
+
# Additional utility methods (no initialization checks needed)
|
|
357
|
+
def copy_data(self) -> Dict[str, Union[str, int, float, bool, datetime]]:
|
|
358
|
+
"""Return a copy of the data dict."""
|
|
359
|
+
return self.data.copy()
|
|
360
|
+
|
|
361
|
+
def is_type_compatible(self, key: str, value) -> bool:
|
|
362
|
+
"""Check if a value is type-compatible with existing key."""
|
|
363
|
+
if key not in self.data:
|
|
364
|
+
return isinstance(value, (str, int, float, bool, datetime))
|
|
365
|
+
return type(self.data[key]) == type(value)
|
|
366
|
+
|
|
367
|
+
def get_key_type(self, key: str) -> type:
|
|
368
|
+
"""Get the type of a specific key."""
|
|
369
|
+
if key not in self.data:
|
|
370
|
+
raise KeyError(f"Key '{key}' not found")
|
|
371
|
+
return type(self.data[key])
|
|
372
|
+
|
|
373
|
+
def get_schema_for_key(self, key: str) -> dict:
|
|
374
|
+
"""Get JSON schema for a specific key."""
|
|
375
|
+
if key not in self.data:
|
|
376
|
+
raise KeyError(f"Key '{key}' not found")
|
|
377
|
+
|
|
378
|
+
value = self.data[key]
|
|
379
|
+
if isinstance(value, str):
|
|
380
|
+
return {"type": "string"}
|
|
381
|
+
elif isinstance(value, bool):
|
|
382
|
+
return {"type": "boolean"}
|
|
383
|
+
elif isinstance(value, int):
|
|
384
|
+
return {"type": "integer"}
|
|
385
|
+
elif isinstance(value, float):
|
|
386
|
+
return {"type": "number"}
|
|
387
|
+
elif isinstance(value, datetime):
|
|
388
|
+
return {"type": "string", "format": "date-time"}
|
|
389
|
+
else:
|
|
390
|
+
return {"type": "string"}
|