synapse-sdk 1.0.0a23__py3-none-any.whl → 2025.12.3__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.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +310 -5
- synapse_sdk/cli/alias/__init__.py +22 -0
- synapse_sdk/cli/alias/create.py +36 -0
- synapse_sdk/cli/alias/dataclass.py +31 -0
- synapse_sdk/cli/alias/default.py +16 -0
- synapse_sdk/cli/alias/delete.py +15 -0
- synapse_sdk/cli/alias/list.py +19 -0
- synapse_sdk/cli/alias/read.py +15 -0
- synapse_sdk/cli/alias/update.py +17 -0
- synapse_sdk/cli/alias/utils.py +61 -0
- synapse_sdk/cli/code_server.py +687 -0
- synapse_sdk/cli/config.py +440 -0
- synapse_sdk/cli/devtools.py +90 -0
- synapse_sdk/cli/plugin/__init__.py +33 -0
- synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
- synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
- synapse_sdk/clients/agent/__init__.py +9 -3
- synapse_sdk/clients/agent/container.py +143 -0
- synapse_sdk/clients/agent/core.py +19 -0
- synapse_sdk/clients/agent/ray.py +298 -9
- synapse_sdk/clients/backend/__init__.py +30 -12
- synapse_sdk/clients/backend/annotation.py +13 -5
- synapse_sdk/clients/backend/core.py +31 -4
- synapse_sdk/clients/backend/data_collection.py +186 -0
- synapse_sdk/clients/backend/hitl.py +17 -0
- synapse_sdk/clients/backend/integration.py +16 -1
- synapse_sdk/clients/backend/ml.py +5 -1
- synapse_sdk/clients/backend/models.py +78 -0
- synapse_sdk/clients/base.py +384 -41
- synapse_sdk/clients/ray/serve.py +2 -0
- synapse_sdk/clients/validators/collections.py +31 -0
- synapse_sdk/devtools/config.py +94 -0
- synapse_sdk/devtools/server.py +41 -0
- synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
- synapse_sdk/devtools/streamlit_app/app.py +128 -0
- synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
- synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
- synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
- synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
- synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
- synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
- synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
- synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
- synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
- synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
- synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
- synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
- synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
- synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
- synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
- synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
- synapse_sdk/devtools/streamlit_app.py +10 -0
- synapse_sdk/loggers.py +120 -9
- synapse_sdk/plugins/README.md +1340 -0
- synapse_sdk/plugins/__init__.py +0 -13
- synapse_sdk/plugins/categories/base.py +117 -11
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
- synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
- synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
- synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
- synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
- synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
- synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
- synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
- synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
- synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
- synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
- synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
- synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
- synapse_sdk/plugins/categories/upload/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
- synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
- synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
- synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
- synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
- synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
- synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
- synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
- synapse_sdk/plugins/enums.py +3 -1
- synapse_sdk/plugins/models.py +148 -11
- synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
- synapse_sdk/plugins/templates/schema.json +491 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
- synapse_sdk/plugins/utils/__init__.py +46 -0
- synapse_sdk/plugins/utils/actions.py +119 -0
- synapse_sdk/plugins/utils/config.py +203 -0
- synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
- synapse_sdk/plugins/utils/ray_gcs.py +66 -0
- synapse_sdk/plugins/utils/registry.py +58 -0
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/shared/enums.py +93 -0
- synapse_sdk/types.py +19 -0
- synapse_sdk/utils/converters/__init__.py +240 -0
- synapse_sdk/utils/converters/coco/__init__.py +0 -0
- synapse_sdk/utils/converters/coco/from_dm.py +322 -0
- synapse_sdk/utils/converters/coco/to_dm.py +215 -0
- synapse_sdk/utils/converters/dm/__init__.py +57 -0
- synapse_sdk/utils/converters/dm/base.py +137 -0
- synapse_sdk/utils/converters/dm/from_v1.py +273 -0
- synapse_sdk/utils/converters/dm/to_v1.py +321 -0
- synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
- synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
- synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
- synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
- synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
- synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
- synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
- synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
- synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
- synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
- synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
- synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
- synapse_sdk/utils/converters/dm/types.py +168 -0
- synapse_sdk/utils/converters/dm/utils.py +162 -0
- synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
- synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
- synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
- synapse_sdk/utils/converters/pascal/__init__.py +0 -0
- synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
- synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
- synapse_sdk/utils/converters/yolo/__init__.py +0 -0
- synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
- synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
- synapse_sdk/utils/dataset.py +46 -0
- synapse_sdk/utils/encryption.py +158 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +32 -0
- synapse_sdk/utils/file/checksum.py +56 -0
- synapse_sdk/utils/file/chunking.py +31 -0
- synapse_sdk/utils/file/download.py +385 -0
- synapse_sdk/utils/file/encoding.py +40 -0
- synapse_sdk/utils/file/io.py +22 -0
- synapse_sdk/utils/file/upload.py +165 -0
- synapse_sdk/utils/file/video/__init__.py +29 -0
- synapse_sdk/utils/file/video/transcode.py +307 -0
- synapse_sdk/utils/file.py.backup +301 -0
- synapse_sdk/utils/http.py +138 -0
- synapse_sdk/utils/network.py +309 -0
- synapse_sdk/utils/storage/__init__.py +72 -0
- synapse_sdk/utils/storage/providers/__init__.py +183 -0
- synapse_sdk/utils/storage/providers/file_system.py +134 -0
- synapse_sdk/utils/storage/providers/gcp.py +13 -0
- synapse_sdk/utils/storage/providers/http.py +190 -0
- synapse_sdk/utils/storage/providers/s3.py +91 -0
- synapse_sdk/utils/storage/providers/sftp.py +47 -0
- synapse_sdk/utils/storage/registry.py +17 -0
- synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
- synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/utils/file.py +0 -168
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
- synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
- /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
- /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
- /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
- {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
|
|
4
|
+
from ..context import StepResult, UploadContext
|
|
5
|
+
from ..enums import LogCode
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseStep(ABC):
|
|
9
|
+
"""Abstract base class for all workflow steps."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
"""Step name for logging and tracking."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def progress_weight(self) -> float:
|
|
20
|
+
"""Relative weight for progress calculation (0.0 to 1.0)."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def execute(self, context: UploadContext) -> StepResult:
|
|
25
|
+
"""Execute the step logic."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def can_skip(self, context: UploadContext) -> bool:
|
|
30
|
+
"""Determine if this step can be skipped based on context."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def rollback(self, context: UploadContext) -> None:
|
|
35
|
+
"""Rollback changes made by this step."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def validate_prerequisites(self, context: UploadContext) -> None:
|
|
39
|
+
"""Validate step prerequisites. Raises exception if not met."""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def log_step_start(self, context: UploadContext) -> None:
|
|
43
|
+
"""Log step start."""
|
|
44
|
+
context.run.log_message_with_code(LogCode.STEP_STARTING, self.name)
|
|
45
|
+
|
|
46
|
+
def log_step_complete(self, context: UploadContext) -> None:
|
|
47
|
+
"""Log step completion."""
|
|
48
|
+
context.run.log_message_with_code(LogCode.STEP_COMPLETED, self.name)
|
|
49
|
+
|
|
50
|
+
def log_step_skipped(self, context: UploadContext) -> None:
|
|
51
|
+
"""Log step skipped."""
|
|
52
|
+
context.run.log_message_with_code(LogCode.STEP_SKIPPED, self.name)
|
|
53
|
+
|
|
54
|
+
def log_step_error(self, context: UploadContext, error: str) -> None:
|
|
55
|
+
"""Log step error."""
|
|
56
|
+
context.run.log_message_with_code(LogCode.STEP_ERROR, self.name, error)
|
|
57
|
+
|
|
58
|
+
def create_success_result(
|
|
59
|
+
self,
|
|
60
|
+
data: Optional[Dict[str, Any]] = None,
|
|
61
|
+
rollback_data: Optional[Dict[str, Any]] = None,
|
|
62
|
+
skipped: bool = False,
|
|
63
|
+
) -> StepResult:
|
|
64
|
+
"""Create a successful step result."""
|
|
65
|
+
rollback_data = rollback_data or {}
|
|
66
|
+
rollback_data['step_name'] = self.name
|
|
67
|
+
return StepResult(success=True, data=data or {}, rollback_data=rollback_data, skipped=skipped)
|
|
68
|
+
|
|
69
|
+
def create_error_result(
|
|
70
|
+
self, error: str, rollback_data: Optional[Dict[str, Any]] = None, original_exception: Optional[Exception] = None
|
|
71
|
+
) -> StepResult:
|
|
72
|
+
"""Create an error step result."""
|
|
73
|
+
rollback_data = rollback_data or {}
|
|
74
|
+
rollback_data['step_name'] = self.name
|
|
75
|
+
return StepResult(
|
|
76
|
+
success=False, error=error, rollback_data=rollback_data, original_exception=original_exception
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def safe_execute(self, context: UploadContext) -> StepResult:
|
|
80
|
+
"""Execute step with error handling and logging."""
|
|
81
|
+
try:
|
|
82
|
+
self.validate_prerequisites(context)
|
|
83
|
+
|
|
84
|
+
if self.can_skip(context):
|
|
85
|
+
self.log_step_skipped(context)
|
|
86
|
+
return self.create_success_result(skipped=True)
|
|
87
|
+
|
|
88
|
+
self.log_step_start(context)
|
|
89
|
+
result = self.execute(context)
|
|
90
|
+
|
|
91
|
+
if result.success:
|
|
92
|
+
self.log_step_complete(context)
|
|
93
|
+
else:
|
|
94
|
+
self.log_step_error(context, result.error or 'Unknown error')
|
|
95
|
+
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
error_msg = f'Exception in step {self.name}: {str(e)}'
|
|
100
|
+
self.log_step_error(context, error_msg)
|
|
101
|
+
return self.create_error_result(error_msg, original_exception=e)
|
|
102
|
+
|
|
103
|
+
def __str__(self):
|
|
104
|
+
return f'{self.__class__.__name__}(name={self.name})'
|
|
105
|
+
|
|
106
|
+
def __repr__(self):
|
|
107
|
+
return f"{self.__class__.__name__}(name='{self.name}', weight={self.progress_weight})"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ..context import StepResult, UploadContext
|
|
6
|
+
from ..enums import LogCode
|
|
7
|
+
from .base import BaseStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CleanupStep(BaseStep):
|
|
11
|
+
"""Cleanup temporary resources and finalize workflow."""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def name(self) -> str:
|
|
15
|
+
return 'cleanup'
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def progress_weight(self) -> float:
|
|
19
|
+
return 0.05
|
|
20
|
+
|
|
21
|
+
def execute(self, context: UploadContext) -> StepResult:
|
|
22
|
+
"""Execute cleanup step."""
|
|
23
|
+
try:
|
|
24
|
+
# Cleanup temporary directory - commented out because duplicated process with ray cleanup process
|
|
25
|
+
# self._cleanup_temp_directory(context)
|
|
26
|
+
|
|
27
|
+
# Log completion
|
|
28
|
+
context.run.log_message_with_code(LogCode.IMPORT_COMPLETED)
|
|
29
|
+
|
|
30
|
+
return self.create_success_result(data={'cleanup_completed': True}, rollback_data={'temp_cleaned': True})
|
|
31
|
+
|
|
32
|
+
except Exception as e:
|
|
33
|
+
# Cleanup failures shouldn't stop the workflow
|
|
34
|
+
context.run.log_message_with_code(LogCode.CLEANUP_WARNING, str(e))
|
|
35
|
+
return self.create_success_result(
|
|
36
|
+
data={'cleanup_completed': False}, rollback_data={'cleanup_error': str(e)}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def can_skip(self, context: UploadContext) -> bool:
|
|
40
|
+
"""Cleanup step can be skipped if disabled."""
|
|
41
|
+
return context.get_param('skip_cleanup', False)
|
|
42
|
+
|
|
43
|
+
def rollback(self, context: UploadContext) -> None:
|
|
44
|
+
"""Rollback cleanup (nothing to rollback for cleanup)."""
|
|
45
|
+
context.run.log_message_with_code(LogCode.ROLLBACK_CLEANUP)
|
|
46
|
+
|
|
47
|
+
def _cleanup_temp_directory(self, context: UploadContext, temp_path: Path = None) -> None:
|
|
48
|
+
"""Clean up temporary directory."""
|
|
49
|
+
if temp_path is None:
|
|
50
|
+
try:
|
|
51
|
+
temp_path = Path(os.getcwd()) / 'temp'
|
|
52
|
+
except (FileNotFoundError, OSError):
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
if not temp_path.exists():
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
shutil.rmtree(temp_path, ignore_errors=True)
|
|
60
|
+
context.run.log_message_with_code(LogCode.CLEANUP_TEMP_DIR_SUCCESS, temp_path)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
context.run.log_message_with_code(LogCode.CLEANUP_TEMP_DIR_FAILED, str(e))
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from synapse_sdk.plugins.exceptions import ActionError
|
|
2
|
+
|
|
3
|
+
from ..context import StepResult, UploadContext
|
|
4
|
+
from ..enums import LogCode
|
|
5
|
+
from .base import BaseStep
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AnalyzeCollectionStep(BaseStep):
|
|
9
|
+
"""Analyze data collection to get file specifications."""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def name(self) -> str:
|
|
13
|
+
return 'analyze_collection'
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def progress_weight(self) -> float:
|
|
17
|
+
return 0.05
|
|
18
|
+
|
|
19
|
+
def execute(self, context: UploadContext) -> StepResult:
|
|
20
|
+
"""Execute collection analysis step."""
|
|
21
|
+
collection_id = context.get_param('data_collection')
|
|
22
|
+
if collection_id is None:
|
|
23
|
+
return self.create_error_result('Data collection parameter is required')
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
# Set initial progress
|
|
27
|
+
context.run.set_progress(0, 2, category='analyze_collection')
|
|
28
|
+
|
|
29
|
+
# Get collection from client
|
|
30
|
+
collection = context.client.get_data_collection(collection_id)
|
|
31
|
+
context.run.set_progress(1, 2, category='analyze_collection')
|
|
32
|
+
|
|
33
|
+
# Extract file specifications
|
|
34
|
+
file_specifications = collection.get('file_specifications', [])
|
|
35
|
+
context.set_file_specifications(file_specifications)
|
|
36
|
+
|
|
37
|
+
# Complete progress
|
|
38
|
+
context.run.set_progress(2, 2, category='analyze_collection')
|
|
39
|
+
|
|
40
|
+
return self.create_success_result(
|
|
41
|
+
data={'file_specifications': file_specifications}, rollback_data={'collection_id': collection_id}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
return self.create_error_result(f'Failed to analyze collection {collection_id}: {str(e)}')
|
|
46
|
+
|
|
47
|
+
def can_skip(self, context: UploadContext) -> bool:
|
|
48
|
+
"""Collection analysis cannot be skipped."""
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
def rollback(self, context: UploadContext) -> None:
|
|
52
|
+
"""Rollback collection analysis."""
|
|
53
|
+
# Clear file specifications
|
|
54
|
+
context.file_specifications.clear()
|
|
55
|
+
context.run.log_message_with_code(LogCode.ROLLBACK_COLLECTION_ANALYSIS)
|
|
56
|
+
|
|
57
|
+
def validate_prerequisites(self, context: UploadContext) -> None:
|
|
58
|
+
"""Validate prerequisites for collection analysis."""
|
|
59
|
+
if context.client is None:
|
|
60
|
+
raise ActionError('Client is required for collection analysis')
|
|
61
|
+
|
|
62
|
+
if context.get_param('data_collection') is None:
|
|
63
|
+
raise ActionError('Data collection parameter is required')
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from ..context import StepResult, UploadContext
|
|
2
|
+
from ..enums import LogCode, UploadStatus
|
|
3
|
+
from .base import BaseStep
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GenerateDataUnitsStep(BaseStep):
|
|
7
|
+
"""Generate data units from uploaded files."""
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
def name(self) -> str:
|
|
11
|
+
return 'generate_data_units'
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def progress_weight(self) -> float:
|
|
15
|
+
return 0.20
|
|
16
|
+
|
|
17
|
+
def execute(self, context: UploadContext) -> StepResult:
|
|
18
|
+
"""Execute data unit generation step."""
|
|
19
|
+
data_unit_strategy = context.strategies.get('data_unit')
|
|
20
|
+
if not data_unit_strategy:
|
|
21
|
+
return self.create_error_result('Data unit strategy not found')
|
|
22
|
+
|
|
23
|
+
if not context.uploaded_files:
|
|
24
|
+
context.run.log_message_with_code(LogCode.NO_DATA_UNITS_GENERATED)
|
|
25
|
+
return self.create_error_result('No uploaded files to generate data units from')
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
# Setup progress tracking
|
|
29
|
+
upload_result_count = len(context.uploaded_files)
|
|
30
|
+
context.run.set_progress(0, upload_result_count, category='generate_data_units')
|
|
31
|
+
context.run.log_message_with_code(LogCode.GENERATING_DATA_UNITS)
|
|
32
|
+
|
|
33
|
+
# Initialize metrics
|
|
34
|
+
initial_metrics = {'stand_by': upload_result_count, 'success': 0, 'failed': 0}
|
|
35
|
+
context.update_metrics('data_units', initial_metrics)
|
|
36
|
+
context.run.set_metrics(initial_metrics, category='data_units')
|
|
37
|
+
|
|
38
|
+
# Get batch size from parameters
|
|
39
|
+
batch_size = context.get_param('creating_data_unit_batch_size', 1)
|
|
40
|
+
|
|
41
|
+
# Generate data units using strategy
|
|
42
|
+
generated_data_units = data_unit_strategy.generate(context.uploaded_files, batch_size)
|
|
43
|
+
|
|
44
|
+
# Update context
|
|
45
|
+
context.add_data_units(generated_data_units)
|
|
46
|
+
|
|
47
|
+
# Log data unit results
|
|
48
|
+
for data_unit in generated_data_units:
|
|
49
|
+
context.run.log_data_unit(
|
|
50
|
+
data_unit.get('id'), UploadStatus.SUCCESS, data_unit_meta=data_unit.get('meta')
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# Update final metrics
|
|
54
|
+
final_metrics = {'stand_by': 0, 'success': len(generated_data_units), 'failed': 0}
|
|
55
|
+
context.update_metrics('data_units', final_metrics)
|
|
56
|
+
context.run.set_metrics(final_metrics, category='data_units')
|
|
57
|
+
|
|
58
|
+
# Handle success vs failure cases
|
|
59
|
+
if generated_data_units:
|
|
60
|
+
# Success: Set completion progress with elapsed time
|
|
61
|
+
context.run.set_progress(upload_result_count, upload_result_count, category='generate_data_units')
|
|
62
|
+
return self.create_success_result(
|
|
63
|
+
data={'generated_data_units': generated_data_units},
|
|
64
|
+
rollback_data={'data_units_count': len(generated_data_units), 'batch_size': batch_size},
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
# Failure: Mark as failed with elapsed time but no completion
|
|
68
|
+
context.run.set_progress_failed(category='generate_data_units')
|
|
69
|
+
return self.create_error_result('No data units were successfully generated')
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
# Exception: Mark as failed with elapsed time
|
|
73
|
+
context.run.set_progress_failed(category='generate_data_units')
|
|
74
|
+
context.run.log_message_with_code(LogCode.DATA_UNIT_BATCH_FAILED, str(e))
|
|
75
|
+
return self.create_error_result(f'Data unit generation failed: {str(e)}')
|
|
76
|
+
|
|
77
|
+
def can_skip(self, context: UploadContext) -> bool:
|
|
78
|
+
"""Data unit generation cannot be skipped."""
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def rollback(self, context: UploadContext) -> None:
|
|
82
|
+
"""Rollback data unit generation."""
|
|
83
|
+
# In a real implementation, this would delete generated data units
|
|
84
|
+
# For now, just clear the data units list and log
|
|
85
|
+
context.data_units.clear()
|
|
86
|
+
context.run.log_message_with_code(LogCode.ROLLBACK_DATA_UNIT_GENERATION)
|
|
87
|
+
|
|
88
|
+
def validate_prerequisites(self, context: UploadContext) -> None:
|
|
89
|
+
"""Validate prerequisites for data unit generation."""
|
|
90
|
+
if not context.uploaded_files:
|
|
91
|
+
raise ValueError('No uploaded files available for data unit generation')
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from synapse_sdk.plugins.exceptions import ActionError
|
|
2
|
+
from synapse_sdk.utils.storage import get_pathlib
|
|
3
|
+
|
|
4
|
+
from ..context import StepResult, UploadContext
|
|
5
|
+
from ..enums import LogCode
|
|
6
|
+
from .base import BaseStep
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InitializeStep(BaseStep):
|
|
10
|
+
"""Initialize upload workflow by setting up storage and paths."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
return 'initialize'
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def progress_weight(self) -> float:
|
|
18
|
+
return 0.05
|
|
19
|
+
|
|
20
|
+
def execute(self, context: UploadContext) -> StepResult:
|
|
21
|
+
"""Execute initialization step."""
|
|
22
|
+
# Get and validate storage
|
|
23
|
+
storage_id = context.get_param('storage')
|
|
24
|
+
if storage_id is None:
|
|
25
|
+
return self.create_error_result('Storage parameter is required')
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
storage = context.client.get_storage(storage_id)
|
|
29
|
+
context.set_storage(storage)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
return self.create_error_result(f'Failed to get storage {storage_id}: {str(e)}')
|
|
32
|
+
|
|
33
|
+
# Check if we're in multi-path mode
|
|
34
|
+
use_single_path = context.get_param('use_single_path', True)
|
|
35
|
+
|
|
36
|
+
# Get and validate path (only required in single-path mode)
|
|
37
|
+
path = context.get_param('path')
|
|
38
|
+
pathlib_cwd = None
|
|
39
|
+
|
|
40
|
+
if use_single_path:
|
|
41
|
+
# Single-path mode: global path is required
|
|
42
|
+
if path is None:
|
|
43
|
+
return self.create_error_result('Path parameter is required in single-path mode')
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
pathlib_cwd = get_pathlib(storage, path)
|
|
47
|
+
context.set_pathlib_cwd(pathlib_cwd)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
return self.create_error_result(f'Failed to get path {path}: {str(e)}')
|
|
50
|
+
else:
|
|
51
|
+
# Multi-path mode: global path is optional (each asset has its own path)
|
|
52
|
+
if path:
|
|
53
|
+
try:
|
|
54
|
+
pathlib_cwd = get_pathlib(storage, path)
|
|
55
|
+
context.set_pathlib_cwd(pathlib_cwd)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
return self.create_error_result(f'Failed to get path {path}: {str(e)}')
|
|
58
|
+
|
|
59
|
+
# Return success with rollback data
|
|
60
|
+
rollback_data = {'storage_id': storage_id, 'path': path, 'use_single_path': use_single_path}
|
|
61
|
+
|
|
62
|
+
return self.create_success_result(
|
|
63
|
+
data={'storage': storage, 'pathlib_cwd': pathlib_cwd}, rollback_data=rollback_data
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def can_skip(self, context: UploadContext) -> bool:
|
|
67
|
+
"""Initialize step cannot be skipped."""
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def rollback(self, context: UploadContext) -> None:
|
|
71
|
+
"""Rollback initialization (cleanup if needed)."""
|
|
72
|
+
# For initialization, there's typically nothing to rollback
|
|
73
|
+
# But we could log the rollback action
|
|
74
|
+
context.run.log_message_with_code(LogCode.ROLLBACK_INITIALIZATION)
|
|
75
|
+
|
|
76
|
+
def validate_prerequisites(self, context: UploadContext) -> None:
|
|
77
|
+
"""Validate prerequisites for initialization."""
|
|
78
|
+
if context.client is None:
|
|
79
|
+
raise ActionError('Client is required for initialization')
|
|
80
|
+
|
|
81
|
+
if context.run is None:
|
|
82
|
+
raise ActionError('Run instance is required for initialization')
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from synapse_sdk.utils.storage import get_pathlib
|
|
4
|
+
|
|
5
|
+
from ..context import StepResult, UploadContext
|
|
6
|
+
from ..enums import LogCode
|
|
7
|
+
from ..exceptions import ExcelParsingError, ExcelSecurityError
|
|
8
|
+
from .base import BaseStep
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProcessMetadataStep(BaseStep):
|
|
12
|
+
"""Process metadata from Excel files or other sources.
|
|
13
|
+
|
|
14
|
+
This step handles Excel metadata file processing, including path resolution,
|
|
15
|
+
file extraction, and validation. Supports multiple path resolution strategies:
|
|
16
|
+
absolute paths, storage-relative paths, and working directory-relative paths.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def name(self) -> str:
|
|
21
|
+
return 'process_metadata'
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def progress_weight(self) -> float:
|
|
25
|
+
return 0.10
|
|
26
|
+
|
|
27
|
+
def execute(self, context: UploadContext) -> StepResult:
|
|
28
|
+
"""Execute metadata processing step.
|
|
29
|
+
|
|
30
|
+
Processes Excel metadata files by resolving the file path, extracting
|
|
31
|
+
metadata, and validating the extracted data. Supports multiple path
|
|
32
|
+
resolution strategies and handles both explicit metadata paths and
|
|
33
|
+
default metadata file discovery.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
context: Upload context containing parameters and state information.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
StepResult containing:
|
|
40
|
+
- success: True if metadata processing succeeded
|
|
41
|
+
- data: Dictionary with 'metadata' key containing extracted metadata
|
|
42
|
+
- rollback_data: Dictionary with 'metadata_processed' flag
|
|
43
|
+
|
|
44
|
+
Note:
|
|
45
|
+
If no metadata strategy is configured, the step completes successfully
|
|
46
|
+
with empty metadata. If excel_metadata_path is specified but the file
|
|
47
|
+
is not found, the step logs a warning and continues with empty metadata.
|
|
48
|
+
"""
|
|
49
|
+
metadata_strategy = context.strategies.get('metadata')
|
|
50
|
+
if not metadata_strategy:
|
|
51
|
+
context.run.log_message_with_code(LogCode.NO_METADATA_STRATEGY)
|
|
52
|
+
return self.create_success_result(data={'metadata': {}})
|
|
53
|
+
|
|
54
|
+
excel_metadata = {}
|
|
55
|
+
temp_file_to_cleanup = None
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
# Check if Excel metadata path is specified
|
|
59
|
+
excel_metadata_path_config = context.get_param('excel_metadata_path')
|
|
60
|
+
|
|
61
|
+
if excel_metadata_path_config:
|
|
62
|
+
# Path-based approach (only supported method)
|
|
63
|
+
excel_path = self._resolve_excel_path_from_string(excel_metadata_path_config, context)
|
|
64
|
+
|
|
65
|
+
if not excel_path or not excel_path.exists():
|
|
66
|
+
context.run.log_message_with_code(LogCode.EXCEL_FILE_NOT_FOUND_PATH)
|
|
67
|
+
return self.create_success_result(data={'metadata': {}})
|
|
68
|
+
|
|
69
|
+
excel_metadata = metadata_strategy.extract(excel_path)
|
|
70
|
+
else:
|
|
71
|
+
# Look for default metadata files (meta.xlsx, meta.xls)
|
|
72
|
+
# Only possible in single-path mode where pathlib_cwd is set
|
|
73
|
+
if context.pathlib_cwd:
|
|
74
|
+
excel_path = self._find_excel_metadata_file(context.pathlib_cwd)
|
|
75
|
+
if excel_path:
|
|
76
|
+
excel_metadata = metadata_strategy.extract(excel_path)
|
|
77
|
+
else:
|
|
78
|
+
context.run.log_message_with_code(LogCode.NO_METADATA_STRATEGY)
|
|
79
|
+
|
|
80
|
+
# Validate extracted metadata
|
|
81
|
+
if excel_metadata:
|
|
82
|
+
validation_result = metadata_strategy.validate(excel_metadata)
|
|
83
|
+
if not validation_result.valid:
|
|
84
|
+
error_msg = f'Metadata validation failed: {", ".join(validation_result.errors)}'
|
|
85
|
+
return self.create_error_result(error_msg)
|
|
86
|
+
context.run.log_message_with_code(LogCode.EXCEL_METADATA_LOADED, len(excel_metadata))
|
|
87
|
+
|
|
88
|
+
return self.create_success_result(
|
|
89
|
+
data={'metadata': excel_metadata}, rollback_data={'metadata_processed': len(excel_metadata) > 0}
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
except ExcelSecurityError as e:
|
|
93
|
+
context.run.log_message_with_code(LogCode.EXCEL_SECURITY_VIOLATION, str(e))
|
|
94
|
+
return self.create_error_result(f'Excel security violation: {str(e)}')
|
|
95
|
+
|
|
96
|
+
except ExcelParsingError as e:
|
|
97
|
+
# If excel_metadata_path was specified, this is an error
|
|
98
|
+
# If we were just looking for default files, it's not an error
|
|
99
|
+
if context.get_param('excel_metadata_path'):
|
|
100
|
+
context.run.log_message_with_code(LogCode.EXCEL_PARSING_ERROR, str(e))
|
|
101
|
+
return self.create_error_result(f'Excel parsing error: {str(e)}')
|
|
102
|
+
else:
|
|
103
|
+
context.run.log_message_with_code(LogCode.EXCEL_PARSING_ERROR, str(e))
|
|
104
|
+
return self.create_success_result(data={'metadata': {}})
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
return self.create_error_result(f'Unexpected error processing metadata: {str(e)}')
|
|
108
|
+
|
|
109
|
+
finally:
|
|
110
|
+
# Clean up temporary file if it was created from base64
|
|
111
|
+
if temp_file_to_cleanup and temp_file_to_cleanup.exists():
|
|
112
|
+
try:
|
|
113
|
+
temp_file_to_cleanup.unlink()
|
|
114
|
+
context.run.log_message_with_code(LogCode.METADATA_TEMP_FILE_CLEANUP, temp_file_to_cleanup)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
context.run.log_message_with_code(
|
|
117
|
+
LogCode.METADATA_TEMP_FILE_CLEANUP_FAILED, temp_file_to_cleanup, str(e)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def can_skip(self, context: UploadContext) -> bool:
|
|
121
|
+
"""Check if metadata step can be skipped.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
context: Upload context containing strategy configuration.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
True if no metadata strategy is configured, False otherwise.
|
|
128
|
+
"""
|
|
129
|
+
return 'metadata' not in context.strategies
|
|
130
|
+
|
|
131
|
+
def rollback(self, context: UploadContext) -> None:
|
|
132
|
+
"""Rollback metadata processing.
|
|
133
|
+
|
|
134
|
+
Clears any loaded metadata from the context to restore the state
|
|
135
|
+
before this step was executed.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
context: Upload context containing metadata to clear.
|
|
139
|
+
"""
|
|
140
|
+
# Clear any loaded metadata
|
|
141
|
+
context.metadata.clear()
|
|
142
|
+
|
|
143
|
+
def _resolve_excel_path_from_string(self, excel_path_str: str, context: UploadContext) -> Path | None:
|
|
144
|
+
"""Resolve Excel metadata path from a string path.
|
|
145
|
+
|
|
146
|
+
Attempts to resolve the Excel metadata file path using multiple strategies
|
|
147
|
+
in the following order:
|
|
148
|
+
1. Absolute filesystem path
|
|
149
|
+
2. Relative to storage default path (if storage is available)
|
|
150
|
+
3. Relative to working directory (if pathlib_cwd is set in single-path mode)
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
excel_path_str: File path string to the Excel metadata file.
|
|
154
|
+
context: Upload context containing storage configuration and working
|
|
155
|
+
directory information for path resolution.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Path object pointing to the Excel file if found, None otherwise.
|
|
159
|
+
|
|
160
|
+
Examples:
|
|
161
|
+
Absolute path:
|
|
162
|
+
|
|
163
|
+
>>> path = self._resolve_excel_path_from_string(
|
|
164
|
+
... "/data/meta.xlsx", context
|
|
165
|
+
... )
|
|
166
|
+
|
|
167
|
+
Storage-relative path:
|
|
168
|
+
|
|
169
|
+
>>> path = self._resolve_excel_path_from_string(
|
|
170
|
+
... "metadata/meta.xlsx", context
|
|
171
|
+
... )
|
|
172
|
+
|
|
173
|
+
Note:
|
|
174
|
+
When resolving storage-relative paths, the method logs the resolved
|
|
175
|
+
path for debugging purposes. Failed storage path resolution is logged
|
|
176
|
+
but does not raise an exception.
|
|
177
|
+
"""
|
|
178
|
+
# Try absolute path first
|
|
179
|
+
path = Path(excel_path_str)
|
|
180
|
+
if path.exists() and path.is_file():
|
|
181
|
+
return path
|
|
182
|
+
|
|
183
|
+
# Try relative to storage default path (if storage is available)
|
|
184
|
+
if context.storage:
|
|
185
|
+
try:
|
|
186
|
+
storage_base_path = get_pathlib(context.storage, excel_path_str)
|
|
187
|
+
if storage_base_path.exists() and storage_base_path.is_file():
|
|
188
|
+
context.run.log_message_with_code(LogCode.EXCEL_PATH_RESOLVED_STORAGE, str(storage_base_path))
|
|
189
|
+
return storage_base_path
|
|
190
|
+
except (FileNotFoundError, PermissionError) as e:
|
|
191
|
+
# Expected errors when path doesn't exist or no permissions
|
|
192
|
+
context.run.log_message_with_code(LogCode.EXCEL_PATH_RESOLUTION_FAILED, type(e).__name__, str(e))
|
|
193
|
+
except Exception as e:
|
|
194
|
+
# Unexpected errors should be logged with more detail for debugging
|
|
195
|
+
context.run.log_message_with_code(LogCode.EXCEL_PATH_RESOLUTION_ERROR, type(e).__name__, str(e))
|
|
196
|
+
|
|
197
|
+
# Try relative to cwd (only if pathlib_cwd is set - single-path mode)
|
|
198
|
+
if context.pathlib_cwd:
|
|
199
|
+
path = context.pathlib_cwd / excel_path_str
|
|
200
|
+
if path.exists() and path.is_file():
|
|
201
|
+
return path
|
|
202
|
+
|
|
203
|
+
# Could not resolve path
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
def _find_excel_metadata_file(self, pathlib_cwd: Path) -> Path:
|
|
207
|
+
"""Find default Excel metadata file in working directory.
|
|
208
|
+
|
|
209
|
+
Searches for standard Excel metadata filenames (meta.xlsx, meta.xls)
|
|
210
|
+
in the specified working directory. Prioritizes .xlsx format over .xls.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
pathlib_cwd: Working directory path to search in. Must not be None.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Path object to the Excel metadata file if found, None otherwise.
|
|
217
|
+
|
|
218
|
+
Note:
|
|
219
|
+
This method only searches the immediate working directory, not
|
|
220
|
+
subdirectories. The search order is: meta.xlsx, then meta.xls.
|
|
221
|
+
"""
|
|
222
|
+
if not pathlib_cwd:
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
# Check .xlsx first as it's more common
|
|
226
|
+
excel_path = pathlib_cwd / 'meta.xlsx'
|
|
227
|
+
if excel_path.exists():
|
|
228
|
+
return excel_path
|
|
229
|
+
|
|
230
|
+
# Fallback to .xls
|
|
231
|
+
excel_path = pathlib_cwd / 'meta.xls'
|
|
232
|
+
if excel_path.exists():
|
|
233
|
+
return excel_path
|
|
234
|
+
|
|
235
|
+
return None
|