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.
Files changed (228) hide show
  1. synapse_sdk/__init__.py +24 -0
  2. synapse_sdk/cli/__init__.py +310 -5
  3. synapse_sdk/cli/alias/__init__.py +22 -0
  4. synapse_sdk/cli/alias/create.py +36 -0
  5. synapse_sdk/cli/alias/dataclass.py +31 -0
  6. synapse_sdk/cli/alias/default.py +16 -0
  7. synapse_sdk/cli/alias/delete.py +15 -0
  8. synapse_sdk/cli/alias/list.py +19 -0
  9. synapse_sdk/cli/alias/read.py +15 -0
  10. synapse_sdk/cli/alias/update.py +17 -0
  11. synapse_sdk/cli/alias/utils.py +61 -0
  12. synapse_sdk/cli/code_server.py +687 -0
  13. synapse_sdk/cli/config.py +440 -0
  14. synapse_sdk/cli/devtools.py +90 -0
  15. synapse_sdk/cli/plugin/__init__.py +33 -0
  16. synapse_sdk/cli/{create_plugin.py → plugin/create.py} +2 -2
  17. synapse_sdk/{plugins/cli → cli/plugin}/publish.py +23 -15
  18. synapse_sdk/clients/agent/__init__.py +9 -3
  19. synapse_sdk/clients/agent/container.py +143 -0
  20. synapse_sdk/clients/agent/core.py +19 -0
  21. synapse_sdk/clients/agent/ray.py +298 -9
  22. synapse_sdk/clients/backend/__init__.py +30 -12
  23. synapse_sdk/clients/backend/annotation.py +13 -5
  24. synapse_sdk/clients/backend/core.py +31 -4
  25. synapse_sdk/clients/backend/data_collection.py +186 -0
  26. synapse_sdk/clients/backend/hitl.py +17 -0
  27. synapse_sdk/clients/backend/integration.py +16 -1
  28. synapse_sdk/clients/backend/ml.py +5 -1
  29. synapse_sdk/clients/backend/models.py +78 -0
  30. synapse_sdk/clients/base.py +384 -41
  31. synapse_sdk/clients/ray/serve.py +2 -0
  32. synapse_sdk/clients/validators/collections.py +31 -0
  33. synapse_sdk/devtools/config.py +94 -0
  34. synapse_sdk/devtools/server.py +41 -0
  35. synapse_sdk/devtools/streamlit_app/__init__.py +5 -0
  36. synapse_sdk/devtools/streamlit_app/app.py +128 -0
  37. synapse_sdk/devtools/streamlit_app/services/__init__.py +11 -0
  38. synapse_sdk/devtools/streamlit_app/services/job_service.py +233 -0
  39. synapse_sdk/devtools/streamlit_app/services/plugin_service.py +236 -0
  40. synapse_sdk/devtools/streamlit_app/services/serve_service.py +95 -0
  41. synapse_sdk/devtools/streamlit_app/ui/__init__.py +15 -0
  42. synapse_sdk/devtools/streamlit_app/ui/config_tab.py +76 -0
  43. synapse_sdk/devtools/streamlit_app/ui/deployment_tab.py +66 -0
  44. synapse_sdk/devtools/streamlit_app/ui/http_tab.py +125 -0
  45. synapse_sdk/devtools/streamlit_app/ui/jobs_tab.py +573 -0
  46. synapse_sdk/devtools/streamlit_app/ui/serve_tab.py +346 -0
  47. synapse_sdk/devtools/streamlit_app/ui/status_bar.py +118 -0
  48. synapse_sdk/devtools/streamlit_app/utils/__init__.py +40 -0
  49. synapse_sdk/devtools/streamlit_app/utils/json_viewer.py +197 -0
  50. synapse_sdk/devtools/streamlit_app/utils/log_formatter.py +38 -0
  51. synapse_sdk/devtools/streamlit_app/utils/styles.py +241 -0
  52. synapse_sdk/devtools/streamlit_app/utils/ui_components.py +289 -0
  53. synapse_sdk/devtools/streamlit_app.py +10 -0
  54. synapse_sdk/loggers.py +120 -9
  55. synapse_sdk/plugins/README.md +1340 -0
  56. synapse_sdk/plugins/__init__.py +0 -13
  57. synapse_sdk/plugins/categories/base.py +117 -11
  58. synapse_sdk/plugins/categories/data_validation/actions/validation.py +72 -0
  59. synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +33 -5
  60. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  61. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  62. synapse_sdk/plugins/categories/export/actions/export/action.py +165 -0
  63. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  64. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  65. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  66. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  67. synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
  68. synapse_sdk/plugins/categories/export/templates/config.yaml +21 -0
  69. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +390 -0
  70. synapse_sdk/plugins/categories/export/templates/plugin/export.py +160 -0
  71. synapse_sdk/plugins/categories/neural_net/actions/deployment.py +13 -12
  72. synapse_sdk/plugins/categories/neural_net/actions/train.py +1134 -31
  73. synapse_sdk/plugins/categories/neural_net/actions/tune.py +534 -0
  74. synapse_sdk/plugins/categories/neural_net/base/inference.py +1 -1
  75. synapse_sdk/plugins/categories/neural_net/templates/config.yaml +32 -4
  76. synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +26 -10
  77. synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +4 -0
  78. synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation/__init__.py +3 -0
  79. synapse_sdk/plugins/categories/{export/actions/export.py → pre_annotation/actions/pre_annotation/action.py} +4 -4
  80. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/__init__.py +28 -0
  81. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/action.py +148 -0
  82. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/enums.py +269 -0
  83. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/exceptions.py +14 -0
  84. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/factory.py +76 -0
  85. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/models.py +100 -0
  86. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/orchestrator.py +248 -0
  87. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/run.py +64 -0
  88. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/__init__.py +17 -0
  89. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/annotation.py +265 -0
  90. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/base.py +170 -0
  91. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/extraction.py +83 -0
  92. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/metrics.py +92 -0
  93. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/preprocessor.py +243 -0
  94. synapse_sdk/plugins/categories/pre_annotation/actions/to_task/strategies/validation.py +143 -0
  95. synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +19 -0
  96. synapse_sdk/plugins/categories/pre_annotation/templates/plugin/to_task.py +40 -0
  97. synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +2 -0
  98. synapse_sdk/plugins/categories/upload/__init__.py +0 -0
  99. synapse_sdk/plugins/categories/upload/actions/__init__.py +0 -0
  100. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +19 -0
  101. synapse_sdk/plugins/categories/upload/actions/upload/action.py +236 -0
  102. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  103. synapse_sdk/plugins/categories/upload/actions/upload/enums.py +493 -0
  104. synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
  105. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +138 -0
  106. synapse_sdk/plugins/categories/upload/actions/upload/models.py +214 -0
  107. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +183 -0
  108. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  109. synapse_sdk/plugins/categories/upload/actions/upload/run.py +179 -0
  110. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  111. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +107 -0
  112. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  113. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +63 -0
  114. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +91 -0
  115. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +82 -0
  116. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +235 -0
  117. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +201 -0
  118. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +104 -0
  119. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +71 -0
  120. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  121. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +82 -0
  122. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  123. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  124. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +29 -0
  125. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  126. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +300 -0
  127. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +287 -0
  128. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  129. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  130. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  131. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  132. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +84 -0
  133. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  134. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +60 -0
  135. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +250 -0
  136. synapse_sdk/plugins/categories/upload/templates/README.md +470 -0
  137. synapse_sdk/plugins/categories/upload/templates/config.yaml +33 -0
  138. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +310 -0
  139. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +102 -0
  140. synapse_sdk/plugins/enums.py +3 -1
  141. synapse_sdk/plugins/models.py +148 -11
  142. synapse_sdk/plugins/templates/plugin-config-schema.json +406 -0
  143. synapse_sdk/plugins/templates/schema.json +491 -0
  144. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +1 -0
  145. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +1 -1
  146. synapse_sdk/plugins/utils/__init__.py +46 -0
  147. synapse_sdk/plugins/utils/actions.py +119 -0
  148. synapse_sdk/plugins/utils/config.py +203 -0
  149. synapse_sdk/plugins/{utils.py → utils/legacy.py} +26 -46
  150. synapse_sdk/plugins/utils/ray_gcs.py +66 -0
  151. synapse_sdk/plugins/utils/registry.py +58 -0
  152. synapse_sdk/shared/__init__.py +25 -0
  153. synapse_sdk/shared/enums.py +93 -0
  154. synapse_sdk/types.py +19 -0
  155. synapse_sdk/utils/converters/__init__.py +240 -0
  156. synapse_sdk/utils/converters/coco/__init__.py +0 -0
  157. synapse_sdk/utils/converters/coco/from_dm.py +322 -0
  158. synapse_sdk/utils/converters/coco/to_dm.py +215 -0
  159. synapse_sdk/utils/converters/dm/__init__.py +57 -0
  160. synapse_sdk/utils/converters/dm/base.py +137 -0
  161. synapse_sdk/utils/converters/dm/from_v1.py +273 -0
  162. synapse_sdk/utils/converters/dm/to_v1.py +321 -0
  163. synapse_sdk/utils/converters/dm/tools/__init__.py +214 -0
  164. synapse_sdk/utils/converters/dm/tools/answer.py +95 -0
  165. synapse_sdk/utils/converters/dm/tools/bounding_box.py +132 -0
  166. synapse_sdk/utils/converters/dm/tools/bounding_box_3d.py +121 -0
  167. synapse_sdk/utils/converters/dm/tools/classification.py +75 -0
  168. synapse_sdk/utils/converters/dm/tools/keypoint.py +117 -0
  169. synapse_sdk/utils/converters/dm/tools/named_entity.py +111 -0
  170. synapse_sdk/utils/converters/dm/tools/polygon.py +122 -0
  171. synapse_sdk/utils/converters/dm/tools/polyline.py +124 -0
  172. synapse_sdk/utils/converters/dm/tools/prompt.py +94 -0
  173. synapse_sdk/utils/converters/dm/tools/relation.py +86 -0
  174. synapse_sdk/utils/converters/dm/tools/segmentation.py +141 -0
  175. synapse_sdk/utils/converters/dm/tools/segmentation_3d.py +83 -0
  176. synapse_sdk/utils/converters/dm/types.py +168 -0
  177. synapse_sdk/utils/converters/dm/utils.py +162 -0
  178. synapse_sdk/utils/converters/dm_legacy/__init__.py +56 -0
  179. synapse_sdk/utils/converters/dm_legacy/from_v1.py +627 -0
  180. synapse_sdk/utils/converters/dm_legacy/to_v1.py +367 -0
  181. synapse_sdk/utils/converters/pascal/__init__.py +0 -0
  182. synapse_sdk/utils/converters/pascal/from_dm.py +244 -0
  183. synapse_sdk/utils/converters/pascal/to_dm.py +214 -0
  184. synapse_sdk/utils/converters/yolo/__init__.py +0 -0
  185. synapse_sdk/utils/converters/yolo/from_dm.py +384 -0
  186. synapse_sdk/utils/converters/yolo/to_dm.py +267 -0
  187. synapse_sdk/utils/dataset.py +46 -0
  188. synapse_sdk/utils/encryption.py +158 -0
  189. synapse_sdk/utils/file/__init__.py +58 -0
  190. synapse_sdk/utils/file/archive.py +32 -0
  191. synapse_sdk/utils/file/checksum.py +56 -0
  192. synapse_sdk/utils/file/chunking.py +31 -0
  193. synapse_sdk/utils/file/download.py +385 -0
  194. synapse_sdk/utils/file/encoding.py +40 -0
  195. synapse_sdk/utils/file/io.py +22 -0
  196. synapse_sdk/utils/file/upload.py +165 -0
  197. synapse_sdk/utils/file/video/__init__.py +29 -0
  198. synapse_sdk/utils/file/video/transcode.py +307 -0
  199. synapse_sdk/utils/file.py.backup +301 -0
  200. synapse_sdk/utils/http.py +138 -0
  201. synapse_sdk/utils/network.py +309 -0
  202. synapse_sdk/utils/storage/__init__.py +72 -0
  203. synapse_sdk/utils/storage/providers/__init__.py +183 -0
  204. synapse_sdk/utils/storage/providers/file_system.py +134 -0
  205. synapse_sdk/utils/storage/providers/gcp.py +13 -0
  206. synapse_sdk/utils/storage/providers/http.py +190 -0
  207. synapse_sdk/utils/storage/providers/s3.py +91 -0
  208. synapse_sdk/utils/storage/providers/sftp.py +47 -0
  209. synapse_sdk/utils/storage/registry.py +17 -0
  210. synapse_sdk-2025.12.3.dist-info/METADATA +123 -0
  211. synapse_sdk-2025.12.3.dist-info/RECORD +279 -0
  212. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/WHEEL +1 -1
  213. synapse_sdk/clients/backend/dataset.py +0 -51
  214. synapse_sdk/plugins/categories/import/actions/import.py +0 -10
  215. synapse_sdk/plugins/cli/__init__.py +0 -21
  216. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
  217. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
  218. synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
  219. synapse_sdk/utils/file.py +0 -168
  220. synapse_sdk/utils/storage.py +0 -91
  221. synapse_sdk-1.0.0a23.dist-info/METADATA +0 -44
  222. synapse_sdk-1.0.0a23.dist-info/RECORD +0 -114
  223. /synapse_sdk/{plugins/cli → cli/plugin}/run.py +0 -0
  224. /synapse_sdk/{plugins/categories/import → clients/validators}/__init__.py +0 -0
  225. /synapse_sdk/{plugins/categories/import/actions → devtools}/__init__.py +0 -0
  226. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info}/entry_points.txt +0 -0
  227. {synapse_sdk-1.0.0a23.dist-info → synapse_sdk-2025.12.3.dist-info/licenses}/LICENSE +0 -0
  228. {synapse_sdk-1.0.0a23.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
@@ -0,0 +1,102 @@
1
+ from pathlib import Path
2
+ from typing import Any, Dict, List
3
+
4
+ from . import BaseUploader
5
+
6
+
7
+ class Uploader(BaseUploader):
8
+ """Plugin upload action interface for organizing files.
9
+
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.
21
+ """
22
+
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.
27
+
28
+ Args:
29
+ run: Plugin run object with logging capabilities.
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)
33
+ file_specification: List of specifications that define the structure of files to be uploaded.
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
88
+ """
89
+ return organized_files
90
+
91
+ def filter_files(self, organized_file: Dict[str, Any]) -> bool:
92
+ """Filter files based on custom criteria.
93
+
94
+ Override this method to implement custom filtering logic.
95
+
96
+ Args:
97
+ organized_file: Single organized file to filter
98
+
99
+ Returns:
100
+ bool: True to include the file, False to filter it out
101
+ """
102
+ return True
@@ -2,6 +2,8 @@ from enum import Enum
2
2
 
3
3
 
4
4
  class RunMethod(Enum):
5
+ """Plugin Execution Methods."""
6
+
5
7
  JOB = 'job'
6
8
  TASK = 'task'
7
9
  RESTAPI = 'restapi'
@@ -10,7 +12,7 @@ class RunMethod(Enum):
10
12
  class PluginCategory(Enum):
11
13
  NEURAL_NET = 'neural_net'
12
14
  EXPORT = 'export'
13
- IMPORT = 'import'
15
+ UPLOAD = 'upload'
14
16
  SMART_TOOL = 'smart_tool'
15
17
  POST_ANNOTATION = 'post_annotation'
16
18
  PRE_ANNOTATION = 'pre_annotation'
@@ -1,10 +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
9
+ from synapse_sdk.devtools.config import get_backend_config
6
10
  from synapse_sdk.loggers import BackendLogger, ConsoleLogger
7
11
  from synapse_sdk.plugins.utils import read_plugin_config
12
+ from synapse_sdk.shared import needs_sentry_init
8
13
  from synapse_sdk.shared.enums import Context
9
14
  from synapse_sdk.utils.storage import get_storage
10
15
  from synapse_sdk.utils.string import hash_text
@@ -41,6 +46,33 @@ class PluginRelease:
41
46
  def name(self):
42
47
  return self.config['name']
43
48
 
49
+ @cached_property
50
+ def package_manager(self):
51
+ return self.config.get('package_manager', 'pip')
52
+
53
+ @cached_property
54
+ def package_manager_options(self):
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}
75
+
44
76
  @cached_property
45
77
  def checksum(self):
46
78
  return hash_text(self.code)
@@ -58,6 +90,11 @@ class PluginRelease:
58
90
  def warm_up():
59
91
  pass
60
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
+
61
98
  nodes = list_nodes(address=self.envs['RAY_DASHBOARD_URL'])
62
99
  node_ids = [n['node_id'] for n in nodes]
63
100
  for node_id in node_ids:
@@ -65,8 +102,12 @@ class PluginRelease:
65
102
 
66
103
  warm_up.options(
67
104
  runtime_env={
68
- 'pip': ['-r ${RAY_RUNTIME_ENV_CREATE_WORKING_DIR}/requirements.txt'],
105
+ self.package_manager: {
106
+ 'packages': ['-r ${RAY_RUNTIME_ENV_CREATE_WORKING_DIR}/requirements.txt']
107
+ ** self.package_manager_options
108
+ },
69
109
  'working_dir': self.get_url(self.envs['SYNAPSE_PLUGIN_STORAGE']),
110
+ **extra_runtime_env,
70
111
  },
71
112
  scheduling_strategy=strategy,
72
113
  ).remote()
@@ -83,23 +124,68 @@ class PluginRelease:
83
124
 
84
125
 
85
126
  class Run:
127
+ """Run class for manage plugin run istance.
128
+
129
+ Attrs:
130
+ job_id: plugin run job id
131
+ context: plugin run context
132
+ client: backend client for communicate with backend
133
+ logger: logger for log plugin run events
134
+ """
135
+
86
136
  logger = None
87
137
  job_id = None
88
138
  context = None
89
139
  client = None
90
140
 
91
- def __init__(self, job_id, context):
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):
92
162
  self.job_id = job_id
93
- self.context = context
94
- self.client = BackendClient(
95
- self.context['envs']['SYNAPSE_PLUGIN_RUN_HOST'],
96
- token=self.context['envs'].get('SYNAPSE_PLUGIN_RUN_USER_TOKEN'),
97
- tenant=self.context['envs'].get('SYNAPSE_PLUGIN_RUN_TENANT'),
98
- )
163
+ self.context = context or {}
164
+ config = get_backend_config()
165
+ if config:
166
+ self.client = BackendClient(
167
+ config['host'],
168
+ access_token=config['token'],
169
+ )
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
+
177
+ self.client = BackendClient(
178
+ host,
179
+ token=token,
180
+ tenant=tenant,
181
+ )
99
182
  self.set_logger()
100
183
 
101
184
  def set_logger(self):
102
- kwargs = {'progress_categories': self.context['progress_categories']}
185
+ kwargs = {
186
+ 'progress_categories': self.context.get('progress_categories'),
187
+ 'metrics_categories': self.context.get('metrics_categories'),
188
+ }
103
189
 
104
190
  if self.job_id:
105
191
  self.logger = BackendLogger(self.client, self.job_id, **kwargs)
@@ -109,11 +195,62 @@ class Run:
109
195
  def set_progress(self, current, total, category=''):
110
196
  self.logger.set_progress(current, total, category)
111
197
 
112
- def log(self, event, data):
113
- self.logger.log(event, data)
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
+
209
+ def set_metrics(self, value: Dict[Any, Any], category: str):
210
+ self.logger.set_metrics(value, category)
211
+
212
+ def log(self, event, data, file=None):
213
+ self.logger.log(event, data, file=file)
114
214
 
115
215
  def log_message(self, message, context=Context.INFO.value):
116
216
  self.logger.log('message', {'context': context, 'content': message})
117
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
+
118
255
  def end_log(self):
119
256
  self.log_message('Plugin run is complete.')