synapse-sdk 1.0.0a31__py3-none-any.whl → 1.0.0a32__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.

@@ -1,4 +1,6 @@
1
1
  from multiprocessing import Pool
2
+ from pathlib import Path
3
+ from typing import Dict, Optional
2
4
 
3
5
  from tqdm import tqdm
4
6
 
@@ -11,21 +13,59 @@ class DatasetClientMixin(BaseClient):
11
13
  path = 'datasets/'
12
14
  return self._list(path)
13
15
 
14
- def create_data_file(self, file_path):
16
+ def get_dataset(self, dataset_id):
17
+ """Get dataset from synapse-backend.
18
+
19
+ Args:
20
+ dataset_id: The dataset id to get.
21
+ """
22
+ path = f'datasets/{dataset_id}/?expand=file_specifications'
23
+ return self._get(path)
24
+
25
+ def create_data_file(self, file_path: Path):
26
+ """Create data file to synapse-backend.
27
+
28
+ Args:
29
+ file_path: The file pathlib object to upload.
30
+ """
15
31
  path = 'data_files/'
16
32
  return self._post(path, files={'file': file_path})
17
33
 
18
34
  def create_data_units(self, data):
35
+ """Create data units to synapse-backend.
36
+
37
+ Args:
38
+ data: The data bindings to upload from create_data_file interface.
39
+ """
19
40
  path = 'data_units/'
20
41
  return self._post(path, data=data)
21
42
 
22
- def import_dataset(self, dataset_id, dataset, project_id=None, batch_size=1000, process_pool=10):
43
+ def upload_dataset(
44
+ self,
45
+ dataset_id: int,
46
+ dataset: Dict,
47
+ project_id: Optional[int] = None,
48
+ batch_size: int = 1000,
49
+ process_pool: int = 10,
50
+ ):
51
+ """Upload dataset to synapse-backend.
52
+
53
+ Args:
54
+ dataset_id: The dataset id to upload the data to.
55
+ dataset: The dataset to upload.
56
+ * structure:
57
+ - files: The files to upload. (key: file name, value: file pathlib object)
58
+ - meta: The meta data to upload.
59
+ project_id: The project id to upload the data to.
60
+ batch_size: The batch size to upload the data.
61
+ process_pool: The process pool to upload the data.
62
+ """
23
63
  # TODO validate dataset with schema
24
64
 
25
65
  params = [(data, dataset_id) for data in dataset]
26
66
 
27
67
  with Pool(processes=process_pool) as pool:
28
- dataset = pool.starmap(self.import_data_file, tqdm(params))
68
+ dataset = pool.starmap(self.upload_data_file, tqdm(params))
29
69
 
30
70
  batches = get_batched_list(dataset, batch_size)
31
71
 
@@ -36,13 +76,25 @@ class DatasetClientMixin(BaseClient):
36
76
  tasks_data = []
37
77
  for data, data_unit in zip(batch, data_units):
38
78
  task_data = {'project': project_id, 'data_unit': data_unit['id']}
39
- # TODO: 추후 import Task data 저장 필요 해당 로직 추가 필요.
79
+ # TODO: Additional logic needed here if task data storage is required during import.
40
80
 
41
81
  tasks_data.append(task_data)
42
82
 
43
83
  self.create_tasks(tasks_data)
44
84
 
45
- def import_data_file(self, data, dataset_id):
85
+ def upload_data_file(self, data: Dict, dataset_id: int) -> Dict:
86
+ """Upload files to synapse-backend.
87
+
88
+ Args:
89
+ data: The data to upload.
90
+ * structure:
91
+ - files: The files to upload. (key: file name, value: file pathlib object)
92
+ - meta: The meta data to upload.
93
+ dataset_id: The dataset id to upload the data to.
94
+
95
+ Returns:
96
+ Dict: The result of the upload.
97
+ """
46
98
  for name, path in data['files'].items():
47
99
  data_file = self.create_data_file(path)
48
100
  data['dataset'] = dataset_id
@@ -47,15 +47,12 @@ class BaseClient:
47
47
  # If files are included in the request, open them as binary files
48
48
  if kwargs.get('files') is not None:
49
49
  for name, file in kwargs['files'].items():
50
- # If file is a path string, bind it as a Path object and open
50
+ # Handle both string and Path object cases
51
51
  if isinstance(file, str):
52
- opened_file = Path(file).open(mode='rb')
53
- kwargs['files'][name] = opened_file
54
- opened_files.append(opened_file)
55
- # If file is a Path object, open it directly
56
- elif isinstance(file, Path):
52
+ file = Path(file)
53
+ if isinstance(file, Path):
57
54
  opened_file = file.open(mode='rb')
58
- kwargs['files'][name] = opened_file
55
+ kwargs['files'][name] = (file.name, opened_file)
59
56
  opened_files.append(opened_file)
60
57
  if 'data' in kwargs:
61
58
  for name, value in kwargs['data'].items():
@@ -67,6 +64,7 @@ class BaseClient:
67
64
  kwargs['data'] = json.dumps(kwargs['data'])
68
65
 
69
66
  try:
67
+ # Send request
70
68
  response = getattr(self.requests_session, method)(url, headers=headers, **kwargs)
71
69
  if not response.ok:
