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,117 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional, Dict, Any
|
|
5
|
+
|
|
6
|
+
from pymongo import MongoClient
|
|
7
|
+
|
|
8
|
+
# Regular expression patterns
|
|
9
|
+
task_pattern = re.compile(r"task_id: ([\w\-\.]+)")
|
|
10
|
+
resource_pattern = re.compile(r"resource_id: ([\w\-\.]+)")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def extract_task_id(message: str) -> Optional[str]:
|
|
14
|
+
"""Extract task ID from log message."""
|
|
15
|
+
match = task_pattern.search(message)
|
|
16
|
+
return match.group(1) if match else None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def extract_resource_id(message: str) -> Optional[str]:
|
|
20
|
+
"""Extract resource ID from log message."""
|
|
21
|
+
match = resource_pattern.search(message)
|
|
22
|
+
return match.group(1) if match else None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DBLogHandler(logging.Handler):
|
|
26
|
+
"""Logging handler that stores logs in MongoDB using pymongo."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
connection_string: str,
|
|
31
|
+
db_name: str,
|
|
32
|
+
collection_name: str = "logs",
|
|
33
|
+
):
|
|
34
|
+
super().__init__()
|
|
35
|
+
|
|
36
|
+
# Validate configuration
|
|
37
|
+
if not connection_string or not isinstance(connection_string, str):
|
|
38
|
+
raise ValueError(f"Invalid connection string {connection_string}")
|
|
39
|
+
if not db_name or not isinstance(db_name, str):
|
|
40
|
+
raise ValueError(f"Invalid database name {db_name}")
|
|
41
|
+
if not collection_name or not isinstance(collection_name, str):
|
|
42
|
+
raise ValueError(f"Invalid collection name {collection_name}")
|
|
43
|
+
|
|
44
|
+
# Store configuration
|
|
45
|
+
self.connection_string = connection_string
|
|
46
|
+
self.db_name = db_name
|
|
47
|
+
self.collection_name = collection_name
|
|
48
|
+
|
|
49
|
+
# Create MongoDB client
|
|
50
|
+
self.client = MongoClient(self.connection_string)
|
|
51
|
+
self.db = self.client[self.db_name]
|
|
52
|
+
self.collection = self.db[self.collection_name]
|
|
53
|
+
|
|
54
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
55
|
+
"""Process a log record by converting it to a log entry and inserting it into MongoDB."""
|
|
56
|
+
try:
|
|
57
|
+
# Create log entry as a dictionary
|
|
58
|
+
log_entry = self.create_log_entry(record)
|
|
59
|
+
|
|
60
|
+
# Insert the log entry into the collection
|
|
61
|
+
self.collection.insert_one(log_entry)
|
|
62
|
+
|
|
63
|
+
except Exception as e:
|
|
64
|
+
# Use print instead of logging to avoid potential recursion
|
|
65
|
+
print(f"Error in emit: {e}")
|
|
66
|
+
self.handleError(record)
|
|
67
|
+
|
|
68
|
+
def create_log_entry(self, record: logging.LogRecord) -> Dict[str, Any]:
|
|
69
|
+
# Format message
|
|
70
|
+
message = self.format(record)
|
|
71
|
+
|
|
72
|
+
# Extract task and resource IDs
|
|
73
|
+
task_id = extract_task_id(message)
|
|
74
|
+
resource_id = extract_resource_id(message)
|
|
75
|
+
|
|
76
|
+
# Create log entry dictionary
|
|
77
|
+
log_entry_dict = {
|
|
78
|
+
"timestamp": datetime.fromtimestamp(record.created),
|
|
79
|
+
"level": record.levelname,
|
|
80
|
+
"logger_name": record.name,
|
|
81
|
+
"message": message,
|
|
82
|
+
"module": record.module,
|
|
83
|
+
"function": record.funcName,
|
|
84
|
+
"line": record.lineno,
|
|
85
|
+
"task_id": task_id,
|
|
86
|
+
"resource": resource_id,
|
|
87
|
+
"thread_name": record.threadName,
|
|
88
|
+
"process_name": record.processName,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Add exception info if available
|
|
92
|
+
if record.exc_info:
|
|
93
|
+
log_entry_dict.update(
|
|
94
|
+
{
|
|
95
|
+
"exception_type": record.exc_info[0].__name__
|
|
96
|
+
if record.exc_info and record.exc_info[0]
|
|
97
|
+
else None,
|
|
98
|
+
"exception_message": str(record.exc_info[1])
|
|
99
|
+
if record.exc_info and record.exc_info[1]
|
|
100
|
+
else None,
|
|
101
|
+
"exception_traceback": record.exc_text,
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return log_entry_dict
|
|
106
|
+
|
|
107
|
+
def close(self) -> None:
|
|
108
|
+
"""Close the MongoDB client connection."""
|
|
109
|
+
try:
|
|
110
|
+
if hasattr(self, "client") and self.client:
|
|
111
|
+
self.client.close()
|
|
112
|
+
except Exception as e:
|
|
113
|
+
print(f"Error closing DB log handler client: {e}")
|
|
114
|
+
|
|
115
|
+
def __del__(self):
|
|
116
|
+
"""Ensure connection is closed when handler is destroyed."""
|
|
117
|
+
self.close()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from odmantic import Model
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def default_from_model(cls_method, model: Model, **kwargs) -> Model:
|
|
10
|
+
model_dict = model.model_dump(exclude={"id"})
|
|
11
|
+
return cls_method.from_dict(model_dict, **kwargs)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def default_from_dict(cls_method, data: dict, **kwargs) -> Any:
|
|
15
|
+
"""
|
|
16
|
+
Create an instance of the model from a dictionary.
|
|
17
|
+
Handles nested models and enum values.
|
|
18
|
+
"""
|
|
19
|
+
if not isinstance(data, dict):
|
|
20
|
+
return data # Return as is if it's not a dictionary
|
|
21
|
+
|
|
22
|
+
processed_data = {}
|
|
23
|
+
for field_name, field in cls_method.model_fields.items():
|
|
24
|
+
if field_name not in data:
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
field_type = field.annotation
|
|
28
|
+
field_value = data[field_name]
|
|
29
|
+
|
|
30
|
+
# Handle enum values
|
|
31
|
+
if hasattr(field_type, "__members__"):
|
|
32
|
+
logger.debug("Handling enum field: %s", field_name)
|
|
33
|
+
if isinstance(field_value, field_type):
|
|
34
|
+
# If it's already an enum instance, use it directly
|
|
35
|
+
logger.debug("Field value is enum instance: %s", field_value)
|
|
36
|
+
processed_data[field_name] = field_value
|
|
37
|
+
else:
|
|
38
|
+
# Try to create enum from the value
|
|
39
|
+
try:
|
|
40
|
+
processed_data[field_name] = field_type(field_value)
|
|
41
|
+
logger.debug(
|
|
42
|
+
"Converted field value to enum: %s", processed_data[field_name]
|
|
43
|
+
)
|
|
44
|
+
except ValueError:
|
|
45
|
+
logger.debug(
|
|
46
|
+
"Invalid enum value for field %s: %s", field_name, field_value
|
|
47
|
+
)
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"Invalid enum value '{field_value}' for field {field_name}"
|
|
50
|
+
)
|
|
51
|
+
# Handle nested models
|
|
52
|
+
elif hasattr(field_type, "from_dict") and isinstance(field_value, dict):
|
|
53
|
+
processed_data[field_name] = field_type.from_dict(field_value)
|
|
54
|
+
else:
|
|
55
|
+
processed_data[field_name] = field_value
|
|
56
|
+
|
|
57
|
+
return cls_method.model_validate(processed_data)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import fnmatch
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Iterator, List, Union, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DirectoryPath:
|
|
8
|
+
"""
|
|
9
|
+
Represents a directory path with excluded subdirectory patterns and provides iteration capabilities.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
root_path: Union[str, Path],
|
|
15
|
+
excluded_patterns: Optional[List[str]] = None,
|
|
16
|
+
file_extensions: Optional[List[str]] = None,
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Initialize the DirectoryPath.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
root_path: The root directory path to traverse
|
|
23
|
+
excluded_patterns: List of patterns to exclude (supports wildcards like *, ?, [])
|
|
24
|
+
file_extensions: List of file extensions to include (e.g., ['.py', '.txt'])
|
|
25
|
+
"""
|
|
26
|
+
self.root_path = Path(root_path).resolve()
|
|
27
|
+
self.excluded_patterns = excluded_patterns or []
|
|
28
|
+
self.file_extensions = file_extensions or []
|
|
29
|
+
|
|
30
|
+
if not self.root_path.exists():
|
|
31
|
+
raise FileNotFoundError(f"Directory does not exist: {self.root_path}")
|
|
32
|
+
if not self.root_path.is_dir():
|
|
33
|
+
raise NotADirectoryError(f"Path is not a directory: {self.root_path}")
|
|
34
|
+
|
|
35
|
+
def _is_excluded(self, path: Path) -> bool:
|
|
36
|
+
"""
|
|
37
|
+
Check if a path matches any of the excluded patterns.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
path: Path to check
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if the path should be excluded, False otherwise
|
|
44
|
+
"""
|
|
45
|
+
path_str = str(path)
|
|
46
|
+
relative_path = str(path.relative_to(self.root_path))
|
|
47
|
+
|
|
48
|
+
for pattern in self.excluded_patterns:
|
|
49
|
+
# Check against full path
|
|
50
|
+
if fnmatch.fnmatch(path_str, pattern):
|
|
51
|
+
return True
|
|
52
|
+
# Check against relative path
|
|
53
|
+
if fnmatch.fnmatch(relative_path, pattern):
|
|
54
|
+
return True
|
|
55
|
+
# Check against directory name only
|
|
56
|
+
if fnmatch.fnmatch(path.name, pattern):
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def _matches_extensions(self, file_path: Path) -> bool:
|
|
62
|
+
"""
|
|
63
|
+
Check if a file matches the specified extensions.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
file_path: File path to check
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if the file matches the extensions or no extensions specified
|
|
70
|
+
"""
|
|
71
|
+
if not self.file_extensions:
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
return file_path.suffix.lower() in [ext.lower() for ext in self.file_extensions]
|
|
75
|
+
|
|
76
|
+
def iterate_files(self) -> Iterator[Path]:
|
|
77
|
+
"""
|
|
78
|
+
Iterate over all files in the directory tree, respecting exclusion patterns.
|
|
79
|
+
|
|
80
|
+
Yields:
|
|
81
|
+
Path objects for each file found
|
|
82
|
+
"""
|
|
83
|
+
for root, dirs, files in os.walk(self.root_path):
|
|
84
|
+
root_path = Path(root)
|
|
85
|
+
|
|
86
|
+
# Filter out excluded directories in-place to prevent walking into them
|
|
87
|
+
dirs[:] = [d for d in dirs if not self._is_excluded(root_path / d)]
|
|
88
|
+
|
|
89
|
+
# Yield files that match criteria
|
|
90
|
+
for file in files:
|
|
91
|
+
file_path = root_path / file
|
|
92
|
+
if not self._is_excluded(file_path) and self._matches_extensions(
|
|
93
|
+
file_path
|
|
94
|
+
):
|
|
95
|
+
yield file_path
|
|
96
|
+
|
|
97
|
+
def iterate_directories(self) -> Iterator[Path]:
|
|
98
|
+
"""
|
|
99
|
+
Iterate over all directories in the directory tree, respecting exclusion patterns.
|
|
100
|
+
|
|
101
|
+
Yields:
|
|
102
|
+
Path objects for each directory found
|
|
103
|
+
"""
|
|
104
|
+
for root, dirs, _ in os.walk(self.root_path):
|
|
105
|
+
root_path = Path(root)
|
|
106
|
+
|
|
107
|
+
# Filter and yield directories
|
|
108
|
+
for dir_name in dirs:
|
|
109
|
+
dir_path = root_path / dir_name
|
|
110
|
+
if not self._is_excluded(dir_path):
|
|
111
|
+
yield dir_path
|
|
112
|
+
|
|
113
|
+
# Filter out excluded directories to prevent walking into them
|
|
114
|
+
dirs[:] = [d for d in dirs if not self._is_excluded(root_path / d)]
|
|
115
|
+
|
|
116
|
+
def __iter__(self) -> Iterator[Path]:
|
|
117
|
+
"""
|
|
118
|
+
Default iterator returns files.
|
|
119
|
+
"""
|
|
120
|
+
return self.iterate_files()
|
|
121
|
+
|
|
122
|
+
def get_files_list(self) -> List[Path]:
|
|
123
|
+
"""
|
|
124
|
+
Get a list of all files matching the criteria.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
List of Path objects
|
|
128
|
+
"""
|
|
129
|
+
return list(self.iterate_files())
|
|
130
|
+
|
|
131
|
+
def get_directories_list(self) -> List[Path]:
|
|
132
|
+
"""
|
|
133
|
+
Get a list of all directories matching the criteria.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of Path objects
|
|
137
|
+
"""
|
|
138
|
+
return list(self.iterate_directories())
|
|
139
|
+
|
|
140
|
+
def add_excluded_pattern(self, pattern: str) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Add an exclusion pattern.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
pattern: Pattern to exclude
|
|
146
|
+
"""
|
|
147
|
+
if pattern not in self.excluded_patterns:
|
|
148
|
+
self.excluded_patterns.append(pattern)
|
|
149
|
+
|
|
150
|
+
def remove_excluded_pattern(self, pattern: str) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Remove an exclusion pattern.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
pattern: Pattern to remove
|
|
156
|
+
"""
|
|
157
|
+
if pattern in self.excluded_patterns:
|
|
158
|
+
self.excluded_patterns.remove(pattern)
|
|
159
|
+
|
|
160
|
+
def __str__(self) -> str:
|
|
161
|
+
return (
|
|
162
|
+
f"DirectoryPath(root='{self.root_path}', excluded={self.excluded_patterns})"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def __repr__(self) -> str:
|
|
166
|
+
return (
|
|
167
|
+
f"DirectoryPath(root_path='{self.root_path}', "
|
|
168
|
+
f"excluded_patterns={self.excluded_patterns}, "
|
|
169
|
+
f"file_extensions={self.file_extensions})"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# Example usage and utility functions
|
|
174
|
+
def find_python_files_with_exclusions(
|
|
175
|
+
root_path: str, excluded_patterns: List[str] = None
|
|
176
|
+
) -> List[Path]:
|
|
177
|
+
"""
|
|
178
|
+
Find Python files using the DirectoryPath class with exclusions.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
root_path: Root directory to search
|
|
182
|
+
excluded_patterns: Patterns to exclude
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
List of Python file paths
|
|
186
|
+
"""
|
|
187
|
+
excluded_patterns = excluded_patterns or [
|
|
188
|
+
"__pycache__",
|
|
189
|
+
"*.pyc",
|
|
190
|
+
".git",
|
|
191
|
+
".venv",
|
|
192
|
+
"venv",
|
|
193
|
+
]
|
|
194
|
+
dir_path = DirectoryPath(root_path, excluded_patterns, [".py"])
|
|
195
|
+
return dir_path.get_files_list()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
# Example usage
|
|
200
|
+
try:
|
|
201
|
+
# Create a DirectoryPath instance
|
|
202
|
+
dp = DirectoryPath(
|
|
203
|
+
root_path=".",
|
|
204
|
+
excluded_patterns=["__pycache__", "*.pyc", ".git", "node_modules", "*.log"],
|
|
205
|
+
file_extensions=[".py", ".txt"],
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
print(f"Directory Path: {dp}")
|
|
209
|
+
print("\nFound files:")
|
|
210
|
+
for file_path in dp:
|
|
211
|
+
print(f" {file_path}")
|
|
212
|
+
|
|
213
|
+
print(f"\nTotal files found: {len(dp.get_files_list())}")
|
|
214
|
+
|
|
215
|
+
except (FileNotFoundError, NotADirectoryError) as e:
|
|
216
|
+
print(f"Error: {e}")
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import re
|
|
3
|
+
from typing import Optional, Dict, Any, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DocstringParser:
|
|
7
|
+
"""
|
|
8
|
+
Parse a docstring into structured components.
|
|
9
|
+
|
|
10
|
+
All component getters return None when the corresponding section is not present.
|
|
11
|
+
Supported sections:
|
|
12
|
+
- Args: / Parameters:
|
|
13
|
+
- Returns:
|
|
14
|
+
- SimstackResult:
|
|
15
|
+
- CalledNodes:
|
|
16
|
+
- Raises:
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_SECTION_NAMES = ("Args:", "Parameters:", "Returns:", "SimstackResult:", "CalledNodes:", "Raises:")
|
|
20
|
+
|
|
21
|
+
def __init__(self, docstring: Optional[str]) -> None:
|
|
22
|
+
self._raw = docstring or ""
|
|
23
|
+
self._doc = inspect.cleandoc(self._raw) if self._raw else ""
|
|
24
|
+
self._cache: Dict[str, Any] = {}
|
|
25
|
+
|
|
26
|
+
def _section(self, name: str, until: tuple[str, ...]) -> Optional[str]:
|
|
27
|
+
"""
|
|
28
|
+
Extract the body text after `name` up to any of the `until` markers (or end).
|
|
29
|
+
Returns None if the section marker isn't present.
|
|
30
|
+
"""
|
|
31
|
+
if not self._doc:
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
# e.g. r"Returns:(.*?)(?:SimstackResult:|CalledNodes:|Raises:|$)"
|
|
35
|
+
until_alt = "|".join(map(re.escape, until)) + "|$"
|
|
36
|
+
pattern = rf"{re.escape(name)}(.*?)(?:{until_alt})"
|
|
37
|
+
match = re.search(pattern, self._doc, re.DOTALL)
|
|
38
|
+
if not match:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
body = match.group(1).strip()
|
|
42
|
+
return body if body else None
|
|
43
|
+
|
|
44
|
+
def description(self) -> Optional[str]:
|
|
45
|
+
if "description" in self._cache:
|
|
46
|
+
return self._cache["description"]
|
|
47
|
+
|
|
48
|
+
if not self._doc:
|
|
49
|
+
self._cache["description"] = None
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
match = re.search(
|
|
53
|
+
r"^(.*?)(?:Args:|Parameters:|Returns:|SimstackResult:|CalledNodes:|Raises:|$)",
|
|
54
|
+
self._doc,
|
|
55
|
+
re.DOTALL,
|
|
56
|
+
)
|
|
57
|
+
desc = match.group(1).strip() if match else ""
|
|
58
|
+
self._cache["description"] = desc if desc else None
|
|
59
|
+
return self._cache["description"]
|
|
60
|
+
|
|
61
|
+
def params(self) -> Optional[Dict[str, Dict[str, Optional[str]]]]:
|
|
62
|
+
if "params" in self._cache:
|
|
63
|
+
return self._cache["params"]
|
|
64
|
+
|
|
65
|
+
param_text = self._section("Args:", ("Returns:", "SimstackResult:", "CalledNodes:", "Raises:"))
|
|
66
|
+
if param_text is None:
|
|
67
|
+
param_text = self._section("Parameters:", ("Returns:", "SimstackResult:", "CalledNodes:", "Raises:"))
|
|
68
|
+
|
|
69
|
+
if not param_text:
|
|
70
|
+
self._cache["params"] = None
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
params: Dict[str, Dict[str, Optional[str]]] = {}
|
|
74
|
+
param_matches = re.finditer(
|
|
75
|
+
r"(\w+)\s*(?:\(([^)]+)\))?\s*:\s*(.+?)(?=\n\s*\w+\s*:|$)",
|
|
76
|
+
param_text,
|
|
77
|
+
re.DOTALL,
|
|
78
|
+
)
|
|
79
|
+
for match in param_matches:
|
|
80
|
+
param_name = match.group(1)
|
|
81
|
+
param_type = match.group(2) # Maybe None
|
|
82
|
+
param_desc = match.group(3).strip()
|
|
83
|
+
params[param_name] = {"type": param_type, "description": param_desc}
|
|
84
|
+
|
|
85
|
+
self._cache["params"] = params if params else None
|
|
86
|
+
return self._cache["params"]
|
|
87
|
+
|
|
88
|
+
def returns(self) -> Optional[Dict[str, str]]:
|
|
89
|
+
if "returns" in self._cache:
|
|
90
|
+
return self._cache["returns"]
|
|
91
|
+
|
|
92
|
+
return_text = self._section("Returns:", ("SimstackResult:", "CalledNodes:", "Raises:"))
|
|
93
|
+
if not return_text:
|
|
94
|
+
self._cache["returns"] = None
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
self._cache["returns"] = {"description": return_text}
|
|
98
|
+
return self._cache["returns"]
|
|
99
|
+
|
|
100
|
+
def simstack_results(self) -> Optional[Dict[str, Dict[str, str]]]:
|
|
101
|
+
if "simstack_results" in self._cache:
|
|
102
|
+
return self._cache["simstack_results"]
|
|
103
|
+
|
|
104
|
+
simstack_text = self._section("SimstackResult:", ("CalledNodes:", "Raises:"))
|
|
105
|
+
if not simstack_text:
|
|
106
|
+
self._cache["simstack_results"] = None
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
simstack_results: Dict[str, Dict[str, str]] = {}
|
|
110
|
+
simstack_matches = re.finditer(
|
|
111
|
+
r"(\w+)\s*\(([^)]+)\)\s*(.+?)(?=\n\s*\w+\s*\(|$)",
|
|
112
|
+
simstack_text,
|
|
113
|
+
re.DOTALL,
|
|
114
|
+
)
|
|
115
|
+
for match in simstack_matches:
|
|
116
|
+
result_name = match.group(1)
|
|
117
|
+
result_type = match.group(2).strip()
|
|
118
|
+
result_desc = match.group(3).strip()
|
|
119
|
+
simstack_results[result_name] = {"name": result_name, "type": result_type, "description": result_desc}
|
|
120
|
+
|
|
121
|
+
self._cache["simstack_results"] = simstack_results if simstack_results else None
|
|
122
|
+
return self._cache["simstack_results"]
|
|
123
|
+
|
|
124
|
+
def called_nodes(self) -> Optional[List[str]]:
|
|
125
|
+
if "called_nodes" in self._cache:
|
|
126
|
+
return self._cache["called_nodes"]
|
|
127
|
+
|
|
128
|
+
called_nodes_text = self._section("Called Nodes:", ("Raises:","Notes"))
|
|
129
|
+
if not called_nodes_text:
|
|
130
|
+
self._cache["called_nodes"] = None
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
nodes = [
|
|
134
|
+
line.strip().lstrip("-").strip()
|
|
135
|
+
for line in called_nodes_text.split("\n")
|
|
136
|
+
if line.strip()
|
|
137
|
+
]
|
|
138
|
+
self._cache["called_nodes"] = nodes if nodes else None
|
|
139
|
+
return self._cache["called_nodes"]
|
|
140
|
+
|
|
141
|
+
def raises(self) -> Optional[Dict[str, Dict[str, str]]]:
|
|
142
|
+
if "raises" in self._cache:
|
|
143
|
+
return self._cache["raises"]
|
|
144
|
+
|
|
145
|
+
raises_text = self._section("Raises:", ())
|
|
146
|
+
if not raises_text:
|
|
147
|
+
self._cache["raises"] = None
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
raises: Dict[str, Dict[str, str]] = {}
|
|
151
|
+
raises_matches = re.finditer(
|
|
152
|
+
r"(\w+)\s*:\s*(.+?)(?=\n\s*\w+\s*:|$)",
|
|
153
|
+
raises_text,
|
|
154
|
+
re.DOTALL,
|
|
155
|
+
)
|
|
156
|
+
for match in raises_matches:
|
|
157
|
+
exception_name = match.group(1)
|
|
158
|
+
exception_desc = match.group(2).strip()
|
|
159
|
+
raises[exception_name] = {"description": exception_desc}
|
|
160
|
+
|
|
161
|
+
self._cache["raises"] = raises if raises else None
|
|
162
|
+
return self._cache["raises"]
|
|
163
|
+
|
|
164
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
165
|
+
"""
|
|
166
|
+
Backward-compatible structure (always includes keys),
|
|
167
|
+
while still using the None-returning component getters internally.
|
|
168
|
+
"""
|
|
169
|
+
return {
|
|
170
|
+
"description": self.description() or "",
|
|
171
|
+
"params": self.params() or {},
|
|
172
|
+
"returns": self.returns() or {},
|
|
173
|
+
"simstack_results": self.simstack_results() or {},
|
|
174
|
+
"called_nodes": self.called_nodes() or [],
|
|
175
|
+
"raises": self.raises() or {},
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def parse_docstring(docstring: Optional[str]) -> Dict[str, Any]:
|
|
180
|
+
"""
|
|
181
|
+
Backward-compatible wrapper around DocstringParser.
|
|
182
|
+
|
|
183
|
+
Prefer using DocstringParser directly when you want per-section access with
|
|
184
|
+
None when a section is missing.
|
|
185
|
+
"""
|
|
186
|
+
return DocstringParser(docstring).as_dict()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Union, BinaryIO
|
|
4
|
+
|
|
5
|
+
def hash_file(
|
|
6
|
+
file_path: Union[str, Path], algorithm: str = "sha256", chunk_size: int = 8192
|
|
7
|
+
) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Calculate hash of a file on disk.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
file_path: Path to the file
|
|
13
|
+
algorithm: Hash algorithm (md5, sha1, sha256, etc.)
|
|
14
|
+
chunk_size: Size of chunks to read
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
str: Hexadecimal digest of the hash
|
|
18
|
+
"""
|
|
19
|
+
hash_obj = hashlib.new(algorithm)
|
|
20
|
+
|
|
21
|
+
with open(file_path, "rb") as f:
|
|
22
|
+
for chunk in iter(lambda: f.read(chunk_size), b""):
|
|
23
|
+
hash_obj.update(chunk)
|
|
24
|
+
|
|
25
|
+
return hash_obj.hexdigest()
|
|
26
|
+
|
|
27
|
+
def hash_string(string: str, algorithm: str = "sha256") -> str:
|
|
28
|
+
hash_obj = hashlib.new(algorithm)
|
|
29
|
+
hash_obj.update(string.encode())
|
|
30
|
+
return hash_obj.hexdigest()
|
|
31
|
+
|
|
32
|
+
def hash_file_object(
|
|
33
|
+
file_obj: BinaryIO, algorithm: str = "sha256", chunk_size: int = 8192
|
|
34
|
+
) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Calculate hash of a file-like object.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
file_obj: File-like object (must be in binary mode)
|
|
40
|
+
algorithm: Hash algorithm (md5, sha1, sha256, etc.)
|
|
41
|
+
chunk_size: Size of chunks to read
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
str: Hexadecimal digest of the hash
|
|
45
|
+
"""
|
|
46
|
+
hash_obj = hashlib.new(algorithm)
|
|
47
|
+
|
|
48
|
+
# Store current position
|
|
49
|
+
try:
|
|
50
|
+
original_position = file_obj.tell()
|
|
51
|
+
except (OSError, IOError, AttributeError):
|
|
52
|
+
original_position = None
|
|
53
|
+
|
|
54
|
+
# Seek to the beginning if possible
|
|
55
|
+
try:
|
|
56
|
+
file_obj.seek(0)
|
|
57
|
+
except (OSError, IOError, AttributeError):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
# Calculate hash
|
|
61
|
+
for chunk in iter(lambda: file_obj.read(chunk_size), b""):
|
|
62
|
+
hash_obj.update(chunk)
|
|
63
|
+
|
|
64
|
+
# Restore original position if possible
|
|
65
|
+
if original_position is not None:
|
|
66
|
+
try:
|
|
67
|
+
file_obj.seek(original_position)
|
|
68
|
+
except (OSError, IOError, AttributeError):
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
return hash_obj.hexdigest()
|
|
72
|
+
|