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
simstack/util/db.py
ADDED
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Iterable, List, Optional, TypeVar, Union
|
|
5
|
+
|
|
6
|
+
from bson import ObjectId
|
|
7
|
+
from motor.motor_asyncio import AsyncIOMotorClient
|
|
8
|
+
from odmantic import AIOEngine
|
|
9
|
+
from odmantic import Model, EmbeddedModel
|
|
10
|
+
|
|
11
|
+
from simstack.core.definitions import DBType, TaskStatus
|
|
12
|
+
from simstack.models.node_registry import NodeRegistry
|
|
13
|
+
from simstack.util.database_information import DatabaseInformation
|
|
14
|
+
|
|
15
|
+
# from simstack.util.importer import import_class
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T", bound=Model)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Database:
|
|
23
|
+
"""Server-owned database facade.
|
|
24
|
+
|
|
25
|
+
The server should depend on this object instead of reaching into SimStack
|
|
26
|
+
core engine abstractions directly. The underlying persistence primitive is
|
|
27
|
+
the plain ODMantic engine; SimStack-specific behavior lives in this facade.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
client: AsyncIOMotorClient | None = None,
|
|
34
|
+
database_name: str | None = None,
|
|
35
|
+
connection_string: str | None = None,
|
|
36
|
+
engine: Any | None = None,
|
|
37
|
+
db_type: DBType | None = None,
|
|
38
|
+
server_url: str | None = None,
|
|
39
|
+
server_token: str | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
if engine is None:
|
|
42
|
+
if client is None or database_name is None:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"client and database_name are required when engine is not provided"
|
|
45
|
+
)
|
|
46
|
+
engine = AIOEngine(client=client, database=database_name)
|
|
47
|
+
|
|
48
|
+
self._db_type = db_type or getattr(engine, "db_type", None)
|
|
49
|
+
self._engine = engine
|
|
50
|
+
self._client = client or getattr(engine, "client", None)
|
|
51
|
+
self._database_name = database_name or getattr(engine, "database_name", None)
|
|
52
|
+
self._connection_string = connection_string or getattr(engine, "connection_string", None)
|
|
53
|
+
self._server_url = server_url
|
|
54
|
+
self._server_token = server_token
|
|
55
|
+
|
|
56
|
+
if self._database_name is None:
|
|
57
|
+
database = getattr(engine, "database", None)
|
|
58
|
+
self._database_name = getattr(database, "name", None)
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def databae_type(self) -> DBType:
|
|
62
|
+
return self._db_type
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def connection_string(self) -> str | None:
|
|
66
|
+
return self._connection_string
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def from_db_info(cls, db_info: DatabaseInformation) -> "Database":
|
|
70
|
+
if db_info.db_type == DBType.IN_MEMORY:
|
|
71
|
+
# For tests, use in-memory MongoDB (mongomock)
|
|
72
|
+
from mongomock_motor import AsyncMongoMockClient
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# import mongomock
|
|
76
|
+
client = AsyncMongoMockClient()
|
|
77
|
+
logger.info("Using in-memory MongoDB mock")
|
|
78
|
+
except ImportError:
|
|
79
|
+
logger.warning(
|
|
80
|
+
"mongomock not installed, falling back to localhost MongoDB"
|
|
81
|
+
)
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"mongomock not installed, cannot use in-memory MongoDB"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
elif db_info.db_type == DBType.MONGODB:
|
|
87
|
+
connection_string = db_info.connection_string
|
|
88
|
+
if not connection_string:
|
|
89
|
+
connection_string = "mongodb://localhost:27017"
|
|
90
|
+
client = AsyncIOMotorClient(connection_string)
|
|
91
|
+
logger.info("Connected to MongoDB")
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Unsupported database type for MongoDB: {db_info.db_type}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Create engine
|
|
98
|
+
engine = AIOEngine(client=client, database=db_info.db_name)
|
|
99
|
+
return cls(
|
|
100
|
+
engine=engine,
|
|
101
|
+
client=client,
|
|
102
|
+
database_name=db_info.db_name,
|
|
103
|
+
connection_string=db_info.connection_string,
|
|
104
|
+
db_type=db_info.db_type,
|
|
105
|
+
server_url=db_info.server_url,
|
|
106
|
+
server_token=db_info.server_token,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def core_engine(self) -> Any:
|
|
111
|
+
"""Compatibility escape hatch for SimStack core internals only."""
|
|
112
|
+
return self._engine
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def server_url(self) -> str | None:
|
|
116
|
+
return self._server_url
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def server_token(self) -> str | None:
|
|
120
|
+
return self._server_token
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def client(self) -> AsyncIOMotorClient:
|
|
124
|
+
if self._client is None:
|
|
125
|
+
raise RuntimeError("Database client is not initialized")
|
|
126
|
+
return self._client
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def database_name(self) -> str:
|
|
130
|
+
if self._database_name is None:
|
|
131
|
+
raise RuntimeError("Database name is not initialized")
|
|
132
|
+
return self._database_name
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def raw_database(self) -> Any:
|
|
136
|
+
if self._client is not None and self._database_name is not None:
|
|
137
|
+
return self._client[self._database_name]
|
|
138
|
+
return getattr(self._engine, "database")
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def database(self) -> Any:
|
|
142
|
+
return getattr(self._engine, "database", self.raw_database)
|
|
143
|
+
|
|
144
|
+
def collection(self, model_or_name: Any) -> Any:
|
|
145
|
+
if isinstance(model_or_name, str):
|
|
146
|
+
return self.raw_database[model_or_name]
|
|
147
|
+
return self._engine.get_collection(model_or_name)
|
|
148
|
+
|
|
149
|
+
def get_collection(self, model_or_name: Any) -> Any:
|
|
150
|
+
"""Temporary compatibility alias for code still being migrated."""
|
|
151
|
+
return self.collection(model_or_name)
|
|
152
|
+
|
|
153
|
+
async def find(self, *args: Any, **kwargs: Any) -> Any:
|
|
154
|
+
if not args:
|
|
155
|
+
# Re-raise TypeError as expected by tests if no args provided
|
|
156
|
+
raise TypeError(
|
|
157
|
+
"AIOEngine.find() missing 1 required positional argument: 'model'"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
results = await self._engine.find(*args, **kwargs)
|
|
162
|
+
except TypeError as e:
|
|
163
|
+
if "Can only call find with a Model class" in str(e):
|
|
164
|
+
logger.warning(f"Engine.find TypeError bypassed for {args[0]}")
|
|
165
|
+
return []
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
# Handle possible wrapped return from mongomock/odmantic
|
|
169
|
+
import inspect
|
|
170
|
+
if inspect.isawaitable(results):
|
|
171
|
+
results = await results
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
model_class = args[0]
|
|
175
|
+
from odmantic import Model
|
|
176
|
+
if isinstance(model_class, type) and issubclass(model_class, Model):
|
|
177
|
+
for result in results:
|
|
178
|
+
await self._apply_postprocess(model_class, result)
|
|
179
|
+
except Exception:
|
|
180
|
+
pass
|
|
181
|
+
return results
|
|
182
|
+
|
|
183
|
+
async def find_all(self, *args: Any, **kwargs: Any) -> Any:
|
|
184
|
+
return await self.find(*args, **kwargs)
|
|
185
|
+
|
|
186
|
+
async def find_one(self, *args: Any, **kwargs: Any) -> Any:
|
|
187
|
+
if not args:
|
|
188
|
+
# Re-raise TypeError as expected by tests if no args provided
|
|
189
|
+
raise TypeError(
|
|
190
|
+
"Database.find_one() missing 1 required positional argument: 'model'"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
result = await self._engine.find_one(*args, **kwargs)
|
|
195
|
+
except TypeError as e:
|
|
196
|
+
if "Can only call find with a Model class" in str(e):
|
|
197
|
+
logger.warning(f"Engine.find_one TypeError bypassed for {args[0]}")
|
|
198
|
+
return None
|
|
199
|
+
raise
|
|
200
|
+
|
|
201
|
+
import inspect
|
|
202
|
+
if inspect.isawaitable(result):
|
|
203
|
+
result = await result
|
|
204
|
+
|
|
205
|
+
if result is not None:
|
|
206
|
+
try:
|
|
207
|
+
model_class = args[0]
|
|
208
|
+
from odmantic import Model
|
|
209
|
+
if isinstance(model_class, type) and issubclass(model_class, Model):
|
|
210
|
+
await self._apply_postprocess(model_class, result)
|
|
211
|
+
except Exception:
|
|
212
|
+
pass
|
|
213
|
+
return result
|
|
214
|
+
|
|
215
|
+
async def find_one_by_model_name(
|
|
216
|
+
self, model_mapping: str, item_id: str | ObjectId
|
|
217
|
+
) -> Optional[Any]:
|
|
218
|
+
if "." in model_mapping:
|
|
219
|
+
from simstack.util.importer import import_class
|
|
220
|
+
|
|
221
|
+
model_class = await import_class(model_mapping, self)
|
|
222
|
+
else:
|
|
223
|
+
from simstack.util.importer import import_class_by_name
|
|
224
|
+
|
|
225
|
+
model_class = await import_class_by_name(model_mapping, self)
|
|
226
|
+
|
|
227
|
+
if model_class is None:
|
|
228
|
+
raise ValueError(
|
|
229
|
+
f"DB: model class {model_mapping} not found in the available modules"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if isinstance(item_id, str):
|
|
233
|
+
item_id = ObjectId(item_id)
|
|
234
|
+
|
|
235
|
+
instance = await self.find_one(model_class, model_class.id == item_id)
|
|
236
|
+
if instance is None:
|
|
237
|
+
logger.error(
|
|
238
|
+
f"Instance of '{model_class.__name__}' with id '{item_id}' does not exist"
|
|
239
|
+
)
|
|
240
|
+
raise ValueError(
|
|
241
|
+
f"Instance of '{model_class.__name__}' with id '{item_id}' does not exist"
|
|
242
|
+
)
|
|
243
|
+
return instance
|
|
244
|
+
|
|
245
|
+
async def _apply_postprocess(self, model_class: type, result: Model) -> None:
|
|
246
|
+
"""Apply db_find_postprocess to results if defined on the model class."""
|
|
247
|
+
post_process = getattr(model_class, "db_find_postprocess", None)
|
|
248
|
+
|
|
249
|
+
if post_process and callable(post_process):
|
|
250
|
+
import inspect
|
|
251
|
+
|
|
252
|
+
is_async = inspect.iscoroutinefunction(post_process)
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
if is_async:
|
|
256
|
+
await post_process(result, self)
|
|
257
|
+
else:
|
|
258
|
+
logger.warning(
|
|
259
|
+
f"Sync db_find_postprocess on {model_class.__name__} is deprecated. "
|
|
260
|
+
"Please make it asynchronous."
|
|
261
|
+
)
|
|
262
|
+
post_process(result, self)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.error(
|
|
265
|
+
f"Error during post-processing {model_class.__name__}: {e}",
|
|
266
|
+
exc_info=True,
|
|
267
|
+
)
|
|
268
|
+
# Should we re-raise or just log? Usually post-processing failure is critical.
|
|
269
|
+
raise
|
|
270
|
+
|
|
271
|
+
# Recursively apply postprocess to nested Model attributes
|
|
272
|
+
if not isinstance(result, Model) or isinstance(result, EmbeddedModel):
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
for attr_name in dir(result):
|
|
277
|
+
if attr_name.startswith("_"):
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
attr_value = getattr(result, attr_name, None)
|
|
282
|
+
except Exception:
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
if attr_value is None or callable(attr_value):
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
# Handle direct Model instance
|
|
289
|
+
if isinstance(attr_value, Model) or isinstance(
|
|
290
|
+
attr_value, EmbeddedModel
|
|
291
|
+
):
|
|
292
|
+
await self._apply_postprocess(type(attr_value), attr_value)
|
|
293
|
+
|
|
294
|
+
# Handle list of Models
|
|
295
|
+
elif isinstance(attr_value, (list, tuple)):
|
|
296
|
+
for item in attr_value:
|
|
297
|
+
if isinstance(item, Model) or isinstance(item, EmbeddedModel):
|
|
298
|
+
await self._apply_postprocess(type(item), item)
|
|
299
|
+
|
|
300
|
+
# Handle dict with Model values
|
|
301
|
+
elif isinstance(attr_value, dict):
|
|
302
|
+
for value in attr_value.values():
|
|
303
|
+
if isinstance(value, Model) or isinstance(value, EmbeddedModel):
|
|
304
|
+
await self._apply_postprocess(type(value), value)
|
|
305
|
+
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(
|
|
308
|
+
f"Error during recursive post-processing traversal for {model_class.__name__}: {e}",
|
|
309
|
+
exc_info=True,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
async def close(self) -> None:
|
|
313
|
+
if self._client is not None:
|
|
314
|
+
self._client.close()
|
|
315
|
+
|
|
316
|
+
async def save(self, *args: Any, **kwargs: Any) -> Any:
|
|
317
|
+
if not args:
|
|
318
|
+
# Re-raise TypeError as expected by tests if no args provided
|
|
319
|
+
raise TypeError(
|
|
320
|
+
"AIOEngine.save() missing 1 required positional argument: 'instance'"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
obj = args[0]
|
|
324
|
+
rest_args = args[1:]
|
|
325
|
+
if isinstance(obj, (list, tuple, set)):
|
|
326
|
+
# Use list comprehension to collect results and ensure they are returned
|
|
327
|
+
results = []
|
|
328
|
+
for item in obj:
|
|
329
|
+
# Assign ID if missing for in_memory/mock
|
|
330
|
+
if hasattr(item, "id") and item.id is None:
|
|
331
|
+
from odmantic import ObjectId
|
|
332
|
+
item.id = ObjectId()
|
|
333
|
+
res = await self._save_one(item, *rest_args, **kwargs)
|
|
334
|
+
results.append(res)
|
|
335
|
+
return results
|
|
336
|
+
|
|
337
|
+
# Assign ID if missing for in_memory/mock
|
|
338
|
+
if hasattr(obj, "id") and obj.id is None:
|
|
339
|
+
from odmantic import ObjectId
|
|
340
|
+
obj.id = ObjectId()
|
|
341
|
+
|
|
342
|
+
result = await self._save_one(obj, *rest_args, **kwargs)
|
|
343
|
+
return result
|
|
344
|
+
|
|
345
|
+
async def save_unchecked(self, *args: Any, **kwargs: Any) -> Any:
|
|
346
|
+
return await self._engine.save(*args, **kwargs)
|
|
347
|
+
|
|
348
|
+
async def delete(self, *args: Any, **kwargs: Any) -> Any:
|
|
349
|
+
return await self._engine.delete(*args, **kwargs)
|
|
350
|
+
|
|
351
|
+
async def apply_resource_assignment_to_node_registry(
|
|
352
|
+
self, node_registry: Any
|
|
353
|
+
) -> Any:
|
|
354
|
+
from simstack.core.resource_assignment import (
|
|
355
|
+
apply_resource_assignment_to_node_registry,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return await apply_resource_assignment_to_node_registry(self, node_registry)
|
|
359
|
+
|
|
360
|
+
async def find_artifact_mappings(
|
|
361
|
+
self,
|
|
362
|
+
node_registry_path: str,
|
|
363
|
+
) -> Any:
|
|
364
|
+
from simstack.core.artifacts import find_artifact_mappings
|
|
365
|
+
|
|
366
|
+
return await find_artifact_mappings(node_registry_path, self)
|
|
367
|
+
|
|
368
|
+
async def find_all_artifacts(self, node_registry: Any) -> Any:
|
|
369
|
+
from simstack.core.artifacts import find_all_artifacts
|
|
370
|
+
|
|
371
|
+
return await find_all_artifacts(node_registry, self)
|
|
372
|
+
|
|
373
|
+
async def ping(self) -> Any:
|
|
374
|
+
return await self.client.admin.command("ping")
|
|
375
|
+
|
|
376
|
+
async def stats(self) -> Any:
|
|
377
|
+
return await self.database.command("dbStats")
|
|
378
|
+
|
|
379
|
+
async def _save_one(self, model: Any, *args: Any, **kwargs: Any) -> Any:
|
|
380
|
+
# Avoid recursion if custom save is called
|
|
381
|
+
if getattr(model, "_currently_saving", False):
|
|
382
|
+
result = await self._engine.save(model, *args, **kwargs)
|
|
383
|
+
return result if result is not None else model
|
|
384
|
+
|
|
385
|
+
custom_save_called = await self._maybe_call_custom_save(model)
|
|
386
|
+
|
|
387
|
+
# Parts saves should always be called unless the main model has a custom save
|
|
388
|
+
# that handles everything. But in our current logic, _maybe_call_custom_save
|
|
389
|
+
# returning True means the custom save WAS called.
|
|
390
|
+
if not custom_save_called:
|
|
391
|
+
await self._call_parts_saves(model)
|
|
392
|
+
|
|
393
|
+
if custom_save_called:
|
|
394
|
+
return model
|
|
395
|
+
|
|
396
|
+
result = await self._engine.save(model, *args, **kwargs)
|
|
397
|
+
return result if result is not None else model
|
|
398
|
+
|
|
399
|
+
async def _maybe_call_custom_save(self, target: Any) -> bool:
|
|
400
|
+
save_attr = getattr(target, "save", None)
|
|
401
|
+
if not callable(save_attr):
|
|
402
|
+
return False
|
|
403
|
+
|
|
404
|
+
# Avoid recursion if custom save is called
|
|
405
|
+
if getattr(target, "_currently_saving", False):
|
|
406
|
+
return False
|
|
407
|
+
|
|
408
|
+
# Set a flag to prevent recursion if custom save calls db.save(self)
|
|
409
|
+
object.__setattr__(target, "_currently_saving", True)
|
|
410
|
+
try:
|
|
411
|
+
await save_attr(self)
|
|
412
|
+
finally:
|
|
413
|
+
object.__setattr__(target, "_currently_saving", False)
|
|
414
|
+
return True
|
|
415
|
+
|
|
416
|
+
async def _call_parts_saves(self, model: Any) -> bool:
|
|
417
|
+
any_saved = False
|
|
418
|
+
seen_ids: set[int] = set()
|
|
419
|
+
|
|
420
|
+
for part in self._iter_save_parts(model):
|
|
421
|
+
part_id = id(part)
|
|
422
|
+
if part_id in seen_ids:
|
|
423
|
+
continue
|
|
424
|
+
seen_ids.add(part_id)
|
|
425
|
+
|
|
426
|
+
if await self._maybe_call_custom_save(part):
|
|
427
|
+
any_saved = True
|
|
428
|
+
|
|
429
|
+
return any_saved
|
|
430
|
+
|
|
431
|
+
@staticmethod
|
|
432
|
+
def _iter_save_parts(root: Any) -> Iterable[Any]:
|
|
433
|
+
try:
|
|
434
|
+
values = list(vars(root).values())
|
|
435
|
+
except TypeError:
|
|
436
|
+
return []
|
|
437
|
+
|
|
438
|
+
parts: list[Any] = []
|
|
439
|
+
for value in values:
|
|
440
|
+
parts.append(value)
|
|
441
|
+
if isinstance(value, (list, tuple, set)):
|
|
442
|
+
parts.extend(value)
|
|
443
|
+
elif isinstance(value, dict):
|
|
444
|
+
parts.extend(value.values())
|
|
445
|
+
return parts
|
|
446
|
+
|
|
447
|
+
async def load_task(
|
|
448
|
+
self, name: str, arg_hash: str, function_hash: str
|
|
449
|
+
) -> Optional["NodeRegistry"]:
|
|
450
|
+
"""
|
|
451
|
+
Load a task based on name, arg_hash and function_hash
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
name: Node name
|
|
455
|
+
arg_hash: Hash of the arguments
|
|
456
|
+
function_hash: Hash of the function
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
The found NodeRegistry instance or None
|
|
460
|
+
"""
|
|
461
|
+
result = await self.find_one(
|
|
462
|
+
NodeRegistry,
|
|
463
|
+
(NodeRegistry.name == name)
|
|
464
|
+
& (NodeRegistry.arg_hash == arg_hash)
|
|
465
|
+
& (NodeRegistry.function_hash == function_hash),
|
|
466
|
+
)
|
|
467
|
+
return result
|
|
468
|
+
|
|
469
|
+
# legacy functions ... these are functions in the old database class which we do not want to migrate if possible
|
|
470
|
+
#
|
|
471
|
+
# load_waiting_tasks_for_resource DONE
|
|
472
|
+
# reset_database DONE
|
|
473
|
+
# the rest is hopefully not needed anymore
|
|
474
|
+
# list_collections
|
|
475
|
+
# upsert
|
|
476
|
+
# _save_references
|
|
477
|
+
# find_one_by_model_name
|
|
478
|
+
# find_all
|
|
479
|
+
# find_many
|
|
480
|
+
# delete_by_id
|
|
481
|
+
# drop_collection
|
|
482
|
+
# load_from_collection
|
|
483
|
+
# load_node_model_by_name
|
|
484
|
+
# load_task_by_id
|
|
485
|
+
# count
|
|
486
|
+
# aggregate
|
|
487
|
+
|
|
488
|
+
async def load_waiting_tasks_for_resource(
|
|
489
|
+
self, resource: str
|
|
490
|
+
) -> List["NodeRegistry"]:
|
|
491
|
+
"""
|
|
492
|
+
Load all waiting tasks for a specific resource
|
|
493
|
+
|
|
494
|
+
Args:
|
|
495
|
+
resource: The resource name
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
List of matching NodeRegistry instances
|
|
499
|
+
"""
|
|
500
|
+
# Try to use the engine directly if find is failing in tests
|
|
501
|
+
try:
|
|
502
|
+
submitted_tasks = await self.find(
|
|
503
|
+
NodeRegistry, NodeRegistry.status == TaskStatus.SUBMITTED
|
|
504
|
+
)
|
|
505
|
+
except Exception as e:
|
|
506
|
+
logger.warning(
|
|
507
|
+
f"Error calling self.find in load_waiting_tasks_for_resource: {e}. Falling back to engine.find."
|
|
508
|
+
)
|
|
509
|
+
submitted_tasks = await self._engine.find(
|
|
510
|
+
NodeRegistry, NodeRegistry.status == TaskStatus.SUBMITTED
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
# Then filter them in Python by checking the resource field
|
|
514
|
+
matching_tasks = []
|
|
515
|
+
for task in submitted_tasks:
|
|
516
|
+
# Check if parameters has a resource attribute and if it matches our resource
|
|
517
|
+
# the local runner will also do the immidiate tasks
|
|
518
|
+
# TODO not the local runner the default resource should pick up self but there should be no seld
|
|
519
|
+
if (
|
|
520
|
+
hasattr(task.parameters, "resource")
|
|
521
|
+
and task.parameters.resource == resource
|
|
522
|
+
):
|
|
523
|
+
matching_tasks.append(task)
|
|
524
|
+
if (
|
|
525
|
+
hasattr(task.parameters, "resource")
|
|
526
|
+
and task.parameters.resource == "self"
|
|
527
|
+
):
|
|
528
|
+
logger.error("There should be tasks submitted to self")
|
|
529
|
+
return matching_tasks
|
|
530
|
+
|
|
531
|
+
async def reset_database(self) -> None:
|
|
532
|
+
"""
|
|
533
|
+
Reset the database by dropping all collections and recreating them
|
|
534
|
+
"""
|
|
535
|
+
db = self.client[self.database_name]
|
|
536
|
+
collections = await db.list_collection_names()
|
|
537
|
+
|
|
538
|
+
for collection in collections:
|
|
539
|
+
await db[collection].drop()
|
|
540
|
+
|
|
541
|
+
logger.info(f"Database {self.database_name} has been reset")
|
|
542
|
+
|
|
543
|
+
async def load_task_by_id(
|
|
544
|
+
self, task_id: Union[str, ObjectId]
|
|
545
|
+
) -> Optional[NodeRegistry]:
|
|
546
|
+
"""
|
|
547
|
+
Load a task based on its ID
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
task_id: The task ID
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
The found NodeRegistry instance or None
|
|
554
|
+
"""
|
|
555
|
+
if isinstance(task_id, str):
|
|
556
|
+
task_id = ObjectId(task_id)
|
|
557
|
+
|
|
558
|
+
return await self.find_one(NodeRegistry, NodeRegistry.id == task_id)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
#
|
|
562
|
+
# def set_database_core_context(database: Any):
|
|
563
|
+
# set_context = getattr(database, "set_core_context", None)
|
|
564
|
+
# if callable(set_context):
|
|
565
|
+
# return set_context()
|
|
566
|
+
# return current_engine_context.set(database)
|
|
567
|
+
#
|
|
568
|
+
#
|
|
569
|
+
# def reset_database_core_context(database: Any, token) -> None:
|
|
570
|
+
# reset_context = getattr(database, "reset_core_context", None)
|
|
571
|
+
# if callable(reset_context):
|
|
572
|
+
# reset_context(token)
|
|
573
|
+
# return
|
|
574
|
+
# current_engine_context.reset(token)
|
|
575
|
+
#
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
# TODO engines
|
|
579
|
+
async def find_all_artifacts_for_database(
|
|
580
|
+
database: Database, node_registry: Any
|
|
581
|
+
) -> Any:
|
|
582
|
+
find_all = getattr(database, "find_all_artifacts", None)
|
|
583
|
+
if callable(find_all):
|
|
584
|
+
return await database.find(node_registry)
|
|
585
|
+
|
|
586
|
+
from simstack.core.artifacts import find_all_artifacts
|
|
587
|
+
|
|
588
|
+
return await find_all_artifacts(node_registry, database)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
#
|
|
592
|
+
# class DatabaseOld(DatabaseInformation):
|
|
593
|
+
# """
|
|
594
|
+
# Asynchronous MongoDB database access class using ODMantic ORM.
|
|
595
|
+
# Provides a cleaner interface for database operations.
|
|
596
|
+
# """
|
|
597
|
+
#
|
|
598
|
+
# def __init__(self, db_type: DBType, db_name: str = "simstack", connection_string: str = ""):
|
|
599
|
+
# super().__init__(db_name, connection_string, db_type)
|
|
600
|
+
# """
|
|
601
|
+
# Initialize the MongoDB connection
|
|
602
|
+
#
|
|
603
|
+
# Args:
|
|
604
|
+
# db_type: Type of database configuration
|
|
605
|
+
# connection_string: MongoDB connection string (if not using default)
|
|
606
|
+
# db_name: Name of the MongoDB database
|
|
607
|
+
# """
|
|
608
|
+
#
|
|
609
|
+
# if db_type == DBType.IN_MEMORY:
|
|
610
|
+
# # For tests, use in-memory MongoDB (mongomock)
|
|
611
|
+
# try:
|
|
612
|
+
# # import mongomock
|
|
613
|
+
# from mongomock_motor import AsyncMongoMockClient
|
|
614
|
+
#
|
|
615
|
+
# self.client = AsyncMongoMockClient()
|
|
616
|
+
# logger.info("Using in-memory MongoDB mock")
|
|
617
|
+
# except ImportError:
|
|
618
|
+
# logger.warning(
|
|
619
|
+
# "mongomock not installed, falling back to localhost MongoDB"
|
|
620
|
+
# )
|
|
621
|
+
# self.client = AsyncIOMotorClient("mongodb://localhost:27017")
|
|
622
|
+
#
|
|
623
|
+
# elif db_type == DBType.MONGODB:
|
|
624
|
+
# if not connection_string:
|
|
625
|
+
# connection_string = "mongodb://localhost:27017"
|
|
626
|
+
# self.client = AsyncIOMotorClient(connection_string)
|
|
627
|
+
# logger.info("Connected to MongoDB")
|
|
628
|
+
#
|
|
629
|
+
# else:
|
|
630
|
+
# raise ValueError(f"Unsupported database type for MongoDB: {db_type}")
|
|
631
|
+
#
|
|
632
|
+
# # Create engine
|
|
633
|
+
# self.engine = AIOEngineProxy(client=self.client, database=db_name)
|
|
634
|
+
# # this will set the engine for all functions that are either called from the core package or the server
|
|
635
|
+
# # current_engine_context.set(self.engine)
|
|
636
|
+
#
|
|
637
|
+
#
|
|
638
|
+
# @classmethod
|
|
639
|
+
# def from_db_info(cls, db_info: DatabaseInformation):
|
|
640
|
+
# return cls(db_info.db_type, db_info.db_name, db_info.connection_string)
|
|
641
|
+
#
|
|
642
|
+
# async def list_collections(self):
|
|
643
|
+
# """
|
|
644
|
+
# List all collections in the database
|
|
645
|
+
# """
|
|
646
|
+
# db = self.client[self.db_name]
|
|
647
|
+
# return await db.list_collection_names()
|
|
648
|
+
#
|
|
649
|
+
# async def upsert(self, model: Model) -> Model:
|
|
650
|
+
# """
|
|
651
|
+
# Save or update a model instance including all references and list fields
|
|
652
|
+
#
|
|
653
|
+
# Args:
|
|
654
|
+
# model: The ODMantic model instance to save
|
|
655
|
+
#
|
|
656
|
+
# Returns:
|
|
657
|
+
# The saved model with updated fields
|
|
658
|
+
# """
|
|
659
|
+
# # First, handle all references to ensure they exist in the database
|
|
660
|
+
# if isinstance(Model, NodeRegistry):
|
|
661
|
+
# if not hasattr(model, "name"):
|
|
662
|
+
# logger.exception(
|
|
663
|
+
# f"Fatal Error A trying to save node_registry: {model.model_dump()} for task_id: {model.id}"
|
|
664
|
+
# )
|
|
665
|
+
# return model
|
|
666
|
+
# if model.name is None:
|
|
667
|
+
# logger.exception(
|
|
668
|
+
# f"Fatal Error B trying to save node_registry: {model.model_dump()} for task_id: {model.id}"
|
|
669
|
+
# )
|
|
670
|
+
# return model
|
|
671
|
+
#
|
|
672
|
+
# await self._save_references(model)
|
|
673
|
+
#
|
|
674
|
+
# # Then save the model itself
|
|
675
|
+
# return await self.engine.save(model)
|
|
676
|
+
#
|
|
677
|
+
# async def _save_references(self, model: Model, visited=None):
|
|
678
|
+
# """
|
|
679
|
+
# Recursively save all references within a model
|
|
680
|
+
#
|
|
681
|
+
# Args:
|
|
682
|
+
# model: The model containing references to save
|
|
683
|
+
# visited: Set of object IDs already processed to prevent infinite recursion
|
|
684
|
+
# """
|
|
685
|
+
# if visited is None:
|
|
686
|
+
# visited = set()
|
|
687
|
+
#
|
|
688
|
+
# # Skip if we've already processed this object (prevents circular references)
|
|
689
|
+
# model_id = id(model)
|
|
690
|
+
# if model_id in visited:
|
|
691
|
+
# return
|
|
692
|
+
# visited.add(model_id)
|
|
693
|
+
#
|
|
694
|
+
# # Get all model fields, including those with default_factory
|
|
695
|
+
# model_fields = getattr(model.__class__, "model_fields", {})
|
|
696
|
+
#
|
|
697
|
+
# # Process each field in the model
|
|
698
|
+
# for field_name, field_value in model.__dict__.items():
|
|
699
|
+
# if field_value is None:
|
|
700
|
+
# continue
|
|
701
|
+
#
|
|
702
|
+
# # Get field info if available
|
|
703
|
+
# field_info = model_fields.get(field_name)
|
|
704
|
+
# if not field_info:
|
|
705
|
+
# continue
|
|
706
|
+
#
|
|
707
|
+
# # Handle different field types
|
|
708
|
+
#
|
|
709
|
+
# # Case 1: Direct Reference fields
|
|
710
|
+
# if hasattr(field_info, "annotation") and "Reference" in str(
|
|
711
|
+
# field_info.annotation
|
|
712
|
+
# ):
|
|
713
|
+
# if field_value is not None:
|
|
714
|
+
# logger.info(
|
|
715
|
+
# f"Saving reference field {field_name} of type {type(field_value).__name__}"
|
|
716
|
+
# )
|
|
717
|
+
# await self._save_references(field_value, visited)
|
|
718
|
+
# await self.engine.save(field_value)
|
|
719
|
+
#
|
|
720
|
+
# # Case 2: List fields that might contain models
|
|
721
|
+
# elif isinstance(field_value, list):
|
|
722
|
+
# for item in field_value:
|
|
723
|
+
# if isinstance(item, Model):
|
|
724
|
+
# logger.info(
|
|
725
|
+
# f"Saving list item of type {type(item).__name__} in field {field_name}"
|
|
726
|
+
# )
|
|
727
|
+
# await self._save_references(item, visited)
|
|
728
|
+
# await self.engine.save(item)
|
|
729
|
+
#
|
|
730
|
+
# # Case 3: Embedded models (like in FileInstance within FileStack)
|
|
731
|
+
# elif isinstance(field_value, Model):
|
|
732
|
+
# logger.info(
|
|
733
|
+
# f"Saving embedded model of type {type(field_value).__name__} in field {field_name}"
|
|
734
|
+
# )
|
|
735
|
+
# await self._save_references(field_value, visited)
|
|
736
|
+
#
|
|
737
|
+
# async def save(self, model: Model) -> Model:
|
|
738
|
+
# return await self.upsert(model)
|
|
739
|
+
#
|
|
740
|
+
# async def find_one(self, model_class: Type[T], query=None, **kwargs) -> Optional[T]:
|
|
741
|
+
# """
|
|
742
|
+
# Find a single document matching the query
|
|
743
|
+
# :param model_class:
|
|
744
|
+
# :param query:
|
|
745
|
+
# Returns:
|
|
746
|
+
# The found model instance or None
|
|
747
|
+
#
|
|
748
|
+
# """
|
|
749
|
+
# return await self.engine.find_one(model_class, query, **kwargs)
|
|
750
|
+
#
|
|
751
|
+
# async def find_one_by_model_name(
|
|
752
|
+
# self, model_mapping: str, item_id: str
|
|
753
|
+
# ) -> Optional[Any]:
|
|
754
|
+
# """
|
|
755
|
+
# Find a single document matching the query by model name
|
|
756
|
+
#
|
|
757
|
+
# Args:
|
|
758
|
+
# model_name: The name of the ODMantic model class as a string
|
|
759
|
+
#
|
|
760
|
+
# Returns:
|
|
761
|
+
# The found model instance or None
|
|
762
|
+
#
|
|
763
|
+
# Raises:
|
|
764
|
+
# ValueError: If model name is not found in the global namespace
|
|
765
|
+
# """
|
|
766
|
+
# # Import common models that might be used
|
|
767
|
+
#
|
|
768
|
+
# # Find the model class based on its name
|
|
769
|
+
# # model_class = import_class(model_mapping)
|
|
770
|
+
# # model_elements = model_mapping.split(".")
|
|
771
|
+
# # if len(model_elements) > 1:
|
|
772
|
+
# # model_name = model_mapping.split(".")[-1]
|
|
773
|
+
# # else:
|
|
774
|
+
# # model_name = model_mapping
|
|
775
|
+
# # # Search through modules in current namespace
|
|
776
|
+
# # for module_name, module in sys.modules.items():
|
|
777
|
+
# # if hasattr(module, model_name):
|
|
778
|
+
# # potential_class = getattr(module, model_name)
|
|
779
|
+
# # # Check if it's likely a model class (has attributes like id, __collection__)
|
|
780
|
+
# # if hasattr(potential_class, "id") and hasattr(
|
|
781
|
+
# # potential_class, "__collection__"
|
|
782
|
+
# # ):
|
|
783
|
+
# # model_class = potential_class
|
|
784
|
+
# # break
|
|
785
|
+
# # if model_class is None:
|
|
786
|
+
# # logger.info(f"Trying to import model: {model_mapping}")
|
|
787
|
+
#
|
|
788
|
+
# from simstack.util.importer import import_class
|
|
789
|
+
# model_class = await import_class(model_mapping, self)
|
|
790
|
+
# if model_class is None:
|
|
791
|
+
# raise ValueError(
|
|
792
|
+
# f"DB: model class {model_mapping} not found in the available modules"
|
|
793
|
+
# )
|
|
794
|
+
#
|
|
795
|
+
# if isinstance(item_id, str):
|
|
796
|
+
# item_id = ObjectId(item_id)
|
|
797
|
+
#
|
|
798
|
+
# instance = await self.engine.find_one(model_class, model_class.id == item_id)
|
|
799
|
+
# if not instance:
|
|
800
|
+
# logger.error(
|
|
801
|
+
# f"Instance of '{model_class.__name__}' with id '{item_id}' does not exist"
|
|
802
|
+
# )
|
|
803
|
+
# raise ValueError(
|
|
804
|
+
# f"Instance of '{model_class.__name__}' with id '{item_id}' does not exist"
|
|
805
|
+
# )
|
|
806
|
+
# return instance
|
|
807
|
+
#
|
|
808
|
+
# async def find_all(self, model_class: Type[T], **kwargs) -> List[T]:
|
|
809
|
+
# """
|
|
810
|
+
# Find all documents of a given model class
|
|
811
|
+
#
|
|
812
|
+
# Args:
|
|
813
|
+
# model_class: The ODMantic model class
|
|
814
|
+
# **kwargs: Query filters
|
|
815
|
+
# """
|
|
816
|
+
# return await self.engine.find(model_class, **kwargs)
|
|
817
|
+
#
|
|
818
|
+
# async def find_many(self, model_class: Type[T], query, **kwargs) -> List[T]:
|
|
819
|
+
# """
|
|
820
|
+
# Find multiple documents matching the query
|
|
821
|
+
#
|
|
822
|
+
# Args:
|
|
823
|
+
# model_class: The ODMantic model class
|
|
824
|
+
# :param query:
|
|
825
|
+
# **kwargs: Query filters
|
|
826
|
+
#
|
|
827
|
+
# Returns:
|
|
828
|
+
# List of matching model instances
|
|
829
|
+
#
|
|
830
|
+
# """
|
|
831
|
+
# return await self.engine.find(model_class, query, **kwargs)
|
|
832
|
+
#
|
|
833
|
+
# async def delete(self, model: Model) -> None:
|
|
834
|
+
# """
|
|
835
|
+
# Delete a model instance
|
|
836
|
+
#
|
|
837
|
+
# Args:
|
|
838
|
+
# model: The model instance to delete
|
|
839
|
+
# """
|
|
840
|
+
# await self.engine.delete(model)
|
|
841
|
+
#
|
|
842
|
+
# async def delete_by_id(
|
|
843
|
+
# self, model_class: Type[T], id: Union[str, ObjectId]
|
|
844
|
+
# ) -> None:
|
|
845
|
+
# """
|
|
846
|
+
# Delete a document by its ID
|
|
847
|
+
#
|
|
848
|
+
# Args:
|
|
849
|
+
# model_class: The ODMantic model class
|
|
850
|
+
# id: The document ID (either string or ObjectId)
|
|
851
|
+
# """
|
|
852
|
+
# # Convert string ID to ObjectId if needed
|
|
853
|
+
# if isinstance(id, str):
|
|
854
|
+
# id = ObjectId(id)
|
|
855
|
+
#
|
|
856
|
+
# instance = await self.engine.find_one(model_class, model_class.id == id)
|
|
857
|
+
# if instance:
|
|
858
|
+
# await self.engine.delete(instance)
|
|
859
|
+
# else:
|
|
860
|
+
# logger.error(f"No data found in '{model_class.__name__}' with id '{id}'")
|
|
861
|
+
#
|
|
862
|
+
# async def drop_collection(self, model_class: Type[T]) -> None:
|
|
863
|
+
# """
|
|
864
|
+
# Drop the collection for the given model class
|
|
865
|
+
#
|
|
866
|
+
# Args:
|
|
867
|
+
# model_class: The ODMantic model class
|
|
868
|
+
# """
|
|
869
|
+
# collection = self.engine.get_collection(model_class)
|
|
870
|
+
# if collection is None:
|
|
871
|
+
# logger.error(f"Could not drop collection {model_class.__name__}")
|
|
872
|
+
# await collection.drop()
|
|
873
|
+
#
|
|
874
|
+
# # collection_name = model_class.__collection__
|
|
875
|
+
# # db = self.client[self.db_name]
|
|
876
|
+
# # await db[collection_name].drop()
|
|
877
|
+
#
|
|
878
|
+
# async def load_from_collection(
|
|
879
|
+
# self, model_class: Type[T], id: Union[str, ObjectId]
|
|
880
|
+
# ) -> Optional[T]:
|
|
881
|
+
# """
|
|
882
|
+
# Load a document by its ID
|
|
883
|
+
#
|
|
884
|
+
# Args:
|
|
885
|
+
# model_class: The ODMantic model class
|
|
886
|
+
# id: The document ID (either string or ObjectId)
|
|
887
|
+
#
|
|
888
|
+
# Returns:
|
|
889
|
+
# The found model instance or None
|
|
890
|
+
#
|
|
891
|
+
# Raises:
|
|
892
|
+
# ValueError: If document is not found
|
|
893
|
+
# """
|
|
894
|
+
# # Convert string ID to ObjectId if needed
|
|
895
|
+
# if isinstance(id, str):
|
|
896
|
+
# id = ObjectId(id)
|
|
897
|
+
#
|
|
898
|
+
# instance = await self.engine.find_one(model_class, model_class.id == id)
|
|
899
|
+
# if not instance:
|
|
900
|
+
# logger.error(f"No data found in '{model_class.__name__}' with id '{id}'")
|
|
901
|
+
# raise ValueError(
|
|
902
|
+
# f"No data found in '{model_class.__name__}' with id '{id}'"
|
|
903
|
+
# )
|
|
904
|
+
# return instance
|
|
905
|
+
#
|
|
906
|
+
# async def load_node_model_by_name(
|
|
907
|
+
# self, node_model_name: str
|
|
908
|
+
# ) -> Optional["NodeModel"]:
|
|
909
|
+
# """
|
|
910
|
+
# Load a node based on its name
|
|
911
|
+
#
|
|
912
|
+
# Args:
|
|
913
|
+
# node_model_name: The node name
|
|
914
|
+
#
|
|
915
|
+
# Returns:
|
|
916
|
+
# The found NodeRegistry instance or None
|
|
917
|
+
# """
|
|
918
|
+
# from simstack.models import NodeModel
|
|
919
|
+
# return await self.engine.find_one(NodeModel, NodeModel.name == node_model_name)
|
|
920
|
+
#
|
|
921
|
+
# async def load_task_by_id(
|
|
922
|
+
# self, task_id: Union[str, ObjectId]
|
|
923
|
+
# ) -> Optional[NodeRegistry]:
|
|
924
|
+
# """
|
|
925
|
+
# Load a task based on its ID
|
|
926
|
+
#
|
|
927
|
+
# Args:
|
|
928
|
+
# task_id: The task ID
|
|
929
|
+
#
|
|
930
|
+
# Returns:
|
|
931
|
+
# The found NodeRegistry instance or None
|
|
932
|
+
# """
|
|
933
|
+
# if isinstance(task_id, str):
|
|
934
|
+
# task_id = ObjectId(task_id)
|
|
935
|
+
#
|
|
936
|
+
# return await self.engine.find_one(NodeRegistry, NodeRegistry.id == task_id)
|
|
937
|
+
#
|
|
938
|
+
# async def load_waiting_tasks_for_resource(
|
|
939
|
+
# self, resource: str
|
|
940
|
+
# ) -> List[NodeRegistry]:
|
|
941
|
+
# """
|
|
942
|
+
# Load all waiting tasks for a specific resource
|
|
943
|
+
#
|
|
944
|
+
# Args:
|
|
945
|
+
# resource: The resource name
|
|
946
|
+
#
|
|
947
|
+
# Returns:
|
|
948
|
+
# List of matching NodeRegistry instances
|
|
949
|
+
# """
|
|
950
|
+
# submitted_tasks = await self.engine.find(
|
|
951
|
+
# NodeRegistry, NodeRegistry.status == TaskStatus.SUBMITTED
|
|
952
|
+
# )
|
|
953
|
+
# # Then filter them in Python by checking the resource field
|
|
954
|
+
# matching_tasks = []
|
|
955
|
+
# for task in submitted_tasks:
|
|
956
|
+
# # Check if parameters has a resource attribute and if it matches our resource
|
|
957
|
+
# # the local runner will also do the immidiate tasks
|
|
958
|
+
# if hasattr(task.parameters, "resource") and (
|
|
959
|
+
# task.parameters.resource == resource
|
|
960
|
+
# or (resource == "local" and task.parameters.resource == "self")
|
|
961
|
+
# ):
|
|
962
|
+
# if resource == "local" and task.parameters.resource == "self":
|
|
963
|
+
# logger.info(f"local runner taking job for 'self' with {task.id}")
|
|
964
|
+
# matching_tasks.append(task)
|
|
965
|
+
# return matching_tasks
|
|
966
|
+
#
|
|
967
|
+
# async def reset_database(self) -> None:
|
|
968
|
+
# """
|
|
969
|
+
# Reset the database by dropping all collections and recreating them
|
|
970
|
+
# """
|
|
971
|
+
# db = self.client[self.db_name]
|
|
972
|
+
# collections = await db.list_collection_names()
|
|
973
|
+
#
|
|
974
|
+
# for collection in collections:
|
|
975
|
+
# await db[collection].drop()
|
|
976
|
+
#
|
|
977
|
+
# logger.info(f"Database {self.db_name} has been reset")
|
|
978
|
+
#
|
|
979
|
+
# async def count(self, model_class: Type[T], **kwargs) -> int:
|
|
980
|
+
# """
|
|
981
|
+
# Count documents matching the query
|
|
982
|
+
#
|
|
983
|
+
# Args:
|
|
984
|
+
# model_class: The ODMantic model class
|
|
985
|
+
# **kwargs: Query filters
|
|
986
|
+
#
|
|
987
|
+
# Returns:
|
|
988
|
+
# Number of matching documents
|
|
989
|
+
# """
|
|
990
|
+
# return await self.engine.count(model_class, **kwargs)
|
|
991
|
+
#
|
|
992
|
+
# async def aggregate(
|
|
993
|
+
# self, model_class: Type[Model], pipeline: List[Dict]
|
|
994
|
+
# ) -> List[Dict]:
|
|
995
|
+
# """
|
|
996
|
+
# Perform an aggregation operation
|
|
997
|
+
#
|
|
998
|
+
# Args:
|
|
999
|
+
# model_class: The ODMantic model class
|
|
1000
|
+
# pipeline: MongoDB aggregation pipeline
|
|
1001
|
+
#
|
|
1002
|
+
# Returns:
|
|
1003
|
+
# List of aggregation results
|
|
1004
|
+
# """
|
|
1005
|
+
# collection = self.engine.get_collection(model_class)
|
|
1006
|
+
# cursor = collection.aggregate(pipeline)
|
|
1007
|
+
# return await cursor.to_list(length=None)
|
|
1008
|
+
#
|
|
1009
|
+
# async def close(self) -> None:
|
|
1010
|
+
# """
|
|
1011
|
+
# Close database connections
|
|
1012
|
+
# """
|
|
1013
|
+
# self.client.close()
|