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

@@ -14,11 +14,20 @@ from synapse_sdk.plugins.categories.decorators import register_action
14
14
  from synapse_sdk.plugins.categories.export.enums import ExportStatus
15
15
  from synapse_sdk.plugins.enums import PluginCategory, RunMethod
16
16
  from synapse_sdk.plugins.models import Run
17
+ from synapse_sdk.shared.enums import Context
17
18
  from synapse_sdk.utils.pydantic.validators import non_blank
18
19
  from synapse_sdk.utils.storage import get_pathlib
19
20
 
20
21
 
21
22
  class ExportRun(Run):
23
+ class ExportEventLog(BaseModel):
24
+ """Export event log model."""
25
+
26
+ target_id: int
27
+ info: str | None = None
28
+ status: Context
29
+ created: str
30
+
22
31
  class DataFileLog(BaseModel):
23
32
  """Data file log model."""
24
33
 
@@ -35,6 +44,13 @@ class ExportRun(Run):
35
44
  failed: int
36
45
  success: int
37
46
 
47
+ LOG_MESSAGES = {
48
+ 'NULL_DATA_DETECTED': {
49
+ 'message': 'Data is null for export item',
50
+ 'level': Context.WARNING,
51
+ },
52
+ }
53
+
38
54
  def log_file(
39
55
  self, log_type: str, target_id: int, data_file_info: dict, status: ExportStatus, error: str | None = None
40
56
  ):
@@ -59,6 +75,35 @@ class ExportRun(Run):
59
75
  ).model_dump(),
60
76
  )
61
77
 
78
+ def log_export_event(self, code: str, target_id: int, *args, level: Context | None = None):
79
+ """Log export event using predefined code.
80
+
81
+ Args:
82
+ code (str): The log message code.
83
+ target_id (int): The ID of the export target.
84
+ *args: Arguments to format the message.
85
+ level (Context | None): Optional context level override.
86
+ """
87
+ if code not in self.LOG_MESSAGES:
88
+ now = datetime.now().isoformat()
89
+ self.log(
90
+ 'export_event',
91
+ self.ExportEventLog(
92
+ target_id=target_id, info=f'Unknown log code: {code}', status=Context.DANGER, created=now
93
+ ).model_dump(),
94
+ )
95
+ return
96
+
97
+ log_config = self.LOG_MESSAGES[code]
98
+ message = log_config['message'].format(*args) if args else log_config['message']
99
+ log_level = level or log_config['level'] or Context.INFO
100
+
101
+ now = datetime.now().isoformat()
102
+ self.log(
103
+ 'export_event',
104
+ self.ExportEventLog(info=message, status=log_level, target_id=target_id, created=now).model_dump(),
105
+ )
106
+
62
107
  def log_metrics(self, record: MetricsRecord, category: str):
63
108
  """Log export metrics.
64
109
 
@@ -67,7 +112,7 @@ class ExportRun(Run):
67
112
  category (str): The category of the metrics.
68
113
  """
69
114
  record = self.MetricsRecord.model_validate(record)
70
- self.set_metrics(value=record.dict(), category=category)
115
+ self.set_metrics(value=record.model_dump(), category=category)
71
116
 
