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,517 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Callable, List, get_type_hints, Dict, Any, Type
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from simstack.core.simstack_result import SimstackResult
|
|
9
|
+
from simstack.core.context import context
|
|
10
|
+
from simstack.tables.node_children import update_node_children
|
|
11
|
+
from simstack.models import Parameters
|
|
12
|
+
from simstack.models.models import NodeModel, ModelMapping, DataMapping
|
|
13
|
+
from simstack.tables.table_builder import TableBuilderBase
|
|
14
|
+
from simstack.util.db import Database
|
|
15
|
+
from simstack.util.docstring_parser import DocstringParser
|
|
16
|
+
from simstack.util.importer import import_class_by_name
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("NodeTable")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_node_function(func: Callable[..., Any]) -> bool:
|
|
22
|
+
"""Check if a function is marked as a node using the @node decorator."""
|
|
23
|
+
return hasattr(func, "_is_node") and getattr(func, "_is_node", False) is True
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CreateNodeTable(TableBuilderBase):
|
|
27
|
+
"""
|
|
28
|
+
Helper class to build the node table without passing around many parameters.
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
creator = CreateNodeTable(database)
|
|
32
|
+
await creator.make_node_table()
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def logger(self) -> logging.Logger:
|
|
37
|
+
return logger
|
|
38
|
+
|
|
39
|
+
async def build(self, *args, **kwargs) -> None:
|
|
40
|
+
if not context.initialized:
|
|
41
|
+
await context.initialize()
|
|
42
|
+
await context.refresh_mappings(models=True, nodes=False)
|
|
43
|
+
await super().build(*args, **kwargs)
|
|
44
|
+
await context.refresh_mappings(models=False, nodes=True)
|
|
45
|
+
|
|
46
|
+
async def _process_module(self, module: Any, drops: str) -> None:
|
|
47
|
+
await self._register_nodes_from_module(module, drops)
|
|
48
|
+
|
|
49
|
+
def _discover_module_functions(
|
|
50
|
+
self, module: Any
|
|
51
|
+
) -> List[tuple[str, Callable[..., Any]]]:
|
|
52
|
+
"""
|
|
53
|
+
Return (name, func) for functions that are defined in `module` (not imported).
|
|
54
|
+
"""
|
|
55
|
+
functions: List[tuple[str, Callable[..., Any]]] = inspect.getmembers(
|
|
56
|
+
module, inspect.isfunction
|
|
57
|
+
)
|
|
58
|
+
module_name = module.__name__
|
|
59
|
+
return [
|
|
60
|
+
(func_name, func)
|
|
61
|
+
for func_name, func in functions
|
|
62
|
+
if func.__module__ == module_name
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
def _build_inputs(
|
|
66
|
+
self,
|
|
67
|
+
sig: inspect.Signature,
|
|
68
|
+
type_hints: Dict[str, Any],
|
|
69
|
+
doc_params: Dict[str, Any] | None,
|
|
70
|
+
) -> List[Dict[str, Any]]:
|
|
71
|
+
inputs: List[Dict[str, Any]] = []
|
|
72
|
+
for param_name, param in sig.parameters.items():
|
|
73
|
+
if param_name == "self": # Skip self parameter for methods
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
param_info: Dict[str, Any] = {
|
|
78
|
+
"name": param_name,
|
|
79
|
+
"type": type_hints.get(param_name, param.annotation.__name__),
|
|
80
|
+
"type_str": str(
|
|
81
|
+
type_hints.get(
|
|
82
|
+
param_name,
|
|
83
|
+
param.annotation.__name__
|
|
84
|
+
if param.annotation != inspect.Parameter.empty
|
|
85
|
+
else "Any",
|
|
86
|
+
)
|
|
87
|
+
),
|
|
88
|
+
}
|
|
89
|
+
except AttributeError as e:
|
|
90
|
+
logger.error(f"Could not parse type for {param_name}: {e}")
|
|
91
|
+
param_info = {}
|
|
92
|
+
|
|
93
|
+
if doc_params and param_name in doc_params:
|
|
94
|
+
param_info["description"] = doc_params[param_name].get("description")
|
|
95
|
+
|
|
96
|
+
if param.default != inspect.Parameter.empty:
|
|
97
|
+
param_info["default"] = param.default
|
|
98
|
+
|
|
99
|
+
inputs.append(param_info)
|
|
100
|
+
|
|
101
|
+
return inputs
|
|
102
|
+
|
|
103
|
+
def _parse_generic_type(self, type_str: str) -> tuple[str | None, str | None]:
|
|
104
|
+
"""
|
|
105
|
+
Parse generic types like List[X] or Dict[str,X] and extract the inner type.
|
|
106
|
+
Returns (wrapper, inner_type) where wrapper is 'List' or 'Dict' and inner_type is the model type.
|
|
107
|
+
"""
|
|
108
|
+
list_match = re.match(r"List\[(.*?)\]", type_str)
|
|
109
|
+
if list_match:
|
|
110
|
+
return "List", list_match.group(1)
|
|
111
|
+
|
|
112
|
+
dict_match = re.match(r"Dict\[str,\s*(.*?)\]", type_str)
|
|
113
|
+
if dict_match:
|
|
114
|
+
return "Dict", dict_match.group(1)
|
|
115
|
+
|
|
116
|
+
return None, None
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _normalize_docstring_type(type_str: str) -> str:
|
|
120
|
+
"""Strip top-level docstring qualifiers like ', optional' from a parsed type string."""
|
|
121
|
+
normalized = (type_str or "").strip()
|
|
122
|
+
bracket_depth = 0
|
|
123
|
+
|
|
124
|
+
for index, char in enumerate(normalized):
|
|
125
|
+
if char == "[":
|
|
126
|
+
bracket_depth += 1
|
|
127
|
+
elif char == "]" and bracket_depth > 0:
|
|
128
|
+
bracket_depth -= 1
|
|
129
|
+
elif char == "," and bracket_depth == 0:
|
|
130
|
+
return normalized[:index].strip()
|
|
131
|
+
|
|
132
|
+
return normalized
|
|
133
|
+
|
|
134
|
+
async def _build_outputs(
|
|
135
|
+
self,
|
|
136
|
+
func_name: str,
|
|
137
|
+
type_hints: Dict[str, Any],
|
|
138
|
+
parser: DocstringParser,
|
|
139
|
+
drops: str,
|
|
140
|
+
) -> List[DataMapping]:
|
|
141
|
+
outputs: List[Dict[str, Any]] = []
|
|
142
|
+
return_type = type_hints.get("return", None)
|
|
143
|
+
doc_returns = parser.returns()
|
|
144
|
+
if return_type and return_type != type(None): # Check for actual return type
|
|
145
|
+
output_info: Dict[str, Any] = {
|
|
146
|
+
"name": "result",
|
|
147
|
+
"type_str": str(return_type),
|
|
148
|
+
"type": return_type,
|
|
149
|
+
}
|
|
150
|
+
if doc_returns:
|
|
151
|
+
output_info["description"] = doc_returns.get("description")
|
|
152
|
+
outputs.append(output_info)
|
|
153
|
+
|
|
154
|
+
returns_simstack_result = any(
|
|
155
|
+
output["type"] == SimstackResult for output in outputs
|
|
156
|
+
)
|
|
157
|
+
result_mappings = []
|
|
158
|
+
db = context.db
|
|
159
|
+
if returns_simstack_result:
|
|
160
|
+
if len(outputs) > 1:
|
|
161
|
+
logger.warning(
|
|
162
|
+
f"Node {func_name} returns more than one output, one of which is SimstackResult"
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
doc_simstack_result = parser.simstack_results()
|
|
166
|
+
if doc_simstack_result is None:
|
|
167
|
+
logger.warning(f"The docstring of {func_name} does not defines its SimstackResult outputs")
|
|
168
|
+
else:
|
|
169
|
+
for name, data in doc_simstack_result.items():
|
|
170
|
+
output_mapping = None
|
|
171
|
+
output_type = self._normalize_docstring_type(data["type"])
|
|
172
|
+
try:
|
|
173
|
+
wrapper, inner_type_str = self._parse_generic_type(
|
|
174
|
+
output_type
|
|
175
|
+
)
|
|
176
|
+
if wrapper:
|
|
177
|
+
if inner_type_str is None:
|
|
178
|
+
raise ValueError(
|
|
179
|
+
f"Could not parse inner type for '{output_type}'"
|
|
180
|
+
)
|
|
181
|
+
# Handle List[type] or Dict[str,type]
|
|
182
|
+
inner_mapping = None
|
|
183
|
+
if "." in inner_type_str:
|
|
184
|
+
inner_mapping = inner_type_str
|
|
185
|
+
else:
|
|
186
|
+
try:
|
|
187
|
+
inner_model = await import_class_by_name(
|
|
188
|
+
inner_type_str, db
|
|
189
|
+
)
|
|
190
|
+
except (ValueError, LookupError):
|
|
191
|
+
inner_model = None
|
|
192
|
+
if inner_model is not None:
|
|
193
|
+
inner_mapping = self.get_class_mapping(
|
|
194
|
+
inner_model, drops
|
|
195
|
+
)
|
|
196
|
+
if inner_mapping is not None:
|
|
197
|
+
if wrapper == "List":
|
|
198
|
+
output_mapping = f"List[{inner_mapping}]"
|
|
199
|
+
else: # Dict
|
|
200
|
+
output_mapping = f"Dict[str,{inner_mapping}]"
|
|
201
|
+
elif "." in output_type:
|
|
202
|
+
# It's a full mapping path
|
|
203
|
+
output_mapping = output_type
|
|
204
|
+
else:
|
|
205
|
+
# It's a single class name
|
|
206
|
+
try:
|
|
207
|
+
output_model = await import_class_by_name(
|
|
208
|
+
output_type, db
|
|
209
|
+
)
|
|
210
|
+
output_mapping = self.get_class_mapping(
|
|
211
|
+
output_model, drops
|
|
212
|
+
)
|
|
213
|
+
except (ValueError, LookupError) as e:
|
|
214
|
+
logger.error(
|
|
215
|
+
f"Could not parse '{data['type']}' to mapping: {e}"
|
|
216
|
+
)
|
|
217
|
+
output_mapping = None
|
|
218
|
+
if output_mapping is not None:
|
|
219
|
+
result_mappings.append(
|
|
220
|
+
DataMapping(
|
|
221
|
+
name=name,
|
|
222
|
+
mapping=output_mapping,
|
|
223
|
+
description=data.get("description"),
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
except (ValueError, LookupError) as e:
|
|
227
|
+
logger.error(
|
|
228
|
+
f"Could not parse '{data['type']}' to mapping: {e}"
|
|
229
|
+
)
|
|
230
|
+
else: # not a SimstackResult
|
|
231
|
+
for output in outputs:
|
|
232
|
+
try:
|
|
233
|
+
output_type = output["type"]
|
|
234
|
+
if isinstance(output_type, str):
|
|
235
|
+
output_mapping = output_type
|
|
236
|
+
if " | None" in output_mapping:
|
|
237
|
+
output_mapping = output_mapping.replace(" | None", "")
|
|
238
|
+
else:
|
|
239
|
+
# Handle Optional[T] / T | None
|
|
240
|
+
from typing import get_args, get_origin, Union
|
|
241
|
+
import types
|
|
242
|
+
origin = get_origin(output_type)
|
|
243
|
+
if origin is types.UnionType or origin is Union:
|
|
244
|
+
args = get_args(output_type)
|
|
245
|
+
if type(None) in args:
|
|
246
|
+
# It's an Optional, take the first non-None argument
|
|
247
|
+
output_type = next(arg for arg in args if arg is not type(None))
|
|
248
|
+
output_mapping = self.get_class_mapping(output_type, drops)
|
|
249
|
+
result_mappings.append(
|
|
250
|
+
DataMapping(
|
|
251
|
+
name=output["name"],
|
|
252
|
+
mapping=output_mapping,
|
|
253
|
+
description=output.get("description"),
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
except ValueError:
|
|
257
|
+
logger.error(f"Could not parse '{output['type']}' to mapping in {func_name}")
|
|
258
|
+
return result_mappings
|
|
259
|
+
|
|
260
|
+
def _extract_default_parameters(self, func: Callable[..., Any]) -> Parameters:
|
|
261
|
+
"""
|
|
262
|
+
Best-effort extraction of node Parameters from either a direct attribute
|
|
263
|
+
or from closure variables. Always returns a non-None Parameters().
|
|
264
|
+
"""
|
|
265
|
+
parameters = Parameters()
|
|
266
|
+
|
|
267
|
+
if hasattr(func, "_node_parameters"):
|
|
268
|
+
return func._node_parameters
|
|
269
|
+
|
|
270
|
+
closures = inspect.getclosurevars(func)
|
|
271
|
+
for name, values in closures._asdict().items():
|
|
272
|
+
if name == "nonlocals":
|
|
273
|
+
continue
|
|
274
|
+
if isinstance(values, dict):
|
|
275
|
+
kwargs_node = values.get("kwargs_node", None)
|
|
276
|
+
if kwargs_node and "parameters" in kwargs_node:
|
|
277
|
+
return kwargs_node["parameters"]
|
|
278
|
+
|
|
279
|
+
return parameters
|
|
280
|
+
|
|
281
|
+
async def _delete_existing_node_model_if_needed(
|
|
282
|
+
self,
|
|
283
|
+
node_name: str,
|
|
284
|
+
function_mapping: str,
|
|
285
|
+
) -> tuple[bool, bool]:
|
|
286
|
+
"""
|
|
287
|
+
If a NodeModel with the same name exists, delete it (and its pickle if present),
|
|
288
|
+
but preserve the 'favorite' flag for the new entry.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
(should_skip, existing_favorite)
|
|
292
|
+
"""
|
|
293
|
+
try:
|
|
294
|
+
existing_model = await self.db.find_one(
|
|
295
|
+
NodeModel, NodeModel.name == node_name
|
|
296
|
+
)
|
|
297
|
+
except Exception as e:
|
|
298
|
+
logger.error(f"Error finding existing NodeModel {node_name}: {e}")
|
|
299
|
+
return False, False
|
|
300
|
+
|
|
301
|
+
if not existing_model:
|
|
302
|
+
return False, False
|
|
303
|
+
|
|
304
|
+
if function_mapping != existing_model.function_mapping:
|
|
305
|
+
logger.error(
|
|
306
|
+
f"NodeModel '{node_name}' already exists in the database\n"
|
|
307
|
+
f" DB Mapping: {existing_model.function_mapping}\n"
|
|
308
|
+
f" New Mapping: {function_mapping}\n"
|
|
309
|
+
f"New Mapping will overwrite DB Mapping."
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
existing_favorite = getattr(existing_model, "favorite", False)
|
|
313
|
+
|
|
314
|
+
if getattr(existing_model, "pickle_function", None):
|
|
315
|
+
try:
|
|
316
|
+
await self.db.delete(existing_model.pickle_function)
|
|
317
|
+
except Exception as e:
|
|
318
|
+
logger.error(f"Error deleting FunctionPickle for {node_name}: {e}")
|
|
319
|
+
|
|
320
|
+
try:
|
|
321
|
+
await self.db.delete(existing_model)
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.error(f"Error deleting existing NodeModel {node_name}: {e}")
|
|
324
|
+
|
|
325
|
+
return False, existing_favorite
|
|
326
|
+
|
|
327
|
+
async def _resolve_input_mappings(
|
|
328
|
+
self,
|
|
329
|
+
node_name: str,
|
|
330
|
+
inputs: List[Dict[str, Any]],
|
|
331
|
+
drops: str,
|
|
332
|
+
) -> List[str]:
|
|
333
|
+
"""
|
|
334
|
+
Convert input python types to ModelMapping.mapping strings and validate their existence.
|
|
335
|
+
"""
|
|
336
|
+
input_mappings: List[str] = []
|
|
337
|
+
if not inputs:
|
|
338
|
+
return input_mappings
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
for specific_input in inputs:
|
|
342
|
+
if (
|
|
343
|
+
specific_input.get("type")
|
|
344
|
+
and hasattr(specific_input["type"], "__module__")
|
|
345
|
+
and hasattr(specific_input["type"], "__name__")
|
|
346
|
+
):
|
|
347
|
+
input_mapping = (
|
|
348
|
+
specific_input["type"].__module__
|
|
349
|
+
+ "."
|
|
350
|
+
+ specific_input["type"].__name__
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if drops and input_mapping.startswith(drops + "."):
|
|
354
|
+
input_mapping = input_mapping[len(drops) + 1 :]
|
|
355
|
+
|
|
356
|
+
input_mapping_found = await self.db.find_one(
|
|
357
|
+
ModelMapping, ModelMapping.mapping == input_mapping
|
|
358
|
+
)
|
|
359
|
+
if not input_mapping_found and input_mapping:
|
|
360
|
+
logger.error(
|
|
361
|
+
f"Processing node: {node_name} model {input_mapping} not found in db!"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
input_mappings.append(input_mapping)
|
|
365
|
+
except Exception as e:
|
|
366
|
+
logger.error(f"Error getting input mapping: {e}")
|
|
367
|
+
|
|
368
|
+
return input_mappings
|
|
369
|
+
|
|
370
|
+
def get_class_mapping(self, type: Type, drops: str = "") -> str:
|
|
371
|
+
"""Return the class mapping for a given type, optionally dropping a prefix."""
|
|
372
|
+
if hasattr(type, "__module__") and hasattr(type, "__name__"):
|
|
373
|
+
mapping = type.__module__ + "." + type.__name__
|
|
374
|
+
if drops and mapping.startswith(drops + "."):
|
|
375
|
+
mapping = mapping[len(drops) + 1 :]
|
|
376
|
+
return mapping
|
|
377
|
+
else:
|
|
378
|
+
raise ValueError(f"Could not parse '{type}' to mapping")
|
|
379
|
+
|
|
380
|
+
async def _register_nodes_from_module(self, module: Any, drops: str) -> None:
|
|
381
|
+
"""
|
|
382
|
+
Core logic to discover node functions in a module and (re)create NodeModel entries.
|
|
383
|
+
|
|
384
|
+
Heuristic:
|
|
385
|
+
- All top-level callables (functions) whose names do not start with '_'
|
|
386
|
+
- Only functions actually defined in this module.
|
|
387
|
+
"""
|
|
388
|
+
functions = self._discover_module_functions(module)
|
|
389
|
+
|
|
390
|
+
for func_name, func in functions:
|
|
391
|
+
if not is_node_function(func):
|
|
392
|
+
continue
|
|
393
|
+
|
|
394
|
+
sig = inspect.signature(func)
|
|
395
|
+
|
|
396
|
+
parser = DocstringParser(inspect.getdoc(func))
|
|
397
|
+
doc_description = parser.description()
|
|
398
|
+
doc_params = parser.params()
|
|
399
|
+
|
|
400
|
+
type_hints = get_type_hints(func)
|
|
401
|
+
|
|
402
|
+
inputs = self._build_inputs(sig, type_hints, doc_params)
|
|
403
|
+
|
|
404
|
+
parameters = self._extract_default_parameters(func)
|
|
405
|
+
node_name = getattr(func, "_node_name", func_name)
|
|
406
|
+
node_description = getattr(func, "_node_description", doc_description or "")
|
|
407
|
+
|
|
408
|
+
if not inputs:
|
|
409
|
+
logger.warning(
|
|
410
|
+
f"{node_name} has no inputs -- this means the node will be executed only once."
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
input_mappings = await self._resolve_input_mappings(
|
|
414
|
+
node_name, inputs, drops
|
|
415
|
+
)
|
|
416
|
+
function_mapping = module.__name__ + "." + func_name
|
|
417
|
+
try:
|
|
418
|
+
(
|
|
419
|
+
should_skip,
|
|
420
|
+
existing_favorite,
|
|
421
|
+
) = await self._delete_existing_node_model_if_needed(
|
|
422
|
+
node_name, function_mapping
|
|
423
|
+
)
|
|
424
|
+
if should_skip:
|
|
425
|
+
continue
|
|
426
|
+
|
|
427
|
+
data_mappings = []
|
|
428
|
+
for data_input, input_mapping in zip(inputs, input_mappings):
|
|
429
|
+
if data_input.get("type") and hasattr(
|
|
430
|
+
data_input["type"], "__name__"
|
|
431
|
+
):
|
|
432
|
+
data_input_mapping = self.get_class_mapping(
|
|
433
|
+
data_input["type"], drops
|
|
434
|
+
)
|
|
435
|
+
if data_input_mapping != input_mapping:
|
|
436
|
+
self.logger.error(
|
|
437
|
+
f"Type mismatch for input '{data_input['name']}': expected '{data_input['type'].__name__}', got '{input_mapping}'"
|
|
438
|
+
)
|
|
439
|
+
else:
|
|
440
|
+
self.logger.error(
|
|
441
|
+
f"No type specified for input '{data_input['name']}'"
|
|
442
|
+
)
|
|
443
|
+
data_mappings.append(
|
|
444
|
+
DataMapping(
|
|
445
|
+
name=data_input["name"],
|
|
446
|
+
mapping=input_mapping,
|
|
447
|
+
description=data_input.get("description"),
|
|
448
|
+
)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
result_mappings = await self._build_outputs(
|
|
452
|
+
func_name, type_hints, parser, drops
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
if node_name is None:
|
|
456
|
+
raise ValueError(f"Node {func_name} has no name")
|
|
457
|
+
|
|
458
|
+
node_model = NodeModel(
|
|
459
|
+
name=node_name,
|
|
460
|
+
function_mapping=function_mapping,
|
|
461
|
+
description=node_description,
|
|
462
|
+
input_mappings=data_mappings,
|
|
463
|
+
result_mappings=result_mappings,
|
|
464
|
+
called_nodes=[], # we need to first build the full list, will be filled in second_stage
|
|
465
|
+
default_parameters=parameters,
|
|
466
|
+
pickle_function=None,
|
|
467
|
+
favorite=existing_favorite,
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
logger.debug(
|
|
471
|
+
f"NodeModel: {node_model.name}, {node_model.function_mapping}, {node_model.input_mappings}"
|
|
472
|
+
)
|
|
473
|
+
await self.db.save(node_model)
|
|
474
|
+
|
|
475
|
+
except Exception as e:
|
|
476
|
+
logger.error(f"Error creating/saving NodeModel {node_name}: {e}")
|
|
477
|
+
import traceback
|
|
478
|
+
|
|
479
|
+
traceback.print_exc()
|
|
480
|
+
|
|
481
|
+
async def second_stage(self, drops: str) -> None:
|
|
482
|
+
await update_node_children(self.db, drops)
|
|
483
|
+
|
|
484
|
+
async def clear_table(self) -> None:
|
|
485
|
+
self.logger.info("Clearing NodeModel collection")
|
|
486
|
+
await self.db.get_collection(NodeModel).drop()
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
async def make_node_table(
|
|
490
|
+
db: Database,
|
|
491
|
+
dirs: list[str] | None = None,
|
|
492
|
+
drops: str | None = None,
|
|
493
|
+
write_schema: bool = False,
|
|
494
|
+
clear: bool = False,
|
|
495
|
+
project_root: Path | None = None,
|
|
496
|
+
ignore_entrypoints: bool = False,
|
|
497
|
+
) -> None:
|
|
498
|
+
"""
|
|
499
|
+
Rebuild the node table using the given databse.
|
|
500
|
+
|
|
501
|
+
This is a thin wrapper around CreateNodeTable for backward compatibility.
|
|
502
|
+
"""
|
|
503
|
+
creator = CreateNodeTable(db, write_schema=write_schema, project_root=project_root)
|
|
504
|
+
await creator.build(dirs=dirs, drops=drops, clear=clear, ignore_entrypoints=ignore_entrypoints)
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def create_node_table_main() -> None:
|
|
508
|
+
"""
|
|
509
|
+
CLI-style entry point to (re)build the node table.
|
|
510
|
+
|
|
511
|
+
Uses a dedicated event loop, matching the pattern used for model table creation.
|
|
512
|
+
"""
|
|
513
|
+
TableBuilderBase.cli_main(CreateNodeTable)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
if __name__ == "__main__":
|
|
517
|
+
create_node_table_main()
|