72
70
  raise ClientError(
@@ -17,6 +17,30 @@ from synapse_sdk.utils.pydantic.errors import pydantic_to_drf_error
17
17
 
18
18
 
19
19
  class Action:
20
+ """Base class for all plugin actions.
21
+
22
+ Attrs:
23
+ name (str): The name of the action.
24
+ category (PluginCategory): The category of the action.
25
+ method (RunMethod): The method to run of the action.
26
+ run_class (Run): The class to run the action.
27
+ params_model (BaseModel): The model to validate the params.
28
+ progress_categories (List[str]): The categories to update the progress.
29
+ params (Dict): The params to run the action.
30
+ plugin_config (Dict): The plugin config.
31
+ plugin_release (PluginRelease): The plugin release.
32
+ config (Dict): The action config.
33
+ requirements (List[str]): The requirements to install.
34
+ job_id (str): The job id.
35
+ direct (bool): The flag to run the action directly.
36
+ debug (bool): The flag to run the action in debug mode.
37
+ envs (Dict): The runtime envs.
38
+ run (Run): The run instance.
39
+
40
+ Raises:
41
+ ActionError: If the action fails.
42
+ """
43
+
20
44
  # class 변수
21
45
  name = None
22
46
  category = None
@@ -159,11 +183,19 @@ class Action:
159
183
  return getattr(self, f'start_by_{self.method.value}')()
160
184
 
161
185
  def start(self):
186
+ """Start the action.
187
+
188
+ TODO: Specify the return type of start method for overrided methods.
189
+ """
162
190
  if self.method == RunMethod.JOB:
163
191
  return self.entrypoint(self.run, **self.params)
164
192
  return self.entrypoint(**self.params)
165
193
 
166
194
  def start_by_task(self):
195
+ """Ray Task based execution.
196
+
197
+ * A task method that simply executes the entrypoint without job management functionality.
198
+ """
167
199
  import ray
168
200
  from ray.exceptions import RayTaskError
169
201
 
@@ -195,6 +227,10 @@ class Action:
195
227
  raise ActionError(e.cause)
196
228
 
197
229
  def start_by_job(self):
230
+ """Ray Job based execution.
231
+
232
+ * Executes the entrypoint with Ray job. Ray job manages the entrypoint execution and stores the results.
233
+ """
198
234
  main_options = []
199
235
  options = ['run', '--direct']
200
236
  arguments = [self.name, f'{json.dumps(json.dumps(self.params))}']
@@ -215,6 +251,10 @@ class Action:
215
251
  )
216
252
 
217
253
  def start_by_restapi(self):
254
+ """Ray Serve based execution.
255
+
256
+ * This method executes a Fastapi endpoint defined within the Plugin.
257
+ """
218
258
  path = self.params.pop('path', '')
219
259
  method = self.params.pop('method')
220
260
 
@@ -1,9 +1,8 @@
1
1
  import json
2
+ from pathlib import Path
2
3
 
3
4
  import requests
4
5
 
5
- from synapse_sdk.plugins.categories.export.actions.utils import get_original_file_path
6
-
7
6
 
8
7
  def export(run, input_dataset, path_root, **params):
9
8
  """Executes the export task.
@@ -14,7 +13,7 @@ def export(run, input_dataset, path_root, **params):
14
13
  - data (dict): dm_schema_data information.
15
14
  - files (dict): File information. Includes file URL, original file path, metadata, etc.
16
15
  - id (int): ground_truth ID
17
- path_root : Save path
16
+ path_root : pathlib object, the path to export
18
17
  **params: Additional parameters
19
18
 
20
19
  Returns:
@@ -24,10 +23,11 @@ def export(run, input_dataset, path_root, **params):
24
23
  path_root.mkdir(parents=True, exist_ok=True)
25
24
  run.log_message('Starting export process.')
26
25
 
27
- # results: Contains all information fetched through the list API.
28
- results = params.get('results', [])
26
+ # results contains all information fetched through the list API.
27
+ # example:
28
+ # params.get('results', [])
29
29
 
30
- save_original_file = params.get('save_original_file')
30
+ save_original_file_flag = params.get('save_original_file')
31
31
  errors_json_file_list = []
32
32
  errors_original_file_list = []
33
33
 
@@ -39,21 +39,28 @@ def export(run, input_dataset, path_root, **params):
39
39
  origin_files_output_path = path_root / 'origin_files'
40
40
  origin_files_output_path.mkdir(parents=True, exist_ok=True)
41
41
 
42
- total = len(results)
43
- for no, input_data in enumerate(input_dataset):
42
+ total = params['count']
43
+ # progress init
44
+ run.set_progress(0, total, category='dataset_conversion')
45
+ for no, input_data in enumerate(input_dataset, start=1):
44
46
  run.set_progress(no, total, category='dataset_conversion')
47
+ if no == 1:
48
+ run.log_message('Converting dataset.')
45
49
  preprocessed_data = before_convert(input_data)
46
50
  converted_data = convert_data(preprocessed_data)
47
51
  final_data = after_convert(converted_data)
48
52
 
49
53
  # Call if original file extraction is needed
50
- if save_original_file:
54
+ if save_original_file_flag:
55
+ if no == 1:
56
+ run.log_message('Saving original file.')
51
57
  save_original_file(final_data, origin_files_output_path, errors_original_file_list)
52
58
 
53
59
  # Extract data as JSON files
60
+ if no == 1:
61
+ run.log_message('Saving json file.')
54
62
  save_as_json(final_data, json_output_path, errors_json_file_list)
55
63
 
56
- run.log_message('Saving converted dataset.')
57
64
  run.end_log()
58
65
 
59
66
  # Save error list files
@@ -62,7 +69,7 @@ def export(run, input_dataset, path_root, **params):
62
69
  with (path_root / 'error_file_list.json').open('w', encoding='utf-8') as f:
63
70
  json.dump(export_error_file, f, indent=4, ensure_ascii=False)
64
71
 
65
- return {'export_path': path_root}
72
+ return {'export_path': str(path_root)}
66
73
 
67
74
 
68
75
  def convert_data(data):
@@ -80,19 +87,29 @@ def after_convert(data):
80
87
  return data
81
88
 
82
89
 
83
- def save_original_file(result, base_path, error_file_list):
84
- """Saves the original file.
90
+ def get_original_file_pathlib(files):
91
+ """Retrieve the original file path from the given file information.
85
92
 
86
93
  Args:
87
- result (dict): Result data
88
- base_path (Path): Save path
89
- error_file_list (list): List of error files
94
+ files (dict): A dictionary containing file information, including file URL,
95
+ original file path, metadata, etc.
90
96
 
91
97
  Returns:
92
- base_path (str): Save path
98
+ pathlib.Path: The original file path extracted from the metadata.
99
+ """
100
+ return Path(next(iter(files.values()))['meta']['path_original'])
101
+
102
+
103
+ def save_original_file(result, base_path, error_file_list):
104
+ """Saves the original file.
105
+
106
+ Args:
107
+ result (dict): API response data containing file information.
108
+ base_path (Path): The directory where the file will be saved.
109
+ error_file_list (list): A list to store error files.
93
110
  """