72
117
  def export_log_json_file(
73
118
  self,
@@ -185,7 +230,7 @@ class GroundTruthExportTargetHandler(ExportTargetHandler):
185
230
  yield {
186
231
  'data': result['data'],
187
232
  'files': result['data_unit']['files'][files_key],
188
- 'id': result['ground_truth'],
233
+ 'id': result['id'],
189
234
  }
190
235
 
191
236
 
@@ -1,4 +1,5 @@
1
1
  import json
2
+ from itertools import tee
2
3
  from pathlib import Path
3
4
 
4
5
  import requests
@@ -49,12 +50,14 @@ def export(run, export_items, path_root, **params):
49
50
  origin_files_output_path = unique_export_path / 'origin_files'
50
51
  origin_files_output_path.mkdir(parents=True, exist_ok=True)
51
52
 
52
- total = params['count']
53
+ export_items_count, export_items_process = tee(export_items)
54
+ total = sum(1 for _ in export_items_count)
55
+
53
56
  original_file_metrics_record = run.MetricsRecord(stand_by=total, success=0, failed=0)
54
57
  data_file_metrics_record = run.MetricsRecord(stand_by=total, success=0, failed=0)
55
58
  # progress init
56
59
  run.set_progress(0, total, category='dataset_conversion')
57
- for no, export_item in enumerate(export_items, start=1):
60
+ for no, export_item in enumerate(export_items_process, start=1):
58
61
  run.set_progress(no, total, category='dataset_conversion')
59
62
  if no == 1:
60
63
  run.log_message('Converting dataset.')
@@ -170,6 +173,16 @@ def save_as_json(run, result, base_path, error_file_list):
170
173
  file_name = Path(get_original_file_name(result['files'])).stem
171
174
  json_data = result['data']
172
175
  file_info = {'file_name': f'{file_name}.json'}
176
+
177
+ if json_data is None:
178
+ error_msg = 'data is Null'
179
+ error_file_list.append([f'{file_name}.json', error_msg])
180
+ status = ExportStatus.FAILED
181
+ run.log_export_event('NULL_DATA_DETECTED', result['id'])
182
+ run.export_log_json_file(result['id'], file_info, status, error_msg)
183
+
184
+ return status
185
+
173
186
  error_msg = ''
174
187
  try:
175
188
  with (base_path / f'{file_name}.json').open('w', encoding='utf-8') as f:
@@ -404,6 +404,7 @@ class UploadParams(BaseModel):
404
404
  project (int | None): The project of the action.
405
405
  excel_metadata_path (str | None): Path to excel file containing metadata.
406
406
  Defaults to 'meta.xlsx' or 'meta.xls' in the path directory.
407
+ is_recursive (bool): Enable recursive file discovery in subdirectories. Defaults to False.
407
408
  """
408
409
 
409
410
  name: Annotated[str, AfterValidator(non_blank)]
@@ -413,6 +414,7 @@ class UploadParams(BaseModel):
413
414
  collection: int
414
415
  project: int | None
415
416
  excel_metadata_path: str | None = None
417
+ is_recursive: bool = False
416
418
 
417
419
  @field_validator('storage', mode='before')
418
420
  @classmethod
@@ -581,6 +583,14 @@ class UploadAction(Action):
581
583
  """Get uploader from entrypoint."""
582
584
  return self.entrypoint(self.run, path, file_specification, organized_files)
583
585
 
586
+ def _discover_files_recursive(self, dir_path: Path) -> List[Path]:
587
+ """Discover files recursively in a directory."""
588
+ return [file_path for file_path in dir_path.rglob('*') if file_path.is_file()]
589
+
590
+ def _discover_files_non_recursive(self, dir_path: Path) -> List[Path]:
591
+ """Discover files in a directory (non-recursive)."""
592
+ return [file_path for file_path in dir_path.glob('*') if file_path.is_file()]
593
+
584
594
  def _validate_excel_security(self, excel_path: Path) -> None:
585
595
  """Validate Excel file security constraints.
586
596
 
@@ -1028,15 +1038,6 @@ class UploadAction(Action):
1028
1038
  ) -> List[Dict[str, Any]]:
