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,214 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from pydantic import AfterValidator, BaseModel, ValidationInfo, field_validator, model_validator
|
|
4
|
+
from pydantic_core import PydanticCustomError
|
|
5
|
+
|
|
6
|
+
from synapse_sdk.clients.exceptions import ClientError
|
|
7
|
+
from synapse_sdk.utils.pydantic.validators import non_blank
|
|
8
|
+
|
|
9
|
+
from .enums import VALIDATION_ERROR_MESSAGES, ValidationErrorCode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AssetConfig(BaseModel):
|
|
13
|
+
"""Configuration for individual asset in multi-path mode.
|
|
14
|
+
|
|
15
|
+
Used when use_single_path=False to specify unique paths and recursive
|
|
16
|
+
settings for each file specification.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
path: File system path for this specific asset.
|
|
20
|
+
is_recursive: Whether to recursively search subdirectories for this
|
|
21
|
+
asset. Defaults to True.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> asset_config = AssetConfig(
|
|
25
|
+
... path="/sensors/camera/front",
|
|
26
|
+
... is_recursive=True
|
|
27
|
+
... )
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
path: str
|
|
31
|
+
is_recursive: bool = True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UploadParams(BaseModel):
|
|
35
|
+
"""Upload action parameter validation model.
|
|
36
|
+
|
|
37
|
+
Defines and validates all parameters required for upload operations.
|
|
38
|
+
Uses Pydantic for type validation and custom validators to ensure
|
|
39
|
+
storage, data_collection, and project resources exist before processing.
|
|
40
|
+
|
|
41
|
+
The model supports two operational modes controlled by the use_single_path
|
|
42
|
+
flag:
|
|
43
|
+
|
|
44
|
+
Single Path Mode (use_single_path=True, DEFAULT):
|
|
45
|
+
Traditional mode where all file specifications share one base path.
|
|
46
|
+
Requires 'path' parameter. Ignores 'assets' parameter.
|
|
47
|
+
|
|
48
|
+
Multi-Path Mode (use_single_path=False):
|
|
49
|
+
Advanced mode where each file specification has its own path.
|
|
50
|
+
Requires 'assets' parameter. Ignores 'path' and 'is_recursive' parameters.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
name: Human-readable name for the upload operation. Must be non-blank.
|
|
54
|
+
description: Optional description of the upload operation.
|
|
55
|
+
use_single_path: Mode selector. True for single path mode, False for
|
|
56
|
+
multi-path mode. Defaults to True.
|
|
57
|
+
path: Base path for single path mode. Required when use_single_path=True.
|
|
58
|
+
is_recursive: Global recursive setting for single path mode.
|
|
59
|
+
Defaults to True.
|
|
60
|
+
assets: Per-asset path configurations for multi-path mode. Dictionary
|
|
61
|
+
mapping file specification names to AssetConfig objects. Required
|
|
62
|
+
when use_single_path=False.
|
|
63
|
+
storage: Storage ID where files will be uploaded. Must exist and be
|
|
64
|
+
accessible via client API.
|
|
65
|
+
data_collection: Data collection ID for organizing uploads. Must exist
|
|
66
|
+
and be accessible via client API.
|
|
67
|
+
project: Optional project ID for grouping. Must exist if specified.
|
|
68
|
+
excel_metadata_path: Path to Excel metadata file. Can be:
|
|
69
|
+
- Absolute path: '/data/metadata.xlsx'
|
|
70
|
+
- Relative to storage default path: 'metadata.xlsx'
|
|
71
|
+
- Relative to working directory (single-path mode): 'metadata.xlsx'
|
|
72
|
+
max_file_size_mb: Maximum file size limit in megabytes. Defaults to 50.
|
|
73
|
+
creating_data_unit_batch_size: Batch size for data unit creation.
|
|
74
|
+
Defaults to 1.
|
|
75
|
+
use_async_upload: Whether to use asynchronous upload processing.
|
|
76
|
+
Defaults to True.
|
|
77
|
+
extra_params: Extra parameters for the action. Optional.
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
Single Path Mode (Traditional):
|
|
81
|
+
|
|
82
|
+
>>> params = UploadParams(
|
|
83
|
+
... name="Standard Upload",
|
|
84
|
+
... use_single_path=True,
|
|
85
|
+
... path="/data/experiment_1",
|
|
86
|
+
... is_recursive=True,
|
|
87
|
+
... storage=1,
|
|
88
|
+
... data_collection=5
|
|
89
|
+
... )
|
|
90
|
+
|
|
91
|
+
Multi-Path Mode (Advanced):
|
|
92
|
+
|
|
93
|
+
>>> params = UploadParams(
|
|
94
|
+
... name="Multi-Source Upload",
|
|
95
|
+
... use_single_path=False,
|
|
96
|
+
... assets={
|
|
97
|
+
... "image_1": AssetConfig(path="/sensors/camera", is_recursive=True),
|
|
98
|
+
... "pcd_1": AssetConfig(path="/sensors/lidar", is_recursive=False)
|
|
99
|
+
... },
|
|
100
|
+
... storage=1,
|
|
101
|
+
... data_collection=5
|
|
102
|
+
... )
|
|
103
|
+
|
|
104
|
+
With Excel Metadata:
|
|
105
|
+
|
|
106
|
+
>>> params = UploadParams(
|
|
107
|
+
... name="Upload with Metadata",
|
|
108
|
+
... path="/data/files",
|
|
109
|
+
... storage=1,
|
|
110
|
+
... data_collection=5,
|
|
111
|
+
... excel_metadata_path="metadata.xlsx"
|
|
112
|
+
... )
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
name: Annotated[str, AfterValidator(non_blank)]
|
|
116
|
+
description: str | None = None
|
|
117
|
+
|
|
118
|
+
# Mode selector flag (True = single path mode, False = multi-path mode)
|
|
119
|
+
use_single_path: bool = True
|
|
120
|
+
|
|
121
|
+
# Single path mode fields (used when use_single_path=True)
|
|
122
|
+
path: str | None = None
|
|
123
|
+
is_recursive: bool = True
|
|
124
|
+
|
|
125
|
+
# Multi-path mode fields (used when use_single_path=False)
|
|
126
|
+
assets: dict[str, AssetConfig] | None = None
|
|
127
|
+
|
|
128
|
+
storage: int
|
|
129
|
+
data_collection: int
|
|
130
|
+
project: int | None = None
|
|
131
|
+
|
|
132
|
+
# Excel metadata file path (absolute or relative to working directory)
|
|
133
|
+
excel_metadata_path: str | None = None
|
|
134
|
+
|
|
135
|
+
max_file_size_mb: int = 50
|
|
136
|
+
creating_data_unit_batch_size: int = 1
|
|
137
|
+
extra_params: dict | None = None
|
|
138
|
+
|
|
139
|
+
@field_validator('storage', mode='before')
|
|
140
|
+
@classmethod
|
|
141
|
+
def check_storage_exists(cls, value, info: ValidationInfo) -> int:
|
|
142
|
+
if info.context is None:
|
|
143
|
+
error_code = ValidationErrorCode.MISSING_CONTEXT
|
|
144
|
+
raise PydanticCustomError(error_code.value, VALIDATION_ERROR_MESSAGES[error_code])
|
|
145
|
+
|
|
146
|
+
action = info.context['action']
|
|
147
|
+
client = action.client
|
|
148
|
+
try:
|
|
149
|
+
client.get_storage(value)
|
|
150
|
+
except ClientError as e:
|
|
151
|
+
error_code = ValidationErrorCode.STORAGE_NOT_FOUND
|
|
152
|
+
error_message = VALIDATION_ERROR_MESSAGES[error_code].format(value, str(e))
|
|
153
|
+
raise PydanticCustomError(error_code.value, error_message)
|
|
154
|
+
return value
|
|
155
|
+
|
|
156
|
+
@field_validator('data_collection', mode='before')
|
|
157
|
+
@classmethod
|
|
158
|
+
def check_data_collection_exists(cls, value, info: ValidationInfo) -> int:
|
|
159
|
+
if info.context is None:
|
|
160
|
+
error_code = ValidationErrorCode.MISSING_CONTEXT
|
|
161
|
+
raise PydanticCustomError(error_code.value, VALIDATION_ERROR_MESSAGES[error_code])
|
|
162
|
+
|
|
163
|
+
action = info.context['action']
|
|
164
|
+
client = action.client
|
|
165
|
+
try:
|
|
166
|
+
client.get_data_collection(value)
|
|
167
|
+
except ClientError as e:
|
|
168
|
+
error_code = ValidationErrorCode.DATA_COLLECTION_NOT_FOUND
|
|
169
|
+
error_message = VALIDATION_ERROR_MESSAGES[error_code].format(value, str(e))
|
|
170
|
+
raise PydanticCustomError(error_code.value, error_message)
|
|
171
|
+
return value
|
|
172
|
+
|
|
173
|
+
@field_validator('project', mode='before')
|
|
174
|
+
@classmethod
|
|
175
|
+
def check_project_exists(cls, value, info: ValidationInfo) -> int | None:
|
|
176
|
+
if not value:
|
|
177
|
+
return value
|
|
178
|
+
|
|
179
|
+
if info.context is None:
|
|
180
|
+
error_code = ValidationErrorCode.MISSING_CONTEXT
|
|
181
|
+
raise PydanticCustomError(error_code.value, VALIDATION_ERROR_MESSAGES[error_code])
|
|
182
|
+
|
|
183
|
+
action = info.context['action']
|
|
184
|
+
client = action.client
|
|
185
|
+
try:
|
|
186
|
+
client.get_project(value)
|
|
187
|
+
except ClientError as e:
|
|
188
|
+
error_code = ValidationErrorCode.PROJECT_NOT_FOUND
|
|
189
|
+
error_message = VALIDATION_ERROR_MESSAGES[error_code].format(value, str(e))
|
|
190
|
+
raise PydanticCustomError(error_code.value, error_message)
|
|
191
|
+
return value
|
|
192
|
+
|
|
193
|
+
@model_validator(mode='after')
|
|
194
|
+
def validate_path_configuration(self) -> 'UploadParams':
|
|
195
|
+
"""Validate path configuration based on use_single_path mode."""
|
|
196
|
+
if self.use_single_path:
|
|
197
|
+
# Single path mode: requires path
|
|
198
|
+
if not self.path:
|
|
199
|
+
raise PydanticCustomError(
|
|
200
|
+
'missing_path', "When use_single_path=true (single path mode), 'path' is required"
|
|
201
|
+
)
|
|
202
|
+
# Warn if assets is provided in single path mode (it will be ignored)
|
|
203
|
+
# For now, we'll silently ignore it
|
|
204
|
+
else:
|
|
205
|
+
# Multi-path mode: requires assets
|
|
206
|
+
if not self.assets:
|
|
207
|
+
raise PydanticCustomError(
|
|
208
|
+
'missing_assets',
|
|
209
|
+
"When use_single_path=false (multi-path mode), 'assets' must be provided "
|
|
210
|
+
'with path configurations for each file specification',
|
|
211
|
+
)
|
|
212
|
+
# path and is_recursive are ignored in multi-path mode
|
|
213
|
+
|
|
214
|
+
return self
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
from .context import UploadContext
|
|
5
|
+
from .enums import LogCode
|
|
6
|
+
from .registry import StepRegistry
|
|
7
|
+
from .steps.base import BaseStep
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UploadOrchestrator:
|
|
11
|
+
"""Facade that orchestrates the upload workflow using strategies and steps."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, context: UploadContext, step_registry: StepRegistry, strategies: Dict[str, Any]):
|
|
14
|
+
self.context = context
|
|
15
|
+
self.step_registry = step_registry
|
|
16
|
+
self.strategies = strategies
|
|
17
|
+
self.executed_steps: List[BaseStep] = []
|
|
18
|
+
self.current_step_index = 0
|
|
19
|
+
|
|
20
|
+
def execute(self) -> Dict[str, Any]:
|
|
21
|
+
"""Execute the complete upload workflow."""
|
|
22
|
+
try:
|
|
23
|
+
self._log_workflow_start()
|
|
24
|
+
self._inject_strategies_into_context()
|
|
25
|
+
|
|
26
|
+
steps = self.step_registry.get_steps()
|
|
27
|
+
total_steps = len(steps)
|
|
28
|
+
|
|
29
|
+
for i, step in enumerate(steps):
|
|
30
|
+
self.current_step_index = i
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
result = step.safe_execute(self.context)
|
|
34
|
+
self.context.update(result)
|
|
35
|
+
|
|
36
|
+
if result.success:
|
|
37
|
+
if not result.skipped:
|
|
38
|
+
self.executed_steps.append(step)
|
|
39
|
+
self._update_progress(i + 1, total_steps)
|
|
40
|
+
else:
|
|
41
|
+
# Step failed, initiate rollback
|
|
42
|
+
self._log_step_failure(step, result.error)
|
|
43
|
+
self._rollback()
|
|
44
|
+
# Re-raise original exception if available, otherwise create new one
|
|
45
|
+
if result.original_exception:
|
|
46
|
+
raise result.original_exception
|
|
47
|
+
else:
|
|
48
|
+
raise Exception(f"Step '{step.name}' failed: {result.error}")
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
self._log_step_exception(step, str(e))
|
|
52
|
+
self._rollback()
|
|
53
|
+
raise
|
|
54
|
+
|
|
55
|
+
self._log_workflow_complete()
|
|
56
|
+
return self.context.get_result()
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
self._log_workflow_error(str(e))
|
|
60
|
+
# Ensure rollback is called if not already done
|
|
61
|
+
if not hasattr(self, '_rollback_executed'):
|
|
62
|
+
self._rollback()
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
def _inject_strategies_into_context(self) -> None:
|
|
66
|
+
"""Inject strategies into context for steps to use."""
|
|
67
|
+
if not hasattr(self.context, 'strategies'):
|
|
68
|
+
self.context.strategies = {}
|
|
69
|
+
self.context.strategies.update(self.strategies)
|
|
70
|
+
|
|
71
|
+
def _rollback(self) -> None:
|
|
72
|
+
"""Rollback executed steps in reverse order."""
|
|
73
|
+
if hasattr(self, '_rollback_executed'):
|
|
74
|
+
return # Prevent multiple rollbacks
|
|
75
|
+
|
|
76
|
+
self._rollback_executed = True
|
|
77
|
+
self._log_rollback_start()
|
|
78
|
+
|
|
79
|
+
# Rollback in reverse order
|
|
80
|
+
for step in reversed(self.executed_steps):
|
|
81
|
+
try:
|
|
82
|
+
self._log_step_rollback(step)
|
|
83
|
+
step.rollback(self.context)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
# Log rollback error but continue with other steps
|
|
86
|
+
self._log_rollback_error(step, str(e))
|
|
87
|
+
|
|
88
|
+
self._log_rollback_complete()
|
|
89
|
+
|
|
90
|
+
def _update_progress(self, current_step: int, total_steps: int) -> None:
|
|
91
|
+
"""Update overall progress based on step completion."""
|
|
92
|
+
if total_steps == 0:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
# Calculate progress based on step weights
|
|
96
|
+
completed_weight = 0.0
|
|
97
|
+
total_weight = self.step_registry.get_total_progress_weight()
|
|
98
|
+
|
|
99
|
+
for i, step in enumerate(self.executed_steps):
|
|
100
|
+
completed_weight += step.progress_weight
|
|
101
|
+
|
|
102
|
+
progress_percentage = (completed_weight / total_weight) * 100 if total_weight > 0 else 0
|
|
103
|
+
|
|
104
|
+
# Update context with progress information
|
|
105
|
+
self.context.update_metrics(
|
|
106
|
+
'workflow',
|
|
107
|
+
{
|
|
108
|
+
'current_step': current_step,
|
|
109
|
+
'total_steps': total_steps,
|
|
110
|
+
'progress_percentage': progress_percentage,
|
|
111
|
+
'completed_weight': completed_weight,
|
|
112
|
+
'total_weight': total_weight,
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def _log_workflow_start(self) -> None:
|
|
117
|
+
"""Log workflow start."""
|
|
118
|
+
steps = self.step_registry.get_steps()
|
|
119
|
+
step_names = [step.name for step in steps]
|
|
120
|
+
self.context.run.log_message_with_code(LogCode.WORKFLOW_STARTING, len(steps), step_names)
|
|
121
|
+
|
|
122
|
+
def _log_workflow_complete(self) -> None:
|
|
123
|
+
"""Log workflow completion."""
|
|
124
|
+
self.context.run.log_message_with_code(LogCode.WORKFLOW_COMPLETED)
|
|
125
|
+
|
|
126
|
+
def _log_workflow_error(self, error: str) -> None:
|
|
127
|
+
"""Log workflow error."""
|
|
128
|
+
self.context.run.log_message_with_code(LogCode.WORKFLOW_FAILED, error)
|
|
129
|
+
|
|
130
|
+
def _log_step_failure(self, step: BaseStep, error: str) -> None:
|
|
131
|
+
"""Log step failure."""
|
|
132
|
+
self.context.run.log_message_with_code(LogCode.STEP_FAILED, step.name, error)
|
|
133
|
+
|
|
134
|
+
def _log_step_exception(self, step: BaseStep, error: str) -> None:
|
|
135
|
+
"""Log step exception."""
|
|
136
|
+
self.context.run.log_message_with_code(LogCode.STEP_EXCEPTION, step.name, error)
|
|
137
|
+
# Log full traceback for debugging
|
|
138
|
+
self.context.run.log_message_with_code(LogCode.STEP_TRACEBACK, traceback.format_exc())
|
|
139
|
+
|
|
140
|
+
def _log_rollback_start(self) -> None:
|
|
141
|
+
"""Log rollback start."""
|
|
142
|
+
self.context.run.log_message_with_code(LogCode.ROLLBACK_STARTING, len(self.executed_steps))
|
|
143
|
+
|
|
144
|
+
def _log_rollback_complete(self) -> None:
|
|
145
|
+
"""Log rollback completion."""
|
|
146
|
+
self.context.run.log_message_with_code(LogCode.ROLLBACK_COMPLETED)
|
|
147
|
+
|
|
148
|
+
def _log_step_rollback(self, step: BaseStep) -> None:
|
|
149
|
+
"""Log step rollback."""
|
|
150
|
+
self.context.run.log_message_with_code(LogCode.STEP_ROLLBACK, step.name)
|
|
151
|
+
|
|
152
|
+
def _log_rollback_error(self, step: BaseStep, error: str) -> None:
|
|
153
|
+
"""Log rollback error."""
|
|
154
|
+
self.context.run.log_message_with_code(LogCode.ROLLBACK_ERROR, step.name, error)
|
|
155
|
+
|
|
156
|
+
def get_executed_steps(self) -> List[BaseStep]:
|
|
157
|
+
"""Get list of successfully executed steps."""
|
|
158
|
+
return self.executed_steps.copy()
|
|
159
|
+
|
|
160
|
+
def get_current_step_index(self) -> int:
|
|
161
|
+
"""Get current step index."""
|
|
162
|
+
return self.current_step_index
|
|
163
|
+
|
|
164
|
+
def get_total_steps(self) -> int:
|
|
165
|
+
"""Get total number of steps."""
|
|
166
|
+
return len(self.step_registry.get_steps())
|
|
167
|
+
|
|
168
|
+
def is_rollback_executed(self) -> bool:
|
|
169
|
+
"""Check if rollback has been executed."""
|
|
170
|
+
return hasattr(self, '_rollback_executed')
|
|
171
|
+
|
|
172
|
+
def get_workflow_summary(self) -> Dict[str, Any]:
|
|
173
|
+
"""Get workflow execution summary."""
|
|
174
|
+
steps = self.step_registry.get_steps()
|
|
175
|
+
return {
|
|
176
|
+
'total_steps': len(steps),
|
|
177
|
+
'executed_steps': len(self.executed_steps),
|
|
178
|
+
'current_step_index': self.current_step_index,
|
|
179
|
+
'step_names': [step.name for step in steps],
|
|
180
|
+
'executed_step_names': [step.name for step in self.executed_steps],
|
|
181
|
+
'rollback_executed': self.is_rollback_executed(),
|
|
182
|
+
'strategies': list(self.strategies.keys()) if self.strategies else [],
|
|
183
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
from .steps.base import BaseStep
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StepRegistry:
|
|
7
|
+
"""Registry for managing workflow steps."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._steps: List[BaseStep] = []
|
|
11
|
+
self._step_by_name: Dict[str, BaseStep] = {}
|
|
12
|
+
|
|
13
|
+
def register(self, step: BaseStep) -> None:
|
|
14
|
+
"""Register a step in the workflow."""
|
|
15
|
+
if step.name in self._step_by_name:
|
|
16
|
+
raise ValueError(f"Step with name '{step.name}' already registered")
|
|
17
|
+
|
|
18
|
+
self._steps.append(step)
|
|
19
|
+
self._step_by_name[step.name] = step
|
|
20
|
+
|
|
21
|
+
def unregister(self, step_name: str) -> bool:
|
|
22
|
+
"""Unregister a step by name. Returns True if step was found and removed."""
|
|
23
|
+
if step_name not in self._step_by_name:
|
|
24
|
+
return False
|
|
25
|
+
|
|
26
|
+
step = self._step_by_name[step_name]
|
|
27
|
+
self._steps.remove(step)
|
|
28
|
+
del self._step_by_name[step_name]
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
def get_steps(self) -> List[BaseStep]:
|
|
32
|
+
"""Get all registered steps in order."""
|
|
33
|
+
return self._steps.copy()
|
|
34
|
+
|
|
35
|
+
def get_step(self, name: str) -> Optional[BaseStep]:
|
|
36
|
+
"""Get a step by name."""
|
|
37
|
+
return self._step_by_name.get(name)
|
|
38
|
+
|
|
39
|
+
def has_step(self, name: str) -> bool:
|
|
40
|
+
"""Check if a step is registered."""
|
|
41
|
+
return name in self._step_by_name
|
|
42
|
+
|
|
43
|
+
def clear(self) -> None:
|
|
44
|
+
"""Clear all registered steps."""
|
|
45
|
+
self._steps.clear()
|
|
46
|
+
self._step_by_name.clear()
|
|
47
|
+
|
|
48
|
+
def get_step_names(self) -> List[str]:
|
|
49
|
+
"""Get list of all registered step names."""
|
|
50
|
+
return list(self._step_by_name.keys())
|
|
51
|
+
|
|
52
|
+
def get_total_progress_weight(self) -> float:
|
|
53
|
+
"""Get total progress weight of all steps."""
|
|
54
|
+
return sum(step.progress_weight for step in self._steps)
|
|
55
|
+
|
|
56
|
+
def insert_step_after(self, after_step_name: str, new_step: BaseStep) -> None:
|
|
57
|
+
"""Insert a step after an existing step."""
|
|
58
|
+
if after_step_name not in self._step_by_name:
|
|
59
|
+
raise ValueError(f"Step '{after_step_name}' not found")
|
|
60
|
+
|
|
61
|
+
if new_step.name in self._step_by_name:
|
|
62
|
+
raise ValueError(f"Step with name '{new_step.name}' already registered")
|
|
63
|
+
|
|
64
|
+
# Find the index of the step to insert after
|
|
65
|
+
after_step = self._step_by_name[after_step_name]
|
|
66
|
+
index = self._steps.index(after_step) + 1
|
|
67
|
+
|
|
68
|
+
# Insert the new step
|
|
69
|
+
self._steps.insert(index, new_step)
|
|
70
|
+
self._step_by_name[new_step.name] = new_step
|
|
71
|
+
|
|
72
|
+
def insert_step_before(self, before_step_name: str, new_step: BaseStep) -> None:
|
|
73
|
+
"""Insert a step before an existing step."""
|
|
74
|
+
if before_step_name not in self._step_by_name:
|
|
75
|
+
raise ValueError(f"Step '{before_step_name}' not found")
|
|
76
|
+
|
|
77
|
+
if new_step.name in self._step_by_name:
|
|
78
|
+
raise ValueError(f"Step with name '{new_step.name}' already registered")
|
|
79
|
+
|
|
80
|
+
# Find the index of the step to insert before
|
|
81
|
+
before_step = self._step_by_name[before_step_name]
|
|
82
|
+
index = self._steps.index(before_step)
|
|
83
|
+
|
|
84
|
+
# Insert the new step
|
|
85
|
+
self._steps.insert(index, new_step)
|
|
86
|
+
self._step_by_name[new_step.name] = new_step
|
|
87
|
+
|
|
88
|
+
def reorder_steps(self, step_names: List[str]) -> None:
|
|
89
|
+
"""Reorder steps according to the provided list of step names."""
|
|
90
|
+
if set(step_names) != set(self._step_by_name.keys()):
|
|
91
|
+
raise ValueError('Step names list must contain all registered steps')
|
|
92
|
+
|
|
93
|
+
# Reorder the steps list
|
|
94
|
+
self._steps = [self._step_by_name[name] for name in step_names]
|
|
95
|
+
|
|
96
|
+
def __len__(self) -> int:
|
|
97
|
+
"""Return number of registered steps."""
|
|
98
|
+
return len(self._steps)
|
|
99
|
+
|
|
100
|
+
def __iter__(self):
|
|
101
|
+
"""Iterate over registered steps."""
|
|
102
|
+
return iter(self._steps)
|
|
103
|
+
|
|
104
|
+
def __contains__(self, step_name: str) -> bool:
|
|
105
|
+
"""Check if step name is registered."""
|
|
106
|
+
return step_name in self._step_by_name
|
|
107
|
+
|
|
108
|
+
def __str__(self) -> str:
|
|
109
|
+
step_names = [step.name for step in self._steps]
|
|
110
|
+
return f'StepRegistry({step_names})'
|
|
111
|
+
|
|
112
|
+
def __repr__(self) -> str:
|
|
113
|
+
return f'StepRegistry(steps={len(self._steps)})'
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from synapse_sdk.plugins.models import Run
|
|
8
|
+
from synapse_sdk.shared.enums import Context
|
|
9
|
+
|
|
10
|
+
from .enums import LOG_MESSAGES, LogCode, UploadStatus
|
|
11
|
+
from .utils import PathAwareJSONEncoder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UploadRun(Run):
|
|
15
|
+
"""Upload-specific run management class.
|
|
16
|
+
|
|
17
|
+
Extends the base Run class with upload-specific logging capabilities
|
|
18
|
+
and event tracking. Provides type-safe logging using LogCode enums
|
|
19
|
+
and specialized methods for tracking upload progress.
|
|
20
|
+
|
|
21
|
+
Manages logging for upload events, data files, data units, and tasks
|
|
22
|
+
throughout the upload lifecycle. Each log entry includes status,
|
|
23
|
+
timestamps, and relevant metadata.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
Inherits all attributes from base Run class plus upload-specific
|
|
27
|
+
logging methods and nested model classes for structured logging.
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
>>> run = UploadRun(job_id, context)
|
|
31
|
+
>>> run.log_message_with_code(LogCode.UPLOAD_STARTED)
|
|
32
|
+
>>> run.log_upload_event(LogCode.FILES_DISCOVERED, file_count)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
class UploadEventLog(BaseModel):
|
|
36
|
+
"""Model for upload event log entries.
|
|
37
|
+
|
|
38
|
+
Records significant events during upload processing with
|
|
39
|
+
status information and timestamps.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
info (str | None): Optional additional information
|
|
43
|
+
status (Context): Event status/severity level
|
|
44
|
+
created (str): Timestamp when event occurred
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
info: Optional[str] = None
|
|
48
|
+
status: Context
|
|
49
|
+
created: str
|
|
50
|
+
|
|
51
|
+
class DataFileLog(BaseModel):
|
|
52
|
+
"""Model for data file processing log entries.
|
|
53
|
+
|
|
54
|
+
Tracks the processing status of individual data files
|
|
55
|
+
during upload operations.
|
|
56
|
+
|
|
57
|
+
Attributes:
|
|
58
|
+
data_file_info (str | None): Information about the data file
|
|
59
|
+
status (UploadStatus): Processing status (SUCCESS/FAILED)
|
|
60
|
+
created (str): Timestamp when log entry was created
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
data_file_info: str | None
|
|
64
|
+
status: UploadStatus
|
|
65
|
+
created: str
|
|
66
|
+
|
|
67
|
+
class DataUnitLog(BaseModel):
|
|
68
|
+
"""Model for data unit creation log entries.
|
|
69
|
+
|
|
70
|
+
Records the creation status of data units generated from
|
|
71
|
+
uploaded files, including metadata and identifiers.
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
data_unit_id (int | None): ID of created data unit
|
|
75
|
+
status (UploadStatus): Creation status (SUCCESS/FAILED)
|
|
76
|
+
created (str): Timestamp when log entry was created
|
|
77
|
+
data_unit_meta (dict | None): Metadata associated with data unit
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
data_unit_id: int | None
|
|
81
|
+
status: UploadStatus
|
|
82
|
+
created: str
|
|
83
|
+
data_unit_meta: dict | None
|
|
84
|
+
|
|
85
|
+
class TaskLog(BaseModel):
|
|
86
|
+
"""Model for task execution log entries.
|
|
87
|
+
|
|
88
|
+
Tracks the execution status of background tasks related
|
|
89
|
+
to upload processing.
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
task_id (int | None): ID of the executed task
|
|
93
|
+
status (UploadStatus): Task execution status (SUCCESS/FAILED)
|
|
94
|
+
created (str): Timestamp when log entry was created
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
task_id: int | None
|
|
98
|
+
status: UploadStatus
|
|
99
|
+
created: str
|
|
100
|
+
|
|
101
|
+
class MetricsRecord(BaseModel):
|
|
102
|
+
"""Model for upload metrics tracking.
|
|
103
|
+
|
|
104
|
+
Records count-based metrics for monitoring upload
|
|
105
|
+
progress and success rates.
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
stand_by (int): Number of items waiting to be processed
|
|
109
|
+
failed (int): Number of items that failed processing
|
|
110
|
+
success (int): Number of items successfully processed
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
stand_by: int
|
|
114
|
+
failed: int
|
|
115
|
+
success: int
|
|
116
|
+
|
|
117
|
+
def log_message_with_code(self, code: LogCode, *args, level: Optional[Context] = None):
|
|
118
|
+
if code not in LOG_MESSAGES:
|
|
119
|
+
# Use direct log_message to avoid recursion
|
|
120
|
+
self.log_message(f'Unknown log code: {code}')
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
log_config = LOG_MESSAGES[code]
|
|
124
|
+
message = log_config['message'].format(*args) if args else log_config['message']
|
|
125
|
+
log_level = level or log_config['level'] or Context.INFO
|
|
126
|
+
|
|
127
|
+
# Always call log_message for basic logging
|
|
128
|
+
if log_level:
|
|
129
|
+
self.log_message(message, context=log_level.value)
|
|
130
|
+
else:
|
|
131
|
+
self.log_message(message)
|
|
132
|
+
|
|
133
|
+
def log_upload_event(self, code: LogCode, *args, level: Optional[Context] = None):
|
|
134
|
+
# Call log_message_with_code to handle the basic logging
|
|
135
|
+
self.log_message_with_code(code, *args, level=level)
|
|
136
|
+
|
|
137
|
+
# Also log the event for upload-specific tracking
|
|
138
|
+
if code not in LOG_MESSAGES:
|
|
139
|
+
now = datetime.now().isoformat()
|
|
140
|
+
self.log(
|
|
141
|
+
'upload_event',
|
|
142
|
+
self.UploadEventLog(info=f'Unknown log code: {code}', status=Context.DANGER, created=now).model_dump(),
|
|
143
|
+
)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
log_config = LOG_MESSAGES[code]
|
|
147
|
+
message = log_config['message'].format(*args) if args else log_config['message']
|
|
148
|
+
log_level = level or log_config['level'] or Context.INFO
|
|
149
|
+
|
|
150
|
+
now = datetime.now().isoformat()
|
|
151
|
+
self.log(
|
|
152
|
+
'upload_event',
|
|
153
|
+
self.UploadEventLog(info=message, status=log_level, created=now).model_dump(),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def log_data_file(self, data_file_info: dict, status: UploadStatus):
|
|
157
|
+
now = datetime.now().isoformat()
|
|
158
|
+
data_file_info_str = json.dumps(data_file_info, ensure_ascii=False, cls=PathAwareJSONEncoder)
|
|
159
|
+
self.log(
|
|
160
|
+
'upload_data_file',
|
|
161
|
+
self.DataFileLog(data_file_info=data_file_info_str, status=status, created=now).model_dump(),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def log_data_unit(self, data_unit_id: int, status: UploadStatus, data_unit_meta: dict | None = None):
|
|
165
|
+
now = datetime.now().isoformat()
|
|
166
|
+
self.log(
|
|
167
|
+
'upload_data_unit',
|
|
168
|
+
self.DataUnitLog(
|
|
169
|
+
data_unit_id=data_unit_id, status=status, created=now, data_unit_meta=data_unit_meta
|
|
170
|
+
).model_dump(),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def log_task(self, task_id: int, status: UploadStatus):
|
|
174
|
+
now = datetime.now().isoformat()
|
|
175
|
+
self.log('upload_task', self.TaskLog(task_id=task_id, status=status, created=now).model_dump())
|
|
176
|
+
|
|
177
|
+
def log_metrics(self, record: MetricsRecord, category: str):
|
|
178
|
+
record = self.MetricsRecord.model_validate(record)
|
|
179
|
+
self.set_metrics(value=record.model_dump(), category=category)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Step-based workflow implementations for upload actions
|