94
111
  file_url = next(iter(result['files'].values()))['url']
95
- file_name = get_original_file_path(result['files']).name
112
+ file_name = get_original_file_pathlib(result['files']).name
96
113
  response = requests.get(file_url)
97
114
  try:
98
115
  with (base_path / file_name).open('wb') as file:
@@ -100,27 +117,20 @@ def save_original_file(result, base_path, error_file_list):
100
117
  except Exception as e:
101
118
  error_file_list.append([file_name, str(e)])
102
119
 
103
- return base_path
104
-
105
120
 
106
121
  def save_as_json(result, base_path, error_file_list):
107
122
  """Saves the data as a JSON file.
108
123
 
109
124
  Args:
110
- result (dict): Result data
111
- base_path (Path): Save path
112
- error_file_list (list): List of error files
113
-
114
- Returns:
115
- base_path (str): Save path
125
+ result (dict): API response data containing file information.
126
+ base_path (Path): The directory where the file will be saved.
127
+ error_file_list (list): A list to store error files.
116
128
  """
117
129
  # Default save file name: original file name
118
- file_name = get_original_file_path(result['files']).stem
130
+ file_name = get_original_file_pathlib(result['files']).stem
119
131
  json_data = result['data']
120
132
  try:
121
133
  with (base_path / f'{file_name}.json').open('w', encoding='utf-8') as f:
122
134
  json.dump(json_data, f, indent=4, ensure_ascii=False)
123
135
  except Exception as e:
124
136
  error_file_list.append([f'{file_name}.json', str(e)])