1029
1039
  """Organize files according to the file specification.
1030
1040
 
1031
- This method handles type-based directory structure where files are organized in
1032
- directories named after file types (e.g., 'image_1/' directory contains image files
1033
- like '1.jpg', '2.jpg'). For each dataset ID found in the primary directory, it attempts
1034
- to find corresponding files in all type directories.
1035
-
1036
- TODO: Add Logic to handle file specific name patterns and extensions.
1037
- (e.g. pcd:S_DCH_230725_0156_LR_037.pcd, image_1:S_DCH_230725_0156_FC_037,
1038
- image_2:S_DCH_230725_0156_LF_037.jpg)
1039
-
1040
1041
  Args:
1041
1042
  directory (Path): Root directory containing files to organize.
1042
1043
  file_specification (List[Dict[str, Any]]): File specification list with metadata.
@@ -1072,23 +1073,32 @@ class UploadAction(Action):
1072
1073
  dataset_files = {}
1073
1074
  required_specs = [spec['name'] for spec in file_specification if spec.get('is_required', False)]
1074
1075
 
1076
+ # Get recursive setting from params
1077
+ is_recursive = self.params.get('is_recursive', False)
1078
+
1075
1079
  # Process all files from all type directories
1076
1080
  for spec_name, dir_path in type_dirs.items():
1077
- for file_path in dir_path.glob('*'):
1078
- if file_path.is_file():
1079
- file_name = file_path.stem
1080
-
1081
- # Initialize dataset entry if it doesn't exist
1082
- if file_name not in dataset_files:
1083
- dataset_files[file_name] = {}
1084
-
1085
- # Map this file to its specification (handle duplicates)
1086
- if spec_name not in dataset_files[file_name]:
1081
+ # Use appropriate method based on recursive setting
1082
+ if is_recursive:
1083
+ files_list = self._discover_files_recursive(dir_path)
1084
+ else:
1085
+ files_list = self._discover_files_non_recursive(dir_path)
1086
+
1087
+ for file_path in files_list:
1088
+ # Always use filename only for matching
1089
+ file_name = file_path.stem
1090
+
1091
+ # Initialize dataset entry if it doesn't exist
1092
+ if file_name not in dataset_files:
1093
+ dataset_files[file_name] = {}
1094
+
1095
+ # Map this file to its specification (handle duplicates)
1096
+ if spec_name not in dataset_files[file_name]:
1097
+ dataset_files[file_name][spec_name] = file_path
1098
+ else:
1099
+ existing_file = dataset_files[file_name][spec_name]
1100
+ if file_path.stat().st_mtime > existing_file.stat().st_mtime:
1087
1101
  dataset_files[file_name][spec_name] = file_path
1088
- else:
1089
- existing_file = dataset_files[file_name][spec_name]
1090
- if file_path.stat().st_mtime > existing_file.stat().st_mtime:
1091
- dataset_files[file_name][spec_name] = file_path
1092
1102
 
1093
1103
  if not dataset_files:
1094
1104
  self.run.log_message_with_code('NO_FILES_FOUND_WARNING')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synapse-sdk
3
- Version: 1.0.0b12
3
+ Version: 1.0.0b13
4
4
  Summary: synapse sdk
5
5
  Author-email: datamaker <developer@datamaker.io>
6
6
  License: MIT
@@ -113,10 +113,10 @@ synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py,sh
113
113
  synapse_sdk/plugins/categories/export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
114
114
  synapse_sdk/plugins/categories/export/enums.py,sha256=gtyngvQ1DKkos9iKGcbecwTVQQ6sDwbrBPSGPNb5Am0,127
115
115
  synapse_sdk/plugins/categories/export/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
- synapse_sdk/plugins/categories/export/actions/export.py,sha256=ieR9ZWOr59X2CAEjQ30PzsWmXaC93HKeFPhYqKIW2fM,11932
116
+ synapse_sdk/plugins/categories/export/actions/export.py,sha256=5pmeexWbRpKlt4wJKGSaSzRibumenZY1u8uUncS_WXI,13477
117
117
  synapse_sdk/plugins/categories/export/templates/config.yaml,sha256=N7YmnFROb3s3M35SA9nmabyzoSb5O2t2TRPicwFNN2o,56
118
118
  synapse_sdk/plugins/categories/export/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
- synapse_sdk/plugins/categories/export/templates/plugin/export.py,sha256=GDb6Ucodsr5aBPMU4alr68-DyFoLR5TyhC_MCaJrkF0,6411
119
+ synapse_sdk/plugins/categories/export/templates/plugin/export.py,sha256=DgoTtJF4Tq5nLUMpq0hF0XSbhXGjDb9XqoUUcrXBbnQ,6860
120
120
  synapse_sdk/plugins/categories/neural_net/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
121
  synapse_sdk/plugins/categories/neural_net/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
122
  synapse_sdk/plugins/categories/neural_net/actions/deployment.py,sha256=njhDp05KXbNB9VCa46VgWa8-ftc_f9HcAVpUr0836AM,1838
@@ -154,7 +154,7 @@ synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py,sha256=47
154
154
  synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py,sha256=eevNg0nOcYFR4z_L_R-sCvVOYoLWSAH1jwDkAf3YCjY,320
155
155
  synapse_sdk/plugins/categories/upload/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
156
156
  synapse_sdk/plugins/categories/upload/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
157
- synapse_sdk/plugins/categories/upload/actions/upload.py,sha256=XIKbXqzarSy6Rt41g03pkq0loAkKWBTX4yv47E25_rs,45203
157
+ synapse_sdk/plugins/categories/upload/actions/upload.py,sha256=Ry83bR0_NKr7Ir3wj_aMkr7viype_FYJjGJU_0KLfbI,45554
158
158
  synapse_sdk/plugins/categories/upload/templates/config.yaml,sha256=6_dRa0_J2aS8NSUfO4MKbPxZcdPS2FpJzzp51edYAZc,281
159
159
  synapse_sdk/plugins/categories/upload/templates/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
160
160
  synapse_sdk/plugins/categories/upload/templates/plugin/upload.py,sha256=IZU4sdSMSLKPCtlNqF7DP2howTdYR6hr74HCUZsGdPk,1559
@@ -211,9 +211,9 @@ synapse_sdk/utils/storage/providers/gcp.py,sha256=i2BQCu1Kej1If9SuNr2_lEyTcr5M_n
211
211
  synapse_sdk/utils/storage/providers/http.py,sha256=2DhIulND47JOnS5ZY7MZUex7Su3peAPksGo1Wwg07L4,5828
212
212
  synapse_sdk/utils/storage/providers/s3.py,sha256=ZmqekAvIgcQBdRU-QVJYv1Rlp6VHfXwtbtjTSphua94,2573
213
213
  synapse_sdk/utils/storage/providers/sftp.py,sha256=_8s9hf0JXIO21gvm-JVS00FbLsbtvly4c-ETLRax68A,1426
214
- synapse_sdk-1.0.0b12.dist-info/licenses/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
215
- synapse_sdk-1.0.0b12.dist-info/METADATA,sha256=pecWR-58KXEkIky8tZLrFknRIX3YKTDAVwYPzONzz38,3745
216
- synapse_sdk-1.0.0b12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
217
- synapse_sdk-1.0.0b12.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
218
- synapse_sdk-1.0.0b12.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
219
- synapse_sdk-1.0.0b12.dist-info/RECORD,,
214
+ synapse_sdk-1.0.0b13.dist-info/licenses/LICENSE,sha256=bKzmC5YAg4V1Fhl8OO_tqY8j62hgdncAkN7VrdjmrGk,1101
215
+ synapse_sdk-1.0.0b13.dist-info/METADATA,sha256=a9HVEH9V67PGdiwKa5c-dPqE9y81AtvwHnk6A0O795Q,3745
216
+ synapse_sdk-1.0.0b13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
217
+ synapse_sdk-1.0.0b13.dist-info/entry_points.txt,sha256=VNptJoGoNJI8yLXfBmhgUefMsmGI0m3-0YoMvrOgbxo,48
218
+ synapse_sdk-1.0.0b13.dist-info/top_level.txt,sha256=ytgJMRK1slVOKUpgcw3LEyHHP7S34J6n_gJzdkcSsw8,12
219
+ synapse_sdk-1.0.0b13.dist-info/RECORD,,