synapse-sdk 2025.9.1__py3-none-any.whl → 2025.9.4__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.

Potentially problematic release.


This version of synapse-sdk might be problematic. Click here for more details.

Files changed (81) hide show
  1. synapse_sdk/devtools/docs/docs/api/clients/annotation-mixin.md +378 -0
  2. synapse_sdk/devtools/docs/docs/api/clients/backend.md +368 -1
  3. synapse_sdk/devtools/docs/docs/api/clients/core-mixin.md +477 -0
  4. synapse_sdk/devtools/docs/docs/api/clients/data-collection-mixin.md +422 -0
  5. synapse_sdk/devtools/docs/docs/api/clients/hitl-mixin.md +554 -0
  6. synapse_sdk/devtools/docs/docs/api/clients/index.md +391 -0
  7. synapse_sdk/devtools/docs/docs/api/clients/integration-mixin.md +571 -0
  8. synapse_sdk/devtools/docs/docs/api/clients/ml-mixin.md +578 -0
  9. synapse_sdk/devtools/docs/docs/plugins/developing-upload-template.md +1463 -0
  10. synapse_sdk/devtools/docs/docs/plugins/export-plugins.md +161 -34
  11. synapse_sdk/devtools/docs/docs/plugins/upload-plugins.md +1497 -213
  12. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/annotation-mixin.md +289 -0
  13. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/backend.md +378 -11
  14. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/core-mixin.md +417 -0
  15. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/data-collection-mixin.md +356 -0
  16. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/hitl-mixin.md +192 -0
  17. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/index.md +391 -0
  18. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/integration-mixin.md +479 -0
  19. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/api/clients/ml-mixin.md +284 -0
  20. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/developing-upload-template.md +1463 -0
  21. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/export-plugins.md +161 -34
  22. synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +1752 -572
  23. synapse_sdk/devtools/docs/sidebars.ts +7 -0
  24. synapse_sdk/plugins/README.md +1 -2
  25. synapse_sdk/plugins/categories/base.py +7 -0
  26. synapse_sdk/plugins/categories/export/actions/__init__.py +3 -0
  27. synapse_sdk/plugins/categories/export/actions/export/__init__.py +28 -0
  28. synapse_sdk/plugins/categories/export/actions/export/action.py +160 -0
  29. synapse_sdk/plugins/categories/export/actions/export/enums.py +113 -0
  30. synapse_sdk/plugins/categories/export/actions/export/exceptions.py +53 -0
  31. synapse_sdk/plugins/categories/export/actions/export/models.py +74 -0
  32. synapse_sdk/plugins/categories/export/actions/export/run.py +195 -0
  33. synapse_sdk/plugins/categories/export/actions/export/utils.py +187 -0
  34. synapse_sdk/plugins/categories/export/templates/plugin/__init__.py +1 -1
  35. synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +1 -2
  36. synapse_sdk/plugins/categories/upload/actions/upload/action.py +154 -531
  37. synapse_sdk/plugins/categories/upload/actions/upload/context.py +185 -0
  38. synapse_sdk/plugins/categories/upload/actions/upload/factory.py +143 -0
  39. synapse_sdk/plugins/categories/upload/actions/upload/models.py +66 -29
  40. synapse_sdk/plugins/categories/upload/actions/upload/orchestrator.py +182 -0
  41. synapse_sdk/plugins/categories/upload/actions/upload/registry.py +113 -0
  42. synapse_sdk/plugins/categories/upload/actions/upload/steps/__init__.py +1 -0
  43. synapse_sdk/plugins/categories/upload/actions/upload/steps/base.py +106 -0
  44. synapse_sdk/plugins/categories/upload/actions/upload/steps/cleanup.py +62 -0
  45. synapse_sdk/plugins/categories/upload/actions/upload/steps/collection.py +62 -0
  46. synapse_sdk/plugins/categories/upload/actions/upload/steps/generate.py +80 -0
  47. synapse_sdk/plugins/categories/upload/actions/upload/steps/initialize.py +66 -0
  48. synapse_sdk/plugins/categories/upload/actions/upload/steps/metadata.py +101 -0
  49. synapse_sdk/plugins/categories/upload/actions/upload/steps/organize.py +89 -0
  50. synapse_sdk/plugins/categories/upload/actions/upload/steps/upload.py +96 -0
  51. synapse_sdk/plugins/categories/upload/actions/upload/steps/validate.py +61 -0
  52. synapse_sdk/plugins/categories/upload/actions/upload/strategies/__init__.py +1 -0
  53. synapse_sdk/plugins/categories/upload/actions/upload/strategies/base.py +86 -0
  54. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/__init__.py +1 -0
  55. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/batch.py +39 -0
  56. synapse_sdk/plugins/categories/upload/actions/upload/strategies/data_unit/single.py +34 -0
  57. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/__init__.py +1 -0
  58. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/flat.py +233 -0
  59. synapse_sdk/plugins/categories/upload/actions/upload/strategies/file_discovery/recursive.py +253 -0
  60. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/__init__.py +1 -0
  61. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/excel.py +174 -0
  62. synapse_sdk/plugins/categories/upload/actions/upload/strategies/metadata/none.py +16 -0
  63. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/__init__.py +1 -0
  64. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/async_upload.py +109 -0
  65. synapse_sdk/plugins/categories/upload/actions/upload/strategies/upload/sync.py +43 -0
  66. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/__init__.py +1 -0
  67. synapse_sdk/plugins/categories/upload/actions/upload/strategies/validation/default.py +45 -0
  68. synapse_sdk/plugins/categories/upload/actions/upload/utils.py +194 -83
  69. synapse_sdk/plugins/categories/upload/templates/config.yaml +4 -0
  70. synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py +269 -0
  71. synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +71 -27
  72. synapse_sdk/plugins/models.py +7 -0
  73. synapse_sdk/shared/__init__.py +21 -0
  74. {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/METADATA +2 -1
  75. {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/RECORD +79 -28
  76. synapse_sdk/plugins/categories/export/actions/export.py +0 -385
  77. synapse_sdk/plugins/categories/export/enums.py +0 -7
  78. {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/WHEEL +0 -0
  79. {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/entry_points.txt +0 -0
  80. {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/licenses/LICENSE +0 -0
  81. {synapse_sdk-2025.9.1.dist-info → synapse_sdk-2025.9.4.dist-info}/top_level.txt +0 -0
@@ -53,7 +53,14 @@ const sidebars: SidebarsConfig = {
53
53
  type: 'category',
54
54
  label: 'Clients',
55
55
  items: [
56
+ 'api/clients/index',
56
57
  'api/clients/backend',
58
+ 'api/clients/annotation-mixin',
59
+ 'api/clients/core-mixin',
60
+ 'api/clients/data-collection-mixin',
61
+ 'api/clients/hitl-mixin',
62
+ 'api/clients/integration-mixin',
63
+ 'api/clients/ml-mixin',
57
64
  'api/clients/agent',
58
65
  'api/clients/ray',
59
66
  'api/clients/base',
@@ -329,7 +329,7 @@ from .enums import LogCode, LOG_MESSAGES, UploadStatus
329
329
  from .exceptions import ExcelParsingError, ExcelSecurityError
330
330
  from .models import UploadParams
331
331
  from .run import UploadRun
332
- from .utils import ExcelMetadataUtils, ExcelSecurityConfig, PathAwareJSONEncoder
332
+ from .utils import ExcelSecurityConfig, PathAwareJSONEncoder
333
333
 
334
334
  __all__ = [
335
335
  'UploadAction',
@@ -342,7 +342,6 @@ __all__ = [
342
342
  'ExcelParsingError',
343
343
  'PathAwareJSONEncoder',
344
344
  'ExcelSecurityConfig',
345
- 'ExcelMetadataUtils',
346
345
  ]
347
346
  ```
348
347
 
@@ -12,6 +12,7 @@ from synapse_sdk.plugins.enums import RunMethod
12
12
  from synapse_sdk.plugins.exceptions import ActionError
13
13
  from synapse_sdk.plugins.models import PluginRelease, Run
14
14
  from synapse_sdk.plugins.upload import archive_and_upload, build_and_upload, download_and_upload
15
+ from synapse_sdk.shared import init_sentry, needs_sentry_init
15
16
  from synapse_sdk.utils.module_loading import import_string
16
17
  from synapse_sdk.utils.pydantic.errors import pydantic_to_drf_error
17
18
 
@@ -181,6 +182,10 @@ class Action:
181
182
  for key, value in self.package_manager_options.items():
182
183
  runtime_env[self.plugin_package_manager][key] = value
183
184
 
185
+ # Sentry init if SENTRY_DSN is set
186
+ if needs_sentry_init():
187
+ runtime_env['worker_process_setup_hook'] = init_sentry
188
+
184
189
  # 맨 마지막에 진행되어야 함
185
190
  runtime_env['env_vars'] = self.envs
186
191
 
@@ -311,5 +316,7 @@ class Action:
311
316
  def ray_init(self):
312
317
  import ray
313
318
 
319
+ init_sentry()
320
+
314
321
  if not ray.is_initialized():
315
322
  ray.init(address=self.envs['RAY_ADDRESS'], ignore_reinit_error=True)
@@ -0,0 +1,3 @@
1
+ from .export import ExportAction
2
+
3
+ __all__ = ['ExportAction']
@@ -0,0 +1,28 @@
1
+ from .action import ExportAction
2
+ from .enums import ExportStatus, LogCode
3
+ from .exceptions import ExportError, ExportTargetError, ExportValidationError
4
+ from .models import ExportParams
5
+ from .run import ExportRun
6
+ from .utils import (
7
+ AssignmentExportTargetHandler,
8
+ ExportTargetHandler,
9
+ GroundTruthExportTargetHandler,
10
+ TargetHandlerFactory,
11
+ TaskExportTargetHandler,
12
+ )
13
+
14
+ __all__ = [
15
+ 'ExportAction',
16
+ 'ExportStatus',
17
+ 'LogCode',
18
+ 'ExportError',
19
+ 'ExportTargetError',
20
+ 'ExportValidationError',
21
+ 'ExportParams',
22
+ 'ExportRun',
23
+ 'ExportTargetHandler',
24
+ 'AssignmentExportTargetHandler',
25
+ 'GroundTruthExportTargetHandler',
26
+ 'TaskExportTargetHandler',
27
+ 'TargetHandlerFactory',
28
+ ]
@@ -0,0 +1,160 @@
1
+ from itertools import tee
2
+ from typing import Any, Dict
3
+
4
+ from pydantic_core import PydanticCustomError
5
+
6
+ from synapse_sdk.clients.exceptions import ClientError
7
+ from synapse_sdk.i18n import gettext as _
8
+ from synapse_sdk.plugins.categories.base import Action
9
+ from synapse_sdk.plugins.categories.decorators import register_action
10
+ from synapse_sdk.plugins.enums import PluginCategory, RunMethod
11
+ from synapse_sdk.utils.storage import get_pathlib
12
+
13
+ from .enums import LogCode
14
+ from .models import ExportParams
15
+ from .run import ExportRun
16
+ from .utils import TargetHandlerFactory
17
+
18
+
19
+ @register_action
20
+ class ExportAction(Action):
21
+ """Main export action for processing and exporting data from various targets.
22
+
23
+ Handles export operations including target validation, data retrieval,
24
+ and file generation. Supports export from assignment, ground_truth, and task
25
+ targets with comprehensive progress tracking and error handling.
26
+
27
+ Features:
28
+ - Multiple target source support (assignment, ground_truth, task)
29
+ - Filter validation and data retrieval
30
+ - Original file and data file export options
31
+ - Progress tracking with detailed metrics
32
+ - Comprehensive error logging
33
+ - Project configuration handling
34
+
35
+ Class Attributes:
36
+ name (str): Action identifier ('export')
37
+ category (PluginCategory): EXPORT category
38
+ method (RunMethod): JOB execution method
39
+ run_class (type): ExportRun for specialized logging
40
+ params_model (type): ExportParams for parameter validation
41
+ progress_categories (dict): Progress tracking configuration
42
+ metrics_categories (dict): Metrics collection configuration
43
+
44
+ Example:
45
+ >>> action = ExportAction(
46
+ ... params={
47
+ ... 'name': 'Assignment Export',
48
+ ... 'storage': 1,
49
+ ... 'path': '/exports/assignments',
50
+ ... 'target': 'assignment',
51
+ ... 'filter': {'project': 123}
52
+ ... },
53
+ ... plugin_config=config
54
+ ... )
55
+ >>> result = action.start()
56
+ """
57
+
58
+ name = 'export'
59
+ category = PluginCategory.EXPORT
60
+ method = RunMethod.JOB
61
+ params_model = ExportParams
62
+ run_class = ExportRun
63
+ progress_categories = {
64
+ 'dataset_conversion': {
65
+ 'proportion': 100,
66
+ }
67
+ }
68
+ metrics_categories = {
69
+ 'data_file': {
70
+ 'stand_by': 0,
71
+ 'failed': 0,
72
+ 'success': 0,
73
+ },
74
+ 'original_file': {
75
+ 'stand_by': 0,
76
+ 'failed': 0,
77
+ 'success': 0,
78
+ },
79
+ }
80
+
81
+ def get_filtered_results(self, filters, handler):
82
+ """Get filtered target results.
83
+
84
+ Retrieves data from the specified target using the provided filters
85
+ through the appropriate target handler.
86
+
87
+ Args:
88
+ filters (dict): Filter criteria to apply
89
+ handler (ExportTargetHandler): Target-specific handler
90
+
91
+ Returns:
92
+ tuple: (results, count) where results is the data and count is total
93
+
94
+ Raises:
95
+ PydanticCustomError: If data retrieval fails
96
+ """
97
+ try:
98
+ result_list = handler.get_results(self.client, filters)
99
+ results = result_list[0]
100
+ count = result_list[1]
101
+ except ClientError:
102
+ raise PydanticCustomError('client_error', _('Unable to get Ground Truth dataset.'))
103
+ return results, count
104
+
105
+ def start(self) -> Dict[str, Any]:
106
+ """Start the export process.
107
+
108
+ Main entry point for export operations. Handles parameter preparation,
109
+ target handler selection, data retrieval, and export execution.
110
+
111
+ Returns:
112
+ Dict[str, Any]: Export results from the entrypoint
113
+
114
+ Raises:
115
+ Various exceptions based on validation and processing failures
116
+ """
117
+ self.run.log_message_with_code(LogCode.EXPORT_STARTED)
118
+
119
+ filters = {'expand': 'data', **self.params['filter']}
120
+ target = self.params['target']
121
+ handler = TargetHandlerFactory.get_handler(target)
122
+
123
+ self.params['results'], self.params['count'] = self.get_filtered_results(filters, handler)
124
+
125
+ if self.params['count'] == 0:
126
+ self.run.log_message_with_code(LogCode.NO_RESULTS_FOUND)
127
+ else:
128
+ self.run.log_message_with_code(LogCode.RESULTS_RETRIEVED, self.params['count'])
129
+
130
+ # For the 'ground_truth' target, retrieve project information from the first result and add configuration
131
+ if target == 'ground_truth':
132
+ try:
133
+ # Split generator into two using tee()
134
+ peek_iter, main_iter = tee(self.params['results'])
135
+ first_result = next(peek_iter) # Peek first value only
136
+ project_pk = first_result['project']
137
+ project_info = self.client.get_project(project_pk)
138
+ self.params['project_id'] = project_pk
139
+ self.params['configuration'] = project_info.get('configuration', {})
140
+ self.params['results'] = main_iter # Keep original generator intact
141
+ except (StopIteration, KeyError):
142
+ self.params['configuration'] = {}
143
+ # For the 'assignment' and 'task' targets, retrieve the project from the filter as before
144
+ elif target in ['assignment', 'task'] and 'project' in self.params['filter']:
145
+ project_pk = self.params['filter']['project']
146
+ project_info = self.client.get_project(project_pk)
147
+ self.params['configuration'] = project_info.get('configuration', {})
148
+
149
+ export_items = handler.get_export_item(self.params['results'])
150
+ storage = self.client.get_storage(self.params['storage'])
151
+ pathlib_cwd = get_pathlib(storage, self.params['path'])
152
+ exporter = self.entrypoint(self.run, export_items, pathlib_cwd, **self.params)
153
+
154
+ try:
155
+ result = exporter.export()
156
+ self.run.log_message_with_code(LogCode.EXPORT_COMPLETED)
157
+ return result
158
+ except Exception as e:
159
+ self.run.log_message_with_code(LogCode.EXPORT_FAILED, str(e))
160
+ raise
@@ -0,0 +1,113 @@
1
+ from enum import Enum
2
+
3
+ from synapse_sdk.shared.enums import Context
4
+
5
+
6
+ class ExportStatus(str, Enum):
7
+ """Export processing status enumeration.
8
+
9
+ Defines the possible states for export operations, data files, and export items
10
+ throughout the export process.
11
+
12
+ Attributes:
13
+ SUCCESS: Export completed successfully
14
+ FAILED: Export failed with errors
15
+ STAND_BY: Export waiting to be processed
16
+ """
17
+
18
+ SUCCESS = 'success'
19
+ FAILED = 'failed'
20
+ STAND_BY = 'stand_by'
21
+
22
+
23
+ class LogCode(str, Enum):
24
+ """Type-safe logging codes for export operations.
25
+
26
+ Enumeration of all possible log events during export processing. Each code
27
+ corresponds to a specific event or error state with predefined message
28
+ templates and log levels.
29
+
30
+ The codes are organized by category:
31
+ - Validation codes (VALIDATION_FAILED, STORAGE_VALIDATION_FAILED, etc.)
32
+ - Export processing codes (EXPORT_STARTED, EXPORT_COMPLETED, etc.)
33
+ - File processing codes (ORIGINAL_FILE_EXPORTED, DATA_FILE_EXPORTED, etc.)
34
+ - Error handling codes (TARGET_HANDLER_ERROR, EXPORT_FAILED, etc.)
35
+
36
+ Each code maps to a configuration in LOG_MESSAGES with message template
37
+ and appropriate log level.
38
+ """
39
+
40
+ STORAGE_VALIDATION_FAILED = 'STORAGE_VALIDATION_FAILED'
41
+ FILTER_VALIDATION_FAILED = 'FILTER_VALIDATION_FAILED'
42
+ TARGET_VALIDATION_FAILED = 'TARGET_VALIDATION_FAILED'
43
+ VALIDATION_FAILED = 'VALIDATION_FAILED'
44
+ EXPORT_STARTED = 'EXPORT_STARTED'
45
+ EXPORT_COMPLETED = 'EXPORT_COMPLETED'
46
+ EXPORT_FAILED = 'EXPORT_FAILED'
47
+ NO_RESULTS_FOUND = 'NO_RESULTS_FOUND'
48
+ RESULTS_RETRIEVED = 'RESULTS_RETRIEVED'
49
+ ORIGINAL_FILE_EXPORTED = 'ORIGINAL_FILE_EXPORTED'
50
+ DATA_FILE_EXPORTED = 'DATA_FILE_EXPORTED'
51
+ FILE_EXPORT_FAILED = 'FILE_EXPORT_FAILED'
52
+ TARGET_HANDLER_ERROR = 'TARGET_HANDLER_ERROR'
53
+ NULL_DATA_DETECTED = 'NULL_DATA_DETECTED'
54
+
55
+
56
+ LOG_MESSAGES = {
57
+ LogCode.STORAGE_VALIDATION_FAILED: {
58
+ 'message': 'Storage validation failed.',
59
+ 'level': Context.DANGER,
60
+ },
61
+ LogCode.FILTER_VALIDATION_FAILED: {
62
+ 'message': 'Filter validation failed.',
63
+ 'level': Context.DANGER,
64
+ },
65
+ LogCode.TARGET_VALIDATION_FAILED: {
66
+ 'message': 'Target validation failed.',
67
+ 'level': Context.DANGER,
68
+ },
69
+ LogCode.VALIDATION_FAILED: {
70
+ 'message': 'Validation failed.',
71
+ 'level': Context.DANGER,
72
+ },
73
+ LogCode.EXPORT_STARTED: {
74
+ 'message': 'Export process started.',
75
+ 'level': None,
76
+ },
77
+ LogCode.EXPORT_COMPLETED: {
78
+ 'message': 'Export process completed.',
79
+ 'level': None,
80
+ },
81
+ LogCode.EXPORT_FAILED: {
82
+ 'message': 'Export process failed: {}',
83
+ 'level': Context.DANGER,
84
+ },
85
+ LogCode.NO_RESULTS_FOUND: {
86
+ 'message': 'No results found for export.',
87
+ 'level': Context.WARNING,
88
+ },
89
+ LogCode.RESULTS_RETRIEVED: {
90
+ 'message': 'Retrieved {} results for export',
91
+ 'level': None,
92
+ },
93
+ LogCode.ORIGINAL_FILE_EXPORTED: {
94
+ 'message': 'Original file exported successfully.',
95
+ 'level': None,
96
+ },
97
+ LogCode.DATA_FILE_EXPORTED: {
98
+ 'message': 'Data file exported successfully.',
99
+ 'level': None,
100
+ },
101
+ LogCode.FILE_EXPORT_FAILED: {
102
+ 'message': 'Failed to export file: {}',
103
+ 'level': Context.DANGER,
104
+ },
105
+ LogCode.TARGET_HANDLER_ERROR: {
106
+ 'message': 'Target handler error: {}',
107
+ 'level': Context.DANGER,
108
+ },
109
+ LogCode.NULL_DATA_DETECTED: {
110
+ 'message': 'Data is null for export item',
111
+ 'level': Context.WARNING,
112
+ },
113
+ }
@@ -0,0 +1,53 @@
1
+ class ExportError(Exception):
2
+ """Base exception for export-related errors.
3
+
4
+ This exception is raised when an export operation encounters errors
5
+ that prevent successful completion. It serves as the base class for
6
+ more specific export-related exceptions.
7
+
8
+ Used during export processing to handle various error conditions
9
+ such as validation failures, data access errors, or processing issues.
10
+
11
+ Example:
12
+ >>> if not validate_export_data(data):
13
+ ... raise ExportError("Export data validation failed")
14
+ """
15
+
16
+ pass
17
+
18
+
19
+ class ExportValidationError(ExportError):
20
+ """Exception raised when export parameter validation fails.
21
+
22
+ This exception is raised when export parameters or configuration
23
+ fail validation checks, preventing the export operation from starting.
24
+
25
+ Used during parameter validation to distinguish validation errors
26
+ from other types of export failures.
27
+
28
+ Example:
29
+ >>> if not storage_exists(storage_id):
30
+ ... raise ExportValidationError(f"Storage {storage_id} does not exist")
31
+ """
32
+
33
+ pass
34
+
35
+
36
+ class ExportTargetError(ExportError):
37
+ """Exception raised when export target handling encounters errors.
38
+
39
+ This exception is raised when target-specific operations (assignment,
40
+ ground_truth, task) fail due to data access issues, filter problems,
41
+ or target-specific validation failures.
42
+
43
+ Used during target data retrieval and processing to handle target-specific
44
+ errors separately from general export errors.
45
+
46
+ Example:
47
+ >>> try:
48
+ ... results = client.list_assignments(params=filters)
49
+ ... except ClientError as e:
50
+ ... raise ExportTargetError(f"Failed to retrieve assignments: {e}")
51
+ """
52
+
53
+ pass
@@ -0,0 +1,74 @@
1
+ from typing import Annotated, Literal
2
+
3
+ from pydantic import AfterValidator, BaseModel, field_validator
4
+ from pydantic_core import PydanticCustomError
5
+
6
+ from synapse_sdk.clients.exceptions import ClientError
7
+ from synapse_sdk.i18n import gettext as _
8
+ from synapse_sdk.utils.pydantic.validators import non_blank
9
+
10
+ from .utils import TargetHandlerFactory
11
+
12
+
13
+ class ExportParams(BaseModel):
14
+ """Export action parameter validation model.
15
+
16
+ Defines and validates all parameters required for export operations.
17
+ Uses Pydantic for type validation and custom validators to ensure
18
+ storage and filter resources exist before processing.
19
+
20
+ Attributes:
21
+ name (str): Human-readable name for the export operation
22
+ description (str | None): Optional description of the export
23
+ storage (int): Storage ID where exported data will be saved
24
+ save_original_file (bool): Whether to save the original file
25
+ path (str): File system path where exported data will be saved
26
+ target (str): The target source to export data from (assignment, ground_truth, task)
27
+ filter (dict): Filter criteria to apply when retrieving data
28
+ extra_params (dict | None): Additional parameters for export customization.
29
+ Example: {"include_metadata": True, "compression": "gzip"}
30
+
31
+ Validation:
32
+ - name: Must be non-blank after validation
33
+ - storage: Must exist and be accessible via client API
34
+ - target: Must be one of the supported target types
35
+ - filter: Must be valid for the specified target type
36
+
37
+ Example:
38
+ >>> params = ExportParams(
39
+ ... name="Assignment Export",
40
+ ... storage=1,
41
+ ... path="/exports/assignments",
42
+ ... target="assignment",
43
+ ... filter={"project": 123}
44
+ ... )
45
+ """
46
+
47
+ name: Annotated[str, AfterValidator(non_blank)]
48
+ description: str | None = None
49
+ storage: int
50
+ save_original_file: bool = True
51
+ path: str
52
+ target: Literal['assignment', 'ground_truth', 'task']
53
+ filter: dict
54
+ extra_params: dict | None = None
55
+
56
+ @field_validator('storage')
57
+ @staticmethod
58
+ def check_storage_exists(value, info):
59
+ action = info.context['action']
60
+ client = action.client
61
+ try:
62
+ client.get_storage(value)
63
+ except ClientError:
64
+ raise PydanticCustomError('client_error', _('Unable to get storage from Synapse backend.'))
65
+ return value
66
+
67
+ @field_validator('filter')
68
+ @staticmethod
69
+ def check_filter_by_target(value, info):
70
+ action = info.context['action']
71
+ client = action.client
72
+ target = action.params['target']
73
+ handler = TargetHandlerFactory.get_handler(target)
74
+ return handler.validate_filter(value, client)
@@ -0,0 +1,195 @@
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, ExportStatus, LogCode
11
+
12
+
13
+ class ExportRun(Run):
14
+ """Export-specific run management class.
15
+
16
+ Extends the base Run class with export-specific logging capabilities
17
+ and event tracking. Provides type-safe logging using LogCode enums
18
+ and specialized methods for tracking export progress.
19
+
20
+ Manages logging for export events, data files, and export targets
21
+ throughout the export lifecycle. Each log entry includes status,
22
+ timestamps, and relevant metadata.
23
+
24
+ Attributes:
25
+ Inherits all attributes from base Run class plus export-specific
26
+ logging methods and nested model classes for structured logging.
27
+
28
+ Example:
29
+ >>> run = ExportRun(job_id, context)
30
+ >>> run.log_message_with_code(LogCode.EXPORT_STARTED)
31
+ >>> run.log_export_event(LogCode.RESULTS_RETRIEVED, target_id, count)
32
+ """
33
+
34
+ class ExportEventLog(BaseModel):
35
+ """Model for export event log entries.
36
+
37
+ Records significant events during export processing with
38
+ target identification and status information.
39
+
40
+ Attributes:
41
+ target_id (int): The ID of the export target
42
+ info (str | None): Optional additional information
43
+ status (Context): Event status/severity level
44
+ created (str): Timestamp when event occurred
45
+ """
46
+
47
+ target_id: int
48
+ info: str | None = None
49
+ status: Context
50
+ created: str
51
+
52
+ class DataFileLog(BaseModel):
53
+ """Model for data file export log entries.
54
+
55
+ Tracks the export status of individual data files during
56
+ export operations.
57
+
58
+ Attributes:
59
+ target_id (int): The ID of the target being exported
60
+ data_file_info (str | None): JSON information about the data file
61
+ status (ExportStatus): Export status (SUCCESS/FAILED/STAND_BY)
62
+ error (str | None): Error message if export failed
63
+ created (str): Timestamp when log entry was created
64
+ """
65
+
66
+ target_id: int
67
+ data_file_info: str | None
68
+ status: ExportStatus
69
+ error: str | None = None
70
+ created: str
71
+
72
+ class MetricsRecord(BaseModel):
73
+ """Model for export metrics tracking.
74
+
75
+ Records count-based metrics for monitoring export
76
+ progress and success rates.
77
+
78
+ Attributes:
79
+ stand_by (int): Number of items waiting to be processed
80
+ failed (int): Number of items that failed processing
81
+ success (int): Number of items successfully processed
82
+ """
83
+
84
+ stand_by: int
85
+ failed: int
86
+ success: int
87
+
88
+ def log_message_with_code(self, code: LogCode, *args, level: Optional[Context] = None):
89
+ """Log message using predefined code with type safety.
90
+
91
+ Args:
92
+ code (LogCode): The log message code
93
+ *args: Arguments to format the message
94
+ level (Context | None): Optional context level override
95
+ """
96
+ if code not in LOG_MESSAGES:
97
+ self.log_message(f'Unknown log code: {code}')
98
+ return
99
+
100
+ log_config = LOG_MESSAGES[code]
101
+ message = log_config['message'].format(*args) if args else log_config['message']
102
+ log_level = level or log_config['level'] or Context.INFO
103
+
104
+ # Always call log_message for basic logging
105
+ if log_level:
106
+ self.log_message(message, context=log_level.value)
107
+ else:
108
+ self.log_message(message)
109
+
110
+ def log_file(
111
+ self, log_type: str, target_id: int, data_file_info: dict, status: ExportStatus, error: str | None = None
112
+ ):
113
+ """Log export file information.
114
+
115
+ Args:
116
+ log_type (str): The type of log ('export_data_file' or 'export_original_file').
117
+ target_id (int): The ID of the data file.
118
+ data_file_info (dict): The JSON info of the data file.
119
+ status (ExportStatus): The status of the data file.
120
+ error (str | None): The error message, if any.
121
+ """
122
+ now = datetime.now().isoformat()
123
+ self.log(
124
+ log_type,
125
+ self.DataFileLog(
126
+ target_id=target_id,
127
+ data_file_info=json.dumps(data_file_info),
128
+ status=status.value,
129
+ error=error,
130
+ created=now,
131
+ ).model_dump(),
132
+ )
133
+
134
+ def log_export_event(self, code: LogCode, target_id: int, *args, level: Context | None = None):
135
+ """Log export event using predefined code.
136
+
137
+ Args:
138
+ code (str): The log message code.
139
+ target_id (int): The ID of the export target.
140
+ *args: Arguments to format the message.
141
+ level (Context | None): Optional context level override.
142
+ """
143
+ # Call log_message_with_code to handle the basic logging
144
+ self.log_message_with_code(code, *args, level=level)
145
+
146
+ # Also log the event for export-specific tracking
147
+ if code not in LOG_MESSAGES:
148
+ now = datetime.now().isoformat()
149
+ self.log(
150
+ 'export_event',
151
+ self.ExportEventLog(
152
+ target_id=target_id, info=f'Unknown log code: {code}', status=Context.DANGER, created=now
153
+ ).model_dump(),
154
+ )
155
+ return
156
+
157
+ log_config = LOG_MESSAGES[code]
158
+ message = log_config['message'].format(*args) if args else log_config['message']
159
+ log_level = level or log_config['level'] or Context.INFO
160
+
161
+ now = datetime.now().isoformat()
162
+ self.log(
163
+ 'export_event',
164
+ self.ExportEventLog(info=message, status=log_level, target_id=target_id, created=now).model_dump(),
165
+ )
166
+
167
+ def log_metrics(self, record: MetricsRecord, category: str):
168
+ """Log export metrics.
169
+
170
+ Args:
171
+ record (MetricsRecord): The metrics record to log.
172
+ category (str): The category of the metrics.
173
+ """
174
+ record = self.MetricsRecord.model_validate(record)
175
+ self.set_metrics(value=record.model_dump(), category=category)
176
+
177
+ def export_log_json_file(
178
+ self,
179
+ target_id: int,
180
+ data_file_info: dict,
181
+ status: ExportStatus = ExportStatus.STAND_BY,
182
+ error: str | None = None,
183
+ ):
184
+ """Log export json data file."""
185
+ self.log_file('export_data_file', target_id, data_file_info, status, error)
186
+
187
+ def export_log_original_file(
188
+ self,
189
+ target_id: int,
190
+ data_file_info: dict,
191
+ status: ExportStatus = ExportStatus.STAND_BY,
192
+ error: str | None = None,
193
+ ):
194
+ """Log export origin data file."""
195
+ self.log_file('export_original_file', target_id, data_file_info, status, error)