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/util/files.py
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional, Dict, Any, List
|
|
4
|
+
|
|
5
|
+
from odmantic import Field
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SshAuthMethod(BaseModel):
|
|
10
|
+
""" """
|
|
11
|
+
|
|
12
|
+
# Authentication type
|
|
13
|
+
auth_type: str = Field(
|
|
14
|
+
description="Authentication type (password, key, agent, none)"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Password authentication
|
|
18
|
+
password: Optional[str] = Field(
|
|
19
|
+
default=None,
|
|
20
|
+
description="Password for authentication (should be stored securely)",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Key-based authentication
|
|
24
|
+
key_filename: Optional[str] = Field(
|
|
25
|
+
default=None, description="Path to the private key file"
|
|
26
|
+
)
|
|
27
|
+
key_passphrase: Optional[str] = Field(
|
|
28
|
+
default=None, description="Passphrase for the private key"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Allow host key to be added automatically to known_hosts
|
|
32
|
+
allow_host_key_add: bool = Field(
|
|
33
|
+
default=True, description="Whether to automatically add host key to known_hosts"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Host key verification options
|
|
37
|
+
host_key_policy: str = Field(
|
|
38
|
+
default="ask", description="Host key policy (ask, auto_add, strict)"
|
|
39
|
+
)
|
|
40
|
+
known_hosts_file: Optional[str] = Field(
|
|
41
|
+
default=None, description="Path to known_hosts file"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
model_config = {"extra": "allow"}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FfspecFile(BaseModel):
|
|
48
|
+
"""
|
|
49
|
+
Representation of a file with protocol-specific details, metadata, and utility methods
|
|
50
|
+
for remote or local operations.
|
|
51
|
+
|
|
52
|
+
This class facilitates working with files across different storage protocols such as
|
|
53
|
+
local filesystem, SFTP, SCP, or cloud-based protocols. It provides attributes for
|
|
54
|
+
authenticating, accessing, and manipulating files as well as methods for operations
|
|
55
|
+
like copying files, creating URIs, and testing connections.
|
|
56
|
+
|
|
57
|
+
:ivar path: Full path to the file.
|
|
58
|
+
:type path: str
|
|
59
|
+
:ivar name: Name of the file.
|
|
60
|
+
:type name: str
|
|
61
|
+
:ivar extension: File extension.
|
|
62
|
+
:type extension: str
|
|
63
|
+
:ivar protocol: Storage protocol (e.g., 'file', 's3', 'http', 'sftp', 'scp').
|
|
64
|
+
:type protocol: str
|
|
65
|
+
:ivar host: Host name or address where the file is located.
|
|
66
|
+
:type host: Optional[str]
|
|
67
|
+
:ivar port: Port number if applicable.
|
|
68
|
+
:type port: Optional[int]
|
|
69
|
+
:ivar username: Username for authentication if required.
|
|
70
|
+
:type username: Optional[str]
|
|
71
|
+
:ivar ssh_auth: SSH authentication configuration.
|
|
72
|
+
:type ssh_auth: Optional[SshAuthMethod]
|
|
73
|
+
:ivar size: Size of the file in bytes.
|
|
74
|
+
:type size: int
|
|
75
|
+
:ivar created_at: Creation timestamp.
|
|
76
|
+
:type created_at: datetime
|
|
77
|
+
:ivar modified_at: Last modification timestamp.
|
|
78
|
+
:type modified_at: datetime
|
|
79
|
+
:ivar storage_options: Protocol-specific storage options.
|
|
80
|
+
:type storage_options: Dict[str, Any]
|
|
81
|
+
:ivar checksum: File content checksum.
|
|
82
|
+
:type checksum: Optional[str]
|
|
83
|
+
:ivar content_type: MIME type of the file.
|
|
84
|
+
:type content_type: Optional[str]
|
|
85
|
+
:ivar parent_path: Path to parent directory.
|
|
86
|
+
:type parent_path: Optional[str]
|
|
87
|
+
:ivar is_directory: Whether this is a directory.
|
|
88
|
+
:type is_directory: bool
|
|
89
|
+
:ivar children: Child file/directory paths if this is a directory.
|
|
90
|
+
:type children: List[str]
|
|
91
|
+
:ivar last_accessed: Last access timestamp.
|
|
92
|
+
:type last_accessed: Optional[datetime]
|
|
93
|
+
:ivar access_count: Number of times this file has been accessed.
|
|
94
|
+
:type access_count: int
|
|
95
|
+
:ivar metadata: Additional metadata as key-value pairs.
|
|
96
|
+
:type metadata: Dict[str, Any]
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
# Basic file information
|
|
100
|
+
path: str = Field(description="Full path to the file")
|
|
101
|
+
name: str = Field(description="Name of the file")
|
|
102
|
+
extension: str = Field(description="File extension")
|
|
103
|
+
|
|
104
|
+
# Protocol and location information
|
|
105
|
+
protocol: str = Field(
|
|
106
|
+
description="Storage protocol (e.g., 'file', 's3', 'http', 'sftp', 'scp')"
|
|
107
|
+
)
|
|
108
|
+
host: Optional[str] = Field(
|
|
109
|
+
default=None, description="Host name or address where the file is located"
|
|
110
|
+
)
|
|
111
|
+
port: Optional[int] = Field(default=None, description="Port number if applicable")
|
|
112
|
+
|
|
113
|
+
# Authentication information
|
|
114
|
+
username: Optional[str] = Field(
|
|
115
|
+
default=None, description="Username for authentication if required"
|
|
116
|
+
)
|
|
117
|
+
ssh_auth: Optional[SshAuthMethod] = Field(
|
|
118
|
+
default=None, description="SSH authentication configuration"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# File metadata
|
|
122
|
+
size: int = Field(description="Size of the file in bytes")
|
|
123
|
+
created_at: datetime = Field(description="Creation timestamp")
|
|
124
|
+
modified_at: datetime = Field(description="Last modification timestamp")
|
|
125
|
+
|
|
126
|
+
# Ffspec-specific attributes
|
|
127
|
+
storage_options: Dict[str, Any] = Field(
|
|
128
|
+
default_factory=dict, description="Protocol-specific storage options"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Content addressing
|
|
132
|
+
checksum: Optional[str] = Field(default=None, description="File content checksum")
|
|
133
|
+
content_type: Optional[str] = Field(
|
|
134
|
+
default=None, description="MIME type of the file"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Hierarchical structure support
|
|
138
|
+
parent_path: Optional[str] = Field(
|
|
139
|
+
default=None, description="Path to parent directory"
|
|
140
|
+
)
|
|
141
|
+
is_directory: bool = Field(default=False, description="Whether this is a directory")
|
|
142
|
+
children: List[str] = Field(
|
|
143
|
+
default_factory=list,
|
|
144
|
+
description="Child file/directory paths if this is a directory",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Access metadata
|
|
148
|
+
last_accessed: Optional[datetime] = Field(
|
|
149
|
+
default=None, description="Last access timestamp"
|
|
150
|
+
)
|
|
151
|
+
access_count: int = Field(
|
|
152
|
+
default=0, description="Number of times this file has been accessed"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Custom metadata
|
|
156
|
+
metadata: Dict[str, Any] = Field(
|
|
157
|
+
default_factory=dict, description="Additional metadata as key-value pairs"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def from_ssh_key(
|
|
162
|
+
cls,
|
|
163
|
+
host: str,
|
|
164
|
+
path: str,
|
|
165
|
+
username: str,
|
|
166
|
+
key_filename: str,
|
|
167
|
+
protocol: str = "sftp",
|
|
168
|
+
port: int = 22,
|
|
169
|
+
key_passphrase: Optional[str] = None,
|
|
170
|
+
host_key_policy: str = "auto_add",
|
|
171
|
+
**kwargs,
|
|
172
|
+
) -> "FfspecFile":
|
|
173
|
+
"""
|
|
174
|
+
Create a FfspecFile instance with SSH key authentication.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
host: Remote host name or IP address
|
|
178
|
+
path: Path to the file on the remote host
|
|
179
|
+
username: SSH username for authentication
|
|
180
|
+
key_filename: Path to the SSH private key file
|
|
181
|
+
protocol: Protocol to use, either 'sftp' or 'scp' (default: 'sftp')
|
|
182
|
+
port: SSH port on the remote host (default: 22)
|
|
183
|
+
key_passphrase: Passphrase for the SSH key if it's encrypted (default: None)
|
|
184
|
+
host_key_policy: Host key policy - 'auto_add', 'strict', or 'ask' (default: 'auto_add')
|
|
185
|
+
**kwargs: Additional attributes to set on the model
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
FfspecFile: A new instance representing the remote file
|
|
189
|
+
"""
|
|
190
|
+
from pathlib import Path
|
|
191
|
+
|
|
192
|
+
# Validate protocol
|
|
193
|
+
if protocol not in ["sftp", "scp"]:
|
|
194
|
+
raise ValueError(f"Protocol must be 'sftp' or 'scp', got '{protocol}'")
|
|
195
|
+
|
|
196
|
+
# Validate that the key file exists
|
|
197
|
+
if not os.path.exists(key_filename):
|
|
198
|
+
raise FileNotFoundError(f"SSH key file not found: {key_filename}")
|
|
199
|
+
|
|
200
|
+
# Ensure path is absolute (starts with /)
|
|
201
|
+
if not path.startswith("/"):
|
|
202
|
+
path = "/" + path
|
|
203
|
+
|
|
204
|
+
# Create SSH authentication config with proper host key policy
|
|
205
|
+
ssh_auth = SshAuthMethod(
|
|
206
|
+
auth_type="key",
|
|
207
|
+
key_filename=key_filename,
|
|
208
|
+
key_passphrase=key_passphrase,
|
|
209
|
+
host_key_policy=host_key_policy,
|
|
210
|
+
allow_host_key_add=(host_key_policy == "auto_add"),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Extract file name and extension from path
|
|
214
|
+
p = Path(path)
|
|
215
|
+
name = p.name
|
|
216
|
+
extension = p.suffix.lstrip(".") if p.suffix else ""
|
|
217
|
+
parent_path = str(p.parent) if p.parent != p else None
|
|
218
|
+
|
|
219
|
+
# Create comprehensive storage options for fsspec
|
|
220
|
+
# Note: Removed missing_host_key_policy as it's not a valid paramiko connection parameter
|
|
221
|
+
storage_options = {
|
|
222
|
+
"host": host,
|
|
223
|
+
"port": port,
|
|
224
|
+
"username": username,
|
|
225
|
+
"key_filename": key_filename,
|
|
226
|
+
# Connection timeouts
|
|
227
|
+
"timeout": 30,
|
|
228
|
+
"banner_timeout": 30,
|
|
229
|
+
"auth_timeout": 30,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
# Add key passphrase if provided
|
|
233
|
+
if key_passphrase:
|
|
234
|
+
storage_options["password"] = key_passphrase
|
|
235
|
+
|
|
236
|
+
# Create the instance
|
|
237
|
+
return cls(
|
|
238
|
+
path=path,
|
|
239
|
+
name=name,
|
|
240
|
+
extension=extension,
|
|
241
|
+
protocol=protocol,
|
|
242
|
+
host=host,
|
|
243
|
+
port=port,
|
|
244
|
+
username=username,
|
|
245
|
+
ssh_auth=ssh_auth,
|
|
246
|
+
storage_options=storage_options,
|
|
247
|
+
parent_path=parent_path,
|
|
248
|
+
# Use values from kwargs or sensible defaults for required fields
|
|
249
|
+
size=kwargs.pop("size", 0),
|
|
250
|
+
created_at=kwargs.pop("created_at", datetime.now()),
|
|
251
|
+
modified_at=kwargs.pop("modified_at", datetime.now()),
|
|
252
|
+
is_directory=kwargs.pop("is_directory", False),
|
|
253
|
+
**kwargs,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def get_filesystem(self):
|
|
257
|
+
"""
|
|
258
|
+
Create and return a fsspec filesystem object for this file.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
object: An fsspec filesystem instance
|
|
262
|
+
"""
|
|
263
|
+
import importlib
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
# Import the appropriate filesystem module
|
|
267
|
+
fsspec = importlib.import_module("fsspec")
|
|
268
|
+
|
|
269
|
+
# Handle SSH-specific protocols
|
|
270
|
+
if self.protocol in ["sftp", "scp"]:
|
|
271
|
+
# Create a copy of storage options to avoid modifying the original
|
|
272
|
+
storage_options = dict(self.storage_options)
|
|
273
|
+
|
|
274
|
+
# Add SSH auth options if available
|
|
275
|
+
if self.ssh_auth:
|
|
276
|
+
if self.ssh_auth.auth_type == "key":
|
|
277
|
+
storage_options["key_filename"] = self.ssh_auth.key_filename
|
|
278
|
+
if self.ssh_auth.key_passphrase:
|
|
279
|
+
storage_options["password"] = self.ssh_auth.key_passphrase
|
|
280
|
+
elif self.ssh_auth.auth_type == "password":
|
|
281
|
+
storage_options["password"] = self.ssh_auth.password
|
|
282
|
+
|
|
283
|
+
# Create the filesystem without the problematic missing_host_key_policy parameter
|
|
284
|
+
return fsspec.filesystem(self.protocol, **storage_options)
|
|
285
|
+
else:
|
|
286
|
+
# Handle other protocols
|
|
287
|
+
return fsspec.filesystem(self.protocol, **self.storage_options)
|
|
288
|
+
|
|
289
|
+
except (ImportError, KeyError) as e:
|
|
290
|
+
raise ValueError(
|
|
291
|
+
f"Could not create filesystem for protocol '{self.protocol}': {e}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def can_connect(self) -> bool:
|
|
295
|
+
"""
|
|
296
|
+
Test if we can connect to the remote host using the provided credentials.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
bool: True if connection is successful, False otherwise
|
|
300
|
+
"""
|
|
301
|
+
if self.protocol not in ["sftp", "scp"]:
|
|
302
|
+
# Only applicable for SSH-based protocols
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
fs = self.get_filesystem()
|
|
307
|
+
# Try to list the parent directory
|
|
308
|
+
fs.ls(os.path.dirname(self.path))
|
|
309
|
+
return True
|
|
310
|
+
except Exception:
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
def to_uri(self) -> str:
|
|
314
|
+
"""
|
|
315
|
+
Convert this model to a URI/URL representation.
|
|
316
|
+
|
|
317
|
+
Note: SSH private key information is not included in the URI.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
str: URI string representing this file
|
|
321
|
+
"""
|
|
322
|
+
import urllib.parse
|
|
323
|
+
|
|
324
|
+
# Build the netloc part
|
|
325
|
+
netloc = ""
|
|
326
|
+
if self.username:
|
|
327
|
+
netloc = f"{self.username}@"
|
|
328
|
+
if self.host:
|
|
329
|
+
netloc += self.host
|
|
330
|
+
if self.port and self.port != 22: # Only include non-standard SSH port
|
|
331
|
+
netloc += f":{self.port}"
|
|
332
|
+
|
|
333
|
+
# Assemble the URI
|
|
334
|
+
return urllib.parse.urlunparse(
|
|
335
|
+
(
|
|
336
|
+
self.protocol, # scheme
|
|
337
|
+
netloc, # netloc
|
|
338
|
+
self.path, # path
|
|
339
|
+
"", # params
|
|
340
|
+
"", # query (not including auth info for security)
|
|
341
|
+
"", # fragment
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
def copy_to_local(self, local_path: Optional[str] = None) -> str:
|
|
346
|
+
"""
|
|
347
|
+
Copy the remote file to a local path.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
local_path: Local path to copy the file to. If None, a temporary file is created.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
str: Path to the local copy of the file
|
|
354
|
+
"""
|
|
355
|
+
import tempfile
|
|
356
|
+
|
|
357
|
+
if self.is_directory:
|
|
358
|
+
raise ValueError(
|
|
359
|
+
"Cannot copy a directory, only individual files are supported"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Create a destination path if none is provided
|
|
363
|
+
if local_path is None:
|
|
364
|
+
temp_dir = tempfile.gettempdir()
|
|
365
|
+
local_path = os.path.join(temp_dir, self.name)
|
|
366
|
+
|
|
367
|
+
# Get the filesystem and copy the file
|
|
368
|
+
fs = self.get_filesystem()
|
|
369
|
+
fs.get(self.path, local_path)
|
|
370
|
+
|
|
371
|
+
return local_path
|
|
372
|
+
|
|
373
|
+
def copy_from_local(self, local_path: str) -> None:
|
|
374
|
+
"""
|
|
375
|
+
Copy a local file to the remote location represented by this model.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
local_path: Path to the local file
|
|
379
|
+
|
|
380
|
+
Returns:
|
|
381
|
+
None
|
|
382
|
+
"""
|
|
383
|
+
if not os.path.exists(local_path):
|
|
384
|
+
raise FileNotFoundError(f"Local file {local_path} does not exist")
|
|
385
|
+
|
|
386
|
+
# Get the filesystem and copy the file
|
|
387
|
+
fs = self.get_filesystem()
|
|
388
|
+
fs.put(local_path, self.path)
|
|
389
|
+
|
|
390
|
+
# Update file size and modification time
|
|
391
|
+
try:
|
|
392
|
+
info = fs.info(self.path)
|
|
393
|
+
self.size = info.get("size", 0)
|
|
394
|
+
self.modified_at = datetime.now()
|
|
395
|
+
except Exception:
|
|
396
|
+
pass
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def find_python_files(directory: str) -> List[str]:
|
|
6
|
+
"""
|
|
7
|
+
Returns full paths of all .py files in the given directory and its subdirectories,
|
|
8
|
+
excluding __init__.py files.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
directory: The root directory to search from
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
List of absolute file paths to all .py files that aren't __init__.py
|
|
15
|
+
"""
|
|
16
|
+
python_files = []
|
|
17
|
+
|
|
18
|
+
# Ensure the directory exists
|
|
19
|
+
if not os.path.isdir(directory):
|
|
20
|
+
raise ValueError(f"'{directory}' is not a valid directory")
|
|
21
|
+
|
|
22
|
+
# Convert to absolute path
|
|
23
|
+
directory = os.path.abspath(directory)
|
|
24
|
+
|
|
25
|
+
# Walk through directory structure
|
|
26
|
+
for root, dirs, files in os.walk(directory):
|
|
27
|
+
for file in files:
|
|
28
|
+
# Check if it's a Python file but not __init__.py
|
|
29
|
+
if file.endswith(".py") and file != "__init__.py":
|
|
30
|
+
full_path = os.path.join(root, file)
|
|
31
|
+
python_files.append(full_path)
|
|
32
|
+
|
|
33
|
+
return python_files
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def generate_ui_schema(cls):
|
|
5
|
+
"""
|
|
6
|
+
Generates a UI schema that uses GenericForm for fields with ui_schema function.
|
|
7
|
+
Also preserves any existing UI schema configurations from the class.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
cls: The model class to generate UI schema for
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
dict: The generated UI schema
|
|
14
|
+
"""
|
|
15
|
+
# Get base UI schema from the class if it exists
|
|
16
|
+
|
|
17
|
+
base_schema = cls.ui_base_schema() if hasattr(cls, "ui_base_schema") else {}
|
|
18
|
+
|
|
19
|
+
# Process each field in the model
|
|
20
|
+
for field_name, field in cls.model_fields.items():
|
|
21
|
+
field_type = field.annotation
|
|
22
|
+
|
|
23
|
+
# Unwrap Optional type to get the underlying type
|
|
24
|
+
if hasattr(field_type, "__origin__") and field_type.__origin__ is Union:
|
|
25
|
+
# Optional[X] is implemented as Union[X, None]
|
|
26
|
+
args = field_type.__args__
|
|
27
|
+
if len(args) == 2 and args[1] is type(None):
|
|
28
|
+
field_type = args[0]
|
|
29
|
+
|
|
30
|
+
# Handle Lists by checking their inner type
|
|
31
|
+
is_list = hasattr(field_type, "__origin__") and field_type.__origin__ is list
|
|
32
|
+
if is_list:
|
|
33
|
+
field_type = field_type.__args__[0]
|
|
34
|
+
|
|
35
|
+
# Check for Optional inside List
|
|
36
|
+
if hasattr(field_type, "__origin__") and field_type.__origin__ is Union:
|
|
37
|
+
args = field_type.__args__
|
|
38
|
+
if len(args) == 2 and args[1] is type(None):
|
|
39
|
+
field_type = args[0]
|
|
40
|
+
|
|
41
|
+
# Check if field type has ui_schema method (this is true for all simstack_model classes)
|
|
42
|
+
if hasattr(field_type, "ui_schema"):
|
|
43
|
+
module_path = field_type.__module__
|
|
44
|
+
full_path = f"{module_path}.{field_type.__name__}"
|
|
45
|
+
# if simstack is in the module path, skip all elements after the last simstack occurrence
|
|
46
|
+
if "simstack" in module_path:
|
|
47
|
+
full_path = ".".join(
|
|
48
|
+
module_path[module_path.rfind("simstack") :].split(".")
|
|
49
|
+
+ [field_type.__name__]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
field_schema = field_type.ui_schema() # get the field schema for the field
|
|
53
|
+
# Don't override if there's already a UI configuration for this field
|
|
54
|
+
if field_name not in base_schema:
|
|
55
|
+
if "ui:field" in field_schema:
|
|
56
|
+
# If the field schema already has a ui:field, use it
|
|
57
|
+
if is_list:
|
|
58
|
+
base_schema[field_name] = {"items": field_schema}
|
|
59
|
+
else:
|
|
60
|
+
base_schema[field_name] = field_schema
|
|
61
|
+
else:
|
|
62
|
+
# Check if field type is a list
|
|
63
|
+
ui_ref = {
|
|
64
|
+
"ui:field": "GenericFormField",
|
|
65
|
+
"ui:options": {
|
|
66
|
+
"model": full_path,
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
# in rjsf the ui_scheme for list element must be in "items" key
|
|
70
|
+
if is_list:
|
|
71
|
+
base_schema[field_name] = {"items": ui_ref}
|
|
72
|
+
else:
|
|
73
|
+
ui_ref["ui:options"]["accordion"] = "true"
|
|
74
|
+
base_schema[field_name] = ui_ref
|
|
75
|
+
|
|
76
|
+
# Always hide the id field if it exists
|
|
77
|
+
if "id" not in base_schema:
|
|
78
|
+
base_schema["id"] = {"ui:widget": "hidden"}
|
|
79
|
+
|
|
80
|
+
return base_schema
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Generic, Union, List, Iterable, Iterator, Optional, Any, TypeVar
|
|
3
|
+
|
|
4
|
+
T = TypeVar("T")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GenericListMixin(Generic[T]):
|
|
8
|
+
"""
|
|
9
|
+
Mixin class containing common functionality for list operations.
|
|
10
|
+
|
|
11
|
+
Notes on typing:
|
|
12
|
+
- Pure list-like operations (append/extend/...) are fully generic over `T`.
|
|
13
|
+
- Convenience helpers like `find()` / `filter_by_size()` rely on optional attributes
|
|
14
|
+
(e.g. `.name`, `.size`). For arbitrary `T`, we use `getattr()` to keep runtime
|
|
15
|
+
behavior flexible while remaining type-safe-ish.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __len__(self):
|
|
19
|
+
return len(self.elements)
|
|
20
|
+
|
|
21
|
+
def append(self, elements: T):
|
|
22
|
+
self.elements.append(elements)
|
|
23
|
+
|
|
24
|
+
def extend(self, elements: Union[List[T], Iterable[T], "GenericListMixin[T]"]):
|
|
25
|
+
if isinstance(elements, GenericListMixin):
|
|
26
|
+
# It's another GenericListMixin object
|
|
27
|
+
self.elements.extend(elements.elements)
|
|
28
|
+
else:
|
|
29
|
+
# It's a list or iterable
|
|
30
|
+
self.elements.extend(list(elements))
|
|
31
|
+
|
|
32
|
+
def insert(self, index: int, elements: T):
|
|
33
|
+
self.elements.insert(index, elements)
|
|
34
|
+
|
|
35
|
+
def remove(self, elements: T):
|
|
36
|
+
self.elements.remove(elements)
|
|
37
|
+
|
|
38
|
+
def pop(self, index: int = -1) -> T:
|
|
39
|
+
return self.elements.pop(index)
|
|
40
|
+
|
|
41
|
+
def clear(self):
|
|
42
|
+
self.elements.clear()
|
|
43
|
+
|
|
44
|
+
def index(self, elements: T, start: int = 0, stop: int = None) -> int:
|
|
45
|
+
if stop is None:
|
|
46
|
+
return self.elements.index(elements, start)
|
|
47
|
+
return self.elements.index(elements, start, stop)
|
|
48
|
+
|
|
49
|
+
def count(self, elements: T) -> int:
|
|
50
|
+
return self.elements.count(elements)
|
|
51
|
+
|
|
52
|
+
def reverse(self):
|
|
53
|
+
self.elements.reverse()
|
|
54
|
+
|
|
55
|
+
def sort(self, key=None, reverse: bool = False):
|
|
56
|
+
self.elements.sort(key=key, reverse=reverse)
|
|
57
|
+
|
|
58
|
+
def copy(self) -> List[T]:
|
|
59
|
+
return self.elements.copy()
|
|
60
|
+
|
|
61
|
+
def __getitem__(self, index: Union[int, slice]) -> Union[T, List[T]]:
|
|
62
|
+
return self.elements[index]
|
|
63
|
+
|
|
64
|
+
def __setitem__(self, index: Union[int, slice], value: Union[T, List[T], Iterable[T]]):
|
|
65
|
+
if isinstance(index, slice):
|
|
66
|
+
self.elements[index] = list(value)
|
|
67
|
+
else:
|
|
68
|
+
self.elements[index] = value
|
|
69
|
+
|
|
70
|
+
def __delitem__(self, index: Union[int, slice]):
|
|
71
|
+
del self.elements[index]
|
|
72
|
+
|
|
73
|
+
def __iter__(self) -> Iterator[T]:
|
|
74
|
+
return iter(self.elements)
|
|
75
|
+
|
|
76
|
+
def __contains__(self, element: T) -> bool:
|
|
77
|
+
return element in self.elements
|
|
78
|
+
|
|
79
|
+
def __bool__(self) -> bool:
|
|
80
|
+
return bool(self.elements)
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return f"{self.__class__.__name__}(elements={self.elements!r})"
|
|
84
|
+
|
|
85
|
+
def find(self, pattern: str) -> Optional[T]:
|
|
86
|
+
regex = re.compile(pattern)
|
|
87
|
+
for element in self.elements:
|
|
88
|
+
name = getattr(element, "name", None)
|
|
89
|
+
if name and regex.search(name):
|
|
90
|
+
return element
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def find_all(self, pattern: str) -> Iterator[T]:
|
|
94
|
+
regex = re.compile(pattern)
|
|
95
|
+
for element in self.elements:
|
|
96
|
+
name = getattr(element, "name", None)
|
|
97
|
+
if name and regex.search(name):
|
|
98
|
+
yield element
|
|
99
|
+
|
|
100
|
+
def filter_by_size(self, min_size: int = None, max_size: int = None) -> Iterator[T]:
|
|
101
|
+
for element in self.elements:
|
|
102
|
+
size = getattr(element, "size", None)
|
|
103
|
+
if size is None:
|
|
104
|
+
continue
|
|
105
|
+
if min_size is not None and size < min_size:
|
|
106
|
+
continue
|
|
107
|
+
if max_size is not None and size > max_size:
|
|
108
|
+
continue
|
|
109
|
+
yield element
|
|
110
|
+
|
|
111
|
+
def filter_by_property(self, property_name: str, value: Any) -> Iterator[T]:
|
|
112
|
+
for element in self.elements:
|
|
113
|
+
if hasattr(element, property_name) and getattr(element, property_name) == value:
|
|
114
|
+
yield element
|
|
115
|
+
|
|
116
|
+
def sort_by_name(self, reverse: bool = False):
|
|
117
|
+
self.elements.sort(key=lambda x: getattr(x, "name", "") or "", reverse=reverse)
|
|
118
|
+
|
|
119
|
+
def sort_by_size(self, reverse: bool = False):
|
|
120
|
+
self.elements.sort(key=lambda x: getattr(x, "size", 0) or 0, reverse=reverse)
|