synapse-sdk 1.0.0b5__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/code_server.py +305 -33
- synapse_sdk/clients/agent/__init__.py +2 -1
- synapse_sdk/clients/agent/container.py +143 -0
- synapse_sdk/clients/agent/ray.py +296 -38
- synapse_sdk/clients/backend/annotation.py +1 -1
- synapse_sdk/clients/backend/core.py +31 -4
- synapse_sdk/clients/backend/data_collection.py +82 -7
- synapse_sdk/clients/backend/hitl.py +1 -1
- synapse_sdk/clients/backend/ml.py +1 -1
- synapse_sdk/clients/base.py +211 -61
- synapse_sdk/loggers.py +46 -0
- synapse_sdk/plugins/README.md +1340 -0
- synapse_sdk/plugins/categories/base.py +59 -9
- 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 +19 -1
- synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
- synapse_sdk/plugins/categories/export/templates/plugin/export.py +153 -177
- synapse_sdk/plugins/categories/neural_net/actions/train.py +1130 -32
- synapse_sdk/plugins/categories/neural_net/actions/tune.py +157 -4
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +7 -4
- 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/pre_annotation/actions/pre_annotation/action.py +10 -0
- 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/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 +28 -2
- synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +82 -20
- synapse_sdk/plugins/models.py +111 -9
- synapse_sdk/plugins/templates/plugin-config-schema.json +7 -0
- synapse_sdk/plugins/templates/schema.json +7 -0
- synapse_sdk/plugins/utils/__init__.py +3 -0
- synapse_sdk/plugins/utils/ray_gcs.py +66 -0
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/converters/dm/__init__.py +42 -41
- synapse_sdk/utils/converters/dm/base.py +137 -0
- synapse_sdk/utils/converters/dm/from_v1.py +208 -562
- synapse_sdk/utils/converters/dm/to_v1.py +258 -304
- 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/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 → file.py.backup} +77 -0
- synapse_sdk/utils/network.py +272 -0
- synapse_sdk/utils/storage/__init__.py +6 -2
- synapse_sdk/utils/storage/providers/file_system.py +6 -0
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/METADATA +19 -2
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/RECORD +134 -74
- synapse_sdk/devtools/docs/.gitignore +0 -20
- synapse_sdk/devtools/docs/README.md +0 -41
- synapse_sdk/devtools/docs/blog/2019-05-28-first-blog-post.md +0 -12
- synapse_sdk/devtools/docs/blog/2019-05-29-long-blog-post.md +0 -44
- synapse_sdk/devtools/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -24
- synapse_sdk/devtools/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- synapse_sdk/devtools/docs/blog/2021-08-26-welcome/index.md +0 -29
- synapse_sdk/devtools/docs/blog/authors.yml +0 -25
- synapse_sdk/devtools/docs/blog/tags.yml +0 -19
- synapse_sdk/devtools/docs/docusaurus.config.ts +0 -138
- synapse_sdk/devtools/docs/package-lock.json +0 -17455
- synapse_sdk/devtools/docs/package.json +0 -47
- synapse_sdk/devtools/docs/sidebars.ts +0 -44
- synapse_sdk/devtools/docs/src/components/HomepageFeatures/index.tsx +0 -71
- synapse_sdk/devtools/docs/src/components/HomepageFeatures/styles.module.css +0 -11
- synapse_sdk/devtools/docs/src/css/custom.css +0 -30
- synapse_sdk/devtools/docs/src/pages/index.module.css +0 -23
- synapse_sdk/devtools/docs/src/pages/index.tsx +0 -21
- synapse_sdk/devtools/docs/src/pages/markdown-page.md +0 -7
- synapse_sdk/devtools/docs/static/.nojekyll +0 -0
- synapse_sdk/devtools/docs/static/img/docusaurus-social-card.jpg +0 -0
- synapse_sdk/devtools/docs/static/img/docusaurus.png +0 -0
- synapse_sdk/devtools/docs/static/img/favicon.ico +0 -0
- synapse_sdk/devtools/docs/static/img/logo.png +0 -0
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_react.svg +0 -170
- synapse_sdk/devtools/docs/static/img/undraw_docusaurus_tree.svg +0 -40
- synapse_sdk/devtools/docs/tsconfig.json +0 -8
- synapse_sdk/plugins/categories/export/actions/export.py +0 -346
- synapse_sdk/plugins/categories/export/enums.py +0 -7
- synapse_sdk/plugins/categories/neural_net/actions/gradio.py +0 -151
- synapse_sdk/plugins/categories/pre_annotation/actions/to_task.py +0 -943
- synapse_sdk/plugins/categories/upload/actions/upload.py +0 -954
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +0 -0
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-1.0.0b5.dist-info → synapse_sdk-2025.12.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseUploader:
|
|
6
|
+
"""Base class for upload plugins with common functionality.
|
|
7
|
+
|
|
8
|
+
This class handles common tasks like file organization, validation, and metadata
|
|
9
|
+
that are shared across all upload plugins. Plugin developers should inherit
|
|
10
|
+
from this class and implement the required methods for their specific logic.
|
|
11
|
+
|
|
12
|
+
Important: Plugin extensions work with already-organized files from the main upload workflow.
|
|
13
|
+
Whether single-path or multi-path mode is used is transparent to plugin developers - you
|
|
14
|
+
simply process the organized_files list provided to you.
|
|
15
|
+
|
|
16
|
+
Core Methods:
|
|
17
|
+
handle_upload_files(): Main upload method - handles the complete upload workflow
|
|
18
|
+
organize_files(): Handle file organization logic (can be overridden)
|
|
19
|
+
validate_files(): Handle file validation logic (can be overridden)
|
|
20
|
+
|
|
21
|
+
Required Methods (should be implemented by subclasses):
|
|
22
|
+
process_files(): Transform/process files during upload
|
|
23
|
+
|
|
24
|
+
Optional Methods (can be overridden by subclasses):
|
|
25
|
+
before_process(): Pre-process files before main processing
|
|
26
|
+
after_process(): Post-process files after main processing
|
|
27
|
+
setup_directories(): Setup custom directories
|
|
28
|
+
validate_file_types(): Custom file type validation
|
|
29
|
+
|
|
30
|
+
Helper Methods:
|
|
31
|
+
_log_validation_warning(): Log validation warnings
|
|
32
|
+
_log_conversion_warning(): Log conversion warnings
|
|
33
|
+
_filter_valid_files(): Filter files based on validation
|
|
34
|
+
|
|
35
|
+
Auto-provided Utilities:
|
|
36
|
+
Logging via self.run.log_message() and other run methods
|
|
37
|
+
File path utilities via self.path
|
|
38
|
+
Specification access via self.file_specification
|
|
39
|
+
|
|
40
|
+
Customization:
|
|
41
|
+
To restrict file extensions, modify get_file_extensions_config() in this file:
|
|
42
|
+
|
|
43
|
+
Example - Allow only MP4 videos:
|
|
44
|
+
def get_file_extensions_config(self):
|
|
45
|
+
return {
|
|
46
|
+
'video': ['.mp4'], # Only MP4 allowed
|
|
47
|
+
'image': ['.jpg', '.png'],
|
|
48
|
+
# ... other types
|
|
49
|
+
}
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
run,
|
|
55
|
+
path: Path,
|
|
56
|
+
file_specification: List = None,
|
|
57
|
+
organized_files: List = None,
|
|
58
|
+
extra_params: Dict = None,
|
|
59
|
+
):
|
|
60
|
+
"""Initialize the base upload class.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
run: Plugin run object with logging capabilities.
|
|
64
|
+
path: Path object pointing to the upload target directory.
|
|
65
|
+
- In single-path mode: Base directory path (Path object)
|
|
66
|
+
- In multi-path mode: None (not needed - use self.assets_config instead)
|
|
67
|
+
Files have already been discovered from their respective asset paths.
|
|
68
|
+
file_specification: List of specifications that define the structure of files to be uploaded.
|
|
69
|
+
organized_files: List of pre-organized files based on the default logic.
|
|
70
|
+
Plugin extensions work with these already-organized files regardless of
|
|
71
|
+
whether single-path or multi-path mode was used.
|
|
72
|
+
extra_params: Additional parameters for customization.
|
|
73
|
+
"""
|
|
74
|
+
self.run = run
|
|
75
|
+
self.path = path
|
|
76
|
+
self.file_specification = file_specification or []
|
|
77
|
+
self.organized_files = organized_files or []
|
|
78
|
+
self.extra_params = extra_params or {}
|
|
79
|
+
|
|
80
|
+
def get_file_extensions_config(self) -> Dict[str, List[str]]:
|
|
81
|
+
"""Get allowed file extensions configuration.
|
|
82
|
+
|
|
83
|
+
Modify this dictionary to restrict file extensions per file type.
|
|
84
|
+
Extensions are case-insensitive and must include the dot prefix.
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
To allow only MP4 videos::
|
|
88
|
+
|
|
89
|
+
def get_file_extensions_config(self):
|
|
90
|
+
return {
|
|
91
|
+
'video': ['.mp4'],
|
|
92
|
+
'image': ['.jpg', '.png'],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Dict[str, List[str]]: Mapping of file types to allowed extensions.
|
|
97
|
+
Each key is a file type (e.g., 'video', 'image') and each value
|
|
98
|
+
is a list of allowed extensions (e.g., ['.mp4', '.avi']).
|
|
99
|
+
"""
|
|
100
|
+
# Configure allowed extensions here
|
|
101
|
+
# Extensions should include the dot (e.g., '.mp4', not 'mp4')
|
|
102
|
+
return {
|
|
103
|
+
'video': ['.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv'],
|
|
104
|
+
'image': ['.jpg', '.jpeg', '.png'],
|
|
105
|
+
'pcd': ['.pcd'],
|
|
106
|
+
'text': ['.txt', '.html'],
|
|
107
|
+
'audio': ['.mp3', '.wav'],
|
|
108
|
+
'data': ['.xml', '.bin', '.json', '.fbx'],
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def _log_validation_warning(self, spec_name: str, invalid_extensions: List[str], expected_extensions: List[str]):
|
|
112
|
+
"""Log validation warning for invalid file extensions."""
|
|
113
|
+
self.run.log_message(
|
|
114
|
+
f"Validation warning in '{spec_name}': File extensions {invalid_extensions} do not match expected extensions {expected_extensions}. These files will be excluded from upload."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def _log_conversion_warning(self, spec_name: str, extension: str, recommended_formats: str):
|
|
118
|
+
"""Log conversion warning for file formats that may need conversion."""
|
|
119
|
+
self.run.log_message(
|
|
120
|
+
f"Conversion warning in '{spec_name}': File extension '{extension}' may require conversion to [{recommended_formats}]."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def _filter_valid_files(self, files_to_validate: List) -> List:
|
|
124
|
+
"""Filter files based on validation criteria.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
files_to_validate: List of organized file dictionaries to validate
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List: Filtered list containing only valid files
|
|
131
|
+
"""
|
|
132
|
+
return files_to_validate # Default: return all files
|
|
133
|
+
|
|
134
|
+
# Abstract methods that should be implemented by subclasses
|
|
135
|
+
def process_files(self, organized_files: List) -> List:
|
|
136
|
+
"""Process files. Should be implemented by subclasses."""
|
|
137
|
+
return organized_files
|
|
138
|
+
|
|
139
|
+
def before_process(self, organized_files: List) -> List:
|
|
140
|
+
"""Pre-process files before main processing. Can be overridden by subclasses."""
|
|
141
|
+
return organized_files
|
|
142
|
+
|
|
143
|
+
def after_process(self, processed_files: List) -> List:
|
|
144
|
+
"""Post-process files after main processing. Can be overridden by subclasses."""
|
|
145
|
+
return processed_files
|
|
146
|
+
|
|
147
|
+
def organize_files(self, files: List) -> List:
|
|
148
|
+
"""Organize files. Can be overridden by subclasses."""
|
|
149
|
+
return files
|
|
150
|
+
|
|
151
|
+
def validate_files(self, files: List) -> List:
|
|
152
|
+
"""Validate files against allowed extensions and custom rules.
|
|
153
|
+
|
|
154
|
+
This method first validates file types against get_file_extensions_config(),
|
|
155
|
+
then applies custom filtering via _filter_valid_files().
|
|
156
|
+
|
|
157
|
+
Override this method for complete custom validation, or override
|
|
158
|
+
_filter_valid_files() to add additional filtering after extension validation.
|
|
159
|
+
"""
|
|
160
|
+
# First, validate file extensions
|
|
161
|
+
files = self.validate_file_types(files)
|
|
162
|
+
|
|
163
|
+
# Then apply custom filtering
|
|
164
|
+
return self._filter_valid_files(files)
|
|
165
|
+
|
|
166
|
+
def setup_directories(self) -> None:
|
|
167
|
+
"""Setup custom directories. Can be overridden by subclasses."""
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
def validate_file_types(self, organized_files: List) -> List:
|
|
171
|
+
"""Validate file types against allowed extensions configuration.
|
|
172
|
+
|
|
173
|
+
Filters files based on their extensions according to get_file_extensions_config().
|
|
174
|
+
Main files (is_required=True) are uploaded if they have valid extensions,
|
|
175
|
+
even when sub files fail validation. Sub files are filtered individually.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
organized_files (List[Dict]): List of organized file dictionaries.
|
|
179
|
+
Each dict contains a 'files' key mapping spec names to file paths.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
List[Dict]: Filtered list containing only files with valid extensions.
|
|
183
|
+
Groups are included if they have at least one valid main file.
|
|
184
|
+
Files with disallowed extensions are removed and logged as WARNING.
|
|
185
|
+
|
|
186
|
+
Note:
|
|
187
|
+
Extension matching is case-insensitive (.mp4 == .MP4).
|
|
188
|
+
Filtered files are logged using LogCode.FILES_FILTERED_BY_EXTENSION.
|
|
189
|
+
"""
|
|
190
|
+
if not organized_files or not self.file_specification:
|
|
191
|
+
return organized_files
|
|
192
|
+
|
|
193
|
+
valid_files = []
|
|
194
|
+
allowed_extensions_config = self.get_file_extensions_config()
|
|
195
|
+
filtered_by_type = {} # Track filtered files per type
|
|
196
|
+
|
|
197
|
+
for file_group in organized_files:
|
|
198
|
+
files_dict = file_group.get('files', {})
|
|
199
|
+
valid_files_dict = {} # Track valid files individually
|
|
200
|
+
|
|
201
|
+
for spec_name, file_path in files_dict.items():
|
|
202
|
+
# Find the specification for this file type
|
|
203
|
+
file_spec = next((s for s in self.file_specification if s['name'] == spec_name), None)
|
|
204
|
+
if not file_spec:
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
# Handle file path lists
|
|
208
|
+
if isinstance(file_path, list):
|
|
209
|
+
file_path = file_path[0] if file_path else None
|
|
210
|
+
|
|
211
|
+
if file_path is None:
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
# Get file type and extension
|
|
215
|
+
file_type = file_spec['file_type']
|
|
216
|
+
file_extension = file_path.suffix.lower()
|
|
217
|
+
|
|
218
|
+
# Check if this file type has allowed extensions
|
|
219
|
+
if file_type in allowed_extensions_config:
|
|
220
|
+
allowed_exts = [ext.lower() for ext in allowed_extensions_config[file_type]]
|
|
221
|
+
|
|
222
|
+
if file_extension not in allowed_exts:
|
|
223
|
+
# Track filtered extension
|
|
224
|
+
if file_type not in filtered_by_type:
|
|
225
|
+
filtered_by_type[file_type] = {'extensions': set(), 'count': 0}
|
|
226
|
+
filtered_by_type[file_type]['extensions'].add(
|
|
227
|
+
file_extension if file_extension else '(no extension)'
|
|
228
|
+
)
|
|
229
|
+
filtered_by_type[file_type]['count'] += 1
|
|
230
|
+
# Skip this file and continue checking others
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
# File is valid - add to valid files dict
|
|
234
|
+
valid_files_dict[spec_name] = file_path
|
|
235
|
+
|
|
236
|
+
# Include group if it has ALL required files valid
|
|
237
|
+
if valid_files_dict:
|
|
238
|
+
# Get all required spec names
|
|
239
|
+
required_specs = [spec['name'] for spec in self.file_specification if spec.get('is_required', False)]
|
|
240
|
+
|
|
241
|
+
# Check if ALL required specs are present and valid
|
|
242
|
+
has_all_required = all(spec_name in valid_files_dict for spec_name in required_specs)
|
|
243
|
+
|
|
244
|
+
if has_all_required:
|
|
245
|
+
# Create new file group with only valid files
|
|
246
|
+
# Preserve all fields from original file_group (e.g., 'group', 'meta', custom fields)
|
|
247
|
+
# Only replace 'files' with the validated files
|
|
248
|
+
valid_group = {**file_group, 'files': valid_files_dict}
|
|
249
|
+
valid_files.append(valid_group)
|
|
250
|
+
|
|
251
|
+
# Log filtered files by type
|
|
252
|
+
self._log_filtered_files(filtered_by_type, allowed_extensions_config)
|
|
253
|
+
|
|
254
|
+
return valid_files
|
|
255
|
+
|
|
256
|
+
def _log_filtered_files(self, filtered_by_type: Dict, allowed_config: Dict):
|
|
257
|
+
"""Log filtered files by type with detailed information.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
filtered_by_type (Dict[str, Dict]): Filtered file information per type.
|
|
261
|
+
Each entry contains 'extensions' (set) and 'count' (int).
|
|
262
|
+
allowed_config (Dict[str, List[str]]): The allowed extensions configuration
|
|
263
|
+
mapping file types to allowed extension lists.
|
|
264
|
+
"""
|
|
265
|
+
from synapse_sdk.plugins.categories.upload.actions.upload.enums import LogCode
|
|
266
|
+
|
|
267
|
+
for file_type, info in filtered_by_type.items():
|
|
268
|
+
if info['count'] > 0:
|
|
269
|
+
extensions_str = ', '.join(sorted(info['extensions']))
|
|
270
|
+
allowed_str = ', '.join(allowed_config.get(file_type, []))
|
|
271
|
+
self.run.log_message_with_code(
|
|
272
|
+
LogCode.FILES_FILTERED_BY_EXTENSION,
|
|
273
|
+
info['count'],
|
|
274
|
+
file_type,
|
|
275
|
+
extensions_str,
|
|
276
|
+
allowed_str,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def handle_upload_files(self) -> List:
|
|
280
|
+
"""Main upload method that handles the complete upload workflow.
|
|
281
|
+
|
|
282
|
+
This method provides the core workflow for upload plugins:
|
|
283
|
+
setup_directories -> organize_files -> before_process -> process_files ->
|
|
284
|
+
after_process -> validate_files
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
List: The final processed and validated list of files ready for upload.
|
|
288
|
+
"""
|
|
289
|
+
# Setup any required directories
|
|
290
|
+
self.setup_directories()
|
|
291
|
+
|
|
292
|
+
# Start with organized files from the workflow
|
|
293
|
+
current_files = self.organized_files
|
|
294
|
+
|
|
295
|
+
# Apply organization logic
|
|
296
|
+
current_files = self.organize_files(current_files)
|
|
297
|
+
|
|
298
|
+
# Pre-process files
|
|
299
|
+
current_files = self.before_process(current_files)
|
|
300
|
+
|
|
301
|
+
# Main processing step
|
|
302
|
+
current_files = self.process_files(current_files)
|
|
303
|
+
|
|
304
|
+
# Post-process files
|
|
305
|
+
current_files = self.after_process(current_files)
|
|
306
|
+
|
|
307
|
+
# Final validation
|
|
308
|
+
current_files = self.validate_files(current_files)
|
|
309
|
+
|
|
310
|
+
return current_files
|
|
@@ -1,40 +1,102 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from typing import List
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
3
|
|
|
4
|
+
from . import BaseUploader
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
class Uploader(BaseUploader):
|
|
6
8
|
"""Plugin upload action interface for organizing files.
|
|
7
9
|
|
|
8
|
-
This class provides a
|
|
9
|
-
their own file organization logic.
|
|
10
|
+
This class provides a template for plugin developers to implement
|
|
11
|
+
their own file organization logic by inheriting from BaseUploader.
|
|
12
|
+
|
|
13
|
+
Important: This plugin extension works with already-organized files from the
|
|
14
|
+
main upload workflow. Files are provided via organized_files parameter regardless
|
|
15
|
+
of whether single-path or multi-path mode was used in the upload configuration.
|
|
16
|
+
|
|
17
|
+
Example usage:
|
|
18
|
+
Override process_files() to implement custom file processing logic.
|
|
19
|
+
Override validate_file_types() to implement custom validation rules.
|
|
20
|
+
Override setup_directories() to create custom directory structures.
|
|
10
21
|
"""
|
|
11
22
|
|
|
12
|
-
def __init__(
|
|
13
|
-
|
|
23
|
+
def __init__(
|
|
24
|
+
self, run, path: Path, file_specification: List = None, organized_files: List = None, extra_params: Dict = None
|
|
25
|
+
):
|
|
26
|
+
"""Initialize the uploader with required parameters.
|
|
14
27
|
|
|
15
28
|
Args:
|
|
16
29
|
run: Plugin run object with logging capabilities.
|
|
17
30
|
path: Path object pointing to the upload target directory.
|
|
31
|
+
- In single-path mode: Base directory path (Path object)
|
|
32
|
+
- In multi-path mode: None (use self.assets_config instead)
|
|
18
33
|
file_specification: List of specifications that define the structure of files to be uploaded.
|
|
19
|
-
|
|
34
|
+
organized_files: List of pre-organized files from the main upload workflow.
|
|
35
|
+
Works transparently with both single-path and multi-path modes.
|
|
36
|
+
extra_params: Additional parameters for customization.
|
|
37
|
+
"""
|
|
38
|
+
super().__init__(run, path, file_specification, organized_files, extra_params)
|
|
39
|
+
|
|
40
|
+
def process_files(self, organized_files: List) -> List:
|
|
41
|
+
"""Process and transform files during upload.
|
|
42
|
+
|
|
43
|
+
Override this method to implement custom file processing logic.
|
|
44
|
+
This is the main method where plugin-specific logic should be implemented.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
organized_files: List of organized file dictionaries from the workflow.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
List: The processed list of files ready for upload.
|
|
51
|
+
"""
|
|
52
|
+
# Default implementation: return files as-is
|
|
53
|
+
# Plugin developers should override this method for custom logic
|
|
54
|
+
return organized_files
|
|
55
|
+
|
|
56
|
+
def validate_file_types(self, organized_files: List) -> List:
|
|
57
|
+
"""Validate file types against specifications.
|
|
58
|
+
|
|
59
|
+
This example shows how to use the BaseUploader's comprehensive validation logic.
|
|
60
|
+
You can override this method for custom validation or call super() to use the base implementation.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
organized_files: List of organized file dictionaries to validate.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
List: Filtered list containing only valid files that match specifications.
|
|
67
|
+
"""
|
|
68
|
+
return super().validate_file_types(organized_files)
|
|
69
|
+
|
|
70
|
+
def handle_upload_files(self) -> List[Dict[str, Any]]:
|
|
71
|
+
"""Executes the upload task using the base class implementation.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
List: The final list of organized files ready for upload
|
|
75
|
+
"""
|
|
76
|
+
return super().handle_upload_files()
|
|
77
|
+
|
|
78
|
+
def organize_files(self, organized_files: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
79
|
+
"""Transform and organize files based on plugin logic.
|
|
80
|
+
|
|
81
|
+
Override this method to implement custom file organization logic.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
organized_files: List of organized files from the default logic
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
List of transformed organized files
|
|
20
88
|
"""
|
|
21
|
-
|
|
22
|
-
self.path = path
|
|
23
|
-
self.file_specification = file_specification
|
|
24
|
-
self.organized_files = organized_files
|
|
89
|
+
return organized_files
|
|
25
90
|
|
|
26
|
-
def
|
|
27
|
-
"""
|
|
91
|
+
def filter_files(self, organized_file: Dict[str, Any]) -> bool:
|
|
92
|
+
"""Filter files based on custom criteria.
|
|
28
93
|
|
|
29
|
-
|
|
30
|
-
You can override this method to filter files, transform data, or add custom metadata
|
|
31
|
-
based on your specific requirements.
|
|
94
|
+
Override this method to implement custom filtering logic.
|
|
32
95
|
|
|
33
96
|
Args:
|
|
34
|
-
|
|
35
|
-
Each item is a dictionary with 'files' and 'meta' keys.
|
|
97
|
+
organized_file: Single organized file to filter
|
|
36
98
|
|
|
37
99
|
Returns:
|
|
38
|
-
|
|
100
|
+
bool: True to include the file, False to filter it out
|
|
39
101
|
"""
|
|
40
|
-
return
|
|
102
|
+
return True
|
synapse_sdk/plugins/models.py
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from datetime import datetime
|
|
2
3
|
from functools import cached_property
|
|
3
4
|
from typing import Any, Dict
|
|
4
5
|
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
5
8
|
from synapse_sdk.clients.backend import BackendClient
|
|
6
9
|
from synapse_sdk.devtools.config import get_backend_config
|
|
7
10
|
from synapse_sdk.loggers import BackendLogger, ConsoleLogger
|
|
8
11
|
from synapse_sdk.plugins.utils import read_plugin_config
|
|
12
|
+
from synapse_sdk.shared import needs_sentry_init
|
|
9
13
|
from synapse_sdk.shared.enums import Context
|
|
10
14
|
from synapse_sdk.utils.storage import get_storage
|
|
11
15
|
from synapse_sdk.utils.string import hash_text
|
|
@@ -48,8 +52,26 @@ class PluginRelease:
|
|
|
48
52
|
|
|
49
53
|
@cached_property
|
|
50
54
|
def package_manager_options(self):
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
# Get user-defined options from config
|
|
56
|
+
user_options = self.config.get('package_manager_options', [])
|
|
57
|
+
|
|
58
|
+
if self.package_manager == 'uv':
|
|
59
|
+
defaults = ['--no-cache']
|
|
60
|
+
# Add defaults if not already present
|
|
61
|
+
options_list = defaults.copy()
|
|
62
|
+
for option in user_options:
|
|
63
|
+
if option not in options_list:
|
|
64
|
+
options_list.append(option)
|
|
65
|
+
return {'uv_pip_install_options': options_list}
|
|
66
|
+
else:
|
|
67
|
+
# For pip, use pip_install_options with --upgrade flag to ensure
|
|
68
|
+
# packages from requirements.txt (like synapse-sdk) override pre-installed versions
|
|
69
|
+
defaults = ['--upgrade']
|
|
70
|
+
options_list = defaults.copy()
|
|
71
|
+
for option in user_options:
|
|
72
|
+
if option not in options_list:
|
|
73
|
+
options_list.append(option)
|
|
74
|
+
return {'pip_install_options': options_list}
|
|
53
75
|
|
|
54
76
|
@cached_property
|
|
55
77
|
def checksum(self):
|
|
@@ -68,6 +90,11 @@ class PluginRelease:
|
|
|
68
90
|
def warm_up():
|
|
69
91
|
pass
|
|
70
92
|
|
|
93
|
+
extra_runtime_env = {}
|
|
94
|
+
|
|
95
|
+
if needs_sentry_init():
|
|
96
|
+
extra_runtime_env['worker_process_setup_hook'] = 'synapse_sdk.shared.worker_process_setup_hook'
|
|
97
|
+
|
|
71
98
|
nodes = list_nodes(address=self.envs['RAY_DASHBOARD_URL'])
|
|
72
99
|
node_ids = [n['node_id'] for n in nodes]
|
|
73
100
|
for node_id in node_ids:
|
|
@@ -80,6 +107,7 @@ class PluginRelease:
|
|
|
80
107
|
** self.package_manager_options
|
|
81
108
|
},
|
|
82
109
|
'working_dir': self.get_url(self.envs['SYNAPSE_PLUGIN_STORAGE']),
|
|
110
|
+
**extra_runtime_env,
|
|
83
111
|
},
|
|
84
112
|
scheduling_strategy=strategy,
|
|
85
113
|
).remote()
|
|
@@ -110,9 +138,29 @@ class Run:
|
|
|
110
138
|
context = None
|
|
111
139
|
client = None
|
|
112
140
|
|
|
113
|
-
|
|
141
|
+
class DevLog(BaseModel):
|
|
142
|
+
"""Model for developer log entries.
|
|
143
|
+
|
|
144
|
+
Records custom events and information that plugin developers want to track
|
|
145
|
+
during plugin execution for debugging and monitoring purposes.
|
|
146
|
+
|
|
147
|
+
Attributes:
|
|
148
|
+
event_type (str): Type/category of the development event
|
|
149
|
+
message (str): Descriptive message about the event
|
|
150
|
+
data (dict | None): Optional additional data/context
|
|
151
|
+
level (Context): Event status/severity level
|
|
152
|
+
created (str): Timestamp when event occurred
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
event_type: str
|
|
156
|
+
message: str
|
|
157
|
+
data: dict | None = None
|
|
158
|
+
level: Context
|
|
159
|
+
created: str
|
|
160
|
+
|
|
161
|
+
def __init__(self, job_id, context=None):
|
|
114
162
|
self.job_id = job_id
|
|
115
|
-
self.context = context
|
|
163
|
+
self.context = context or {}
|
|
116
164
|
config = get_backend_config()
|
|
117
165
|
if config:
|
|
118
166
|
self.client = BackendClient(
|
|
@@ -120,17 +168,23 @@ class Run:
|
|
|
120
168
|
access_token=config['token'],
|
|
121
169
|
)
|
|
122
170
|
else:
|
|
171
|
+
# Handle missing environment variables for test environments
|
|
172
|
+
envs = self.context.get('envs', {})
|
|
173
|
+
host = envs.get('SYNAPSE_PLUGIN_RUN_HOST', os.getenv('SYNAPSE_PLUGIN_RUN_HOST', 'http://localhost:8000'))
|
|
174
|
+
token = envs.get('SYNAPSE_PLUGIN_RUN_USER_TOKEN', os.getenv('SYNAPSE_PLUGIN_RUN_USER_TOKEN'))
|
|
175
|
+
tenant = envs.get('SYNAPSE_PLUGIN_RUN_TENANT', os.getenv('SYNAPSE_PLUGIN_RUN_TENANT'))
|
|
176
|
+
|
|
123
177
|
self.client = BackendClient(
|
|
124
|
-
|
|
125
|
-
token=
|
|
126
|
-
tenant=
|
|
178
|
+
host,
|
|
179
|
+
token=token,
|
|
180
|
+
tenant=tenant,
|
|
127
181
|
)
|
|
128
182
|
self.set_logger()
|
|
129
183
|
|
|
130
184
|
def set_logger(self):
|
|
131
185
|
kwargs = {
|
|
132
|
-
'progress_categories': self.context
|
|
133
|
-
'metrics_categories': self.context
|
|
186
|
+
'progress_categories': self.context.get('progress_categories'),
|
|
187
|
+
'metrics_categories': self.context.get('metrics_categories'),
|
|
134
188
|
}
|
|
135
189
|
|
|
136
190
|
if self.job_id:
|
|
@@ -141,6 +195,17 @@ class Run:
|
|
|
141
195
|
def set_progress(self, current, total, category=''):
|
|
142
196
|
self.logger.set_progress(current, total, category)
|
|
143
197
|
|
|
198
|
+
def set_progress_failed(self, category: str | None = None):
|
|
199
|
+
"""Mark progress as failed with elapsed time but no completion.
|
|
200
|
+
|
|
201
|
+
This method should be called when an operation fails to indicate that
|
|
202
|
+
no progress was made, but still track how long the operation ran before failing.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
category (str | None): progress category
|
|
206
|
+
"""
|
|
207
|
+
self.logger.set_progress_failed(category)
|
|
208
|
+
|
|
144
209
|
def set_metrics(self, value: Dict[Any, Any], category: str):
|
|
145
210
|
self.logger.set_metrics(value, category)
|
|
146
211
|
|
|
@@ -150,5 +215,42 @@ class Run:
|
|
|
150
215
|
def log_message(self, message, context=Context.INFO.value):
|
|
151
216
|
self.logger.log('message', {'context': context, 'content': message})
|
|
152
217
|
|
|
218
|
+
def log_dev_event(self, message: str, data: dict | None = None, level: Context = Context.INFO):
|
|
219
|
+
"""Log development event for plugin developers.
|
|
220
|
+
|
|
221
|
+
This function allows plugin developers to log custom events and information
|
|
222
|
+
during plugin execution for debugging, monitoring, and development purposes.
|
|
223
|
+
The event_type is automatically constructed as '{action_name}_dev_log' and cannot
|
|
224
|
+
be modified by plugin developers.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
message (str): Descriptive message about the event
|
|
228
|
+
data (dict | None): Optional additional data or context to include
|
|
229
|
+
level (Context): Event severity level (INFO, WARNING, DANGER, SUCCESS)
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
>>> run = Run(job_id, context)
|
|
233
|
+
>>> run.log_dev_event('Data validation completed', {'records_count': 100})
|
|
234
|
+
>>> run.log_dev_event('Processing time recorded', {'duration_ms': 1500})
|
|
235
|
+
>>> run.log_dev_event('Variable state at checkpoint', {'variable_x': 42}, level=Context.WARNING)
|
|
236
|
+
"""
|
|
237
|
+
# Construct event_type from action name - this cannot be modified by developers
|
|
238
|
+
action_name = self.context.get('action_name', 'unknown')
|
|
239
|
+
event_type = f'{action_name}_dev_log'
|
|
240
|
+
|
|
241
|
+
# Log the structured event for development tracking only
|
|
242
|
+
# Do NOT use log_message to avoid showing debug logs to end users
|
|
243
|
+
now = datetime.now().isoformat()
|
|
244
|
+
self.log(
|
|
245
|
+
'dev_event',
|
|
246
|
+
self.DevLog(
|
|
247
|
+
event_type=event_type,
|
|
248
|
+
message=message,
|
|
249
|
+
data=data,
|
|
250
|
+
level=level,
|
|
251
|
+
created=now,
|
|
252
|
+
).model_dump(),
|
|
253
|
+
)
|
|
254
|
+
|
|
153
255
|
def end_log(self):
|
|
154
256
|
self.log_message('Plugin run is complete.')
|
|
@@ -51,6 +51,13 @@
|
|
|
51
51
|
"enum": ["pip", "uv"],
|
|
52
52
|
"default": "pip"
|
|
53
53
|
},
|
|
54
|
+
"package_manager_options": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"description": "User-provided options for package manager install command. Currently only supported for uv in Ray 2.44.1 (defaults to ['--no-cache']). pip_install_options requires newer Ray versions.",
|
|
57
|
+
"items": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
54
61
|
"data_type": {
|
|
55
62
|
"type": "string",
|
|
56
63
|
"description": "Primary data type the plugin works with",
|
|
@@ -44,6 +44,13 @@
|
|
|
44
44
|
"enum": ["pip", "uv"],
|
|
45
45
|
"default": "pip"
|
|
46
46
|
},
|
|
47
|
+
"package_manager_options": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"description": "User-provided options for package manager install command. Currently only supported for uv in Ray 2.44.1 (defaults to ['--no-cache']). pip_install_options requires newer Ray versions.",
|
|
50
|
+
"items": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
47
54
|
"data_type": {
|
|
48
55
|
"type": "string",
|
|
49
56
|
"description": "Primary data type handled by the plugin",
|
|
@@ -15,6 +15,7 @@ from .config import (
|
|
|
15
15
|
|
|
16
16
|
# Import legacy functions for backward compatibility
|
|
17
17
|
from .legacy import read_requirements, run_plugin
|
|
18
|
+
from .ray_gcs import convert_http_to_ray_gcs
|
|
18
19
|
from .registry import (
|
|
19
20
|
get_category_display_name,
|
|
20
21
|
get_plugin_categories,
|
|
@@ -37,6 +38,8 @@ __all__ = [
|
|
|
37
38
|
'get_plugin_categories',
|
|
38
39
|
'is_valid_category',
|
|
39
40
|
'get_category_display_name',
|
|
41
|
+
# Ray utilities
|
|
42
|
+
'convert_http_to_ray_gcs',
|
|
40
43
|
# Legacy utilities for backward compatibility
|
|
41
44
|
'read_requirements',
|
|
42
45
|
'run_plugin',
|