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,41 @@
|
|
|
1
|
+
from typing import Dict, Optional, List
|
|
2
|
+
from simstack.models.models import ModelMapping, NodeModel
|
|
3
|
+
from odmantic import AIOEngine
|
|
4
|
+
|
|
5
|
+
class ModelMappingTable:
|
|
6
|
+
def __init__(self, mappings: List[ModelMapping]):
|
|
7
|
+
self._by_name: Dict[str, ModelMapping] = {m.name: m for m in mappings}
|
|
8
|
+
self._by_mapping: Dict[str, ModelMapping] = {m.mapping: m for m in mappings}
|
|
9
|
+
|
|
10
|
+
def get_by_name(self, name: str) -> Optional[ModelMapping]:
|
|
11
|
+
return self._by_name.get(name)
|
|
12
|
+
|
|
13
|
+
def get_by_mapping(self, mapping: str) -> Optional[ModelMapping]:
|
|
14
|
+
return self._by_mapping.get(mapping)
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
async def load(cls, engine: AIOEngine):
|
|
18
|
+
mappings = await engine.find(ModelMapping)
|
|
19
|
+
return cls(list(mappings))
|
|
20
|
+
|
|
21
|
+
class NodeMappingTable:
|
|
22
|
+
def __init__(self, nodes: List[NodeModel]):
|
|
23
|
+
self._by_name: Dict[str, NodeModel] = {n.name: n for n in nodes}
|
|
24
|
+
self._by_mapping: Dict[str, NodeModel] = {n.function_mapping: n for n in nodes}
|
|
25
|
+
|
|
26
|
+
def get_by_name(self, name: str) -> Optional[NodeModel]:
|
|
27
|
+
return self._by_name.get(name)
|
|
28
|
+
|
|
29
|
+
def get_by_mapping(self, mapping: str) -> Optional[NodeModel]:
|
|
30
|
+
return self._by_mapping.get(mapping)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
async def load(cls, engine: AIOEngine):
|
|
34
|
+
try:
|
|
35
|
+
nodes = await engine.find(NodeModel)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"Error loading NodeModel from database: {e}")
|
|
38
|
+
print("WARNING: Returning empty NodeMappingTable to fix obviously corrupted case.")
|
|
39
|
+
print("Rebuild the NodeMappingTable by rerunning the database initialization or migration scripts.")
|
|
40
|
+
nodes = []
|
|
41
|
+
return cls(list(nodes))
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from typing import List, Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def find_minimal_route(
|
|
6
|
+
routes: List[Dict[str, str]], source: str, target: str
|
|
7
|
+
) -> List[Dict[str, str]]:
|
|
8
|
+
"""
|
|
9
|
+
Find the minimal (shortest) route from source to target.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
routes: List of route dictionaries with 'source', 'target', and 'host'
|
|
13
|
+
source: The starting node
|
|
14
|
+
target: The destination node
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
A list of route dictionaries representing the shortest path from source to target,
|
|
18
|
+
or an empty list if no path exists
|
|
19
|
+
"""
|
|
20
|
+
if not routes:
|
|
21
|
+
return []
|
|
22
|
+
|
|
23
|
+
# Build the graph and route map
|
|
24
|
+
adj: Dict[str, List[str]] = {}
|
|
25
|
+
route_map: Dict[tuple, Dict[str, str]] = {}
|
|
26
|
+
nodes = set()
|
|
27
|
+
|
|
28
|
+
for route in routes:
|
|
29
|
+
src = route["source"]
|
|
30
|
+
dst = route["target"]
|
|
31
|
+
nodes.add(src)
|
|
32
|
+
nodes.add(dst)
|
|
33
|
+
|
|
34
|
+
if src not in adj:
|
|
35
|
+
adj[src] = []
|
|
36
|
+
|
|
37
|
+
# Avoid adding duplicates to adjacency list, but allow route_map update (last one wins)
|
|
38
|
+
if dst not in adj[src]:
|
|
39
|
+
adj[src].append(dst)
|
|
40
|
+
|
|
41
|
+
route_map[(src, dst)] = route
|
|
42
|
+
|
|
43
|
+
if source not in nodes or target not in nodes:
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
# BFS to find shortest path
|
|
47
|
+
queue = deque([(source, [source])])
|
|
48
|
+
visited = {source}
|
|
49
|
+
|
|
50
|
+
while queue:
|
|
51
|
+
current_node, path = queue.popleft()
|
|
52
|
+
|
|
53
|
+
if current_node == target:
|
|
54
|
+
# Convert the path of nodes to a list of route dictionaries
|
|
55
|
+
path_routes = []
|
|
56
|
+
for i in range(len(path) - 1):
|
|
57
|
+
u = path[i]
|
|
58
|
+
v = path[i + 1]
|
|
59
|
+
path_routes.append(route_map[(u, v)])
|
|
60
|
+
return path_routes
|
|
61
|
+
|
|
62
|
+
if current_node in adj:
|
|
63
|
+
for neighbor in adj[current_node]:
|
|
64
|
+
if neighbor not in visited:
|
|
65
|
+
visited.add(neighbor)
|
|
66
|
+
queue.append((neighbor, path + [neighbor]))
|
|
67
|
+
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Add an alias for find_minimal_route as find_shortest_route for compatibility
|
|
72
|
+
find_shortest_route = find_minimal_route
|
simstack/util/mock_db.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Type, TypeVar, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from odmantic import Model, ObjectId
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T", bound=Model)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InMemoryCollection:
|
|
10
|
+
def __init__(self, engine: "InMemoryAIOEngine"):
|
|
11
|
+
self._engine = engine
|
|
12
|
+
self._store: List[Dict] = []
|
|
13
|
+
|
|
14
|
+
def __getitem__(self, index: int) -> Dict:
|
|
15
|
+
return self._store[index]
|
|
16
|
+
|
|
17
|
+
def __setitem__(self, index: int, value: Dict):
|
|
18
|
+
self._store[index] = value
|
|
19
|
+
|
|
20
|
+
def __iter__(self):
|
|
21
|
+
return iter(self._store)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InMemoryAIOEngine:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
# Stores data as {collection_name: [documents]}
|
|
27
|
+
self._store: Dict[str, InMemoryCollection] = {}
|
|
28
|
+
|
|
29
|
+
async def save(self, instance: T) -> T:
|
|
30
|
+
collection_name = (
|
|
31
|
+
instance.__collection__
|
|
32
|
+
if hasattr(instance, "__collection__")
|
|
33
|
+
else instance.__class__.__name__.lower()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
obj_dict = instance.model_dump()
|
|
37
|
+
|
|
38
|
+
# Ensure the instance has an ObjectId assigned
|
|
39
|
+
if not instance.id:
|
|
40
|
+
instance.id = ObjectId()
|
|
41
|
+
obj_dict["id"] = instance.id
|
|
42
|
+
|
|
43
|
+
# Initialize collection if it doesn't exist
|
|
44
|
+
if collection_name not in self._store:
|
|
45
|
+
self._store[collection_name] = InMemoryCollection(self)
|
|
46
|
+
|
|
47
|
+
# Update if already exists
|
|
48
|
+
existing_index = next(
|
|
49
|
+
(
|
|
50
|
+
i
|
|
51
|
+
for i, obj in enumerate(self._store[collection_name])
|
|
52
|
+
if obj["id"] == instance.id
|
|
53
|
+
),
|
|
54
|
+
None,
|
|
55
|
+
)
|
|
56
|
+
if existing_index is not None:
|
|
57
|
+
self._store[collection_name][existing_index] = obj_dict
|
|
58
|
+
else:
|
|
59
|
+
self._store[collection_name].append(obj_dict)
|
|
60
|
+
|
|
61
|
+
return instance
|
|
62
|
+
|
|
63
|
+
async def find(self, model: Type[T], **query) -> List[T]:
|
|
64
|
+
collection_name = (
|
|
65
|
+
model.__collection__
|
|
66
|
+
if hasattr(model, "__collection__")
|
|
67
|
+
else model.__name__.lower()
|
|
68
|
+
)
|
|
69
|
+
all_objects = self._store.get(collection_name, [])
|
|
70
|
+
results = []
|
|
71
|
+
|
|
72
|
+
for obj_dict in all_objects:
|
|
73
|
+
if all(obj_dict.get(k) == v for k, v in query.items()):
|
|
74
|
+
results.append(model.model_validate(obj_dict))
|
|
75
|
+
return results
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
async def find_one(self, model: Type[T], condition=None, **query) -> Optional[T]:
|
|
79
|
+
# Handle both keyword arguments and condition objects
|
|
80
|
+
if condition is not None:
|
|
81
|
+
# For conditions like ModelMapping.name == class_name
|
|
82
|
+
# This is a more robust approach to extract field name and value
|
|
83
|
+
condition_str = str(condition)
|
|
84
|
+
|
|
85
|
+
# Look for the pattern: field_name == value or field_name: {$eq: value}
|
|
86
|
+
if "==" in condition_str:
|
|
87
|
+
# Handle direct comparison like ModelMapping.name == 'TestClass'
|
|
88
|
+
parts = condition_str.split("==")
|
|
89
|
+
if len(parts) == 2:
|
|
90
|
+
field_name = parts[0].strip().split(".")[-1]
|
|
91
|
+
value = parts[1].strip().strip("'\"")
|
|
92
|
+
query[field_name] = value
|
|
93
|
+
elif "$eq" in condition_str:
|
|
94
|
+
# Handle MongoDB-style query like {'mapping': {'$eq': 'tests.core.test_import_class.TestClass'}}
|
|
95
|
+
import re
|
|
96
|
+
|
|
97
|
+
# Extract field name and value using regex
|
|
98
|
+
field_match = re.search(
|
|
99
|
+
r"'(\w+)':\s*\{'?\$eq'?:\s*'([^']+)'", condition_str
|
|
100
|
+
)
|
|
101
|
+
if field_match:
|
|
102
|
+
field_name = field_match.group(1)
|
|
103
|
+
value = field_match.group(2)
|
|
104
|
+
query[field_name] = value
|
|
105
|
+
else:
|
|
106
|
+
# Fallback - try to extract from the string representation
|
|
107
|
+
# Look for pattern like "ModelMapping.field_name"
|
|
108
|
+
field_match = re.search(r"(\w+)\.(\w+)", condition_str)
|
|
109
|
+
if field_match:
|
|
110
|
+
field_name = field_match.group(2)
|
|
111
|
+
# Try to extract the value (this is still a fallback)
|
|
112
|
+
value_match = re.search(r"'([^']+)'(?:[^']*$)", condition_str)
|
|
113
|
+
if value_match:
|
|
114
|
+
value = value_match.group(1)
|
|
115
|
+
query[field_name] = value
|
|
116
|
+
|
|
117
|
+
found = await self.find(model, **query)
|
|
118
|
+
return found[0] if found else None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Example Usage:
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
from odmantic import Model
|
|
124
|
+
|
|
125
|
+
class BinaryOperationInput(Model):
|
|
126
|
+
arg1: int
|
|
127
|
+
arg2: int
|
|
128
|
+
|
|
129
|
+
async def test_engine():
|
|
130
|
+
engine = InMemoryAIOEngine()
|
|
131
|
+
obj = BinaryOperationInput(arg1=1, arg2=2)
|
|
132
|
+
|
|
133
|
+
# Saving object
|
|
134
|
+
await engine.save(obj)
|
|
135
|
+
|
|
136
|
+
# Finding objects
|
|
137
|
+
found_objs = await engine.find(BinaryOperationInput, arg1=1)
|
|
138
|
+
print("Found objects:", found_objs)
|
|
139
|
+
|
|
140
|
+
# Find one object
|
|
141
|
+
found_one = await engine.find_one(BinaryOperationInput, arg2=2)
|
|
142
|
+
print("Found single object:", found_one)
|
|
143
|
+
|
|
144
|
+
asyncio.run(test_engine())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from simstack.util.project_root_finder import find_project_root
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def is_module_subpath_of_path(module_path: str, path_info_path: Path) -> bool:
|
|
8
|
+
"""
|
|
9
|
+
Check whether a dot-separated module path is a subpath of an absolute path.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
module_path: A dot-separated path relative to the project root (e.g., "src.simstack.core.artifacts")
|
|
13
|
+
path_info_path: An absolute path below the project root (e.g., "/path/to/project/src/simstack/core")
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
True if the module path corresponds to a location under the given absolute path, False otherwise
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Convert the module path to the file system path
|
|
20
|
+
# Replace dots with path separators
|
|
21
|
+
module_file_path = module_path.replace(".", os.sep)
|
|
22
|
+
|
|
23
|
+
# Convert path_info_path to the Path object for easier manipulation
|
|
24
|
+
path_info_path_obj = Path(path_info_path)
|
|
25
|
+
|
|
26
|
+
# Get the project root using the existing utility function
|
|
27
|
+
project_root = find_project_root()
|
|
28
|
+
|
|
29
|
+
# Convert the module path to the absolute path relative to the project root
|
|
30
|
+
module_absolute_path = project_root / module_file_path
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
# Check if the module path is under the given path_info_path
|
|
34
|
+
module_absolute_path.relative_to(path_info_path_obj)
|
|
35
|
+
return True
|
|
36
|
+
except ValueError:
|
|
37
|
+
# If relative_to raises ValueError, the module path is not under path_info_path
|
|
38
|
+
return False
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Generic, Optional, List, Iterator, Type, Union, Iterable, TypeVar, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from odmantic import ObjectId, Model
|
|
5
|
+
|
|
6
|
+
from simstack.util.generic_list_mixin import GenericListMixin
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from simstack.util.db import Database
|
|
10
|
+
|
|
11
|
+
T = TypeVar("T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ObjectListMixin(GenericListMixin[ObjectId], Generic[T]):
|
|
15
|
+
"""
|
|
16
|
+
Mixin class for lists of Model ObjectIDs.
|
|
17
|
+
Stores ObjectId instances in `elements`, but allows interaction with Model instances.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def _normalize_elements_for_init(data: dict) -> tuple[dict, Optional[List[Model]]]:
|
|
22
|
+
elements = data.get("elements")
|
|
23
|
+
if not elements:
|
|
24
|
+
return data, None
|
|
25
|
+
|
|
26
|
+
elements = list(elements)
|
|
27
|
+
contains_models = any(isinstance(element, Model) for element in elements)
|
|
28
|
+
|
|
29
|
+
if not contains_models:
|
|
30
|
+
return data, None
|
|
31
|
+
|
|
32
|
+
if not all(isinstance(element, Model) for element in elements):
|
|
33
|
+
raise ValueError("elements must contain either only model instances or only ObjectIds")
|
|
34
|
+
|
|
35
|
+
normalized_data = dict(data)
|
|
36
|
+
normalized_data["elements"] = [element.id for element in elements]
|
|
37
|
+
return normalized_data, elements
|
|
38
|
+
|
|
39
|
+
def __iter__(self) -> Iterator[T]:
|
|
40
|
+
return iter(self._get_cache())
|
|
41
|
+
|
|
42
|
+
def _get_model_class(self) -> Type[T]:
|
|
43
|
+
# T is the first type argument of ObjectListMixin
|
|
44
|
+
if hasattr(self, "__orig_bases__"):
|
|
45
|
+
for base in self.__class__.__orig_bases__:
|
|
46
|
+
if hasattr(base, "__origin__") and base.__origin__ is ObjectListMixin:
|
|
47
|
+
return base.__args__[0]
|
|
48
|
+
raise RuntimeError(f"Could not determine model class for {self.__class__.__name__}")
|
|
49
|
+
|
|
50
|
+
def _set_cache(self, cache: List[T]):
|
|
51
|
+
object.__getattribute__(self, "__dict__")["_cache"] = cache
|
|
52
|
+
return cache
|
|
53
|
+
|
|
54
|
+
def _get_cache(self) -> List[T]:
|
|
55
|
+
self_dict = object.__getattribute__(self, "__dict__")
|
|
56
|
+
cache = self_dict.get("_cache",None)
|
|
57
|
+
if cache is None:
|
|
58
|
+
cache = []
|
|
59
|
+
cache = self._set_cache(cache)
|
|
60
|
+
return cache
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def append(self, element: T):
|
|
64
|
+
if not isinstance(element, Model):
|
|
65
|
+
raise ValueError("can only append models to ObjectListMixin")
|
|
66
|
+
cache = self._get_cache()
|
|
67
|
+
if element not in cache:
|
|
68
|
+
cache.append(element)
|
|
69
|
+
self._set_cache(cache)
|
|
70
|
+
|
|
71
|
+
obj_id = getattr(element, "id", None)
|
|
72
|
+
if obj_id is None:
|
|
73
|
+
raise ValueError("attempting to append a model without id to an ObjectListMixin class")
|
|
74
|
+
if obj_id not in self.elements:
|
|
75
|
+
self.elements.append(obj_id)
|
|
76
|
+
|
|
77
|
+
def extend(self, elements: Union[List[T], "ObjectListMixin[T]"]):
|
|
78
|
+
for element in elements:
|
|
79
|
+
self.append(element)
|
|
80
|
+
|
|
81
|
+
async def save(self, db: "Database"):
|
|
82
|
+
cache = self._get_cache()
|
|
83
|
+
# Save each element first (this might trigger their own custom saves)
|
|
84
|
+
for element in cache:
|
|
85
|
+
await db.save(element)
|
|
86
|
+
if isinstance(self, Model): # embedded models are save with the parent
|
|
87
|
+
await db.save_unchecked(self)
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def insert(self, index: int, element: T):
|
|
91
|
+
self.elements.insert(index, element.id)
|
|
92
|
+
cache = self._get_cache()
|
|
93
|
+
if index <= len(cache):
|
|
94
|
+
cache.insert(index, element)
|
|
95
|
+
|
|
96
|
+
def remove(self, element: T):
|
|
97
|
+
self.elements.remove(element.id)
|
|
98
|
+
cache = self._get_cache()
|
|
99
|
+
if element in cache:
|
|
100
|
+
cache.remove(element)
|
|
101
|
+
|
|
102
|
+
def pop(self, index: int = -1) -> T:
|
|
103
|
+
self.elements.pop(index)
|
|
104
|
+
cache = self._get_cache()
|
|
105
|
+
if index < 0:
|
|
106
|
+
index = len(self.elements) + 1 + index # elements already popped
|
|
107
|
+
if index < len(cache):
|
|
108
|
+
return cache.pop(index)
|
|
109
|
+
else:
|
|
110
|
+
# If it's not in cache, we might have a problem if it was never loaded.
|
|
111
|
+
# But pop is usually used on loaded lists.
|
|
112
|
+
raise IndexError("Index out of range for cache. Load elements first.")
|
|
113
|
+
|
|
114
|
+
def get(self, index: int) -> T:
|
|
115
|
+
"""Get an element by index, loading it from DB if necessary."""
|
|
116
|
+
if index < 0 or index >= len(self.elements):
|
|
117
|
+
raise IndexError("list index out of range")
|
|
118
|
+
|
|
119
|
+
cache = self._get_cache()
|
|
120
|
+
if index < len(cache):
|
|
121
|
+
return cache[index]
|
|
122
|
+
|
|
123
|
+
raise IndexError("Index out of range for cache. Load elements first.")
|
|
124
|
+
|
|
125
|
+
def count(self, element: T) -> int:
|
|
126
|
+
return self.elements.count(element.id)
|
|
127
|
+
|
|
128
|
+
def index(self, element: T, start: int = 0, stop: int = None) -> int:
|
|
129
|
+
return self.elements.index(element.id, start, stop if stop is not None else len(self.elements))
|
|
130
|
+
|
|
131
|
+
def reverse(self):
|
|
132
|
+
self.elements.reverse()
|
|
133
|
+
self._get_cache().reverse()
|
|
134
|
+
|
|
135
|
+
def sort(self, key=None, reverse: bool = False):
|
|
136
|
+
# This is tricky because we need to sort both.
|
|
137
|
+
# Simplest is to sort the cache and then update elements.
|
|
138
|
+
# But we might not have a full cache.
|
|
139
|
+
if len(self._get_cache()) != len(self.elements):
|
|
140
|
+
# We can't easily sort if we don't have all elements in cache.
|
|
141
|
+
# For now, let's just sort the elements if key only depends on ObjectId,
|
|
142
|
+
# or raise if it needs the Model.
|
|
143
|
+
# Actually, standard list.sort() uses the objects.
|
|
144
|
+
raise RuntimeError("Sort requires all elements to be loaded in cache. Call _load_all_to_cache() first.")
|
|
145
|
+
|
|
146
|
+
cache = self._get_cache()
|
|
147
|
+
cache.sort(key=key, reverse=reverse)
|
|
148
|
+
self.elements[:] = [obj.id for obj in cache]
|
|
149
|
+
|
|
150
|
+
def __setitem__(self, index: Union[int, slice], value: Union[T, List[T]]):
|
|
151
|
+
if isinstance(index, slice):
|
|
152
|
+
if isinstance(value, Iterable):
|
|
153
|
+
ids = [v.id for v in value]
|
|
154
|
+
self.elements[index] = ids
|
|
155
|
+
cache = self._get_cache()
|
|
156
|
+
# Slice assignment on cache is only safe if it matches the current loaded state
|
|
157
|
+
# For simplicity, let's just clear cache or try to update if it fits
|
|
158
|
+
if index.start is not None and index.stop is not None and index.stop <= len(cache):
|
|
159
|
+
cache[index] = list(value)
|
|
160
|
+
self._set_cache(cache)
|
|
161
|
+
else:
|
|
162
|
+
raise TypeError("Can only assign an iterable to a slice")
|
|
163
|
+
else:
|
|
164
|
+
self.elements[index] = value.id
|
|
165
|
+
cache = self._get_cache()
|
|
166
|
+
if index < len(cache):
|
|
167
|
+
cache[index] = value
|
|
168
|
+
self._set_cache(cache)
|
|
169
|
+
|
|
170
|
+
def __delitem__(self, index: Union[int, slice]):
|
|
171
|
+
del self.elements[index]
|
|
172
|
+
cache = self._get_cache()
|
|
173
|
+
if isinstance(index, slice):
|
|
174
|
+
if index.start is not None and index.stop is not None and index.stop <= len(cache):
|
|
175
|
+
del cache[index]
|
|
176
|
+
else:
|
|
177
|
+
raise IndexError("Slice out of range for cache")
|
|
178
|
+
else:
|
|
179
|
+
if index < len(cache):
|
|
180
|
+
del cache[index]
|
|
181
|
+
self._set_cache(cache)
|
|
182
|
+
|
|
183
|
+
def copy(self) -> "ObjectListMixin[T]":
|
|
184
|
+
import copy
|
|
185
|
+
return copy.copy(self)
|
|
186
|
+
|
|
187
|
+
def __getitem__(self, index: Union[int, slice]) -> Union[T, List[T], ObjectId, List[ObjectId]]:
|
|
188
|
+
cache = self._get_cache()
|
|
189
|
+
if isinstance(index, slice):
|
|
190
|
+
if index.start is not None and index.stop is not None and index.stop <= len(cache):
|
|
191
|
+
return cache[index]
|
|
192
|
+
return self.elements[index]
|
|
193
|
+
else:
|
|
194
|
+
if 0 <= index < len(cache):
|
|
195
|
+
return cache[index]
|
|
196
|
+
return self.elements[index]
|
|
197
|
+
|
|
198
|
+
def find(self, pattern: str) -> Optional[T]:
|
|
199
|
+
regex = re.compile(pattern)
|
|
200
|
+
cache = self._get_cache()
|
|
201
|
+
for obj in cache:
|
|
202
|
+
name = getattr(obj, "name", None)
|
|
203
|
+
if name and regex.search(name):
|
|
204
|
+
return obj
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
def find_all(self, pattern: str) -> Iterator[T]:
|
|
208
|
+
regex = re.compile(pattern)
|
|
209
|
+
cache = self._get_cache()
|
|
210
|
+
for obj in cache:
|
|
211
|
+
name = getattr(obj, "name", None)
|
|
212
|
+
if name and regex.search(name):
|
|
213
|
+
yield obj
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
async def db_find_postprocess(self, db: "Database"):
|
|
217
|
+
"""Instance-level post-processing"""
|
|
218
|
+
cache = self._get_cache()
|
|
219
|
+
model_class = self._get_model_class()
|
|
220
|
+
# Identify which IDs are missing from the cache
|
|
221
|
+
missing_ids = [obj_id for obj_id in self.elements if not any(getattr(o, "id", None) == obj_id for o in cache)]
|
|
222
|
+
|
|
223
|
+
if missing_ids:
|
|
224
|
+
# Load all missing elements at once
|
|
225
|
+
query = model_class.id.in_(missing_ids)
|
|
226
|
+
# engine.find might return a cursor or a list
|
|
227
|
+
results = await db.find(model_class, query)
|
|
228
|
+
|
|
229
|
+
# Map results by ID for efficient lookup
|
|
230
|
+
loaded_map = {}
|
|
231
|
+
for obj in results:
|
|
232
|
+
# Recursively unwrap nested collections (mock DB behavior)
|
|
233
|
+
it = obj
|
|
234
|
+
while isinstance(it, (list, tuple)) and len(it) > 0:
|
|
235
|
+
it = it[0]
|
|
236
|
+
|
|
237
|
+
# Check for Model or duck-typed object
|
|
238
|
+
if hasattr(it, "id"):
|
|
239
|
+
loaded_map[it.id] = it
|
|
240
|
+
|
|
241
|
+
# Update cache maintaining the order of self.elements
|
|
242
|
+
new_cache = []
|
|
243
|
+
for obj_id in self.elements:
|
|
244
|
+
# Find in existing cache
|
|
245
|
+
existing = next((o for o in cache if getattr(o, "id", None) == obj_id), None)
|
|
246
|
+
if existing:
|
|
247
|
+
new_cache.append(existing)
|
|
248
|
+
elif obj_id in loaded_map:
|
|
249
|
+
new_cache.append(loaded_map[obj_id])
|
|
250
|
+
else:
|
|
251
|
+
import logging
|
|
252
|
+
logging.getLogger(__name__).warning(f"Could not load object {obj_id} for {model_class.__name__}")
|
|
253
|
+
self._set_cache(new_cache)
|
|
254
|
+
return self
|
|
255
|
+
|
|
256
|
+
def delete_element(self, element: Union[T, ObjectId]):
|
|
257
|
+
cache = self._get_cache()
|
|
258
|
+
if isinstance(element, Model):
|
|
259
|
+
obj_id = element.id
|
|
260
|
+
if element in cache:
|
|
261
|
+
cache.remove(element)
|
|
262
|
+
else:
|
|
263
|
+
obj_id = element
|
|
264
|
+
# If it's an ObjectId, we might need to find the object in cache to remove it
|
|
265
|
+
cache = [o for o in cache if getattr(o, "id", None) != obj_id]
|
|
266
|
+
self._set_cache(cache)
|
|
267
|
+
if obj_id in self.elements:
|
|
268
|
+
self.elements.remove(obj_id)
|
|
269
|
+
|
|
270
|
+
def clear(self):
|
|
271
|
+
self.elements.clear()
|
|
272
|
+
self._set_cache([])
|
|
273
|
+
|
|
274
|
+
def __contains__(self, element: Union[T, ObjectId]) -> bool:
|
|
275
|
+
if isinstance(element, Model):
|
|
276
|
+
return element.id in self.elements
|
|
277
|
+
elif isinstance(element, ObjectId):
|
|
278
|
+
return element in self.elements
|
|
279
|
+
return element in self._get_cache()
|
|
280
|
+
|
|
281
|
+
def __len__(self) -> int:
|
|
282
|
+
return len(self.elements)
|