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,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import shutil
|
|
5
|
+
import uuid
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Optional, Union
|
|
9
|
+
|
|
10
|
+
from odmantic import EmbeddedModel, Field, ObjectId
|
|
11
|
+
from pydantic import model_validator
|
|
12
|
+
|
|
13
|
+
from simstack.models import simstack_model
|
|
14
|
+
from simstack.models.parameters import Resource
|
|
15
|
+
from simstack.util.file_transfer_client import transfer_resource_name
|
|
16
|
+
from simstack.util.file_hashing import hash_file
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger("file_instance")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@simstack_model
|
|
22
|
+
class FileInstance(EmbeddedModel):
|
|
23
|
+
"""
|
|
24
|
+
Represents an embedded model for a file instance.
|
|
25
|
+
|
|
26
|
+
The `FileInstance` class is used to encapsulate details about a file,
|
|
27
|
+
including its path, associated resource, and creation timestamp.
|
|
28
|
+
It provides a class method for initializing a `FileInstance` object
|
|
29
|
+
from a local file path, ensuring proper handling of file-related operations.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
path (str): Path to the file relative to the host work directory.
|
|
33
|
+
resource (Resource): Name of the resource associated with the file.
|
|
34
|
+
created_at (datetime): Timestamp indicating when the file instance was created.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
id: Optional[str] = Field(default=None, description="File instance id")
|
|
38
|
+
path: str = Field(
|
|
39
|
+
description="Path to the file relative to the host work directory"
|
|
40
|
+
)
|
|
41
|
+
resource: Resource = Field(description="Resource name")
|
|
42
|
+
created_at: datetime = Field(description="Creation timestamp")
|
|
43
|
+
runner_id: Optional[str] = Field(
|
|
44
|
+
default=None, description="Runner identity that created the instance"
|
|
45
|
+
)
|
|
46
|
+
location_type: str = Field(default="local_path", description="File location type")
|
|
47
|
+
size_bytes: Optional[int] = Field(default=None, description="File size in bytes")
|
|
48
|
+
checksum_sha256: Optional[str] = Field(default=None, description="SHA256 checksum")
|
|
49
|
+
last_accessed_at: Optional[datetime] = Field(
|
|
50
|
+
default=None, description="Last access timestamp"
|
|
51
|
+
)
|
|
52
|
+
expires_at: Optional[datetime] = Field(
|
|
53
|
+
default=None, description="Expiration timestamp"
|
|
54
|
+
)
|
|
55
|
+
is_authoritative: bool = Field(
|
|
56
|
+
default=True, description="Whether this instance is authoritative"
|
|
57
|
+
)
|
|
58
|
+
is_cached: bool = Field(
|
|
59
|
+
default=False,
|
|
60
|
+
description="Whether this instance is a reusable local cache copy",
|
|
61
|
+
)
|
|
62
|
+
status: str = Field(default="available", description="Instance lifecycle status")
|
|
63
|
+
|
|
64
|
+
@model_validator(mode="before")
|
|
65
|
+
def migration(cls, values: Any) -> Any:
|
|
66
|
+
if isinstance(values.get("resource"), str):
|
|
67
|
+
values["resource"] = Resource(value=values["resource"])
|
|
68
|
+
if "path" in values and isinstance(values["path"], Path):
|
|
69
|
+
values["path"] = str(values["path"])
|
|
70
|
+
if not values.get("id"):
|
|
71
|
+
values["id"] = str(uuid.uuid4())
|
|
72
|
+
if not values.get("location_type"):
|
|
73
|
+
values["location_type"] = "local_path"
|
|
74
|
+
if not values.get("status"):
|
|
75
|
+
values["status"] = "available"
|
|
76
|
+
if values.get("is_authoritative") is None:
|
|
77
|
+
values["is_authoritative"] = True
|
|
78
|
+
if values.get("is_cached") is None:
|
|
79
|
+
values["is_cached"] = False
|
|
80
|
+
return values
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_local_file(
|
|
84
|
+
cls,
|
|
85
|
+
path: Union[Path, str],
|
|
86
|
+
file_stack_id: ObjectId,
|
|
87
|
+
make_copy: bool = True,
|
|
88
|
+
tasks_id: str = "",
|
|
89
|
+
) -> FileInstance:
|
|
90
|
+
"""
|
|
91
|
+
Creates a FileInstance object from a local file path.
|
|
92
|
+
|
|
93
|
+
This class method is responsible for initializing a `FileInstance` based
|
|
94
|
+
on a local file's path. It supports options to hash the file, make a
|
|
95
|
+
user-specific copy, and tracks additional metadata. The method handles
|
|
96
|
+
local file operations such as copying files to a secure directory when
|
|
97
|
+
necessary and organizes resources under a configurable working directory.
|
|
98
|
+
|
|
99
|
+
:param file_stack_id: the id of the filestack where the file is in
|
|
100
|
+
:param path: The file path to the local file. Can be either a string
|
|
101
|
+
or `Path`.
|
|
102
|
+
:param make_copy: Indicates whether a secure local copy of the file should
|
|
103
|
+
be made within the application's working directory. Defaults to True.
|
|
104
|
+
:return: A `FileInstance` object initialized with file details.
|
|
105
|
+
:rtype: FileInstance
|
|
106
|
+
:raises ValueError: If there are issues during the creation of the
|
|
107
|
+
`FileInstance` from the specified local file.
|
|
108
|
+
"""
|
|
109
|
+
source_path = path if isinstance(path, Path) else Path(path)
|
|
110
|
+
|
|
111
|
+
# Prepare the content field if in_memory is True
|
|
112
|
+
resolved_path = Path(path).resolve()
|
|
113
|
+
# Find 'simstack' in the path and compute the relative path from its parent
|
|
114
|
+
from simstack.core.context import context
|
|
115
|
+
|
|
116
|
+
workdir = context.config.workdir
|
|
117
|
+
resolved_workdir = Path(workdir).resolve()
|
|
118
|
+
if tasks_id == "":
|
|
119
|
+
logger.debug(f"workdir is {resolved_workdir} path is {resolved_path}")
|
|
120
|
+
else:
|
|
121
|
+
logger.debug(
|
|
122
|
+
f"task_id: {tasks_id} workdir is {resolved_workdir} path is {resolved_path}"
|
|
123
|
+
)
|
|
124
|
+
try:
|
|
125
|
+
relative_path = resolved_path.relative_to(resolved_workdir)
|
|
126
|
+
except ValueError:
|
|
127
|
+
if tasks_id == "":
|
|
128
|
+
logger.error(
|
|
129
|
+
f"Path {resolved_path} is not under workdir {resolved_workdir}"
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
logger.error(
|
|
133
|
+
f"Path {resolved_path} is not under workdir {resolved_workdir} for task_id: {tasks_id}"
|
|
134
|
+
)
|
|
135
|
+
import getpass
|
|
136
|
+
|
|
137
|
+
username = getpass.getuser()
|
|
138
|
+
relative_path = Path(username) / str(file_stack_id)
|
|
139
|
+
absolute_dir = Path(context.config.workdir) / relative_path
|
|
140
|
+
absolute_dir.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
shutil.copy(source_path, absolute_dir)
|
|
142
|
+
|
|
143
|
+
file_instance = FileInstance(
|
|
144
|
+
path=str(relative_path),
|
|
145
|
+
resource=Resource(value=transfer_resource_name(context.config.resource)),
|
|
146
|
+
created_at=datetime.now(),
|
|
147
|
+
size_bytes=source_path.stat().st_size if source_path.exists() else None,
|
|
148
|
+
checksum_sha256=hash_file(source_path) if source_path.exists() else None,
|
|
149
|
+
)
|
|
150
|
+
return file_instance
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import List, Optional, Iterator
|
|
3
|
+
|
|
4
|
+
from odmantic import EmbeddedModel, Field, Model, ObjectId
|
|
5
|
+
|
|
6
|
+
from simstack.models import simstack_model
|
|
7
|
+
from simstack.util.object_list_mixin import ObjectListMixin
|
|
8
|
+
from simstack.models.files import FileStack
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@simstack_model
|
|
12
|
+
class FileList(EmbeddedModel, ObjectListMixin[FileStack]):
|
|
13
|
+
elements: List[ObjectId] = Field(default_factory=list)
|
|
14
|
+
|
|
15
|
+
def __init__(self, **data):
|
|
16
|
+
data, cache = self._normalize_elements_for_init(data)
|
|
17
|
+
EmbeddedModel.__init__(self, **data)
|
|
18
|
+
if cache is not None:
|
|
19
|
+
self._set_cache(cache)
|
|
20
|
+
|
|
21
|
+
def __iter__(self) -> Iterator[FileStack]:
|
|
22
|
+
return ObjectListMixin.__iter__(self)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@simstack_model
|
|
26
|
+
class FileListModel(Model, ObjectListMixin[FileStack]):
|
|
27
|
+
elements: List[ObjectId] = Field(default_factory=list)
|
|
28
|
+
|
|
29
|
+
def __init__(self, **data):
|
|
30
|
+
data, cache = self._normalize_elements_for_init(data)
|
|
31
|
+
Model.__init__(self, **data)
|
|
32
|
+
if cache is not None:
|
|
33
|
+
self._set_cache(cache)
|
|
34
|
+
|
|
35
|
+
def __iter__(self) -> Iterator[FileStack]:
|
|
36
|
+
return ObjectListMixin.__iter__(self)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@simstack_model
|
|
40
|
+
class FileListIO(Model):
|
|
41
|
+
file_list: FileList = Field(default_factory=FileList)
|
|
42
|
+
task_status: Optional[str] = None
|
|
43
|
+
error: Optional[str] = None
|
|
44
|
+
message: Optional[str] = None
|
|
45
|
+
|
simstack/models/files.py
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import zlib
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Optional, Union, Dict, Any
|
|
9
|
+
|
|
10
|
+
from odmantic import Model, Field, ObjectId, Reference
|
|
11
|
+
|
|
12
|
+
from simstack.models import simstack_model
|
|
13
|
+
from simstack.models.file_instance import FileInstance
|
|
14
|
+
from simstack.models.parameters import Resource
|
|
15
|
+
from simstack.util.file_transfer_client import (
|
|
16
|
+
FileTransferClient,
|
|
17
|
+
first_available_remote_location,
|
|
18
|
+
path_for_file_instance,
|
|
19
|
+
transfer_resource_name,
|
|
20
|
+
)
|
|
21
|
+
from simstack.util.file_hashing import hash_file, hash_string
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
# MongoDB has a 16MB document size limit
|
|
26
|
+
MONGODB_MAX_DOCUMENT_SIZE = 16 * 1024 * 1024 # 16 MB in bytes
|
|
27
|
+
|
|
28
|
+
# TODO: its a mystery to me why we should have files with no name or unknown size, both values are actually used in __str__
|
|
29
|
+
|
|
30
|
+
@simstack_model
|
|
31
|
+
class FileStack(Model):
|
|
32
|
+
name: Optional[str] = Field(description="Name of the file", default=None)
|
|
33
|
+
size: Optional[int] = Field(description="Size of the file in bytes", default=None)
|
|
34
|
+
is_hashable: bool = Field(
|
|
35
|
+
default=False, description="Whether the file stack is hashable"
|
|
36
|
+
)
|
|
37
|
+
hash: Optional[str] = Field(
|
|
38
|
+
default=None, description="Hash of the file stack content", index=True
|
|
39
|
+
)
|
|
40
|
+
in_memory: bool = Field(
|
|
41
|
+
default=False, description="Whether the file stack is in memory"
|
|
42
|
+
)
|
|
43
|
+
content: Optional[bytes] = Field(
|
|
44
|
+
description="Compressed file content", default=None
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
locations: List[FileInstance] = Field(
|
|
48
|
+
default_factory=list, description="List of file locations"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
return f"FileStack(name={self.name}, size={self.size}, is_hashable={self.is_hashable}, in_memory={self.in_memory}, locations={self.locations})"
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
return f"FileStack(name={self.name}, size={self.size}, is_hashable={self.is_hashable}, in_memory={self.in_memory}, locations={self.locations})"
|
|
56
|
+
|
|
57
|
+
async def custom_model_dump(self, **kwargs: Any) -> Dict[str, Any]:
|
|
58
|
+
dumped_data: Dict[str, Any] = self.model_dump()
|
|
59
|
+
del dumped_data["content"] # Exclude content from the dumped data
|
|
60
|
+
return dumped_data
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def ui_base_schema(cls, **kwargs: Any) -> Dict[str, Any]:
|
|
64
|
+
# TODO get the model programatically
|
|
65
|
+
return {
|
|
66
|
+
"ui:field": "FileField",
|
|
67
|
+
"ui:options": {
|
|
68
|
+
"model": "simstack.models.files.FileStack",
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_string(cls, data_string: str, file_name: str) -> FileStack:
|
|
74
|
+
content = zlib.compress(data_string.encode("utf-8"))
|
|
75
|
+
file_hash = hash_string(data_string)
|
|
76
|
+
size = len(content)
|
|
77
|
+
|
|
78
|
+
if len(content) > MONGODB_MAX_DOCUMENT_SIZE:
|
|
79
|
+
logger.error(
|
|
80
|
+
f"Compressed content size {len(content)} bytes exceeds MongoDB limit of {MONGODB_MAX_DOCUMENT_SIZE} bytes for file {file_name}"
|
|
81
|
+
)
|
|
82
|
+
raise ValueError(
|
|
83
|
+
f"Compressed content size {len(content)} bytes exceeds MongoDB document size limit of {MONGODB_MAX_DOCUMENT_SIZE} bytes"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
file_stack = cls(
|
|
87
|
+
name=file_name,
|
|
88
|
+
in_memory=True,
|
|
89
|
+
content=content,
|
|
90
|
+
is_hashable=True,
|
|
91
|
+
size=size,
|
|
92
|
+
hash=file_hash,
|
|
93
|
+
)
|
|
94
|
+
return file_stack
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_local_file(
|
|
98
|
+
cls,
|
|
99
|
+
path: Union[Path, str],
|
|
100
|
+
is_hashable: bool = True,
|
|
101
|
+
in_memory: bool = True,
|
|
102
|
+
secure_source: bool = False,
|
|
103
|
+
task_id: str = "",
|
|
104
|
+
) -> FileStack:
|
|
105
|
+
"""
|
|
106
|
+
Creates a FileStack object from a local file path.
|
|
107
|
+
|
|
108
|
+
:param task_id: task_id of the task that created the file stack, used for logging and tracking
|
|
109
|
+
:type task_id: str
|
|
110
|
+
:param secure_source: specifies if the source is secure (already in a directory generated within Simstack II)
|
|
111
|
+
:type secure_source: bool
|
|
112
|
+
:param path: The path to the local file. Can be provided as a string or Path object.
|
|
113
|
+
:type path: Union[Path, str]
|
|
114
|
+
:param is_hashable: A flag indicating whether the file hash needs to be calculated.
|
|
115
|
+
:type is_hashable: bool
|
|
116
|
+
:param in_memory: Whether to store the compressed file content in memory. Defaults to True.
|
|
117
|
+
:type in_memory: bool
|
|
118
|
+
|
|
119
|
+
:return: A FileStack object containing FileInstances for the file.
|
|
120
|
+
:rtype: FileStack
|
|
121
|
+
"""
|
|
122
|
+
# TODO make a second version where a unique directory is already there
|
|
123
|
+
source_path = path if isinstance(path, Path) else Path(path)
|
|
124
|
+
|
|
125
|
+
# Check if the source exists
|
|
126
|
+
if not source_path.exists():
|
|
127
|
+
logger.error(f"Source file not found: {source_path}")
|
|
128
|
+
raise FileNotFoundError(f"Source file not found: {source_path}")
|
|
129
|
+
|
|
130
|
+
# Check if it's a file (not a directory)
|
|
131
|
+
if not source_path.is_file():
|
|
132
|
+
logger.error(f"Source file {path} is not a file")
|
|
133
|
+
raise ValueError(f"Source file is not a file: {path}")
|
|
134
|
+
|
|
135
|
+
# Check read permission using os.access
|
|
136
|
+
if not os.access(path, os.R_OK):
|
|
137
|
+
logger.error(f"No read permission for file: {path}")
|
|
138
|
+
raise ValueError(f"No read permission for file: {path}")
|
|
139
|
+
|
|
140
|
+
content = None
|
|
141
|
+
file_hash = hash_file(source_path) if is_hashable else None
|
|
142
|
+
name = source_path.name
|
|
143
|
+
size = source_path.stat().st_size if source_path.exists() else None
|
|
144
|
+
|
|
145
|
+
if in_memory:
|
|
146
|
+
try:
|
|
147
|
+
# Read the file content
|
|
148
|
+
with open(source_path, "rb") as f:
|
|
149
|
+
file_content = f.read()
|
|
150
|
+
# Compress the content using zlib
|
|
151
|
+
content = zlib.compress(file_content)
|
|
152
|
+
if task_id == "":
|
|
153
|
+
logger.debug(
|
|
154
|
+
f"Compressed file {source_path} from {len(file_content)} bytes to {len(content)} bytes"
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
logger.debug(
|
|
158
|
+
f"task_id: {task_id} Compressed file {source_path} from {len(file_content)} bytes to {len(content)} bytes"
|
|
159
|
+
)
|
|
160
|
+
# Check if compressed content exceeds MongoDB document size limit
|
|
161
|
+
if len(content) > 0.9 * MONGODB_MAX_DOCUMENT_SIZE:
|
|
162
|
+
if task_id == "":
|
|
163
|
+
logger.error(
|
|
164
|
+
f"Compressed content size {len(content)} bytes exceeds MongoDB limit of {MONGODB_MAX_DOCUMENT_SIZE} bytes for file {source_path}"
|
|
165
|
+
)
|
|
166
|
+
logger.error(
|
|
167
|
+
f"Setting in_memory to False for file {source_path} and clear content"
|
|
168
|
+
)
|
|
169
|
+
else:
|
|
170
|
+
logger.error(
|
|
171
|
+
f"task_id: {task_id} Compressed content size {len(content)} bytes exceeds MongoDB limit of {MONGODB_MAX_DOCUMENT_SIZE} bytes for file {source_path}"
|
|
172
|
+
)
|
|
173
|
+
logger.error(
|
|
174
|
+
f"task_id: {task_id} Setting in_memory to False for file {source_path} and clear content"
|
|
175
|
+
)
|
|
176
|
+
in_memory = False
|
|
177
|
+
content = None
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.warning(f"Failed to compress file {source_path}: {e}")
|
|
180
|
+
raise
|
|
181
|
+
|
|
182
|
+
file_stack = cls(
|
|
183
|
+
name=name,
|
|
184
|
+
in_memory=in_memory,
|
|
185
|
+
content=content,
|
|
186
|
+
is_hashable=is_hashable,
|
|
187
|
+
size=size,
|
|
188
|
+
hash=file_hash,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if not in_memory:
|
|
192
|
+
location = FileInstance.from_local_file(
|
|
193
|
+
path=path,
|
|
194
|
+
file_stack_id=file_stack.id,
|
|
195
|
+
make_copy=not secure_source and not in_memory,
|
|
196
|
+
)
|
|
197
|
+
file_stack.locations.append(location)
|
|
198
|
+
|
|
199
|
+
return file_stack
|
|
200
|
+
|
|
201
|
+
def complex_hash(self) -> str:
|
|
202
|
+
if self.is_hashable:
|
|
203
|
+
if self.hash:
|
|
204
|
+
return self.hash
|
|
205
|
+
else:
|
|
206
|
+
raise ValueError("FileStack is hashable but hash is not set.")
|
|
207
|
+
# elif self.in_memory and self.content:
|
|
208
|
+
# # If the content is in memory, hash the compressed content
|
|
209
|
+
# return complex_hash_function(zlib.decompress(self.content))
|
|
210
|
+
# else:
|
|
211
|
+
# temp_dir = Path(tempfile.mkdtemp())
|
|
212
|
+
# local_file = self.get(None, local_dir=temp_dir)
|
|
213
|
+
# return complex_hash_function(local_file.read_bytes())
|
|
214
|
+
else:
|
|
215
|
+
logger.warning(
|
|
216
|
+
f"FileStack {self.id} is not hashable, returning unique hash."
|
|
217
|
+
)
|
|
218
|
+
return str(ObjectId())
|
|
219
|
+
|
|
220
|
+
def append(self, file_instance: FileInstance) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Appends a FileInstance to the file stack.
|
|
223
|
+
|
|
224
|
+
:param file_instance: The FileInstance to append.
|
|
225
|
+
:type file_instance: FileInstance
|
|
226
|
+
"""
|
|
227
|
+
self.locations.append(file_instance)
|
|
228
|
+
|
|
229
|
+
def get(self, local_dir: Path | None = None) -> Path:
|
|
230
|
+
"""
|
|
231
|
+
Copies the file stack to a local directory. This is the version to be used in applications
|
|
232
|
+
|
|
233
|
+
:param local_dir: The local directory to copy the file stack to.
|
|
234
|
+
:type local_dir: Path
|
|
235
|
+
"""
|
|
236
|
+
from simstack.core.context import context
|
|
237
|
+
|
|
238
|
+
return self.get_raw(context.config.resource, local_dir)
|
|
239
|
+
|
|
240
|
+
def get_raw(self, local_resource: Resource, local_dir: Path | None = None) -> Path:
|
|
241
|
+
"""
|
|
242
|
+
Copies the file stack to a local directory, assumes no context.
|
|
243
|
+
|
|
244
|
+
:param local_resource: the local resource to copy the file stack to. Defaults to the current resource.
|
|
245
|
+
:param local_dir: The local directory to copy the file stack to.
|
|
246
|
+
:type local_dir: Path
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
# select the best instance
|
|
250
|
+
# first search for an instance with "in_memory" set to True
|
|
251
|
+
|
|
252
|
+
if local_dir is None:
|
|
253
|
+
local_dir = Path.cwd()
|
|
254
|
+
|
|
255
|
+
if self.in_memory:
|
|
256
|
+
local_dir.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
try:
|
|
258
|
+
if self.content is None:
|
|
259
|
+
raise ValueError("FileStack is in-memory but content is missing.")
|
|
260
|
+
file_name = self.name or "file"
|
|
261
|
+
# Decompress the content
|
|
262
|
+
decompressed_content = zlib.decompress(self.content)
|
|
263
|
+
# Write the decompressed content to the local directory
|
|
264
|
+
target_path = local_dir / file_name
|
|
265
|
+
if target_path.exists(): # the user must make sure that the file does not exist, otherwise the file will be overwritten
|
|
266
|
+
raise FileExistsError(f"File {target_path} already exists.")
|
|
267
|
+
with open(target_path, "wb") as f:
|
|
268
|
+
f.write(decompressed_content)
|
|
269
|
+
return target_path
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.error(f"Failed to decompress and write file {self.name}: {e}")
|
|
272
|
+
raise ValueError(
|
|
273
|
+
f"Failed to decompress and write file {self.name}: {e}"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
local_resource_name = transfer_resource_name(local_resource)
|
|
277
|
+
local_transfer_resource = Resource(value=local_resource_name)
|
|
278
|
+
|
|
279
|
+
# If in-memory instance not found or decompression failed, try finding instance on same resource
|
|
280
|
+
same_resource_instance = next(
|
|
281
|
+
(
|
|
282
|
+
f
|
|
283
|
+
for f in self.locations
|
|
284
|
+
if transfer_resource_name(f.resource) == local_resource_name
|
|
285
|
+
and getattr(f, "status", "available") == "available"
|
|
286
|
+
),
|
|
287
|
+
None,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if same_resource_instance is not None:
|
|
291
|
+
same_resource_instance.last_accessed_at = datetime.now()
|
|
292
|
+
# Return the absolute path by joining with the resource's workdir if it's relative
|
|
293
|
+
path = Path(same_resource_instance.path)
|
|
294
|
+
if not path.is_absolute():
|
|
295
|
+
from simstack.core.context import context
|
|
296
|
+
|
|
297
|
+
return Path(context.config.workdir) / path
|
|
298
|
+
logger.info(f"Using existing instance {path} for {self.name}")
|
|
299
|
+
return path
|
|
300
|
+
|
|
301
|
+
remote_instance = first_available_remote_location(
|
|
302
|
+
self.locations, local_transfer_resource
|
|
303
|
+
)
|
|
304
|
+
if remote_instance is None:
|
|
305
|
+
logger.error("No suitable file instance found for copying.")
|
|
306
|
+
raise ValueError(
|
|
307
|
+
f"FileStack {self.id} {self.name} is unavailable: no accessible file instance for resource {local_transfer_resource}."
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
local_dir.mkdir(parents=True, exist_ok=True)
|
|
311
|
+
return self._get_via_server_transfer(
|
|
312
|
+
remote_instance, local_transfer_resource, local_dir
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def _get_via_server_transfer(
|
|
316
|
+
self,
|
|
317
|
+
remote_instance: FileInstance,
|
|
318
|
+
local_resource: Resource,
|
|
319
|
+
local_dir: Path,
|
|
320
|
+
) -> Path:
|
|
321
|
+
from simstack.core.context import context
|
|
322
|
+
|
|
323
|
+
client = FileTransferClient.from_context(required=True)
|
|
324
|
+
assert client is not None
|
|
325
|
+
|
|
326
|
+
transfer = client.create_transfer(
|
|
327
|
+
file_stack_id=str(self.id),
|
|
328
|
+
source_file_instance_id=getattr(remote_instance, "id", None),
|
|
329
|
+
source_resource_name=transfer_resource_name(remote_instance.resource),
|
|
330
|
+
target_resource_name=transfer_resource_name(local_resource),
|
|
331
|
+
)
|
|
332
|
+
transfer_id = str(transfer["transfer_id"])
|
|
333
|
+
logger.info(
|
|
334
|
+
"Created FileStack transfer %s for file_stack=%s from resource=%s to resource=%s",
|
|
335
|
+
transfer_id,
|
|
336
|
+
self.id,
|
|
337
|
+
transfer_resource_name(remote_instance.resource),
|
|
338
|
+
transfer_resource_name(local_resource),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
client.wait_until_uploaded(transfer_id)
|
|
343
|
+
except Exception as exc:
|
|
344
|
+
try:
|
|
345
|
+
client.fail_transfer(
|
|
346
|
+
transfer_id,
|
|
347
|
+
error_message=str(exc),
|
|
348
|
+
error_code="SOURCE_RUNNER_UNAVAILABLE",
|
|
349
|
+
)
|
|
350
|
+
except Exception:
|
|
351
|
+
logger.warning(
|
|
352
|
+
"Failed to report failed FileStack transfer %s", transfer_id
|
|
353
|
+
)
|
|
354
|
+
raise
|
|
355
|
+
target_path = local_dir / (self.name or Path(str(remote_instance.path)).name)
|
|
356
|
+
if target_path.exists():
|
|
357
|
+
raise FileExistsError(f"File {target_path} already exists.")
|
|
358
|
+
downloaded = client.download_file(transfer_id, target_path)
|
|
359
|
+
instance_path = path_for_file_instance(downloaded.path, context.config.workdir)
|
|
360
|
+
completed = client.complete_transfer(
|
|
361
|
+
transfer_id=transfer_id,
|
|
362
|
+
target_resource_name=transfer_resource_name(local_resource),
|
|
363
|
+
target_path=instance_path,
|
|
364
|
+
size_bytes=downloaded.size_bytes,
|
|
365
|
+
checksum_sha256=downloaded.checksum_sha256,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
file_instance_id = completed.get("file_instance_id")
|
|
369
|
+
existing = next(
|
|
370
|
+
(
|
|
371
|
+
location
|
|
372
|
+
for location in self.locations
|
|
373
|
+
if getattr(location, "id", None) == file_instance_id
|
|
374
|
+
),
|
|
375
|
+
None,
|
|
376
|
+
)
|
|
377
|
+
if existing is None:
|
|
378
|
+
self.locations.append(
|
|
379
|
+
FileInstance(
|
|
380
|
+
id=str(file_instance_id) if file_instance_id else None,
|
|
381
|
+
path=instance_path,
|
|
382
|
+
resource=local_resource,
|
|
383
|
+
created_at=datetime.now(),
|
|
384
|
+
size_bytes=downloaded.size_bytes,
|
|
385
|
+
checksum_sha256=downloaded.checksum_sha256,
|
|
386
|
+
location_type="local_path",
|
|
387
|
+
is_cached=True,
|
|
388
|
+
status="available",
|
|
389
|
+
)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
return downloaded.path
|
|
393
|
+
|
|
394
|
+
def str(self) -> str:
|
|
395
|
+
return f"FileStack(name={self.name}, size={self.size}, is_hashable={self.is_hashable}, in_memory={self.in_memory}, locations={self.locations})"
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class FileGetterArgs(Model):
|
|
399
|
+
file_stack: FileStack = Reference()
|
|
400
|
+
local_resource: Resource
|
|
401
|
+
local_dir: Path
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
async def main() -> None:
|
|
405
|
+
from simstack.core.context import context
|
|
406
|
+
|
|
407
|
+
await context.initialize()
|
|
408
|
+
|
|
409
|
+
# write a file test.txt
|
|
410
|
+
with open("test.txt", "w") as f:
|
|
411
|
+
f.write("Hello World")
|
|
412
|
+
file_stack = FileStack.from_local_file("test.txt", is_hashable=True, in_memory=True)
|
|
413
|
+
print(file_stack)
|
|
414
|
+
await context.db.save(file_stack)
|
|
415
|
+
local_dir = Path(context.config.workdir) / "samira" / str(file_stack.id)
|
|
416
|
+
retrieved = file_stack.get(local_dir=local_dir)
|
|
417
|
+
print("Retrieved file path:", retrieved)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
if __name__ == "__main__":
|
|
421
|
+
import asyncio
|
|
422
|
+
|
|
423
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
424
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from odmantic import Model, Field
|
|
3
|
+
from simstack.models.simstack_model import simstack_model
|
|
4
|
+
|
|
5
|
+
@simstack_model
|
|
6
|
+
class FireAndForgetResult(Model):
|
|
7
|
+
"""
|
|
8
|
+
Model representing the result of a fire-and-forget task.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
call_path: The full call path of the node.
|
|
12
|
+
models: A dictionary mapping argument/result names to their values.
|
|
13
|
+
success: Whether the task was successful.
|
|
14
|
+
next_step: Whether this result triggers a next step.
|
|
15
|
+
"""
|
|
16
|
+
call_path: str = Field(description="The full call path of the node")
|
|
17
|
+
models: Dict[str, Any] = Field(description="A dictionary mapping argument/result names to their values")
|
|
18
|
+
success: bool = Field(description="Whether the task was successful")
|
|
19
|
+
next_step: bool = Field(default=False, description="Whether this result triggers a next step")
|