125
-
126
- return base_path
@@ -0,0 +1,292 @@
1
+ from enum import Enum
2
+ from typing import Annotated, Dict, List
3
+
4
+ from pydantic import AfterValidator, BaseModel, field_validator
5
+ from pydantic_core import PydanticCustomError
6
+
7
+ from synapse_sdk.clients.exceptions import ClientError
8
+ from synapse_sdk.clients.utils import get_batched_list
9
+ from synapse_sdk.i18n import gettext as _
10
+ from synapse_sdk.plugins.categories.base import Action
11
+ from synapse_sdk.plugins.categories.decorators import register_action
12
+ from synapse_sdk.plugins.enums import PluginCategory, RunMethod
13
+ from synapse_sdk.plugins.models import Run
14
+ from synapse_sdk.shared.enums import Context
15
+ from synapse_sdk.utils.pydantic.validators import non_blank
16
+ from synapse_sdk.utils.storage import get_pathlib
17
+
18
+
19
+ class UploadRun(Run):
20
+ pass
21
+
22
+
23
+ class UploadParams(BaseModel):
24
+ """Upload action parameters.
25
+
26
+ Args:
27
+ name (str): The name of the action.
28
+ description (str | None): The description of the action.
29
+ checkpoint (int | None): The checkpoint of the action.
30
+ path (str): The path of the action.
31
+ storage (int): The storage of the action.
32
+ collection (int): The collection of the action.
33
+ project (int | None): The project of the action.
34
+ is_generate_tasks (bool): The flag to generate tasks.
35
+ is_generate_ground_truths (bool): The flag to generate ground truths
36
+ """
37
+
38
+ name: Annotated[str, AfterValidator(non_blank)]
39
+ description: str | None
40
+ path: str
41
+ storage: int
42
+ collection: int
43
+ project: int | None
44
+ is_generate_tasks: bool = False
45
+ is_generate_ground_truths: bool = False
46
+
47
+ @field_validator('storage', mode='before')
48
+ @classmethod
49
+ def check_storage_exists(cls, value: str, info) -> str:
50
+ """Validate synapse-backend storage exists.
51
+
52
+ TODO: Need to define validation method naming convention.
53
+ TODO: Need to make validation method reusable.
54
+ """
55
+ action = info.context['action']
56
+ client = action.client
57
+ try:
58
+ client.get_storage(value)
59
+ except ClientError:
60
+ raise PydanticCustomError('client_error', _('Error occurred while checking storage exists.'))
61
+ return value
62
+
63
+ @field_validator('collection', mode='before')
64
+ @classmethod
65
+ def check_collection_exists(cls, value: str, info) -> str:
66
+ """Validate synapse-backend collection exists."""
67
+ action = info.context['action']
68
+ client = action.client
69
+ try:
70
+ client.get_dataset(value)
71
+ except ClientError:
72
+ raise PydanticCustomError('client_error', _('Error occurred while checking collection exists.'))
73
+ return value
74
+
75
+ @field_validator('project', mode='before')
76
+ @classmethod
77
+ def check_project_exists(cls, value: str, info) -> str:
78
+ """Validate synapse-backend project exists."""
79
+ if not value:
80
+ return value
81
+
82
+ action = info.context['action']
83
+ client = action.client
84
+ try:
85
+ client.get_project(value)
86
+ except ClientError:
87
+ raise PydanticCustomError('client_error', _('Error occurred while checking project exists.'))
88
+ return value
89
+
90
+
91
+ @register_action
92
+ class UploadAction(Action):
93
+ """Upload action class.
94
+
95
+ Attrs:
96
+ name (str): The name of the action.
97
+ category (PluginCategory): The category of the action.
98
+ method (RunMethod): The method to run of the action.
99
+
100
+ Progress Categories:
101
+ analyze_collection: The progress category for the analyze collection process.
102
+ data_file_upload: The progress category for the upload process.
103
+ generate_data_units: The progress category for the generate data units process.
104
+ generate_tasks: The progress category for the generate tasks process.
105
+ generate_ground_truths: The progress category for the generate ground truths process.
106
+ """
107
+
108
+ name = 'upload'
109
+ category = PluginCategory.UPLOAD
110
+ method = RunMethod.JOB
111
+ progress_categories = {
112
+ 'analyze_collection': {
113
+ 'proportion': 5,
114
+ },
115
+ 'upload_data_files': {
116
+ 'proportion': 35,
117
+ },
118
+ 'generate_data_units': {
119
+ 'proportion': 20,
120
+ },
121
+ 'generate_tasks': {
122
+ 'proportion': 20,
123
+ },
124
+ 'generate_ground_truths': {
125
+ 'proportion': 20,
126
+ },
127
+ }
128
+
129
+ def get_uploader(self, path):
130
+ """Get uploader from entrypoint."""
131
+ return self.entrypoint(self.run, path)
132
+
133
+ def start(self) -> Dict:
134
+ """Start upload process.
135
+
136
+ Returns:
137
+ Dict: The result of the upload process.
138
+ """
139
+ # Setup path object with path and storage.
140
+ storage = self.client.get_storage(self.params['storage'])
141
+ pathlib_cwd = get_pathlib(storage, self.params['path'])
142
+
143
+ # Initialize uploader.
144
+ uploader = self.get_uploader(pathlib_cwd)
145
+
146
+ # Analyze Collection file specifications to determine the data structure for upload.
147
+ self.run.set_progress(0, 1, category='analyze_collection')
148
+ file_specification_skeleton = self._analyze_collection()
149
+ self.run.set_progress(1, 1, category='analyze_collection')
150
+
151
+ # Setup result dict.
152
+ result = {}
153
+
154
+ # Organize data according to Collection file specification structure.
155
+ organized_files = uploader.handle_upload_files()
156
+ if not self._validate_organized_files(file_specification_skeleton, organized_files):
157
+ self.run.log_message('Validate organized files failed.')
158
+ return result
159
+
160
+ # Upload files to synapse-backend.
161
+ organized_files_count = len(organized_files)
162
+ if not organized_files_count:
163
+ self.run.log_message('Files not found on the path.', context=Context.WARNING.value)
164
+ return result
165
+
166
+ self.run.set_progress(0, organized_files_count, category='upload_data_files')
167
+ self.run.log_message('Uploading data files...')
168
+ result['uploaded_files'] = self._upload_files(organized_files)
169
+ self.run.set_progress(organized_files_count, organized_files_count, category='upload_data_files')
170
+ self.run.log_message('Upload data files completed.')
171
+
172
+ # Generate data units for the uploaded data.
173
+ upload_result_count = len(result['uploaded_files'])
174
+ if not upload_result_count:
175
+ self.run.log_message('No files were uploaded.', context=Context.WARNING.value)
176
+ return result
177
+
178
+ self.run.set_progress(0, upload_result_count, category='generate_data_units')
179
+ generated_data_units = self._generate_data_units(result['uploaded_files'])
180
+ result['generated_data_units'] = generated_data_units
181
+ self.run.set_progress(upload_result_count, upload_result_count, category='generate_data_units')
182
+
183
+ # Setup task with uploaded synapse-backend data units.
184
+ if not len(generated_data_units):
185
+ self.run.log_message('No data units were generated.', context=Context.WARNING.value)
186
+ return result
187
+
188
+ self.run.set_progress(0, 1, category='generate_tasks')
189
+ if self.config['options']['allow_generate_tasks'] and self.params['is_generate_tasks']:
190
+ self.run.log_message('Generating tasks with data files...')
191
+ self._generate_tasks(generated_data_units)
192
+ self.run.log_message('Generating tasks completed')
193
+ else:
194
+ self.run.log_message('Generating tasks process has passed.')
195
+
196
+ self.run.set_progress(1, 1, category='generate_tasks')
197
+
198
+ # Generate ground truths for the uploaded data.
199
+ # TODO: Need to add ground truths generation logic later.
200
+ self.run.set_progress(0, 1, category='generate_ground_truths')
201
+ if self.config['options']['allow_generate_ground_truths'] and self.params['is_generate_ground_truths']:
202
+ self.run.log_message('Generating ground truths...')
203
+ self._generate_ground_truths()
204
+ self.run.log_message('Generating ground truths completed')
205
+ else:
206
+ self.run.log_message('Generating ground truths process has passed.')
207
+ self.run.set_progress(1, 1, category='generate_ground_truths')
208
+
209
+ return result
210
+
211
+ def _analyze_collection(self) -> Dict:
212
+ """Analyze Synapse Collection Specifications.
213
+
214
+ Returns:
215
+ Dict: The file specifications of the collection.
216
+ """
217
+ client = self.run.client
218
+ collection_id = self.params['collection']
219
+ collection = client.get_dataset(collection_id)
220
+ return collection['file_specifications']
221
+
222
+ def _validate_organized_files(self, file_specification_skeleton: Dict, organized_files: List) -> bool:
223
+ """Validate organized files from Uploader."""
224
+ return True
225
+
226
+ def _upload_files(self, organized_files) -> List:
227
+ """Upload files to synapse-backend.
228
+
229
+ Returns:
230
+ Dict: The result of the upload.
231
+ """
232
+ client = self.run.client
233
+ collection_id = self.params['collection']
234
+ upload_result = []
235
+ organized_files_count = len(organized_files)
236
+ current_progress = 0
237
+ for organized_file in organized_files:
238
+ upload_result.append(client.upload_data_file(organized_file, collection_id))
239
+ self.run.set_progress(current_progress, organized_files_count, category='upload_data_files')
240
+ current_progress += 1
241
+ return upload_result
242
+
243
+ def _generate_data_units(self, uploaded_files: List) -> List:
244
+ """Generate data units for the uploaded data.
245
+
246
+ TODO: make batch size configurable.
247
+
248
+ Returns:
249
+ Dict: The result of the generate data units process.
250
+ """
251
+ client = self.run.client
252
+
253
+ generation_result = []
254
+ current_progress = 0
255
+ batches = get_batched_list(uploaded_files, 100)
256
+ batches_count = len(batches)
257
+ for batch in batches:
258
+ generation_result.append(client.create_data_units(batch))
259
+ self.run.set_progress(current_progress, batches_count, category='generate_data_units')
260
+ current_progress += 1
261
+ return generation_result
262
+
263
+ def _generate_tasks(self, generated_data_units: List):
264
+ """Setup task with uploaded synapse-backend data units.
265
+
266
+ TODO: make batch size configurable.
267
+ """
268
+
269
+ # Prepare batches for processing
270
+ client = self.run.client
271
+ project_id = self.params['project']
272
+ current_progress = 0
273
+
274
+ # Generate tasks
275
+ generated_data_units_count = len(generated_data_units)
276
+ for data_units in generated_data_units:
277
+ tasks_data = []
278
+ for data_unit in data_units:
279
+ task_data = {'project': project_id, 'data_unit': data_unit['id']}
280
+ tasks_data.append(task_data)
281
+
282
+ if tasks_data:
283
+ client.create_tasks(tasks_data)
284
+
285
+ self.run.set_progress(current_progress, generated_data_units_count, category='generate_tasks')
286
+ current_progress += 1
287
+
288
+ def _generate_ground_truths(self):
289
+ """Generate ground truths for the uploaded data.
290
+
291
+ TODO: Need to add ground truths generation logic later.
292
+ """
@@ -0,0 +1,6 @@
1
+ actions:
2
+ upload:
3
+ entrypoint: plugin.upload.Uploader
4
+ options:
5
+ allow_generate_tasks: false
6
+ allow_generate_ground_truths: false
@@ -0,0 +1,44 @@
1
+ from pathlib import Path
2
+ from typing import List
3
+
4
+
5
+ class Uploader:
6
+ """Plugin upload action class.
7
+
8
+ * Organize, upload, setup task, generate ground truths for the uploaded data.
9
+ """
10
+
11
+ def __init__(self, run, path: Path, *args, **kwargs):
12
+ """Initialize the plugin upload action class.
13
+
14
+ Args:
15
+ run: Plugin run object.
16
+ path: pathlib object by upload target destination path.
17
+ """
18
+ self.run = run
19
+ self.path = path
20
+
21
+ def handle_upload_files(self) -> List:
22
+ """Handle upload files.
23
+
24
+ * Organize data according to collection file specification structure.
25
+ * Structure files according to the file specification of the target collection.
26
+
27
+ Returns:
28
+ List: List of dictionaries containing 'files' and 'meta'.
29
+
30
+ Examples:
31
+ [
32
+ {
33
+ "files": {
34
+ 'image_1': image_1_pathlib_object,
35
+ 'image_2': image_2_pathlib_object,
36
+ 'meta_1': meta_1_pathlib_object,
37
+ },
38
+ "meta": {
39
+ "key": "value"
40
+ }
41
+ }
42
+ ]
43
+ """
44
+ return []
@@ -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'
@@ -87,6 +87,15 @@ class PluginRelease:
87
87
 
