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,118 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _normalize_vector(v: List[float]) -> List[float]:
|
|
6
|
+
"""Normalize a vector to unit length."""
|
|
7
|
+
norm = math.sqrt(sum(x * x for x in v))
|
|
8
|
+
if norm < 1e-10:
|
|
9
|
+
return [1.0, 0.0, 0.0] # Default direction for zero vector
|
|
10
|
+
return [x / norm for x in v]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _cross_product(a: List[float], b: List[float]) -> List[float]:
|
|
14
|
+
"""Calculate cross product of two 3D vectors."""
|
|
15
|
+
return [
|
|
16
|
+
a[1] * b[2] - a[2] * b[1],
|
|
17
|
+
a[2] * b[0] - a[0] * b[2],
|
|
18
|
+
a[0] * b[1] - a[1] * b[0],
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_perpendicular_direction(v1: List[float]) -> List[float]:
|
|
23
|
+
"""Get a direction perpendicular to v1."""
|
|
24
|
+
# Find the component with smallest absolute value
|
|
25
|
+
min_idx = min(range(3), key=lambda i: abs(v1[i]))
|
|
26
|
+
|
|
27
|
+
# Create perpendicular vector
|
|
28
|
+
v2 = [0.0, 0.0, 0.0]
|
|
29
|
+
v2[min_idx] = 1.0
|
|
30
|
+
|
|
31
|
+
# Make it perpendicular using Gram-Schmidt
|
|
32
|
+
dot_product = sum(v1[i] * v2[i] for i in range(3))
|
|
33
|
+
for i in range(3):
|
|
34
|
+
v2[i] -= dot_product * v1[i]
|
|
35
|
+
|
|
36
|
+
return v2
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _get_principal_direction(matrix: List[List[float]]) -> List[float]:
|
|
40
|
+
"""Get the principal direction (largest eigenvalue direction) using power iteration."""
|
|
41
|
+
# Start with random vector
|
|
42
|
+
v = [1.0, 1.0, 1.0]
|
|
43
|
+
|
|
44
|
+
# Power iteration
|
|
45
|
+
for _ in range(10): # Usually converges quickly
|
|
46
|
+
# v = matrix * v
|
|
47
|
+
new_v = [0.0, 0.0, 0.0]
|
|
48
|
+
for i in range(3):
|
|
49
|
+
for j in range(3):
|
|
50
|
+
new_v[i] += matrix[i][j] * v[j]
|
|
51
|
+
|
|
52
|
+
# Normalize
|
|
53
|
+
norm = math.sqrt(sum(x * x for x in new_v))
|
|
54
|
+
if norm > 1e-10:
|
|
55
|
+
v = [x / norm for x in new_v]
|
|
56
|
+
|
|
57
|
+
return v
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_eigenvectors_3x3(matrix: List[List[float]]) -> List[List[float]]:
|
|
61
|
+
"""
|
|
62
|
+
Get eigenvectors of a 3x3 matrix using simplified approach.
|
|
63
|
+
For the inertia tensor, we'll use a simplified canonical form.
|
|
64
|
+
"""
|
|
65
|
+
# For molecular hashing, we can use a simplified approach
|
|
66
|
+
# that still provides rotation invariance
|
|
67
|
+
|
|
68
|
+
# Create an orthonormal basis from the inertia tensor
|
|
69
|
+
# Start with the direction of maximum variance
|
|
70
|
+
v1 = _get_principal_direction(matrix)
|
|
71
|
+
|
|
72
|
+
# Get a perpendicular direction
|
|
73
|
+
v2 = _get_perpendicular_direction(v1)
|
|
74
|
+
|
|
75
|
+
# Third direction is cross product
|
|
76
|
+
v3 = _cross_product(v1, v2)
|
|
77
|
+
|
|
78
|
+
# Normalize all vectors
|
|
79
|
+
v1 = _normalize_vector(v1)
|
|
80
|
+
v2 = _normalize_vector(v2)
|
|
81
|
+
v3 = _normalize_vector(v3)
|
|
82
|
+
|
|
83
|
+
# Return as columns of transformation matrix
|
|
84
|
+
return [[v1[0], v2[0], v3[0]], [v1[1], v2[1], v3[1]], [v1[2], v2[2], v3[2]]]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _get_canonical_orientation(coords: List[List[float]]) -> List[List[float]]:
|
|
88
|
+
"""
|
|
89
|
+
Get coordinates in a canonical orientation using the inertia tensor.
|
|
90
|
+
|
|
91
|
+
:param coords: Centered coordinates of atoms
|
|
92
|
+
:return: Coordinates in canonical orientation
|
|
93
|
+
"""
|
|
94
|
+
if len(coords) < 2:
|
|
95
|
+
return coords
|
|
96
|
+
|
|
97
|
+
# Calculate inertia tensor (3x3 matrix)
|
|
98
|
+
inertia_tensor = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]
|
|
99
|
+
|
|
100
|
+
for pos in coords:
|
|
101
|
+
# I_ij = sum(r_k * r_l) where k,l are coordinate indices
|
|
102
|
+
for i in range(3):
|
|
103
|
+
for j in range(3):
|
|
104
|
+
inertia_tensor[i][j] += pos[i] * pos[j]
|
|
105
|
+
|
|
106
|
+
# Get eigenvalues and eigenvectors using power iteration method
|
|
107
|
+
eigenvectors = _get_eigenvectors_3x3(inertia_tensor)
|
|
108
|
+
|
|
109
|
+
# Transform coordinates to canonical orientation
|
|
110
|
+
canonical_coords = []
|
|
111
|
+
for coord in coords:
|
|
112
|
+
new_coord = [0.0, 0.0, 0.0]
|
|
113
|
+
for i in range(3):
|
|
114
|
+
for j in range(3):
|
|
115
|
+
new_coord[i] += coord[j] * eigenvectors[j][i]
|
|
116
|
+
canonical_coords.append(new_coord)
|
|
117
|
+
|
|
118
|
+
return canonical_coords
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from simstack.core.context import context
|
|
5
|
+
from simstack.util.project_root_finder import find_project_root
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Get the module name from the current file path
|
|
9
|
+
def get_module_path(file_path: str):
|
|
10
|
+
# Get the absolute path of the current file
|
|
11
|
+
file_path = Path(file_path).resolve()
|
|
12
|
+
# If not found in sys.path, use an alternative approach
|
|
13
|
+
project_root = context.config.project_root # Assumes running from project root
|
|
14
|
+
try:
|
|
15
|
+
relative_path = file_path.relative_to(project_root)
|
|
16
|
+
|
|
17
|
+
module_path = str(relative_path.with_suffix("")).replace(os.sep, ".")
|
|
18
|
+
return module_path
|
|
19
|
+
except ValueError:
|
|
20
|
+
return None
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("git_pull")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def git_pull_periodically(repo_path, interval_minutes=1, log_file=None):
|
|
11
|
+
"""
|
|
12
|
+
Performs git pull in the specified repository at regular intervals.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
repo_path (str): Path to the Git repository
|
|
16
|
+
interval_minutes (int): Time interval between pulls in minutes
|
|
17
|
+
log_file (str, optional): Path to log file. If None, logs to console.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
None
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Convert minutes to seconds
|
|
24
|
+
interval_seconds = interval_minutes * 60
|
|
25
|
+
logger.info("git_pull " + repo_path)
|
|
26
|
+
# Check if the path is a valid git repository
|
|
27
|
+
if not os.path.exists(os.path.join(repo_path, ".git")):
|
|
28
|
+
logging.error(f"The directory {repo_path} is not a valid Git repository.")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
logging.info(
|
|
32
|
+
f"Starting periodic git pull every {interval_minutes} minutes for {repo_path}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
while True:
|
|
37
|
+
try:
|
|
38
|
+
# Change to the repository directory
|
|
39
|
+
os.chdir(repo_path)
|
|
40
|
+
|
|
41
|
+
# Log the start time
|
|
42
|
+
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
43
|
+
logging.info(f"Executing git pull at {now}")
|
|
44
|
+
|
|
45
|
+
# Execute git pull
|
|
46
|
+
result = subprocess.run(
|
|
47
|
+
["git", "pull", "--recurse-submodules"], capture_output=True, text=True, check=True
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Log the output
|
|
51
|
+
logging.info(f"Git pull output: {result.stdout.strip()}")
|
|
52
|
+
if result.stderr:
|
|
53
|
+
logging.warning(f"Git pull stderr: {result.stderr.strip()}")
|
|
54
|
+
|
|
55
|
+
except subprocess.CalledProcessError as e:
|
|
56
|
+
logging.error(f"Git pull failed: {e.stderr.strip()}")
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logging.error(f"Error during git pull: {str(e)}")
|
|
59
|
+
|
|
60
|
+
# Wait for the next interval
|
|
61
|
+
logging.info(f"Waiting {interval_minutes} minutes until next pull...")
|
|
62
|
+
time.sleep(interval_seconds)
|
|
63
|
+
|
|
64
|
+
except KeyboardInterrupt:
|
|
65
|
+
logging.info("Git pull process stopped by user.")
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
import subprocess
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_git_status(repo_path: Path) -> Dict[str, Optional[object]]:
|
|
8
|
+
"""
|
|
9
|
+
Get the current Git short commit hash and whether the branch is up to date with its upstream.
|
|
10
|
+
|
|
11
|
+
Returns a dict:
|
|
12
|
+
{
|
|
13
|
+
"short_hash": Optional[str], # e.g., "a1b2c3d", None if not a git repo
|
|
14
|
+
"branch": Optional[str], # current branch name or "HEAD" if detached, None if not a repo
|
|
15
|
+
"up_to_date": Optional[bool], # True/False if upstream exists, None if unknown/no upstream
|
|
16
|
+
"ahead": Optional[int], # commits ahead of upstream (0 if up to date), None if unknown
|
|
17
|
+
"behind": Optional[int], # commits behind upstream (0 if up to date), None if unknown
|
|
18
|
+
}
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def run_git(args, check=False):
|
|
22
|
+
|
|
23
|
+
return subprocess.run(
|
|
24
|
+
["git", "-C", str(repo_path), *args],
|
|
25
|
+
capture_output=True,
|
|
26
|
+
text=True,
|
|
27
|
+
check=check,
|
|
28
|
+
timeout=30,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
result: Dict[str, Optional[object]] = {
|
|
32
|
+
"short_hash": None,
|
|
33
|
+
"branch": None,
|
|
34
|
+
"up_to_date": None,
|
|
35
|
+
"ahead": None,
|
|
36
|
+
"behind": None,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Verify we're in a git repository
|
|
40
|
+
try:
|
|
41
|
+
inside = run_git(["rev-parse", "--is-inside-work-tree"])
|
|
42
|
+
if inside.returncode != 0 or inside.stdout.strip() != "true":
|
|
43
|
+
return result # Not a repository
|
|
44
|
+
except Exception:
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
# Get short hash
|
|
48
|
+
try:
|
|
49
|
+
hash_res = run_git(["rev-parse", "--short", "HEAD"], check=True)
|
|
50
|
+
result["short_hash"] = hash_res.stdout.strip()
|
|
51
|
+
except subprocess.CalledProcessError:
|
|
52
|
+
# Detached or other state where HEAD isn't resolvable
|
|
53
|
+
result["short_hash"] = None
|
|
54
|
+
|
|
55
|
+
# Get branch (or "HEAD" if detached)
|
|
56
|
+
try:
|
|
57
|
+
branch_res = run_git(["rev-parse", "--abbrev-ref", "HEAD"], check=True)
|
|
58
|
+
result["branch"] = branch_res.stdout.strip()
|
|
59
|
+
except subprocess.CalledProcessError:
|
|
60
|
+
result["branch"] = None
|
|
61
|
+
|
|
62
|
+
# Find upstream; if none, we can't determine up-to-date status against a remote
|
|
63
|
+
try:
|
|
64
|
+
upstream_res = run_git(
|
|
65
|
+
["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]
|
|
66
|
+
)
|
|
67
|
+
if upstream_res.returncode != 0:
|
|
68
|
+
# No upstream configured
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
upstream_ref = upstream_res.stdout.strip()
|
|
72
|
+
|
|
73
|
+
# Ensure we compare against the latest remote state
|
|
74
|
+
run_git(["fetch", "--quiet"])
|
|
75
|
+
|
|
76
|
+
# Compute ahead/behind relative to upstream
|
|
77
|
+
# Format: "<behind> <ahead>" because left is upstream, right is HEAD
|
|
78
|
+
counts_res = run_git(
|
|
79
|
+
["rev-list", "--left-right", "--count", f"{upstream_ref}...HEAD"],
|
|
80
|
+
check=True,
|
|
81
|
+
)
|
|
82
|
+
behind_str, ahead_str = counts_res.stdout.strip().split()
|
|
83
|
+
behind, ahead = int(behind_str), int(ahead_str)
|
|
84
|
+
|
|
85
|
+
result["behind"] = behind
|
|
86
|
+
result["ahead"] = ahead
|
|
87
|
+
result["up_to_date"] = behind == 0 and ahead == 0
|
|
88
|
+
except subprocess.CalledProcessError:
|
|
89
|
+
# If comparison fails, leave up_to_date/ahead/behind as None
|
|
90
|
+
pass
|
|
91
|
+
except ValueError:
|
|
92
|
+
# Unexpected parsing format
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
return result
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger("import_module_from_file")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def import_module_from_file(file_path: Path, root_dir: Path):
|
|
10
|
+
"""
|
|
11
|
+
Import a Python file as a module.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
root_dir:
|
|
15
|
+
file_path: Path object pointing to the Python file to import
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Imported module or None if import failed
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
# logger.debug(f"Attempting to import module from: {file_path}")
|
|
22
|
+
if not file_path.exists():
|
|
23
|
+
print(f"File not found: {file_path}")
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
relative_path = file_path.relative_to(root_dir)
|
|
27
|
+
|
|
28
|
+
basename = relative_path.stem
|
|
29
|
+
module_name = ".".join(relative_path.parts[:-1]) + "." + basename
|
|
30
|
+
|
|
31
|
+
if root_dir not in sys.path:
|
|
32
|
+
sys.path.insert(0, str(root_dir))
|
|
33
|
+
# logger.debug(f"Added {root_dir} to sys.path")
|
|
34
|
+
# Try a simple import first
|
|
35
|
+
try:
|
|
36
|
+
if module_name is None or module_name == "":
|
|
37
|
+
raise ImportError(f"Module name is None for file: {file_path}")
|
|
38
|
+
module = importlib.import_module(module_name)
|
|
39
|
+
return module
|
|
40
|
+
except ImportError as e:
|
|
41
|
+
logger.warning(f"Direct import failed: {e} -- Trying spec-based import")
|
|
42
|
+
|
|
43
|
+
# Fall back to spec-based import
|
|
44
|
+
spec = importlib.util.spec_from_file_location(module_name, str(file_path))
|
|
45
|
+
if spec is None or spec.loader is None:
|
|
46
|
+
logger.error(f"Failed to create spec for {file_path}")
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
module = importlib.util.module_from_spec(spec)
|
|
50
|
+
sys.modules[module_name] = module
|
|
51
|
+
try:
|
|
52
|
+
spec.loader.exec_module(module)
|
|
53
|
+
return module
|
|
54
|
+
except Exception as e:
|
|
55
|
+
logger.error(f"Import error when processing module: {file_path} {e}")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.error(f"Error importing module from {file_path}: {e}")
|
|
60
|
+
import traceback
|
|
61
|
+
|
|
62
|
+
traceback.print_exc()
|
|
63
|
+
return None
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Callable, Optional, Type, cast
|
|
5
|
+
from odmantic import Model, AIOEngine, ObjectId
|
|
6
|
+
|
|
7
|
+
from simstack.core.context import context
|
|
8
|
+
from simstack.models.models import ModelMapping, NodeModel
|
|
9
|
+
from simstack.util.db import Database
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("importer")
|
|
12
|
+
|
|
13
|
+
NODES_SEARCH_BY_NAME_FALLBACK = True
|
|
14
|
+
MODELS_SEARCH_BY_NAME_FALLBACK = True
|
|
15
|
+
|
|
16
|
+
def _get_initialized_context() -> Any | None:
|
|
17
|
+
try:
|
|
18
|
+
from simstack.core.context import context
|
|
19
|
+
|
|
20
|
+
if context.initialized:
|
|
21
|
+
return context
|
|
22
|
+
except RuntimeError:
|
|
23
|
+
return None
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _context_cache_matches_engine(ctx: Any | None, db: Database | None = None) -> bool:
|
|
28
|
+
if ctx is None:
|
|
29
|
+
return False
|
|
30
|
+
if db is None:
|
|
31
|
+
return True
|
|
32
|
+
try:
|
|
33
|
+
return ctx.db is not None and db is ctx.db
|
|
34
|
+
except RuntimeError:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _resolve_engine(ctx: Any | None, engine: AIOEngine | None = None) -> AIOEngine:
|
|
39
|
+
if engine is not None:
|
|
40
|
+
return engine
|
|
41
|
+
if ctx is not None:
|
|
42
|
+
try:
|
|
43
|
+
if ctx.db is not None:
|
|
44
|
+
return cast(AIOEngine, ctx.db.core_engine)
|
|
45
|
+
except RuntimeError:
|
|
46
|
+
pass
|
|
47
|
+
raise RuntimeError(
|
|
48
|
+
"Could not resolve engine both engine and context have no engine"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _lookup_node_cache(node_mappings: Any, function_path: str) -> Optional[NodeModel]:
|
|
53
|
+
if node_mappings is None:
|
|
54
|
+
return None
|
|
55
|
+
node_model = node_mappings.get_by_mapping(function_path)
|
|
56
|
+
if node_model is None and NODES_SEARCH_BY_NAME_FALLBACK:
|
|
57
|
+
if "." in function_path:
|
|
58
|
+
_, function_name = function_path.rsplit(".", 1)
|
|
59
|
+
else:
|
|
60
|
+
function_name = function_path
|
|
61
|
+
node_model = node_mappings.get_by_name(function_name)
|
|
62
|
+
return node_model
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def _find_node_model(function_path: str, db: Database) -> Optional[NodeModel]:
|
|
66
|
+
if context.node_mappings is None:
|
|
67
|
+
await context.refresh_mappings(models=False, nodes=True)
|
|
68
|
+
|
|
69
|
+
node_model = _lookup_node_cache(context.node_mappings, function_path)
|
|
70
|
+
if node_model is not None:
|
|
71
|
+
return node_model
|
|
72
|
+
|
|
73
|
+
await context.refresh_mappings(models=False, nodes=True)
|
|
74
|
+
node_model = _lookup_node_cache(context.node_mappings, function_path)
|
|
75
|
+
if node_model is not None:
|
|
76
|
+
return node_model
|
|
77
|
+
|
|
78
|
+
node_model = await db.find_one(
|
|
79
|
+
NodeModel, NodeModel.function_mapping == function_path
|
|
80
|
+
)
|
|
81
|
+
if node_model is None and NODES_SEARCH_BY_NAME_FALLBACK:
|
|
82
|
+
if "." in function_path:
|
|
83
|
+
_, function_name = function_path.rsplit(".", 1)
|
|
84
|
+
else:
|
|
85
|
+
function_name = function_path
|
|
86
|
+
node_model = await db.find_one(NodeModel, NodeModel.name == function_name)
|
|
87
|
+
return node_model
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def _find_node_model_by_name(
|
|
91
|
+
function_name: str, db: Database
|
|
92
|
+
) -> Optional[NodeModel]:
|
|
93
|
+
if context.node_mappings is None:
|
|
94
|
+
await context.refresh_mappings(models=False, nodes=True)
|
|
95
|
+
|
|
96
|
+
node_model = context.node_mappings.get_by_name(function_name)
|
|
97
|
+
if node_model is not None:
|
|
98
|
+
return node_model
|
|
99
|
+
|
|
100
|
+
await context.refresh_mappings(models=False, nodes=True)
|
|
101
|
+
node_model = context.node_mappings.get_by_name(function_name)
|
|
102
|
+
if node_model is not None:
|
|
103
|
+
return node_model
|
|
104
|
+
return await db.find_one(NodeModel, NodeModel.name == function_name)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _lookup_model_cache(
|
|
108
|
+
model_mappings: Any, class_path: str, class_name: str
|
|
109
|
+
) -> Optional[ModelMapping]:
|
|
110
|
+
if model_mappings is None:
|
|
111
|
+
return None
|
|
112
|
+
model_mapping = None
|
|
113
|
+
if MODELS_SEARCH_BY_NAME_FALLBACK:
|
|
114
|
+
model_mapping = model_mappings.get_by_name(class_name)
|
|
115
|
+
if not model_mapping:
|
|
116
|
+
model_mapping = model_mappings.get_by_mapping(class_path)
|
|
117
|
+
return model_mapping
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async def _find_model_mapping(model_path: str, db: Database) -> Optional[ModelMapping]:
|
|
121
|
+
_, model_name = model_path.rsplit(".", 1)
|
|
122
|
+
|
|
123
|
+
if context.model_mappings is None:
|
|
124
|
+
await context.refresh_mappings(models=True, nodes=False)
|
|
125
|
+
|
|
126
|
+
model_mapping = _lookup_model_cache(context.model_mappings, model_path, model_name)
|
|
127
|
+
if model_mapping is not None:
|
|
128
|
+
return model_mapping
|
|
129
|
+
|
|
130
|
+
await context.refresh_mappings(models=True, nodes=False)
|
|
131
|
+
model_mapping = _lookup_model_cache(context.model_mappings, model_path, model_name)
|
|
132
|
+
if model_mapping is not None:
|
|
133
|
+
return model_mapping
|
|
134
|
+
|
|
135
|
+
model_mapping = None
|
|
136
|
+
if MODELS_SEARCH_BY_NAME_FALLBACK:
|
|
137
|
+
model_mapping = await db.find_one(ModelMapping, ModelMapping.name == model_name)
|
|
138
|
+
if model_mapping is None:
|
|
139
|
+
model_mapping = await db.find_one(
|
|
140
|
+
ModelMapping, ModelMapping.mapping == model_path
|
|
141
|
+
)
|
|
142
|
+
return model_mapping
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# TODO engines remove: duplicate of find_class_mapping_by_name
|
|
146
|
+
async def _find_model_mapping_by_name(
|
|
147
|
+
class_name: str, db: Database
|
|
148
|
+
) -> Optional[ModelMapping]:
|
|
149
|
+
if context.model_mappings is None:
|
|
150
|
+
await context.refresh_mappings(models=True, nodes=False)
|
|
151
|
+
|
|
152
|
+
model_mapping = context.model_mappings.get_by_name(class_name)
|
|
153
|
+
if model_mapping is not None:
|
|
154
|
+
return model_mapping
|
|
155
|
+
|
|
156
|
+
await context.refresh_mappings(models=True, nodes=False)
|
|
157
|
+
model_mapping = context.model_mappings.get_by_name(class_name)
|
|
158
|
+
if model_mapping is not None:
|
|
159
|
+
return model_mapping
|
|
160
|
+
|
|
161
|
+
return await db.find_one(ModelMapping, ModelMapping.name == class_name)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
async def _function_from_model(
|
|
165
|
+
node_model: NodeModel, task_id: ObjectId | None = None
|
|
166
|
+
) -> Callable[..., Any]:
|
|
167
|
+
"""
|
|
168
|
+
Get the function from the NodeModel. Here the mapping may already be fixed if the original mapping was wrong
|
|
169
|
+
Otherwise, it is imported from the function_mapping.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
node_model: NodeModel object
|
|
173
|
+
task_id: Optional task id
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
The function object
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
function_path = node_model.function_mapping
|
|
180
|
+
try:
|
|
181
|
+
module_path, function_name = function_path.rsplit(".", 1)
|
|
182
|
+
module = importlib.import_module(module_path)
|
|
183
|
+
return cast(Callable[..., Any], getattr(module, function_name))
|
|
184
|
+
except (ImportError, AttributeError, ValueError) as e:
|
|
185
|
+
if NODES_SEARCH_BY_NAME_FALLBACK:
|
|
186
|
+
try:
|
|
187
|
+
# Try to load by the name field which might contain the correct path
|
|
188
|
+
# if it was a name-only search that found this model.
|
|
189
|
+
if "." in node_model.name:
|
|
190
|
+
module_path, function_name = node_model.name.rsplit(".", 1)
|
|
191
|
+
module = importlib.import_module(module_path)
|
|
192
|
+
return cast(Callable[..., Any], getattr(module, function_name))
|
|
193
|
+
except (ImportError, AttributeError, ValueError):
|
|
194
|
+
pass
|
|
195
|
+
logger.error(
|
|
196
|
+
f"task_id: {task_id} Error importing function {function_path}: {e}"
|
|
197
|
+
)
|
|
198
|
+
raise e
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
async def import_function(
|
|
202
|
+
function_path: str,
|
|
203
|
+
db: Database,
|
|
204
|
+
task_id: ObjectId | None = None,
|
|
205
|
+
tolerate_missing_function: bool = False,
|
|
206
|
+
) -> Optional[Callable[..., Any]]:
|
|
207
|
+
"""
|
|
208
|
+
Dynamically import a function from a module using its full path, including a migration mechanism.
|
|
209
|
+
load the function information using NodeModel
|
|
210
|
+
load the pickled version if it exists
|
|
211
|
+
if there is no pickled version, use regular import.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
function_path: Dot notation path to the function (e.g. 'methods.submodule.function_name')
|
|
215
|
+
db: Database object
|
|
216
|
+
task_id: Optional task id
|
|
217
|
+
tolerate_missing_function: If True, return None if function is not found, otherwise raise exception
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
The imported function object or None if import fails
|
|
221
|
+
"""
|
|
222
|
+
node_model = await _find_node_model(function_path, db)
|
|
223
|
+
|
|
224
|
+
if node_model is None:
|
|
225
|
+
try:
|
|
226
|
+
module_path, function_name = function_path.rsplit(".", 1)
|
|
227
|
+
module = importlib.import_module(module_path)
|
|
228
|
+
return cast(Callable[..., Any], getattr(module, function_name))
|
|
229
|
+
except (ImportError, AttributeError, ValueError):
|
|
230
|
+
raise LookupError(
|
|
231
|
+
f"task_id: {task_id} Function {function_path} not found in the NodeModel Table"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
return await _function_from_model(node_model, task_id)
|
|
236
|
+
except Exception as e:
|
|
237
|
+
if tolerate_missing_function:
|
|
238
|
+
return None
|
|
239
|
+
else:
|
|
240
|
+
raise e
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async def import_function_by_name(
|
|
244
|
+
function_name: str, db: Database, task_id: ObjectId
|
|
245
|
+
) -> Optional[Callable[..., Any]]:
|
|
246
|
+
node_model = await _find_node_model_by_name(function_name, db)
|
|
247
|
+
|
|
248
|
+
if node_model is None:
|
|
249
|
+
logger.error(f"Could not find function mapping for name: {function_name}")
|
|
250
|
+
raise ValueError(f"Could not find function mapping for name: {function_name}")
|
|
251
|
+
|
|
252
|
+
return await _function_from_model(node_model, task_id)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
async def import_class(class_path: str, db: Database) -> Type[Model] | None:
|
|
256
|
+
"""
|
|
257
|
+
Dynamically import a class from a module using its full path.
|
|
258
|
+
First tries to load the class from the database using ModelMapping
|
|
259
|
+
|
|
260
|
+
A pickled version of the class is used primarily
|
|
261
|
+
Args:
|
|
262
|
+
|
|
263
|
+
:param class_path: class_path: Dot notation path to the class (e.g. 'models.submodule.ClassName')
|
|
264
|
+
:param db: db: Database object
|
|
265
|
+
Returns:
|
|
266
|
+
The imported class object or None if import fails
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
try:
|
|
270
|
+
# Split the path into module path and class name
|
|
271
|
+
module_path, class_name = class_path.rsplit(".", 1)
|
|
272
|
+
model_mapping = await _find_model_mapping(class_path, db)
|
|
273
|
+
|
|
274
|
+
# If not found by name, try by mapping
|
|
275
|
+
if not model_mapping:
|
|
276
|
+
model_mapping = await db.find_one(
|
|
277
|
+
ModelMapping, ModelMapping.mapping == class_path
|
|
278
|
+
)
|
|
279
|
+
else: # when searching by name, the path may have changed
|
|
280
|
+
module_path, class_name = model_mapping.mapping.rsplit(".", 1)
|
|
281
|
+
|
|
282
|
+
if model_mapping is None:
|
|
283
|
+
try:
|
|
284
|
+
# Import the module
|
|
285
|
+
module = importlib.import_module(module_path)
|
|
286
|
+
# Get the class from the module
|
|
287
|
+
return cast(Type[Model], getattr(module, class_name))
|
|
288
|
+
except (ImportError, AttributeError):
|
|
289
|
+
logger.error(f"Error finding ModelMapping for {class_name}")
|
|
290
|
+
raise LookupError(f"Error finding ModelMapping for {class_name}")
|
|
291
|
+
|
|
292
|
+
# Import the module
|
|
293
|
+
module = importlib.import_module(module_path)
|
|
294
|
+
|
|
295
|
+
# Get the class from the module
|
|
296
|
+
return cast(Type[Model], getattr(module, class_name))
|
|
297
|
+
except (ImportError, AttributeError, ValueError) as e:
|
|
298
|
+
logger.error(f"Error importing class {class_path}: {e}")
|
|
299
|
+
raise e
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
async def import_class_by_name(class_name: str, db: Database) -> Type[Model]:
|
|
303
|
+
model_mapping = await _find_model_mapping_by_name(class_name, db)
|
|
304
|
+
|
|
305
|
+
if not model_mapping:
|
|
306
|
+
logger.error(f"Error finding ModelMapping for {class_name}")
|
|
307
|
+
raise LookupError(f"Error finding ModelMapping for {class_name}")
|
|
308
|
+
|
|
309
|
+
model_class = await import_class(model_mapping.mapping, db)
|
|
310
|
+
if model_class is None:
|
|
311
|
+
raise LookupError(f"Error importing mapped model class for {class_name}")
|
|
312
|
+
return model_class
|