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/core/context.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
from urllib.parse import urlparse, urlunparse
|
|
6
|
+
|
|
7
|
+
from simstack.util.database_information import DatabaseInformation
|
|
8
|
+
from simstack.util.db import DBType
|
|
9
|
+
from simstack.util.project_root_finder import find_project_root
|
|
10
|
+
from simstack.util.toml_reader import TomlReader
|
|
11
|
+
|
|
12
|
+
from simstack.util.resource_config import ResourceConfig
|
|
13
|
+
from simstack.util.setup_logging import setup_logging
|
|
14
|
+
from simstack.util.mappings import ModelMappingTable, NodeMappingTable
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from simstack.util.db import Database
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def remove_password_from_connection_string(connection_string):
|
|
21
|
+
parsed_url = urlparse(connection_string)
|
|
22
|
+
|
|
23
|
+
# Extract username and rebuild netloc without password
|
|
24
|
+
netloc = parsed_url.hostname
|
|
25
|
+
if parsed_url.username:
|
|
26
|
+
netloc = f"{parsed_url.username}@{netloc}"
|
|
27
|
+
if parsed_url.port:
|
|
28
|
+
netloc += f":{parsed_url.port}"
|
|
29
|
+
|
|
30
|
+
clean_url = parsed_url._replace(netloc=netloc)
|
|
31
|
+
|
|
32
|
+
return urlunparse(clean_url)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def initialize_git_list(db: "Database", toml_reader: TomlReader | None):
|
|
36
|
+
from simstack.models.resource_definition import GitRepo
|
|
37
|
+
git_list = await db.find_all(GitRepo)
|
|
38
|
+
if git_list is None and toml_reader is not None:
|
|
39
|
+
git_list = toml_reader.get("parameters.common.git", [])
|
|
40
|
+
return git_list
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GlobalState:
|
|
44
|
+
_instance = None
|
|
45
|
+
_initialized = False
|
|
46
|
+
|
|
47
|
+
def __new__(cls, *args, **kwargs):
|
|
48
|
+
if not cls._instance:
|
|
49
|
+
cls._instance = super().__new__(cls)
|
|
50
|
+
cls._instance._initialized = False
|
|
51
|
+
cls._instance._db = None
|
|
52
|
+
cls._instance._log_handler = None
|
|
53
|
+
cls._instance._path_manager = None
|
|
54
|
+
cls._instance._config = None
|
|
55
|
+
cls._instance._model_mappings = None
|
|
56
|
+
cls._instance._node_mappings = None
|
|
57
|
+
cls._instance._resource_config = None
|
|
58
|
+
return cls._instance
|
|
59
|
+
|
|
60
|
+
def __init__(self, **kwargs):
|
|
61
|
+
"""Initialize GlobalState instance.
|
|
62
|
+
|
|
63
|
+
This method only runs once due to the singleton pattern.
|
|
64
|
+
Use the initialize() method to set up the instance with database settings.
|
|
65
|
+
"""
|
|
66
|
+
# We don't call self.initialize(**kwargs) here because it is async
|
|
67
|
+
# and __init__ must be sync. The user should call initialize() explicitly.
|
|
68
|
+
|
|
69
|
+
self._db = None
|
|
70
|
+
self._log_handler = None
|
|
71
|
+
self._path_manager = None
|
|
72
|
+
self._config = None
|
|
73
|
+
self._model_mappings = None
|
|
74
|
+
self._node_mappings = None
|
|
75
|
+
self._resource_config = None
|
|
76
|
+
|
|
77
|
+
def __getattribute__(self, name):
|
|
78
|
+
# These special attributes should always be accessible
|
|
79
|
+
if name in (
|
|
80
|
+
"_initialized",
|
|
81
|
+
"initialize",
|
|
82
|
+
"initialized",
|
|
83
|
+
):
|
|
84
|
+
return object.__getattribute__(self, name)
|
|
85
|
+
|
|
86
|
+
# For other attributes, check initialization
|
|
87
|
+
if not object.__getattribute__(self, "_initialized"):
|
|
88
|
+
raise RuntimeError(
|
|
89
|
+
"GlobalState must be initialized with database settings before use"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return object.__getattribute__(self, name)
|
|
93
|
+
|
|
94
|
+
async def initialize(self, **kwargs):
|
|
95
|
+
"""
|
|
96
|
+
Initializes the global state with the given configuration parameters.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
**kwargs: Arbitrary keyword arguments for configuration. The following keys are expected:
|
|
100
|
+
- project_root (str, optional): The project root directory. If not provided, it will be
|
|
101
|
+
determined using `find_project_root`.
|
|
102
|
+
- db_name (str, optional): The name of the database. Required when not using a TOML
|
|
103
|
+
configuration.
|
|
104
|
+
- connection_string (str, optional): The connection string for the database. Required
|
|
105
|
+
when not using a TOML configuration.
|
|
106
|
+
- db_type (DBType, optional): The type of the database. Required when not using a TOML
|
|
107
|
+
configuration.
|
|
108
|
+
- is_test (bool, optional): Indicates whether the initialization is for testing purposes.
|
|
109
|
+
Defaults to `False`.
|
|
110
|
+
- log_level (str, optional): Specifies the logging level. Defaults to `"INFO"`.
|
|
111
|
+
- resource (str, optional): Specifies the resource identifier for the configuration reader.
|
|
112
|
+
Defaults to `"self"`.
|
|
113
|
+
- config_file (str, optional): Specifies the path to the configuration file, defaults to simstack.toml
|
|
114
|
+
|
|
115
|
+
Logic:
|
|
116
|
+
if all values for the DB are provided in the kwargs, use the provided values
|
|
117
|
+
otherwise the TOML file in the project root is used
|
|
118
|
+
|
|
119
|
+
The project root is determined using `find_project_root`, which by default looks for a
|
|
120
|
+
set of marker files, starting from the directory of find_project root and traversing up the directory tree.
|
|
121
|
+
In normal runs, it should skip the project root of the simstack package
|
|
122
|
+
This fails in the tests of the simstack package. The trick is to determine
|
|
123
|
+
project_root manually outside the call to initialize and pass it as a kwarg.
|
|
124
|
+
|
|
125
|
+
The TOML file decides whether the database or the file is used to set the variables
|
|
126
|
+
for the specific resource
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
is_test = kwargs.get("is_test", False)
|
|
130
|
+
resource_str = kwargs.get("resource", "self")
|
|
131
|
+
|
|
132
|
+
if self._initialized:
|
|
133
|
+
# If already initialized, we just return if not in test mode,
|
|
134
|
+
if not is_test:
|
|
135
|
+
return
|
|
136
|
+
self._initialized = True # required because some later functions use context
|
|
137
|
+
|
|
138
|
+
project_root = kwargs.get("project_root", find_project_root())
|
|
139
|
+
if project_root is None: # maybe None was passed
|
|
140
|
+
project_root = find_project_root()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
project_root = Path(project_root)
|
|
144
|
+
kwargs["project_root"] = project_root
|
|
145
|
+
|
|
146
|
+
db_name: str | None = kwargs.get("db_name", None)
|
|
147
|
+
connection_string: str | None = kwargs.get("connection_string", None)
|
|
148
|
+
db_type: DBType | None = kwargs.get("db_type", None)
|
|
149
|
+
|
|
150
|
+
toml_reader = None
|
|
151
|
+
if db_name is None or connection_string is None or db_type is None:
|
|
152
|
+
# use not all info in the kwargs
|
|
153
|
+
simstack_toml_path = project_root / "simstack.toml"
|
|
154
|
+
toml_reader = TomlReader(project_root, config_file=Path(simstack_toml_path).resolve())
|
|
155
|
+
db_info = DatabaseInformation.from_config(toml_reader.config, **kwargs)
|
|
156
|
+
else:
|
|
157
|
+
db_info = DatabaseInformation(db_name, connection_string, db_type)
|
|
158
|
+
|
|
159
|
+
# check that the database can be reached and set logging up
|
|
160
|
+
self.initialize_database(db_info, is_test)
|
|
161
|
+
self.initialize_logging(db_info.connection_string, db_info.db_name, is_test, kwargs.get("log_level", "INFO"))
|
|
162
|
+
|
|
163
|
+
logger = logging.getLogger("Context")
|
|
164
|
+
if db_info.connection_string is not None:
|
|
165
|
+
db_type = DBType.MONGODB.value
|
|
166
|
+
safe_connection_string = remove_password_from_connection_string(db_info.connection_string)
|
|
167
|
+
logger.info(f"Database connection to {db_type} {safe_connection_string}/{db_name}")
|
|
168
|
+
else:
|
|
169
|
+
logger.info(f"Database connection in_memory {db_type}")
|
|
170
|
+
|
|
171
|
+
if not kwargs.get("skip_config", False):
|
|
172
|
+
await self.initialize_configs(self._db, toml_reader, **kwargs)
|
|
173
|
+
|
|
174
|
+
# Initialize memory-loaded mappings
|
|
175
|
+
await self.refresh_mappings()
|
|
176
|
+
|
|
177
|
+
self._initialized = True
|
|
178
|
+
|
|
179
|
+
async def initialize_configs(self, db, toml_reader, **kwargs):
|
|
180
|
+
project_root = kwargs["project_root"]
|
|
181
|
+
resource_str = kwargs.get("resource", "self")
|
|
182
|
+
is_test = kwargs.get("is_test", False)
|
|
183
|
+
try:
|
|
184
|
+
from simstack.util.config_reader import ConfigReader
|
|
185
|
+
from simstack.util.resource_config import ResourceConfig
|
|
186
|
+
resource_config_file = kwargs.get("config_file")
|
|
187
|
+
if resource_config_file is None:
|
|
188
|
+
resource_config_file = project_root / "config.toml"
|
|
189
|
+
else:
|
|
190
|
+
resource_config_file = Path(resource_config_file)
|
|
191
|
+
self._config = await ConfigReader.create(resource_str, db, toml_reader, **kwargs)
|
|
192
|
+
self._resource_config = ResourceConfig(resource_config_file, resource_str)
|
|
193
|
+
except Exception as e:
|
|
194
|
+
if is_test:
|
|
195
|
+
logger = logging.getLogger("Context")
|
|
196
|
+
logger.warning(f"Failed to initialize ConfigReader in test mode: {e}")
|
|
197
|
+
else:
|
|
198
|
+
raise e
|
|
199
|
+
|
|
200
|
+
async def refresh_mappings(self, *, models: bool = True, nodes: bool = True):
|
|
201
|
+
if models:
|
|
202
|
+
self._model_mappings = await ModelMappingTable.load(self._db)
|
|
203
|
+
if nodes:
|
|
204
|
+
self._node_mappings = await NodeMappingTable.load(self._db)
|
|
205
|
+
|
|
206
|
+
def initialize_logging(self, connection_string: str, db_name: str, is_test: bool, log_level: str = "INFO"):
|
|
207
|
+
if is_test:
|
|
208
|
+
# For tests, use simple console logging without the database handler
|
|
209
|
+
# We use force=True to ensure it overrides any existing configuration (e.g. from pytest or PyCharm)
|
|
210
|
+
# We also ensure the stream is sys.stderr so PyCharm/pytest can capture it properly if configured
|
|
211
|
+
logging.basicConfig(
|
|
212
|
+
level=log_level,
|
|
213
|
+
format="%(asctime)s - %(name)-15s - %(levelname)-10s - %(filename)-20s:%(lineno)4d - %(message)s",
|
|
214
|
+
stream=sys.stderr,
|
|
215
|
+
force=True,
|
|
216
|
+
)
|
|
217
|
+
self._log_handler = logging.getLogger()
|
|
218
|
+
# Flush stdout and stderr to ensure logs are not buffered
|
|
219
|
+
sys.stdout.flush()
|
|
220
|
+
sys.stderr.flush()
|
|
221
|
+
else:
|
|
222
|
+
self._log_handler = setup_logging(
|
|
223
|
+
connection_string,
|
|
224
|
+
db_name,
|
|
225
|
+
log_level,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
def initialize_database(self, db_info: DatabaseInformation, is_test: bool):
|
|
229
|
+
from simstack.util.db import Database
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
self._db = Database.from_db_info(db_info)
|
|
233
|
+
if self._db is not None and db_info.db_type == DBType.MONGODB:
|
|
234
|
+
# Only ping real MongoDB connections
|
|
235
|
+
self._db.client.admin.command("ping")
|
|
236
|
+
|
|
237
|
+
except ConnectionError as e:
|
|
238
|
+
if not is_test:
|
|
239
|
+
print(f"Could not connect to the database: {e}")
|
|
240
|
+
sys.exit(-1)
|
|
241
|
+
else:
|
|
242
|
+
# For tests, continue ignoring the database connection failure
|
|
243
|
+
print(f"Warning: Database connection failed in test mode: {e}")
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def db(self):
|
|
247
|
+
return self._db
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def config(self):
|
|
251
|
+
return self._config
|
|
252
|
+
|
|
253
|
+
@config.setter
|
|
254
|
+
def config(self, value):
|
|
255
|
+
self._config = value
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def model_mappings(self):
|
|
259
|
+
return self._model_mappings
|
|
260
|
+
|
|
261
|
+
@model_mappings.setter
|
|
262
|
+
def model_mappings(self, value):
|
|
263
|
+
self._model_mappings = value
|
|
264
|
+
|
|
265
|
+
@property
|
|
266
|
+
def node_mappings(self):
|
|
267
|
+
return self._node_mappings
|
|
268
|
+
|
|
269
|
+
@node_mappings.setter
|
|
270
|
+
def node_mappings(self, value):
|
|
271
|
+
self._node_mappings = value
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def resource_config(self) -> "ResourceConfig":
|
|
275
|
+
return self._resource_config
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def initialized(self):
|
|
279
|
+
return self._initialized
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# Create the singleton instance, but it's not initialized yet
|
|
283
|
+
context = GlobalState()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class TaskStatus(str, Enum):
|
|
5
|
+
SUBMITTED = "submitted"
|
|
6
|
+
RUNNING = "running"
|
|
7
|
+
SLURM_QUEUED = "slurm_queued"
|
|
8
|
+
SLURM_RUNNING = "slurm_running"
|
|
9
|
+
RETRIEVED = "retrieved"
|
|
10
|
+
TIME_OUT = "timeout"
|
|
11
|
+
FAILED = "failed"
|
|
12
|
+
COMPLETED = "completed"
|
|
13
|
+
RECOVERED = "recovered"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# TODO: eliminate
|
|
17
|
+
class DBType(str, Enum):
|
|
18
|
+
SQLITE = "sqlite"
|
|
19
|
+
MONGODB = "mongodb"
|
|
20
|
+
POSTGRES = "postgres"
|
|
21
|
+
IN_MEMORY = "in_memory"
|
|
22
|
+
WITH_PATH = "with_path"
|
simstack/core/engine.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from contextvars import ContextVar
|
|
2
|
+
from typing import Optional, Any, Iterable
|
|
3
|
+
from odmantic import AIOEngine
|
|
4
|
+
|
|
5
|
+
# TDOD engine remove AIOEngineProxy
|
|
6
|
+
class AIOEngineProxy(AIOEngine):
|
|
7
|
+
"""
|
|
8
|
+
A proxy engine that inherits all behavior from AIOEngine but overrides `save`.
|
|
9
|
+
|
|
10
|
+
The overridden `save` will:
|
|
11
|
+
- If the model has a `save` member function, call it with the engine instance.
|
|
12
|
+
- Else, if any direct part/attribute of the model has a `save` member function, call those with the engine instance.
|
|
13
|
+
- Otherwise, fall back to the original AIOEngine.save implementation.
|
|
14
|
+
|
|
15
|
+
Notes:
|
|
16
|
+
- Supports single model instances or iterables of model instances (list/tuple/set).
|
|
17
|
+
- Avoids recursion by delegating to super().save for the fallback.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
async def save(self, obj: Any, *args, **kwargs) -> Any: # type: ignore[override]
|
|
21
|
+
# Handle collections of models
|
|
22
|
+
if isinstance(obj, (list, tuple, set)):
|
|
23
|
+
results = []
|
|
24
|
+
for item in obj:
|
|
25
|
+
results.append(await self._save_one(item, *args, **kwargs))
|
|
26
|
+
return results
|
|
27
|
+
# Single model
|
|
28
|
+
return await self._save_one(obj, *args, **kwargs)
|
|
29
|
+
|
|
30
|
+
async def save_unchecked(self, obj: Any, *args, **kwargs) -> Any:
|
|
31
|
+
"""
|
|
32
|
+
Save without checking for custom save methods.
|
|
33
|
+
This is a direct call to the original AIOEngine.save.
|
|
34
|
+
"""
|
|
35
|
+
return await super().save(obj, *args, **kwargs)
|
|
36
|
+
|
|
37
|
+
async def _save_one(self, model: Any, *args, **kwargs) -> Any:
|
|
38
|
+
# 1) Try model's own `save`
|
|
39
|
+
if await self._maybe_call_custom_save(model):
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
# 2) Try parts' `save` (shallow scan of attributes and common containers)
|
|
43
|
+
parts_saved = await self._call_parts_saves(model)
|
|
44
|
+
|
|
45
|
+
# 3) If neither the model nor any part handled saving, fallback to AIOEngine
|
|
46
|
+
if not parts_saved:
|
|
47
|
+
return await super().save(model, *args, **kwargs)
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
async def _maybe_call_custom_save(self, target: Any) -> bool:
|
|
51
|
+
"""
|
|
52
|
+
If `target` has a callable `save`, invoke it with this engine.
|
|
53
|
+
Tries positional engine and keyword engine forms, awaits if coroutine.
|
|
54
|
+
Returns True if a save was invoked, False otherwise.
|
|
55
|
+
"""
|
|
56
|
+
if not hasattr(target, "save"):
|
|
57
|
+
return False
|
|
58
|
+
save_attr = getattr(target, "save")
|
|
59
|
+
if not callable(save_attr):
|
|
60
|
+
raise AttributeError(f"Model {target} has no callable `save` method")
|
|
61
|
+
return await target.save(self)
|
|
62
|
+
|
|
63
|
+
async def _call_parts_saves(self, model: Any) -> bool:
|
|
64
|
+
"""
|
|
65
|
+
Inspect immediate parts/attributes of the model and call their `save` if present.
|
|
66
|
+
Handles:
|
|
67
|
+
- Direct attributes from vars(model)
|
|
68
|
+
- Items inside lists/tuples/sets
|
|
69
|
+
- Values of dicts
|
|
70
|
+
Returns True if at least one part save was called.
|
|
71
|
+
"""
|
|
72
|
+
any_saved = False
|
|
73
|
+
|
|
74
|
+
def iter_parts(root: Any) -> Iterable[Any]:
|
|
75
|
+
# Direct attributes
|
|
76
|
+
try:
|
|
77
|
+
for v in vars(root).values():
|
|
78
|
+
yield v
|
|
79
|
+
except TypeError:
|
|
80
|
+
# Objects without __dict__
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
# For convenience, also scan common container-typed attributes one level deep
|
|
84
|
+
try:
|
|
85
|
+
for v in vars(root).values():
|
|
86
|
+
if isinstance(v, (list, tuple, set)):
|
|
87
|
+
for item in v:
|
|
88
|
+
yield item
|
|
89
|
+
elif isinstance(v, dict):
|
|
90
|
+
for item in v.values():
|
|
91
|
+
yield item
|
|
92
|
+
except TypeError:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
seen_ids = set()
|
|
96
|
+
for part in iter_parts(model):
|
|
97
|
+
pid = id(part)
|
|
98
|
+
if pid in seen_ids:
|
|
99
|
+
continue
|
|
100
|
+
seen_ids.add(pid)
|
|
101
|
+
|
|
102
|
+
if await self._maybe_call_custom_save(part):
|
|
103
|
+
any_saved = True
|
|
104
|
+
|
|
105
|
+
return any_saved
|
|
106
|
+
|
|
107
|
+
#
|
|
108
|
+
# current_engine_context: ContextVar[Optional[AIOEngineProxy]] = ContextVar(
|
|
109
|
+
# "current_engine", default=None
|
|
110
|
+
# )
|
|
111
|
+
#
|
|
112
|
+
#
|
|
113
|
+
# def get_current_engine_from_context() -> Optional[AIOEngineProxy]:
|
|
114
|
+
# """Get the current engine from context"""
|
|
115
|
+
# return current_engine_context.get()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import pkgutil
|
|
3
|
+
from importlib.metadata import entry_points
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
|
|
6
|
+
logger = getLogger("find_simstack_modules")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def walk_packages(package_name, all_modules):
|
|
10
|
+
"""Walk through all packages and modules."""
|
|
11
|
+
try:
|
|
12
|
+
package = importlib.import_module(package_name)
|
|
13
|
+
package_path = package.__path__
|
|
14
|
+
|
|
15
|
+
logger.info(f"Package: {package_name}")
|
|
16
|
+
# print(f"Path: {package_path}")
|
|
17
|
+
|
|
18
|
+
for importer, modname, ispkg in pkgutil.walk_packages(
|
|
19
|
+
package_path, prefix=f"{package_name}."
|
|
20
|
+
):
|
|
21
|
+
if ispkg:
|
|
22
|
+
logger.debug(f" Subpackage: {modname}")
|
|
23
|
+
else:
|
|
24
|
+
logger.debug(f" Module: {modname}")
|
|
25
|
+
# Split module name by periods
|
|
26
|
+
all_modules.append(modname)
|
|
27
|
+
|
|
28
|
+
except Exception as e:
|
|
29
|
+
logger.error(f"Error walking {package_name}: {e}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def find_simstack_modules():
|
|
33
|
+
"""Find all packages and modules within the simstack package."""
|
|
34
|
+
|
|
35
|
+
all_modules = []
|
|
36
|
+
|
|
37
|
+
entry_point_list = entry_points(group="simstack.modules")
|
|
38
|
+
logger.info(f"Entry points for simstack modules: {entry_point_list}.")
|
|
39
|
+
for entry_point in entry_point_list:
|
|
40
|
+
walk_packages(entry_point.value, all_modules)
|
|
41
|
+
return all_modules
|
simstack/core/hash.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import inspect
|
|
3
|
+
from collections.abc import Callable, Iterable, Mapping
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
from odmantic import ObjectId
|
|
7
|
+
|
|
8
|
+
max_iterable_hash_count = 10000
|
|
9
|
+
|
|
10
|
+
hash_exclusions = [
|
|
11
|
+
"pandas",
|
|
12
|
+
"numpy",
|
|
13
|
+
"ase",
|
|
14
|
+
"indigo",
|
|
15
|
+
"openbabel",
|
|
16
|
+
"pybel",
|
|
17
|
+
"pymatgen",
|
|
18
|
+
"ase",
|
|
19
|
+
"numpy",
|
|
20
|
+
"sqlalchemy",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
HashResult = str | int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_iterable(obj: Any) -> bool:
|
|
28
|
+
try:
|
|
29
|
+
iter(obj)
|
|
30
|
+
return True
|
|
31
|
+
except TypeError:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_primitive_type(obj: Any) -> bool:
|
|
36
|
+
return isinstance(obj, (int, float, bytes, bool, bytearray, type(None)))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def hash_value(value: Any) -> str:
|
|
40
|
+
return hashlib.sha256(str(value).encode()).hexdigest()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def hash_class_def(cls: type[Any]) -> str:
|
|
44
|
+
try:
|
|
45
|
+
source_code = inspect.getsource(cls)
|
|
46
|
+
source_hash = hashlib.sha256(source_code.encode("utf-8")).hexdigest()
|
|
47
|
+
return source_hash
|
|
48
|
+
except Exception:
|
|
49
|
+
mro = cls.__mro__
|
|
50
|
+
for mro_class in mro:
|
|
51
|
+
try:
|
|
52
|
+
source_code = inspect.getsource(mro_class)
|
|
53
|
+
source_hash = hashlib.sha256(source_code.encode("utf-8")).hexdigest()
|
|
54
|
+
return source_hash
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
return "no source code"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def hash_iterable(iterable: Iterable[Any]) -> str:
|
|
61
|
+
hash_text = ""
|
|
62
|
+
for count, item in enumerate(iterable):
|
|
63
|
+
hash_text += str(complex_hash_function(item))
|
|
64
|
+
if count > max_iterable_hash_count:
|
|
65
|
+
break
|
|
66
|
+
return hashlib.sha256(hash_text.encode("utf-8")).hexdigest()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def hash_function_body(func: Callable[..., Any]) -> str:
|
|
70
|
+
# Get the source code of the function
|
|
71
|
+
source_code = inspect.getsource(func)
|
|
72
|
+
# Compute the hash of the source code
|
|
73
|
+
hash_object = hashlib.sha256(source_code.encode("utf-8"))
|
|
74
|
+
hash_digest = hash_object.hexdigest()
|
|
75
|
+
return hash_digest
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def hash_non_callable_members(instance: Any) -> dict[str, str]:
|
|
79
|
+
hashed_values: dict[str, str] = {}
|
|
80
|
+
for attr_name, attr_value in vars(instance).items():
|
|
81
|
+
if not callable(attr_value):
|
|
82
|
+
hashed_values[attr_name] = hashlib.sha256(
|
|
83
|
+
str(attr_value).encode()
|
|
84
|
+
).hexdigest()
|
|
85
|
+
return hashed_values
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ComplexHash:
|
|
89
|
+
def __init__(self, obj: Any) -> None:
|
|
90
|
+
self.hash_history: list[Any] = []
|
|
91
|
+
|
|
92
|
+
def hash_dict(self, obj: Mapping[Any, Any]) -> str:
|
|
93
|
+
hashed_values: dict[Any, HashResult] = {}
|
|
94
|
+
for k, v in obj.items():
|
|
95
|
+
hashed_values[k] = self.complex_hash(v)
|
|
96
|
+
combined_hash = "".join(f"{k}:{v}" for k, v in sorted(hashed_values.items()))
|
|
97
|
+
return hashlib.sha256(combined_hash.encode("utf-8")).hexdigest()
|
|
98
|
+
|
|
99
|
+
def hash_class(self, cls_obj: Any) -> str:
|
|
100
|
+
# check is the class name starts with a name in the hash_exclusions list
|
|
101
|
+
class_type = cls_obj.__class__
|
|
102
|
+
class_name = class_type.__module__ + "." + class_type.__name__
|
|
103
|
+
if class_type.__module__ == "builtins":
|
|
104
|
+
return "builtin"
|
|
105
|
+
if any([class_name.startswith(exclusion) for exclusion in hash_exclusions]):
|
|
106
|
+
return "excluded"
|
|
107
|
+
# print("hashing class", cls_obj.__class__.__name__)
|
|
108
|
+
class_hash = hash_class_def(cls_obj.__class__)
|
|
109
|
+
# TODO is __dict__ better than vars ?
|
|
110
|
+
# dict_hash = self.hash_dict(vars(cls_obj))
|
|
111
|
+
|
|
112
|
+
obj_dict = cls_obj.__dict__.copy()
|
|
113
|
+
if hasattr(cls_obj, "__class__") and hasattr(cls_obj.__class__, "__bases__"):
|
|
114
|
+
for base in cls_obj.__class__.__bases__:
|
|
115
|
+
if base.__name__ == "Model" and "odmantic.model" in str(
|
|
116
|
+
base.__module__
|
|
117
|
+
):
|
|
118
|
+
if "id" in obj_dict:
|
|
119
|
+
del obj_dict["id"]
|
|
120
|
+
|
|
121
|
+
dict_hash = self.hash_dict(obj_dict)
|
|
122
|
+
if hasattr(cls_obj, "model_extra") and cls_obj.model_extra is not None:
|
|
123
|
+
dict_hash = dict_hash + self.hash_dict(cls_obj.model_extra)
|
|
124
|
+
combined_hash = class_hash + dict_hash
|
|
125
|
+
return hashlib.sha256(combined_hash.encode("utf-8")).hexdigest()
|
|
126
|
+
|
|
127
|
+
def complex_hash(self, obj: Any) -> HashResult:
|
|
128
|
+
# TODO: are the functions of a class hashed correctly?
|
|
129
|
+
|
|
130
|
+
if isinstance(obj, type):
|
|
131
|
+
return hash_class_def(obj)
|
|
132
|
+
elif is_primitive_type(obj):
|
|
133
|
+
return hash(obj)
|
|
134
|
+
elif isinstance(obj, ObjectId):
|
|
135
|
+
return str(obj)
|
|
136
|
+
elif isinstance(
|
|
137
|
+
obj, str
|
|
138
|
+
): # strings are iterable but we want to hash them directly
|
|
139
|
+
return hash_value(obj)
|
|
140
|
+
elif inspect.isfunction(obj):
|
|
141
|
+
return hash_function_body(obj)
|
|
142
|
+
elif inspect.ismethod(obj):
|
|
143
|
+
if hasattr(obj, "__self__"):
|
|
144
|
+
if obj in self.hash_history:
|
|
145
|
+
return "recursive"
|
|
146
|
+
self.hash_history.append(obj)
|
|
147
|
+
if hasattr(obj, "complex_hash"):
|
|
148
|
+
return cast(HashResult, obj.complex_hash())
|
|
149
|
+
return self.hash_class(obj.__self__)
|
|
150
|
+
else:
|
|
151
|
+
return hash_function_body(obj)
|
|
152
|
+
# this should be instantiated classes
|
|
153
|
+
elif hasattr(obj, "__dict__"):
|
|
154
|
+
if obj in self.hash_history:
|
|
155
|
+
return "recursive"
|
|
156
|
+
self.hash_history.append(obj)
|
|
157
|
+
if hasattr(obj, "complex_hash"):
|
|
158
|
+
return cast(HashResult, obj.complex_hash())
|
|
159
|
+
return self.hash_class(obj)
|
|
160
|
+
elif isinstance(obj, dict):
|
|
161
|
+
return self.hash_dict(obj)
|
|
162
|
+
elif is_iterable(obj):
|
|
163
|
+
hashed_values: dict[int, HashResult] = {}
|
|
164
|
+
count = 0
|
|
165
|
+
for i, item in enumerate(obj):
|
|
166
|
+
hashed_values[i] = self.complex_hash(item)
|
|
167
|
+
count += 1
|
|
168
|
+
if count > max_iterable_hash_count:
|
|
169
|
+
break
|
|
170
|
+
combined_hash = "".join(
|
|
171
|
+
f"{k}:{v}" for k, v in sorted(hashed_values.items())
|
|
172
|
+
)
|
|
173
|
+
return hashlib.sha256(combined_hash.encode("utf-8")).hexdigest()
|
|
174
|
+
else:
|
|
175
|
+
return hash_value(obj)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def complex_hash_function(obj: Any) -> HashResult:
|
|
179
|
+
return ComplexHash(obj).complex_hash(obj)
|