88
88
 
89
89
  class Run:
90
+ """Run class for manage plugin run istance.
91
+
92
+ Attrs:
93
+ job_id: plugin run job id
94
+ context: plugin run context
95
+ client: backend client for communicate with backend
96
+ logger: logger for log plugin run events
97
+ """
98
+
90
99
  logger = None
91
100
  job_id = None
92
101
  context = None
@@ -111,6 +120,13 @@ class Run:
111
120
  self.logger = ConsoleLogger(**kwargs)
112
121
 
113
122
  def set_progress(self, current, total, category=''):
123
+ """Set progress for plugin run.
124
+
125
+ Args:
126
+ current: current progress value
127
+ total: total progress value
128
+ category: progress category
129
+ """
114
130
  self.logger.set_progress(current, total, category)
115
131
 
116
132
  def log(self, event, data, file=None):
@@ -1,3 +1,4 @@
1
+ from pathlib import Path
1
2
  from urllib.parse import urlparse
2
3
 
3
4
  from synapse_sdk.i18n import gettext as _
@@ -15,6 +16,15 @@ def get_storage(connection_param: str | dict):
15
16
  return STORAGE_PROVIDERS[storage_scheme](connection_param)
16
17
 
17
18
 
18
- def get_pathlib(storage_config, path_root):
19
+ def get_pathlib(storage_config: str | dict, path_root: str) -> Path:
20
+ """Get pathlib object with synapse-backend storage config.
21
+
22
+ Args:
23
+ storage_config (str | dict): The storage config by synapse-backend storage api.
24
+ path_root (str): The path root.
25
+
26
+ Returns:
27
+ pathlib.Path: The pathlib object.
28
+ """
19
29
  storage_class = get_storage(storage_config)
