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,31 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from pymongo import ReturnDocument
|
|
4
|
+
|
|
5
|
+
from simstack.core.context import context
|
|
6
|
+
from simstack.core.definitions import TaskStatus
|
|
7
|
+
from simstack.models import NodeRegistry
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("node_claim")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def claim_submitted_node(registry_entry: NodeRegistry) -> bool:
|
|
13
|
+
"""Automatically claim a submitted node for execution or submission."""
|
|
14
|
+
if registry_entry.id is None:
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
collection = context.db.get_collection(NodeRegistry)
|
|
18
|
+
claimed = await collection.find_one_and_update(
|
|
19
|
+
{"_id": registry_entry.id, "status": TaskStatus.SUBMITTED.value},
|
|
20
|
+
{"$set": {"status": TaskStatus.RETRIEVED.value}},
|
|
21
|
+
return_document=ReturnDocument.AFTER,
|
|
22
|
+
)
|
|
23
|
+
if claimed is None:
|
|
24
|
+
logger.debug(
|
|
25
|
+
"Task task_id: %s was already claimed before this runner could claim it",
|
|
26
|
+
registry_entry.id,
|
|
27
|
+
)
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
registry_entry.status = TaskStatus.RETRIEVED
|
|
31
|
+
return True
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import uuid
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Set, List, Tuple, Union
|
|
8
|
+
|
|
9
|
+
from odmantic import ObjectId, Model
|
|
10
|
+
|
|
11
|
+
from simstack.core.definitions import TaskStatus
|
|
12
|
+
from simstack.core.simstack_result import SimstackResult
|
|
13
|
+
from simstack.models.files import FileStack
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
local_logger = logging.getLogger("NodeRunner")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NodeRunner(SimstackResult):
|
|
20
|
+
"""
|
|
21
|
+
A task runner class that extends SimstackResult for executing subprocess commands and managing task execution.
|
|
22
|
+
|
|
23
|
+
This class provides functionality for running shell commands, collecting output files, and managing
|
|
24
|
+
task status with comprehensive logging capabilities. It acts as a bridge between the Simstack node
|
|
25
|
+
execution environment and external processes, ensuring that output, errors, and informational files
|
|
26
|
+
are correctly captured and associated with the task.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
task_id (str): Unique identifier for the task, typically a string representation of an ObjectId.
|
|
30
|
+
name (str): Name of the task runner instance, usually derived from the node function name.
|
|
31
|
+
logger (logging.Logger): Logger instance for recording task activities.
|
|
32
|
+
last_stdout (str): Most recent stdout from subprocess execution.
|
|
33
|
+
last_stderr (str): Most recent stderr from subprocess execution.
|
|
34
|
+
log_string (str): Cumulative log of messages recorded via the `log` method.
|
|
35
|
+
info_file_patterns (Set[str]): Set of file patterns (e.g., "*.log") used to collect information files.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, name: str, task_id: str | ObjectId, logger: logging.Logger = None, **kwargs):
|
|
39
|
+
"""
|
|
40
|
+
Initialize the NodeRunner instance.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name (str): Name for this task runner.
|
|
44
|
+
task_id (str | ObjectId): Unique identifier for the task.
|
|
45
|
+
logger (logging.Logger, optional): Logger instance to use. If None, uses a logger named 'NodeRunner'.
|
|
46
|
+
kwargs: Additional keyword arguments.
|
|
47
|
+
"""
|
|
48
|
+
super().__init__()
|
|
49
|
+
self.task_id = str(task_id) if isinstance(task_id, ObjectId) else task_id
|
|
50
|
+
self.name = name
|
|
51
|
+
self.logger = logger or local_logger
|
|
52
|
+
self.last_stdout = ""
|
|
53
|
+
self.last_stderr = ""
|
|
54
|
+
self.log_string = ""
|
|
55
|
+
self.info_file_patterns = {"*.in", "*.out", "*.err", "*.log"}
|
|
56
|
+
self.info(f"NodeRunner '{self.name}' initialized for task_id: {self.task_id}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def from_kwargs(cls, **kwargs) -> "NodeRunner":
|
|
61
|
+
"""
|
|
62
|
+
Create a NodeRunner instance from keyword arguments.
|
|
63
|
+
|
|
64
|
+
If 'node_runner' is already present in kwargs and is an instance of NodeRunner, it returns it.
|
|
65
|
+
Otherwise, it initializes a new NodeRunner using 'name', 'task_id', and optionally 'logger' from kwargs.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
**kwargs: Keyword arguments containing 'node_runner' or initialization parameters.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
NodeRunner: An existing or newly created NodeRunner instance.
|
|
72
|
+
"""
|
|
73
|
+
node_runner = kwargs.get("node_runner")
|
|
74
|
+
if isinstance(node_runner, cls):
|
|
75
|
+
return node_runner
|
|
76
|
+
|
|
77
|
+
return cls(
|
|
78
|
+
name=kwargs["name"],
|
|
79
|
+
task_id=kwargs["task_id"],
|
|
80
|
+
logger=kwargs.get("logger"),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def make_info_files(self, *args, cwd: str | Path = ""):
|
|
84
|
+
"""
|
|
85
|
+
Collect and process information files based on patterns and explicit file paths.
|
|
86
|
+
|
|
87
|
+
This method processes the provided arguments to identify file patterns and explicit
|
|
88
|
+
file paths, then collects all matching files into FileStack objects for later use.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
*args: Variable arguments that can be:
|
|
92
|
+
- File patterns (strings containing '*')
|
|
93
|
+
- Explicit file paths (existing readable files)
|
|
94
|
+
cwd (str | Path, optional): The directory where to look for files.
|
|
95
|
+
Defaults to current directory.
|
|
96
|
+
|
|
97
|
+
Note:
|
|
98
|
+
Files are added to the info_files list as FileStack objects.
|
|
99
|
+
Only readable files are processed.
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
if not cwd:
|
|
103
|
+
search_cwd = Path.cwd()
|
|
104
|
+
else:
|
|
105
|
+
search_cwd = Path(cwd)
|
|
106
|
+
|
|
107
|
+
files_set: Set[str] = set()
|
|
108
|
+
# Process args for patterns and files
|
|
109
|
+
for value in args:
|
|
110
|
+
if isinstance(value, str):
|
|
111
|
+
if "*" in value:
|
|
112
|
+
self.info_file_patterns.add(value)
|
|
113
|
+
else:
|
|
114
|
+
filepath = value if os.path.isabs(value) else str(search_cwd / value)
|
|
115
|
+
if os.path.exists(filepath) and os.access(filepath, os.R_OK):
|
|
116
|
+
files_set.add(filepath)
|
|
117
|
+
|
|
118
|
+
self.info(f"Processing patterns: {self.info_file_patterns} in {search_cwd}")
|
|
119
|
+
|
|
120
|
+
# Add files matching patterns
|
|
121
|
+
for pattern in self.info_file_patterns:
|
|
122
|
+
# If pattern is not absolute, join it with search_cwd
|
|
123
|
+
search_pattern = pattern if os.path.isabs(pattern) else str(search_cwd / pattern)
|
|
124
|
+
for filepath in glob.glob(search_pattern):
|
|
125
|
+
if os.path.exists(filepath) and os.access(filepath, os.R_OK):
|
|
126
|
+
files_set.add(filepath)
|
|
127
|
+
|
|
128
|
+
self.info(f"Found info files: {files_set}")
|
|
129
|
+
|
|
130
|
+
for file_path in files_set:
|
|
131
|
+
if os.path.exists(file_path):
|
|
132
|
+
file_stack = FileStack.from_local_file(
|
|
133
|
+
file_path, in_memory=True, is_hashable=True, secure_source=True
|
|
134
|
+
)
|
|
135
|
+
self.info_files.append(file_stack)
|
|
136
|
+
self.info(f"Info file added: {file_path}")
|
|
137
|
+
except Exception as e:
|
|
138
|
+
self.error(f"Error in make_info_files: {str(e)}")
|
|
139
|
+
raise RuntimeError(f"Error in make_info_files: {str(e)}") from e
|
|
140
|
+
|
|
141
|
+
def debug(self, msg):
|
|
142
|
+
"""
|
|
143
|
+
Log a debug message with task context.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
msg (str): Debug message to log
|
|
147
|
+
"""
|
|
148
|
+
self.logger.debug(
|
|
149
|
+
f"Task {self.name}: {msg} for task_id: {self.task_id}",
|
|
150
|
+
stacklevel=2,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def log(self, msg: str):
|
|
154
|
+
"""
|
|
155
|
+
Log a message and append it to the cumulative log string.
|
|
156
|
+
|
|
157
|
+
This method records a message in the internal `log_string` and also logs it
|
|
158
|
+
as a debug message using the logger. The cumulative log string can later
|
|
159
|
+
be saved as a log file when the task finishes.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
msg (str): Message to log.
|
|
163
|
+
"""
|
|
164
|
+
self.log_string += f"{msg}\n"
|
|
165
|
+
self.logger.debug(
|
|
166
|
+
f"Task {self.name}: {msg} for task_id: {self.task_id}",
|
|
167
|
+
stacklevel=2,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def info(self, msg):
|
|
171
|
+
"""
|
|
172
|
+
Log an info message with task context.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
msg (str): Info message to log.
|
|
176
|
+
"""
|
|
177
|
+
self.logger.info(
|
|
178
|
+
f"Task {self.name}: {msg} task_id: {self.task_id}",
|
|
179
|
+
stacklevel=2,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def warning(self, msg):
|
|
183
|
+
"""
|
|
184
|
+
Log a warning message with task context.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
msg (str): Warning message to log.
|
|
188
|
+
"""
|
|
189
|
+
self.logger.warning(
|
|
190
|
+
f"Task {self.name}: {msg} task_id: {self.task_id}",
|
|
191
|
+
stacklevel=2,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def error(self, msg):
|
|
195
|
+
"""
|
|
196
|
+
Log an error message with task context and exception info.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
msg (str): Error message to log.
|
|
200
|
+
"""
|
|
201
|
+
self.logger.error(
|
|
202
|
+
f"Task {self.name}: {msg} task_id: {self.task_id}",
|
|
203
|
+
stacklevel=2,
|
|
204
|
+
exc_info=True,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def subprocess(self, name: str, command: str | List[str], cwd: str = "") -> bool:
|
|
208
|
+
"""
|
|
209
|
+
Execute a shell command as a subprocess and capture its output.
|
|
210
|
+
|
|
211
|
+
This method runs a shell command, captures stdout and stderr, creates a log file
|
|
212
|
+
with the execution details, and adds the log file to the info_files collection.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
name (str): Name identifier for the subprocess (used for log file naming)
|
|
216
|
+
command (str): Shell command to execute
|
|
217
|
+
cwd (str, optional): Working directory for command execution. Defaults to current directory.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
bool: True if the subprocess completed successfully (return code 0), False otherwise
|
|
221
|
+
|
|
222
|
+
Note:
|
|
223
|
+
- Creates a log file named "{name}.log" containing command details and output
|
|
224
|
+
- Updates last_stdout and last_stderr attributes with the most recent output
|
|
225
|
+
- Log file is automatically added to info_files collection
|
|
226
|
+
"""
|
|
227
|
+
if name == "":
|
|
228
|
+
name = "process"
|
|
229
|
+
if isinstance(command, list):
|
|
230
|
+
command = " ".join(command)
|
|
231
|
+
|
|
232
|
+
with open(f"{name}.log", "w", encoding="utf-8") as process_log:
|
|
233
|
+
process_log.write(f"Command: {name}\n{command}\n")
|
|
234
|
+
# TODO adapt for docker
|
|
235
|
+
process = subprocess.run(
|
|
236
|
+
command,
|
|
237
|
+
shell=True, # Important: use shell=True for shell operators like &&
|
|
238
|
+
capture_output=True,
|
|
239
|
+
text=True,
|
|
240
|
+
encoding="utf-8",
|
|
241
|
+
cwd=cwd if cwd else None,
|
|
242
|
+
)
|
|
243
|
+
self.info(f"run script {name} finished: {process.returncode}")
|
|
244
|
+
self.last_stdout = process.stdout
|
|
245
|
+
self.last_stderr = process.stderr
|
|
246
|
+
process_log.write(f"Process return code:\n{process.returncode}\n\n")
|
|
247
|
+
process_log.write(f"Process output:\n{process.stdout}\n\n")
|
|
248
|
+
process_log.write(f"Process error:\n{process.stderr}\n\n")
|
|
249
|
+
file_stack = FileStack.from_local_file(
|
|
250
|
+
f"{name}.log", in_memory=True, is_hashable=True, secure_source=True
|
|
251
|
+
)
|
|
252
|
+
self.info_files.append(file_stack)
|
|
253
|
+
self.info(f"Subprocess '{name}' log added to info files: {file_stack.name}")
|
|
254
|
+
return process.returncode == 0
|
|
255
|
+
|
|
256
|
+
def submit_to_watchdog(self, name: str, command: str) -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Submit a command to an external watchdog process for execution.
|
|
259
|
+
|
|
260
|
+
This method prepares the necessary files and environment to submit a command
|
|
261
|
+
to a watchdog process that will handle its execution. It uses a specified queue directory
|
|
262
|
+
to manage job files and signals.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
name (str): Name identifier for the watchdog job (used for log file naming).
|
|
266
|
+
command (str): Command to be executed by the watchdog.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
bool: True if the submission and execution were successful, False otherwise.
|
|
270
|
+
|
|
271
|
+
Note:
|
|
272
|
+
- The method sets the task status to FAILED if the submission or execution fails.
|
|
273
|
+
- Job logs are automatically added to the info_files collection.
|
|
274
|
+
"""
|
|
275
|
+
# we are inside a slurm job, so we need to use the queue dir of the job
|
|
276
|
+
queue_dir = Path.cwd() / "queue"
|
|
277
|
+
queue_dir.mkdir(parents=True, exist_ok=True)
|
|
278
|
+
if name == "":
|
|
279
|
+
name = "process"
|
|
280
|
+
from simstack.util.submit_to_watchdog import submit_to_watchdog
|
|
281
|
+
|
|
282
|
+
try:
|
|
283
|
+
with open(f"{name}.log", "w") as process_log:
|
|
284
|
+
process_log.write(f"Command: {name}\n{command}\n")
|
|
285
|
+
|
|
286
|
+
job_id = str(self.task_id) + "_" + uuid.uuid4().hex
|
|
287
|
+
result = submit_to_watchdog(command, job_id, queue_dir)
|
|
288
|
+
if result.status == "ok":
|
|
289
|
+
self.info(f"watchdog {result.exit_code}")
|
|
290
|
+
else:
|
|
291
|
+
err_msg = f"watchdog submission failed: {result.returncode}"
|
|
292
|
+
self.fail(err_msg)
|
|
293
|
+
self.last_stdout = result.stdout
|
|
294
|
+
self.last_stderr = result.stderr
|
|
295
|
+
process_log.write(f"Process return code:\n{result.exit_code}\n\n")
|
|
296
|
+
process_log.write(f"Process output:\n{result.stdout}\n\n")
|
|
297
|
+
process_log.write(f"Process error:\n{result.stderr}\n\n")
|
|
298
|
+
file_stack = FileStack.from_local_file(
|
|
299
|
+
f"{name}.log", in_memory=True, is_hashable=True, secure_source=True
|
|
300
|
+
)
|
|
301
|
+
self.info_files.append(file_stack)
|
|
302
|
+
self.info(f"Watchdog job '{name}' log added to info files: {file_stack.name}")
|
|
303
|
+
return result.status == "ok" and result.returncode == 0
|
|
304
|
+
except Exception as e:
|
|
305
|
+
err_msg = f"watchdog submission exception: {str(e)}"
|
|
306
|
+
self.fail(err_msg)
|
|
307
|
+
return False
|
|
308
|
+
|
|
309
|
+
def _make_log_file(self):
|
|
310
|
+
"""
|
|
311
|
+
Create a log file from the cumulative log string and add it to info_files.
|
|
312
|
+
|
|
313
|
+
This internal method checks if `log_string` contains any messages. If it does,
|
|
314
|
+
it creates a `FileStack` from the string and appends it to the `info_files` list,
|
|
315
|
+
making it available as part of the task's results.
|
|
316
|
+
"""
|
|
317
|
+
if len(self.log_string) > 0:
|
|
318
|
+
log_file = FileStack.from_string(self.log_string, file_name="log.txt")
|
|
319
|
+
self.info_files.append(log_file)
|
|
320
|
+
self.info(f"Log string saved to info files as: {log_file.name}")
|
|
321
|
+
|
|
322
|
+
def fail(self, msg: str) -> "NodeRunner":
|
|
323
|
+
"""
|
|
324
|
+
Mark the task as failed and record an error message.
|
|
325
|
+
|
|
326
|
+
This method updates the task status to FAILED, sets the error message,
|
|
327
|
+
logs the error, and ensures the cumulative log file is created.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
msg (str): Error message describing the failure.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
NodeRunner: Self reference for chaining.
|
|
334
|
+
"""
|
|
335
|
+
self._make_log_file()
|
|
336
|
+
self.logger.error(
|
|
337
|
+
f"Task {self.name}: {msg} task_id: {self.task_id}",
|
|
338
|
+
stacklevel=2,
|
|
339
|
+
exc_info=True,
|
|
340
|
+
)
|
|
341
|
+
self.error_message = msg
|
|
342
|
+
self.status = TaskStatus.FAILED
|
|
343
|
+
return self
|
|
344
|
+
|
|
345
|
+
def succeed(self, msg: str = "") -> "NodeRunner":
|
|
346
|
+
"""
|
|
347
|
+
Mark the task as successfully completed.
|
|
348
|
+
|
|
349
|
+
This method updates the task status to COMPLETED, sets an optional
|
|
350
|
+
success message, logs the success, and ensures the cumulative log file is created.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
msg (str, optional): Success message. Defaults to "".
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
NodeRunner: Self reference for chaining.
|
|
357
|
+
"""
|
|
358
|
+
self._make_log_file()
|
|
359
|
+
self.info(f"succeeded {msg}")
|
|
360
|
+
self.message = msg
|
|
361
|
+
self.status = TaskStatus.COMPLETED
|
|
362
|
+
return self
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from typing import Any, Tuple, List, Union
|
|
2
|
+
from odmantic import ObjectId, Model
|
|
3
|
+
from simstack.core.simstack_result import SimstackResult
|
|
4
|
+
from simstack.models import BooleanData
|
|
5
|
+
from simstack.models.files import FileStack
|
|
6
|
+
|
|
7
|
+
from simstack.core.context import context
|
|
8
|
+
from simstack.models import ModelMapping
|
|
9
|
+
from simstack.models.file_list import FileListModel
|
|
10
|
+
from simstack.models.simstack_model import is_simstack_model
|
|
11
|
+
|
|
12
|
+
from simstack.models.named_data_reference import NamedDataReference
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
logger = logging.getLogger("process_results")
|
|
16
|
+
|
|
17
|
+
async def process_result_helper(
|
|
18
|
+
result: Union[SimstackResult,Model, bool, BooleanData], task_id: str = "NA"
|
|
19
|
+
) -> Tuple[List[NamedDataReference], List[Model]]:
|
|
20
|
+
"""
|
|
21
|
+
Computes the results_references and returns a List[Model].
|
|
22
|
+
It works if the result is a SimstackResult, a single Model or a bool.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
results_references: List[NamedDataReference] = []
|
|
26
|
+
result_models: List[Model] = []
|
|
27
|
+
|
|
28
|
+
if isinstance(result, bool) or isinstance(result, BooleanData) :
|
|
29
|
+
# Convert bool to BooleanData
|
|
30
|
+
if isinstance(result, bool):
|
|
31
|
+
boolean_data = BooleanData(value=result)
|
|
32
|
+
else:
|
|
33
|
+
boolean_data = result
|
|
34
|
+
result_model = await context.db.save(boolean_data)
|
|
35
|
+
result_models.append(result_model)
|
|
36
|
+
results_references.append(NamedDataReference.from_variable(
|
|
37
|
+
boolean_data,
|
|
38
|
+
variable_name="value",
|
|
39
|
+
task_id=task_id
|
|
40
|
+
))
|
|
41
|
+
|
|
42
|
+
return results_references, result_models
|
|
43
|
+
|
|
44
|
+
if isinstance(result, SimstackResult):
|
|
45
|
+
# check if there are files in the result
|
|
46
|
+
if len(result.files) > 0:
|
|
47
|
+
file_list_model = FileListModel()
|
|
48
|
+
# this goes into the results must be a model
|
|
49
|
+
for file_stack in result.files:
|
|
50
|
+
if file_stack:
|
|
51
|
+
if isinstance(file_stack, FileStack):
|
|
52
|
+
logger.info(f"Task task_id: {task_id} saving file: {file_stack.name} {file_stack.id}")
|
|
53
|
+
file_list_model.append(file_stack)
|
|
54
|
+
else:
|
|
55
|
+
logger.error(f"Task task_id: {task_id} cannot save file: FileStack expected but got {file_stack}")
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"Task task_id: {task_id} cannot save file: FileStack expected but got {type(file_stack)}"
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
logger.error(f"Task task_id: {task_id} saving file is NONE")
|
|
61
|
+
raise ValueError("saving file is NONE")
|
|
62
|
+
|
|
63
|
+
saved = await context.db.save(file_list_model)
|
|
64
|
+
results_references.append(NamedDataReference.from_variable(
|
|
65
|
+
saved,
|
|
66
|
+
variable_name="files",
|
|
67
|
+
task_id=task_id
|
|
68
|
+
))
|
|
69
|
+
result_models.append(saved)
|
|
70
|
+
|
|
71
|
+
extra = getattr(result, "__pydantic_extra__", {})
|
|
72
|
+
if extra is None:
|
|
73
|
+
extra = {}
|
|
74
|
+
for key, value in extra.items():
|
|
75
|
+
# gather only non-callable public attributes
|
|
76
|
+
if (
|
|
77
|
+
not key.startswith("_")
|
|
78
|
+
and not callable(value)
|
|
79
|
+
and is_simstack_model(value)
|
|
80
|
+
):
|
|
81
|
+
if isinstance(value, Model):
|
|
82
|
+
result_model = await context.db.save(value)
|
|
83
|
+
result_models.append(result_model)
|
|
84
|
+
results_references.append(NamedDataReference.from_variable(
|
|
85
|
+
result_model,
|
|
86
|
+
variable_name=key,
|
|
87
|
+
task_id=task_id
|
|
88
|
+
))
|
|
89
|
+
else:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"task_id: {task_id} cannot save model: {key} is not a model"
|
|
92
|
+
)
|
|
93
|
+
elif is_simstack_model(result) and isinstance(result, Model):
|
|
94
|
+
result_model = await context.db.save(result)
|
|
95
|
+
result_models.append(result_model)
|
|
96
|
+
results_references.append(NamedDataReference.from_variable(
|
|
97
|
+
result_model,
|
|
98
|
+
variable_name=result.__class__.__name__,
|
|
99
|
+
task_id=task_id
|
|
100
|
+
))
|
|
101
|
+
|
|
102
|
+
return results_references, result_models
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from simstack.core.artifacts import ArtifactArguments, create_artifacts
|
|
4
|
+
from simstack.core.context import context
|
|
5
|
+
from simstack.core.definitions import TaskStatus
|
|
6
|
+
from simstack.core.node import node_from_database
|
|
7
|
+
from simstack.models import NodeRegistry, ArtifactModel
|
|
8
|
+
from simstack.models.charts_artifact import ChartArtifactModel
|
|
9
|
+
from simstack.models.table_artifact import TableArtifactModel
|
|
10
|
+
from simstack.util.importer import import_class
|
|
11
|
+
from simstack.util.importer import import_function
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("recompute_artifacts")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def recompute_artifacts(node_registry: NodeRegistry):
|
|
17
|
+
"""
|
|
18
|
+
Recomputes artifacts for a node and all its children recursively.
|
|
19
|
+
|
|
20
|
+
First processes all children nodes recursively, then removes and recomputes
|
|
21
|
+
the current node's artifacts.
|
|
22
|
+
|
|
23
|
+
:param node_registry: The registry entry for the node to process
|
|
24
|
+
:type node_registry: NodeRegistry
|
|
25
|
+
"""
|
|
26
|
+
# Create Node from the registry
|
|
27
|
+
|
|
28
|
+
db = context.db
|
|
29
|
+
node = await node_from_database(node_registry)
|
|
30
|
+
if node is None:
|
|
31
|
+
logger.error(f"Failed to create node from registry task_id: {node_registry.id}")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
if node.status != TaskStatus.COMPLETED:
|
|
35
|
+
logger.error(f"Cannot recompute artifacts for task_id: {node_registry.id} name: {node.name} status: {node.status}")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# Find all children of this node
|
|
39
|
+
children = await db.find(NodeRegistry, NodeRegistry.parent_ids.in_([node_registry.id]))
|
|
40
|
+
|
|
41
|
+
# Recursively recompute artifacts for all children first
|
|
42
|
+
for child_registry in children:
|
|
43
|
+
await recompute_artifacts(child_registry)
|
|
44
|
+
|
|
45
|
+
# Remove current node's artifacts
|
|
46
|
+
table_artifacts = await db.find(
|
|
47
|
+
TableArtifactModel, TableArtifactModel.parent_id == node_registry.id
|
|
48
|
+
)
|
|
49
|
+
for table_artifact in table_artifacts:
|
|
50
|
+
await db.delete(table_artifact)
|
|
51
|
+
|
|
52
|
+
chart_artifacts = await db.find(
|
|
53
|
+
ChartArtifactModel, ChartArtifactModel.parent_id == node_registry.id
|
|
54
|
+
)
|
|
55
|
+
for chart_artifact in chart_artifacts:
|
|
56
|
+
await db.delete(chart_artifact)
|
|
57
|
+
|
|
58
|
+
if node_registry.artifact_ids:
|
|
59
|
+
logger.info(f"Removing {len(node_registry.artifact_ids)} artifacts for node {node_registry.id}")
|
|
60
|
+
|
|
61
|
+
# Delete artifacts from the database
|
|
62
|
+
for artifact_id in node_registry.artifact_ids:
|
|
63
|
+
instance = await db.find_one(
|
|
64
|
+
ArtifactModel, ArtifactModel.id == artifact_id
|
|
65
|
+
)
|
|
66
|
+
if instance:
|
|
67
|
+
await db.delete(instance)
|
|
68
|
+
else:
|
|
69
|
+
logger.warning(
|
|
70
|
+
f"task_id: {node_registry.id} Failed to delete artifact {artifact_id} from database"
|
|
71
|
+
)
|
|
72
|
+
node_registry.artifact_ids = []
|
|
73
|
+
|
|
74
|
+
# Recompute artifacts for this node
|
|
75
|
+
if node_registry.status == TaskStatus.COMPLETED:
|
|
76
|
+
logger.info(f"Recomputing artifacts for node {node_registry.id} {node_registry.name}")
|
|
77
|
+
|
|
78
|
+
# Load the result to create new artifacts
|
|
79
|
+
result = await node.load_results()
|
|
80
|
+
if result is not None:
|
|
81
|
+
artifact_arguments = ArtifactArguments(result, node_registry.id)
|
|
82
|
+
# Reconstruct the function arguments for artifact creation
|
|
83
|
+
args = []
|
|
84
|
+
for ref in node_registry.input_references:
|
|
85
|
+
model = await import_class(ref.variable_mapping, context.db)
|
|
86
|
+
arg = await db.find_one(model, model.id == ref.reference)
|
|
87
|
+
if arg:
|
|
88
|
+
args.append(arg)
|
|
89
|
+
|
|
90
|
+
# Get the function for artifact creation
|
|
91
|
+
wrapped_func = await import_function(node_registry.func_mapping, context.db)
|
|
92
|
+
func = (
|
|
93
|
+
wrapped_func
|
|
94
|
+
if not hasattr(wrapped_func, "_inner")
|
|
95
|
+
else wrapped_func._inner
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Create node kwargs similar to run_local
|
|
99
|
+
node_kwargs = {
|
|
100
|
+
"parent_id": node_registry.id,
|
|
101
|
+
"task_id": node_registry.id,
|
|
102
|
+
"call_path": node_registry.call_path,
|
|
103
|
+
"parent_parameters": node_registry.parameters,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
artifact_arguments.add_attributes(func, *args, **node_kwargs)
|
|
107
|
+
node_registry.artifact_ids = await create_artifacts(artifact_arguments, node_registry)
|
|
108
|
+
|
|
109
|
+
# Save the updated registry
|
|
110
|
+
await db.save(node_registry)
|
|
111
|
+
logger.info(
|
|
112
|
+
f"Recomputed {len(node_registry.artifact_ids)} artifacts for node {node_registry.id}"
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
logger.warning(
|
|
116
|
+
f"Node {node_registry.id} is not completed, cannot recompute artifacts"
|
|
117
|
+
)
|