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,125 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, List
|
|
8
|
+
|
|
9
|
+
from odmantic import Model, ObjectId
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from simstack.core.context import context
|
|
13
|
+
from simstack.models import NodeRegistry, NamedDataReference
|
|
14
|
+
from simstack.util.importer import import_class
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("generate_test")
|
|
17
|
+
|
|
18
|
+
async def load_models(references: List[NamedDataReference]) -> Dict[str, Any]:
|
|
19
|
+
"""Load models from the database based on references."""
|
|
20
|
+
result = {}
|
|
21
|
+
db = context.db
|
|
22
|
+
for ref in references:
|
|
23
|
+
try:
|
|
24
|
+
model_cls = await import_class(ref.variable_mapping, db)
|
|
25
|
+
if not model_cls:
|
|
26
|
+
raise RuntimeError(f"Could not import class {ref.variable_mapping}")
|
|
27
|
+
|
|
28
|
+
obj = await db.find_one(model_cls, model_cls.id == ref.reference)
|
|
29
|
+
if obj is None:
|
|
30
|
+
raise RuntimeError(
|
|
31
|
+
f"Object {ref.reference} not found in {ref.variable_mapping}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Preserve the argument/result name captured at execution time.
|
|
35
|
+
key = ref.variable_name
|
|
36
|
+
if not key:
|
|
37
|
+
from simstack.models import ModelMapping
|
|
38
|
+
|
|
39
|
+
model_mapping = await db.find_one(
|
|
40
|
+
ModelMapping,
|
|
41
|
+
ModelMapping.mapping == ref.variable_mapping,
|
|
42
|
+
)
|
|
43
|
+
key = model_mapping.name if model_mapping else ref.variable_mapping
|
|
44
|
+
|
|
45
|
+
if key in result:
|
|
46
|
+
key = f"{key}_{str(ref.reference)}"
|
|
47
|
+
result[key] = obj
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.exception(f"Error loading {ref.variable_mapping} {ref.reference}: {e}")
|
|
50
|
+
raise RuntimeError(
|
|
51
|
+
f"Failed to load {ref.variable_mapping} {ref.reference}"
|
|
52
|
+
) from e
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
def serialize_models(models: Dict[str, Any]) -> str:
|
|
56
|
+
"""Serialize models to a JSON string."""
|
|
57
|
+
data = {}
|
|
58
|
+
for key, obj in models.items():
|
|
59
|
+
if isinstance(obj, (Model, BaseModel)):
|
|
60
|
+
data[key] = obj.model_dump()
|
|
61
|
+
else:
|
|
62
|
+
data[key] = str(obj)
|
|
63
|
+
return json.dumps(data, indent=4, default=str)
|
|
64
|
+
|
|
65
|
+
async def generate_test(node_id: str, target_base: Path):
|
|
66
|
+
"""Generate a test case for a given node ID."""
|
|
67
|
+
db = context.db
|
|
68
|
+
registry_entry = await db.find_one(NodeRegistry, NodeRegistry.id == ObjectId(node_id))
|
|
69
|
+
if not registry_entry:
|
|
70
|
+
raise ValueError(f"NodeRegistry entry with ID {node_id} not found.")
|
|
71
|
+
|
|
72
|
+
node_name = registry_entry.name
|
|
73
|
+
arg_hash = registry_entry.arg_hash
|
|
74
|
+
|
|
75
|
+
# Target directory: target/node_name/arg_hash
|
|
76
|
+
target_dir = target_base / node_name / arg_hash
|
|
77
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
78
|
+
|
|
79
|
+
# Source directory: workdir/node_name/id
|
|
80
|
+
workdir = context.config.workdir
|
|
81
|
+
source_dir = workdir / node_name / str(registry_entry.id)
|
|
82
|
+
|
|
83
|
+
if source_dir.exists() and source_dir.is_dir():
|
|
84
|
+
print(f"Copying files from {source_dir} to {target_dir}")
|
|
85
|
+
# Copy all files from source to target
|
|
86
|
+
for item in source_dir.iterdir():
|
|
87
|
+
if item.is_file():
|
|
88
|
+
shutil.copy2(item, target_dir / item.name)
|
|
89
|
+
elif item.is_dir():
|
|
90
|
+
shutil.copytree(item, target_dir / item.name, dirs_exist_ok=True)
|
|
91
|
+
else:
|
|
92
|
+
print(f"Source directory {source_dir} does not exist. Skipping file copy.")
|
|
93
|
+
|
|
94
|
+
# Load and serialize inputs
|
|
95
|
+
print(f"Serializing inputs for {node_id}")
|
|
96
|
+
inputs = await load_models(registry_entry.input_references)
|
|
97
|
+
with open(target_dir / "inputs.json", "w") as f:
|
|
98
|
+
f.write(serialize_models(inputs))
|
|
99
|
+
|
|
100
|
+
# Load and serialize outputs
|
|
101
|
+
print(f"Serializing outputs for {node_id}")
|
|
102
|
+
outputs = await load_models(registry_entry.results_references)
|
|
103
|
+
with open(target_dir / "outputs.json", "w") as f:
|
|
104
|
+
f.write(serialize_models(outputs))
|
|
105
|
+
|
|
106
|
+
print(f"Test generation for node {node_id} completed at {target_dir}")
|
|
107
|
+
|
|
108
|
+
async def async_main():
|
|
109
|
+
parser = argparse.ArgumentParser(description="Generate a test case from a node execution.")
|
|
110
|
+
parser.add_argument("--id", required=True, help="ID of the NodeRegistry entry")
|
|
111
|
+
parser.add_argument("--target", default=str(Path.cwd() / "tests"), help="Target base directory for tests")
|
|
112
|
+
parser.add_argument("--resource", default="self", help="Resource to use for database connection")
|
|
113
|
+
|
|
114
|
+
args = parser.parse_args()
|
|
115
|
+
|
|
116
|
+
await context.initialize(resource=args.resource)
|
|
117
|
+
|
|
118
|
+
await generate_test(args.id, Path(args.target))
|
|
119
|
+
|
|
120
|
+
def generate_test_main():
|
|
121
|
+
logging.basicConfig(level=logging.INFO)
|
|
122
|
+
asyncio.run(async_main())
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
generate_test_main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from simstack.core.node import node
|
|
4
|
+
from simstack.models.files import FileStack, FileGetterArgs
|
|
5
|
+
from simstack.models.parameters import Resource
|
|
6
|
+
|
|
7
|
+
@node
|
|
8
|
+
def get_file(file_getter_args: FileGetterArgs) -> FileStack:
|
|
9
|
+
"""
|
|
10
|
+
Retrieves a file from a given source and saves it to the specified target path.
|
|
11
|
+
|
|
12
|
+
This function is responsible for handling the retrieval of files from a
|
|
13
|
+
FileStack source and storing them to a local resource's target location.
|
|
14
|
+
It will update the FileStack
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
file_getter_args (FileGetterArgs): The arguments for the file retrieval operation.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
FileStack: The modified FileStack with the local resource.
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
NotImplementedError: If the function is not yet implemented.
|
|
24
|
+
"""
|
|
25
|
+
raise NotImplementedError("get_file is not implemented yet.")
|
|
26
|
+
return False
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import List, Callable, Any, Optional
|
|
3
|
+
|
|
4
|
+
from odmantic import Model
|
|
5
|
+
|
|
6
|
+
from simstack.core.context import context
|
|
7
|
+
from simstack.core.definitions import TaskStatus
|
|
8
|
+
from simstack.core.hash import complex_hash_function
|
|
9
|
+
from simstack.core.simstack_result import SimstackResult
|
|
10
|
+
from simstack.core.node_runner import NodeRunner
|
|
11
|
+
from simstack.core.process_results import process_result_helper
|
|
12
|
+
from simstack.models import DataSetMetadata, DataSet, DataSetSection, BooleanData, StringData
|
|
13
|
+
|
|
14
|
+
class MassRunner(NodeRunner):
|
|
15
|
+
def __init__(self, node: Callable[..., Any],max_concurrency: Optional[int] = None, **kwargs):
|
|
16
|
+
super().__init__(kwargs["node_runner"].name, kwargs["node_runner"].task_id, kwargs["node_runner"].logger )
|
|
17
|
+
node_runner = kwargs["node_runner"]
|
|
18
|
+
arg_hash = kwargs["arg_hash"]
|
|
19
|
+
self._kwargs = kwargs.copy()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
database_metadata = DataSetMetadata(
|
|
23
|
+
field_name=node_runner.name,
|
|
24
|
+
data={
|
|
25
|
+
"arg_hash": str(arg_hash),
|
|
26
|
+
"task_id": str(node_runner.task_id),
|
|
27
|
+
"call_path": getattr(node_runner, "call_path", "NA"),
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
self._max_concurrency = max_concurrency
|
|
32
|
+
|
|
33
|
+
self.dataset = DataSet(
|
|
34
|
+
field_name=f"{node_runner.name}.{self.task_id}",
|
|
35
|
+
metadata=database_metadata
|
|
36
|
+
)
|
|
37
|
+
self._existing_dataset = None
|
|
38
|
+
self._failure = False
|
|
39
|
+
self.dataset["tasks"] = DataSetSection()
|
|
40
|
+
self._tasks = []
|
|
41
|
+
self._node = node
|
|
42
|
+
self._semaphore = asyncio.Semaphore(max_concurrency) if max_concurrency else None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def recover_orphaned_datasets(self):
|
|
46
|
+
previous_node_name = self._kwargs.get("previous_node_name", None)
|
|
47
|
+
previous_task_id = self._kwargs.get("previous_task_id", None)
|
|
48
|
+
if previous_node_name and previous_task_id:
|
|
49
|
+
db = context.db
|
|
50
|
+
previous_dataset_field_name = f"{previous_node_name}.{previous_task_id}"
|
|
51
|
+
existing_dataset = await db.find_one(DataSet, DataSet.field_name == previous_dataset_field_name)
|
|
52
|
+
if existing_dataset:
|
|
53
|
+
self._existing_dataset = existing_dataset
|
|
54
|
+
self.info(f"Found existing dataset: {previous_dataset_field_name}")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
async def _run_node(self, args: List[Model]):
|
|
58
|
+
await self.recover_orphaned_datasets()
|
|
59
|
+
|
|
60
|
+
import inspect
|
|
61
|
+
sig = inspect.signature(self._node)
|
|
62
|
+
param_names = list(sig.parameters.keys())
|
|
63
|
+
|
|
64
|
+
arg_hashes = [complex_hash_function(arg) for arg in args]
|
|
65
|
+
combined_arg_hash = complex_hash_function(arg_hashes)
|
|
66
|
+
|
|
67
|
+
if self._existing_dataset and combined_arg_hash in self._existing_dataset["tasks"].data:
|
|
68
|
+
row = self._existing_dataset["tasks"].get_item(combined_arg_hash)
|
|
69
|
+
success = row.get("success", None)
|
|
70
|
+
if success is not None:
|
|
71
|
+
success = success.value
|
|
72
|
+
if success:
|
|
73
|
+
self.info(f"Skipping node with arg_hash: {combined_arg_hash}")
|
|
74
|
+
self.dataset["tasks"].add_row(row, combined_arg_hash)
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
self.info(f"Running node with arg_hash: {combined_arg_hash}")
|
|
78
|
+
|
|
79
|
+
task_dict = {}
|
|
80
|
+
|
|
81
|
+
for i, m in enumerate(args):
|
|
82
|
+
if i < len(param_names):
|
|
83
|
+
task_dict[f"arg_{param_names[i]}"] = m
|
|
84
|
+
else:
|
|
85
|
+
task_dict[f"arg_{i}"] = m
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
if self._semaphore:
|
|
89
|
+
async with self._semaphore:
|
|
90
|
+
if asyncio.iscoroutinefunction(self._node):
|
|
91
|
+
result = await self._node(*args, **self._kwargs)
|
|
92
|
+
else:
|
|
93
|
+
result = self._node(*args, **self._kwargs)
|
|
94
|
+
else:
|
|
95
|
+
if asyncio.iscoroutinefunction(self._node):
|
|
96
|
+
result = await self._node(*args, **self._kwargs)
|
|
97
|
+
else:
|
|
98
|
+
result = self._node(*args, **self._kwargs)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
self._failure = True
|
|
101
|
+
self.error(f"Error running node: {e}")
|
|
102
|
+
task_dict["success"] = BooleanData(value=False)
|
|
103
|
+
task_dict["error"] = StringData(field_name="error_message",value=str(e))
|
|
104
|
+
self.dataset["tasks"].add_row(task_dict, name=combined_arg_hash)
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
if result is None:
|
|
108
|
+
task_dict["success"] = BooleanData(value=False)
|
|
109
|
+
elif isinstance(result, bool):
|
|
110
|
+
task_dict["success"] = BooleanData(value=result)
|
|
111
|
+
elif isinstance(result, (SimstackResult, Model)):
|
|
112
|
+
references, models = await process_result_helper(result, self.task_id)
|
|
113
|
+
if isinstance(result, SimstackResult):
|
|
114
|
+
if result.status != TaskStatus.COMPLETED:
|
|
115
|
+
self._failure = True
|
|
116
|
+
task_dict["success"] = BooleanData(value=result.status == TaskStatus.COMPLETED)
|
|
117
|
+
else:
|
|
118
|
+
task_dict["success"] = BooleanData(value=True)
|
|
119
|
+
if references:
|
|
120
|
+
for reference, model in zip(references, models):
|
|
121
|
+
task_dict[f"result_{reference.variable_name}"] = model
|
|
122
|
+
elif isinstance(result, (list, tuple)) and all(isinstance(m, Model) for m in result):
|
|
123
|
+
task_dict["success"] = BooleanData(value=True)
|
|
124
|
+
for i, m in enumerate(result):
|
|
125
|
+
task_dict[f"result_{i}"] = m
|
|
126
|
+
|
|
127
|
+
self.dataset["tasks"].add_row(task_dict, name=combined_arg_hash)
|
|
128
|
+
return result
|
|
129
|
+
|
|
130
|
+
def create_tasks(self, *args: Model):
|
|
131
|
+
|
|
132
|
+
task = asyncio.create_task(self._run_node(list(args)))
|
|
133
|
+
self._tasks.append(task)
|
|
134
|
+
return task
|
|
135
|
+
|
|
136
|
+
async def __aenter__(self):
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
140
|
+
if self._tasks:
|
|
141
|
+
await asyncio.gather(*self._tasks)
|
|
142
|
+
|
|
143
|
+
db = context.db
|
|
144
|
+
await self.dataset.save(db)
|
|
145
|
+
self.info("MassRunner saving dataset")
|
|
146
|
+
|
|
147
|
+
#
|
|
148
|
+
# # Check if any task failed
|
|
149
|
+
# for idx in range(len(self.dataset['tasks'].data)):
|
|
150
|
+
# item = await self.dataset['tasks'].get_item(idx)
|
|
151
|
+
# if 'success' in item and hasattr(item['success'], 'value') and not item['success'].value:
|
|
152
|
+
# return self.fail("One or more tasks failed")
|
|
153
|
+
|
|
154
|
+
return self.succeed()
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List, Callable, TypeVar, Generic, Dict, Any, AsyncGenerator, Union
|
|
4
|
+
|
|
5
|
+
from odmantic import Model, Field, ObjectId, Reference
|
|
6
|
+
|
|
7
|
+
from simstack.core.asnyc_helper import async_helper
|
|
8
|
+
from simstack.core.context import context
|
|
9
|
+
from simstack.core.node import node
|
|
10
|
+
from simstack.models import (
|
|
11
|
+
simstack_model,
|
|
12
|
+
ModelMapping,
|
|
13
|
+
BinaryOperationInput,
|
|
14
|
+
FloatData,
|
|
15
|
+
)
|
|
16
|
+
from simstack.util.async_zip_utils import async_zip
|
|
17
|
+
from simstack.util.importer import import_function, import_class
|
|
18
|
+
from simstack.util.make_table import make_table_entries
|
|
19
|
+
|
|
20
|
+
# Define your prefix
|
|
21
|
+
PREFIX = "[ParallelExecutor]"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@node
|
|
25
|
+
def multiple_executor_adder(
|
|
26
|
+
arg1: BinaryOperationInput, arg2: BinaryOperationInput
|
|
27
|
+
) -> BinaryOperationInput:
|
|
28
|
+
return BinaryOperationInput(arg1=arg1.arg1 + arg2.arg1, arg2=arg1.arg2 + arg2.arg2)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PrefixFilter(logging.Filter):
|
|
32
|
+
def filter(self, record):
|
|
33
|
+
record.msg = f"{PREFIX} {record.msg}"
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Set up the logger
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
logger.addFilter(PrefixFilter())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@async_helper
|
|
43
|
+
async def get_mappings(func: Callable[[Model], Model]):
|
|
44
|
+
"""
|
|
45
|
+
Decorator to map a function to a model.
|
|
46
|
+
"""
|
|
47
|
+
node_model = await context.db.load_node_model_by_name(func.__name__)
|
|
48
|
+
if node_model is None:
|
|
49
|
+
raise ValueError(f"Node model {func.__name__} not found in database.")
|
|
50
|
+
|
|
51
|
+
# Get return type annotation from function
|
|
52
|
+
return_type = func.__annotations__.get("return")
|
|
53
|
+
if return_type is None:
|
|
54
|
+
raise ValueError(f"Function {func.__name__} must have return type annotation")
|
|
55
|
+
|
|
56
|
+
result_model = await context.db.find_one(
|
|
57
|
+
ModelMapping, ModelMapping.name == return_type.__name__
|
|
58
|
+
)
|
|
59
|
+
if result_model is None:
|
|
60
|
+
raise ValueError(f"Result model {return_type.__name__} not found in database.")
|
|
61
|
+
|
|
62
|
+
return node_model.function_mapping, node_model.input_mappings, result_model.mapping
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
T = TypeVar("T", bound=Model)
|
|
66
|
+
R = TypeVar("R", bound=Model)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _prefix_dict_keys(original_dict, prefix):
|
|
70
|
+
"""Helper method to prefix dictionary keys."""
|
|
71
|
+
return {f"{prefix}{key}": value for key, value in original_dict.items()}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def _get_formatted_column_defs(mapping, prefix):
|
|
75
|
+
"""Get column definitions for a model with proper field prefixes."""
|
|
76
|
+
model_class = await import_class(mapping)
|
|
77
|
+
column_defs = model_class.make_column_defs()
|
|
78
|
+
|
|
79
|
+
# Add prefix to field names
|
|
80
|
+
for col in column_defs:
|
|
81
|
+
if "field" in col:
|
|
82
|
+
col["field"] = f"{prefix}{col['field']}"
|
|
83
|
+
|
|
84
|
+
# Filter out id fields
|
|
85
|
+
return [col for col in column_defs if col.get("field") != f"{prefix}id"]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@simstack_model
|
|
89
|
+
class MultipleExecutorInput(Model, Generic[T]):
|
|
90
|
+
"""
|
|
91
|
+
Represents a MultipleExecutor which handles multiple calculations in parallel and aggregates the results.
|
|
92
|
+
# TODO store the inputs as ids not in memory
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
function_mapping: str
|
|
96
|
+
input_mappings: List[str]
|
|
97
|
+
result_mapping: str
|
|
98
|
+
timeout: int = 600
|
|
99
|
+
inputs_data: List[List[ObjectId]] = Field(default_factory=list)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def from_function(cls, func: Callable[[T], R]):
|
|
103
|
+
function_mapping, input_mappings, result_mapping = get_mappings(func)
|
|
104
|
+
print(
|
|
105
|
+
f"Function mapping: {function_mapping}, Input mappings: {input_mappings}, Result mapping: {result_mapping}"
|
|
106
|
+
)
|
|
107
|
+
return cls(
|
|
108
|
+
function_mapping=function_mapping,
|
|
109
|
+
input_mappings=input_mappings,
|
|
110
|
+
result_mapping=result_mapping,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
async def input_generator(self) -> AsyncGenerator[List[Model], None]:
|
|
114
|
+
"""Deserialize the input models"""
|
|
115
|
+
input_model_classes = [
|
|
116
|
+
await import_class(input_mapping) for input_mapping in self.input_mappings
|
|
117
|
+
]
|
|
118
|
+
for input_item in self.inputs_data:
|
|
119
|
+
input_models = [
|
|
120
|
+
await context.db.find_one(
|
|
121
|
+
input_model_class, input_model_class.id == model_id
|
|
122
|
+
)
|
|
123
|
+
for input_model_class, model_id in zip(input_model_classes, input_item)
|
|
124
|
+
]
|
|
125
|
+
# if one models is None: fail
|
|
126
|
+
if any(model is None for model in input_models):
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"One or more input models not found in database for input IDs: {input_item}"
|
|
129
|
+
)
|
|
130
|
+
yield input_models
|
|
131
|
+
|
|
132
|
+
async def append(self, input_models: Union[T, List[T]], with_save=True):
|
|
133
|
+
"""Add the inputs for a single function call"""
|
|
134
|
+
if isinstance(input_models, Model):
|
|
135
|
+
input_models = [input_models]
|
|
136
|
+
if with_save:
|
|
137
|
+
for input_model in input_models:
|
|
138
|
+
await context.db.save(input_model)
|
|
139
|
+
input_ids = [input_model.id for input_model in input_models]
|
|
140
|
+
self.inputs_data.append(input_ids)
|
|
141
|
+
|
|
142
|
+
async def execute(self, **kwargs) -> "MultipleExecutorOutput":
|
|
143
|
+
task_id = kwargs.get("task_id", "NA")
|
|
144
|
+
global PREFIX
|
|
145
|
+
if task_id != "NA":
|
|
146
|
+
PREFIX = f"[ParallelExecutor task_id: {task_id}]"
|
|
147
|
+
|
|
148
|
+
# Prepare all jobs to run concurrently
|
|
149
|
+
tasks = []
|
|
150
|
+
result_ids = [None] * len(self.inputs_data)
|
|
151
|
+
kwargs["parent_id"] = task_id
|
|
152
|
+
try:
|
|
153
|
+
func = await import_function(self.function_mapping, context.db, task_id)
|
|
154
|
+
i = 0
|
|
155
|
+
async for task_input in self.input_generator():
|
|
156
|
+
# Create an async task that calls our async wrapper
|
|
157
|
+
task = asyncio.create_task(func(*task_input, **kwargs))
|
|
158
|
+
# Store index for later reference
|
|
159
|
+
task.index = i
|
|
160
|
+
tasks.append(task)
|
|
161
|
+
i += 1
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"Error preparing tasks: {str(e)}")
|
|
164
|
+
|
|
165
|
+
# Wait for all tasks with timeout
|
|
166
|
+
done, pending = await asyncio.wait(tasks, timeout=self.timeout)
|
|
167
|
+
|
|
168
|
+
# Process completed tasks
|
|
169
|
+
for task in done:
|
|
170
|
+
try:
|
|
171
|
+
task_result = task.result()
|
|
172
|
+
result_ids[task.index] = task_result.id
|
|
173
|
+
logger.info(f"Successfully processed task {task.index}")
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"Error processing task {task.index}: {str(e)}")
|
|
176
|
+
|
|
177
|
+
# Log information about pending tasks
|
|
178
|
+
if pending:
|
|
179
|
+
logger.error(f"{len(pending)} tasks didn't complete within the timeout")
|
|
180
|
+
for task in pending:
|
|
181
|
+
logger.error(
|
|
182
|
+
f"Task at index {task.index} didn't complete within timeout"
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
output = MultipleExecutorOutput(
|
|
186
|
+
executor_input=self,
|
|
187
|
+
result_mapping=self.result_mapping,
|
|
188
|
+
result_ids=result_ids,
|
|
189
|
+
)
|
|
190
|
+
logger.info("finished")
|
|
191
|
+
return output
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def ui_base_schema(cls):
|
|
195
|
+
return {"inputs_data": {"ui:widget": "hidden"}}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@simstack_model
|
|
199
|
+
class MultipleExecutorOutput(Model, Generic[T, R]):
|
|
200
|
+
"""
|
|
201
|
+
Represents the output of a MultipleExecutor.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
executor_input: MultipleExecutorInput = Reference()
|
|
205
|
+
result_mapping: str # there is one result: either a Model or a SimstackResult
|
|
206
|
+
result_ids: List[Union[ObjectId, None]] = Field(default_factory=list)
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def json_schema(cls):
|
|
210
|
+
return {
|
|
211
|
+
"type": "object",
|
|
212
|
+
"properties": {"results": {"type": "array", "items": {"type": "object"}}},
|
|
213
|
+
"required": ["result_mapping", "results"],
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@async_helper
|
|
217
|
+
async def custom_model_dump(self) -> Dict[str, Any]:
|
|
218
|
+
"""
|
|
219
|
+
Retrieve all results and return them as a list of dictionaries.
|
|
220
|
+
"""
|
|
221
|
+
results = []
|
|
222
|
+
async for result in self.result_generator():
|
|
223
|
+
if result is not None:
|
|
224
|
+
results.append(result.model_dump())
|
|
225
|
+
else:
|
|
226
|
+
results.append(None)
|
|
227
|
+
print(f"{PREFIX} custom_model_dump: {results}")
|
|
228
|
+
return {"result": results}
|
|
229
|
+
|
|
230
|
+
async def result_generator(self) -> AsyncGenerator[Union[R, None], None]:
|
|
231
|
+
"""Deserialize the result models"""
|
|
232
|
+
result_model_class = await import_class(self.result_mapping)
|
|
233
|
+
for result_id in self.result_ids:
|
|
234
|
+
if result_id is not None:
|
|
235
|
+
result_model = await context.db.find_one(
|
|
236
|
+
result_model_class, result_model_class.id == result_id
|
|
237
|
+
)
|
|
238
|
+
if result_model is not None:
|
|
239
|
+
yield result_model
|
|
240
|
+
else:
|
|
241
|
+
yield None
|
|
242
|
+
else:
|
|
243
|
+
yield None
|
|
244
|
+
|
|
245
|
+
@async_helper
|
|
246
|
+
async def make_table(self):
|
|
247
|
+
"""
|
|
248
|
+
Create a table representation of the results with formatted columns.
|
|
249
|
+
Returns a dictionary with table data and column definitions.
|
|
250
|
+
"""
|
|
251
|
+
# Get inputs and results data
|
|
252
|
+
inputs = await self.get_inputs()
|
|
253
|
+
results = await self.get_results()
|
|
254
|
+
|
|
255
|
+
# Create tables for inputs and results
|
|
256
|
+
inputs_table = make_table_entries(inputs)
|
|
257
|
+
results_table = make_table_entries(results)
|
|
258
|
+
|
|
259
|
+
# Merge data based on finished indices
|
|
260
|
+
merged_entries = []
|
|
261
|
+
for i, input_entry in enumerate(inputs_table["rowData"]):
|
|
262
|
+
entry = {**input_entry}
|
|
263
|
+
if i in self.finished_indices:
|
|
264
|
+
result_idx = self.finished_indices.index(i)
|
|
265
|
+
entry.update(results_table["rowData"][result_idx])
|
|
266
|
+
# TODO find all artifacts to this result and add the data to the entry
|
|
267
|
+
merged_entries.append(entry)
|
|
268
|
+
|
|
269
|
+
# Combine column definitions
|
|
270
|
+
merged_columns = inputs_table["columnDefs"] + results_table["columnDefs"]
|
|
271
|
+
|
|
272
|
+
return {"rowData": merged_entries, "columnDefs": merged_columns}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@node
|
|
276
|
+
async def multiple_executor(
|
|
277
|
+
executor: MultipleExecutorInput, **kwargs
|
|
278
|
+
) -> MultipleExecutorOutput:
|
|
279
|
+
return await executor.execute(**kwargs)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@node
|
|
283
|
+
async def async_adder(arg: BinaryOperationInput, **kwargs) -> FloatData:
|
|
284
|
+
return FloatData(value=arg.arg1 + arg.arg2)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
async def main():
|
|
288
|
+
context.initialize()
|
|
289
|
+
executor_input = MultipleExecutorInput.from_function(async_adder)
|
|
290
|
+
# Create a list of input models
|
|
291
|
+
inputs = [BinaryOperationInput(arg1=2, arg2=value) for value in range(1, 3)]
|
|
292
|
+
|
|
293
|
+
# Append the input models to the executor
|
|
294
|
+
for input_model in inputs:
|
|
295
|
+
await executor_input.append(input_model)
|
|
296
|
+
|
|
297
|
+
executor_output = await multiple_executor(executor_input)
|
|
298
|
+
|
|
299
|
+
print("Results: ", executor_output)
|
|
300
|
+
async for item1, item2 in async_zip(
|
|
301
|
+
executor_input.input_generator(), executor_output.result_generator()
|
|
302
|
+
):
|
|
303
|
+
print(f"Input: {item1}, Result: {item2}")
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
if __name__ == "__main__":
|
|
307
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from simstack.core.node import node
|
|
2
|
+
from simstack.models import StringData
|
|
3
|
+
from simstack.core.context import context
|
|
4
|
+
|
|
5
|
+
@node
|
|
6
|
+
def switch_git(branch: StringData, **kwargs) -> bool:
|
|
7
|
+
node_runner = kwargs["node_runner"]
|
|
8
|
+
|
|
9
|
+
project_root = context.project_root.resolve()
|
|
10
|
+
|
|
11
|
+
cmd = f"git stash && git pull --recurse-submodules && git checkout {branch.value}"
|
|
12
|
+
|
|
13
|
+
node_runner.run_command("git_switch", cmd, str(project_root))
|
|
14
|
+
|
|
15
|
+
return node_runner.success
|
|
16
|
+
|