20
30
  return storage_class.get_pathlib(path_root)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: synapse-sdk
3
- Version: 1.0.0a31
3
+ Version: 1.0.0a32
4
4
  Summary: synapse sdk
5
5
  Author-email: datamaker <developer@datamaker.io>
6
6
  License: MIT
@@ -21,6 +21,7 @@ Requires-Dist: universal-pathlib
21
21
  Requires-Dist: fsspec[gcs,s3,sftp]
22
22
  Provides-Extra: all
23
23
  Requires-Dist: ray[all]; extra == "all"
24
+ Dynamic: license-file
24
25
 
25
26
  This is the SDK to develop synapse plugins
26
27
 
@@ -21,7 +21,7 @@ synapse_sdk/cli/plugin/create.py,sha256=HpYTpohV1NbSrULaVUlc4jWLWznPrx7glgydTM3s
21
21
  synapse_sdk/cli/plugin/publish.py,sha256=sIl1wiuSC3lAUpE3rOF4UDKDy2G5EVLlelMjk2aT05g,1221
22
22
  synapse_sdk/cli/plugin/run.py,sha256=xz5LRm3zh8Y9DMjw5FFRFVRWSCWtYfZJskfCmrPikaQ,2598
23
23
  synapse_sdk/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- synapse_sdk/clients/base.py,sha256=T-PR20BH0oPIelnR-owSGsjY_wG8yIie2opFjW_bskw,4589
24
+ synapse_sdk/clients/base.py,sha256=m3zEX1wv_sSxa-jBCnE-9Q5-f71o1TdODkWHRE9Vo-E,4403
25
25
  synapse_sdk/clients/exceptions.py,sha256=ylv7x10eOp4aA3a48jwonnvqvkiYwzJYXjkVkRTAjwk,220
26
26
  synapse_sdk/clients/utils.py,sha256=8pPJTdzHiRPSbZMoQYHAgR2BAMO6u_R_jMV6a2p34iQ,392
27
27
  synapse_sdk/clients/agent/__init__.py,sha256=Pz8_iTbIbnb7ywGJ3feqoZVmO2I3mEbwpWsISIxh0BU,1968
@@ -31,20 +31,20 @@ synapse_sdk/clients/agent/service.py,sha256=s7KuPK_DB1nr2VHrigttV1WyFonaGHNrPvU8
31
31
  synapse_sdk/clients/backend/__init__.py,sha256=aozhPhvRTPHz1P90wxEay07B-Ct4vj_yTw5H9_PJEBE,1105
32
32
  synapse_sdk/clients/backend/annotation.py,sha256=eZc5EidgR_RfMGwvv1r1_mLkPdRd8e52c4zuuMjMX34,979
33
33
  synapse_sdk/clients/backend/core.py,sha256=5XAOdo6JZ0drfk-FMPJ96SeTd9oja-VnTwzGXdvK7Bg,1027
34
- synapse_sdk/clients/backend/dataset.py,sha256=w7izflbTjHKysiDl7ia7MAO391_dzN2ofK40A7QwtBQ,1721
34
+ synapse_sdk/clients/backend/dataset.py,sha256=11R5LuTva9jgXatxQAlKy7UEJmwIWzTsLVdFf3MZ9F8,3400
35
35
  synapse_sdk/clients/backend/integration.py,sha256=MCfeChpLySqlVRc1aZxCfDpQiRH--pfevkCdJDCNZEQ,2506
36
36
  synapse_sdk/clients/backend/ml.py,sha256=JoPH9Ly2E3HJ7S5mdGLtcGq7ruQVVrYfWArogwZLlms,1193
37
37
  synapse_sdk/clients/ray/__init__.py,sha256=9ZSPXVVxlJ8Wp8ku7l021ENtPjVrGgQDgqifkkVAXgM,187
38
38
  synapse_sdk/clients/ray/core.py,sha256=a4wyCocAma2HAm-BHlbZnoVbpfdR-Aad2FM0z6vPFvw,731
39
39
  synapse_sdk/clients/ray/serve.py,sha256=rbCpXZYWf0oP8XJ9faa9QFNPYU7h8dltIG8xn9ZconY,907
40
40
  synapse_sdk/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- synapse_sdk/plugins/enums.py,sha256=s59P6Oz2WAK9IX-kLVhNOvNKYJifKlWBhPpZbc9-ttE,486
41
+ synapse_sdk/plugins/enums.py,sha256=ibixwqA3sCNSriG1jAtL54JQc_Zwo3MufwYUqGhVncc,523
42
42
  synapse_sdk/plugins/exceptions.py,sha256=Qs7qODp_RRLO9y2otU2T4ryj5LFwIZODvSIXkAh91u0,691
43
- synapse_sdk/plugins/models.py,sha256=7En1biVK_7kR8aI-3I-kJ-lXbveRRobppMGOeFd3ZpU,3709
43
+ synapse_sdk/plugins/models.py,sha256=njTQIT-c2d7TsqAN__q1aoYm8hLEPC7ludTj665iN-4,4148
44
44
  synapse_sdk/plugins/upload.py,sha256=VJOotYMayylOH0lNoAGeGHRkLdhP7jnC_A0rFQMvQpQ,3228
45
45
  synapse_sdk/plugins/utils.py,sha256=4_K6jIl0WrsXOEhFp94faMOriSsddOhIiaXcawYYUUA,3300
46
46
  synapse_sdk/plugins/categories/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- synapse_sdk/plugins/categories/base.py,sha256=_bNB_TeCN1hIKeDLxb3Cj9UAGO8BLju97__pLN625A8,8500
47
+ synapse_sdk/plugins/categories/base.py,sha256=cfk7mgidCBnldZ8nCo_Nq_qzP9C1LJYgSXWtYT6hJDw,10046
48
48
  synapse_sdk/plugins/categories/decorators.py,sha256=Gw6T-UHwpCKrSt596X-g2sZbY_Z1zbbogowClj7Pr5Q,518
49
49
  synapse_sdk/plugins/categories/registry.py,sha256=KdQR8SUlLT-3kgYzDNWawS1uJnAhrcw2j4zFaTpilRs,636
50
50
  synapse_sdk/plugins/categories/templates.py,sha256=FF5FerhkZMeW1YcKLY5cylC0SkWSYdJODA_Qcm4OGYQ,887
@@ -57,13 +57,9 @@ synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py,sh
57
57
  synapse_sdk/plugins/categories/export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  synapse_sdk/plugins/categories/export/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  synapse_sdk/plugins/categories/export/actions/export.py,sha256=45hnvM3myFgRJyGpC_jpDu_VaDC-iaNCqy4DcH24eDU,2926
60
- synapse_sdk/plugins/categories/export/actions/utils.py,sha256=ixilT_VtgwtbX9kbBTrIybbAyW1kPLhNEkOHABKmZ88,131
61
60
  synapse_sdk/plugins/categories/export/templates/config.yaml,sha256=N7YmnFROb3s3M35SA9nmabyzoSb5O2t2TRPicwFNN2o,56
62
61
  synapse_sdk/plugins/categories/export/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
- synapse_sdk/plugins/categories/export/templates/plugin/export.py,sha256=422HPdxCTor0PlOwSw7J7-HyD3i0kq_1D_VjKegkr8c,3904
64
- synapse_sdk/plugins/categories/import/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
- synapse_sdk/plugins/categories/import/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
- synapse_sdk/plugins/categories/import/actions/import.py,sha256=URn6TOp081odMT5D4NlZ2XEcyKelJx8fxzdoKSkXSAI,320
62
+ synapse_sdk/plugins/categories/export/templates/plugin/export.py,sha256=cSUhgY5FDqMWVj4XlrOfzDN8q-NxOAvB3BTMXdqd-Eg,4604
67
63
  synapse_sdk/plugins/categories/neural_net/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
64
  synapse_sdk/plugins/categories/neural_net/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
65
  synapse_sdk/plugins/categories/neural_net/actions/deployment.py,sha256=Wmi7in_Mgizt1d5XcDR080h1CIMWKh2_mjub9N380qA,1917
@@ -95,6 +91,12 @@ synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py,sha256=fHiqA8ntm
95
91
  synapse_sdk/plugins/categories/smart_tool/templates/config.yaml,sha256=7bvb4M1PLaoTOVDYF05L7yb8ix4rZOrAnEuZ-thMsyo,206
96
92
  synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
93
  synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py,sha256=eevNg0nOcYFR4z_L_R-sCvVOYoLWSAH1jwDkAf3YCjY,320
94
+ synapse_sdk/plugins/categories/upload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
+ synapse_sdk/plugins/categories/upload/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
96
+ synapse_sdk/plugins/categories/upload/actions/upload.py,sha256=V7gArcj7FZO8AT_ihUGodrc_7q49Q7j9BKc9DvoYOYQ,11178
97
+ synapse_sdk/plugins/categories/upload/templates/config.yaml,sha256=0PhB2uD-9ufavZs7EiF6xj4aBgZuif9mFFGGfzG7HuY,147
98
+ synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
+ synapse_sdk/plugins/categories/upload/templates/plugin/upload.py,sha256=dnK8gy33GjG5ettayawDJv1gM3xCm1K6lM-PfeeTjQw,1163
98
100
  synapse_sdk/plugins/templates/cookiecutter.json,sha256=NxOWk9A_v1pO0Ny4IYT9Cj5iiJ16--cIQrGC67QdR0I,396
99
101
  synapse_sdk/plugins/templates/hooks/post_gen_project.py,sha256=jqlYkY1O2TxIR-Vh3gnwILYy8k-D39Xx66d2KNQVMCs,147
100
102
  synapse_sdk/plugins/templates/hooks/pre_prompt.py,sha256=aOAMM623s0sKFGjTZaotAOYFvsNMxeii4tPyhOAFKVE,539
@@ -117,15 +119,15 @@ synapse_sdk/utils/pydantic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
117
119
  synapse_sdk/utils/pydantic/config.py,sha256=1vYOcUI35GslfD1rrqhFkNXXJOXt4IDqOPSx9VWGfNE,123
118
120
  synapse_sdk/utils/pydantic/errors.py,sha256=0v0T12eQBr1KrFiEOBu6KMaPK4aPEGEC6etPJGoR5b4,1061
119
121
  synapse_sdk/utils/pydantic/validators.py,sha256=G47P8ObPhsePmd_QZDK8EdPnik2CbaYzr_N4Z6En8dc,193
120
- synapse_sdk/utils/storage/__init__.py,sha256=oDRyXwwp9xxdCkNtHzDF2-cUwv6EMhKt4rWYbyAKaEk,689
122
+ synapse_sdk/utils/storage/__init__.py,sha256=AOPEo1_4Tssr3X0_pu5cbmJP_V5ywx9qSocIzZTc2kM,1005
121
123
  synapse_sdk/utils/storage/registry.py,sha256=WaSN9SJR7s9sZgmTVl5k4mLFz-9R6X4ii82wefxs95A,335
122
124
  synapse_sdk/utils/storage/providers/__init__.py,sha256=NM9yRIWcPkH53DeNHgIhH9zaDFK8SJv0KptP1Afulyw,1125
123
125
  synapse_sdk/utils/storage/providers/gcp.py,sha256=i2BQCu1Kej1If9SuNr2_lEyTcr5M_ncGITZrL0u5wEA,363
124
126
  synapse_sdk/utils/storage/providers/s3.py,sha256=r94aUGVNf8yxihU0lN62yaXdxSS7P783_sfD-hCiK40,1191
125
127
  synapse_sdk/utils/storage/providers/sftp.py,sha256=TUQXkKJf0-fh8NhGC_1zzqI4autFNHZVCqngwkQ1aD4,523
126
- synapse_sdk-1.0.0a31.dist-info/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
127
- synapse_sdk-1.0.0a31.dist-info/METADATA,sha256=Fy5pk7TcwtwzXs8dCrEAF4yMyUa_eTLmsYvX3c90Q-o,1138
128
- synapse_sdk-1.0.0a31.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
129
- synapse_sdk-1.0.0a31.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
130
- synapse_sdk-1.0.0a31.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
131
- synapse_sdk-1.0.0a31.dist-info/RECORD,,
128
+ synapse_sdk-1.0.0a32.dist-info/licenses/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
129
+ synapse_sdk-1.0.0a32.dist-info/METADATA,sha256=jJsMUwX1BNln7bpCx7CGxkQQuE6UCCvSk4P2i9as7x4,1160
130
+ synapse_sdk-1.0.0a32.dist-info/WHEEL,sha256=tTnHoFhvKQHCh4jz3yCn0WPTYIy7wXx3CJtJ7SJGV7c,91
131
+ synapse_sdk-1.0.0a32.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
132
+ synapse_sdk-1.0.0a32.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
133
+ synapse_sdk-1.0.0a32.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.0.0)
2
+ Generator: setuptools (77.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,5 +0,0 @@
1
- from pathlib import Path
2
-
3
-
4
- def get_original_file_path(files):
5
- return Path(next(iter(files.values()))['meta']['path_original'])
@@ -1,10 +0,0 @@
1
- from synapse_sdk.plugins.categories.base import Action
2
- from synapse_sdk.plugins.categories.decorators import register_action
3
- from synapse_sdk.plugins.enums import PluginCategory, RunMethod
4
-
5
-
6
- @register_action
7
- class ImportAction(Action):
8
- name = 'import'
9
- category = PluginCategory.IMPORT
10
- method = RunMethod.JOB