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
@@ -1,240 +1,802 @@
1
+ ---
2
+ id: upload-plugins
3
+ title: 업로드 플러그인
4
+ sidebar_position: 3
5
+ ---
6
+
1
7
  # 업로드 플러그인
2
8
 
3
- 업로드 플러그인은 다양한 데이터 소스에서 Synapse 데이터셋으로 파일을 업로드하기 위한 강력하고 유연한 시스템입니다. 로컬 파일, Excel 스프레드시트, 이미지 컬렉션을 위한 포괄적인 업로드 기능을 제공합니다.
9
+ 업로드 플러그인은 포괄적인 메타데이터 지원, 보안 검증 체계적인 데이터 단위 생성을 통해 파일을 Synapse 플랫폼으로 처리하기 위한 파일 업로드 데이터 수집 작업을 제공합니다.
4
10
 
5
11
  ## 개요
6
12
 
7
- 업로드 플러그인은 메타데이터 추출, 보안 검증, 진행률 추적을 포함하여 파일 업로드 과정의 모든 측면을 처리합니다. 대용량 파일 배치와 복잡한 디렉터리 구조를 효율적으로 관리하도록 설계되었습니다.
13
+ **사용 가능한 액션:**
14
+
15
+ - `upload` - 선택적 Excel 메타데이터 처리를 통한 파일 및 디렉토리 스토리지 업로드
16
+
17
+ **사용 사례:**
18
+
19
+ - 메타데이터 주석을 포함한 대량 파일 업로드
20
+ - Excel 기반 메타데이터 매핑 및 검증
21
+ - 재귀적 디렉토리 처리
22
+ - 타입 기반 파일 구성
23
+ - 배치 데이터 단위 생성
24
+ - 크기 및 내용 검증을 통한 안전한 파일 처리
25
+
26
+ **지원되는 업로드 소스:**
27
+
28
+ - 로컬 파일 시스템 경로 (파일 및 디렉토리)
29
+ - 재귀적 디렉토리 스캔
30
+ - 향상된 파일 주석을 위한 Excel 메타데이터 파일
31
+ - 자동 구성을 통한 혼합 파일 타입
8
32
 
9
- ### 주요 기능
33
+ ## 업로드 액션 아키텍처
34
+
35
+ 업로드 시스템은 검증된 디자인 패턴을 기반으로 구축된 현대적이고 확장 가능한 아키텍처를 사용합니다. 리팩토링된 구현은 이전의 모놀리식 접근 방식을 관심사의 명확한 분리가 있는 모듈식 전략 기반 시스템으로 변환합니다.
36
+
37
+ ### 디자인 패턴
10
38
 
11
- - **다중 파일 형식 지원**: Excel, 이미지, 텍스트 파일 등
12
- - **메타데이터 추출**: Excel 파일에서 자동 메타데이터 수집
13
- - **보안 검증**: 파일 액세스 제어 및 권한 검사
14
- - **진행률 추적**: 실시간 업로드 진행률 모니터링
15
- - **배치 처리**: 여러 파일의 효율적인 배치 업로드
16
- - **분산 실행**: Ray를 사용한 확장 가능한 처리
39
+ 아키텍처는 여러 핵심 디자인 패턴을 활용합니다:
17
40
 
18
- ## 아키텍처
41
+ - **전략 패턴**: 검증, 파일 발견, 메타데이터 처리, 업로드 작업 및 데이터 단위 생성을 위한 플러그형 동작
42
+ - **파사드 패턴**: UploadOrchestrator는 복잡한 워크플로우를 조정하기 위한 단순화된 인터페이스를 제공
43
+ - **팩토리 패턴**: StrategyFactory는 런타임 매개변수를 기반으로 적절한 전략 구현을 생성
44
+ - **컨텍스트 패턴**: UploadContext는 워크플로우 구성 요소 간의 공유 상태 및 통신을 유지
19
45
 
20
- 업로드 플러그인은 각각 특정한 책임을 가진 여러 모듈로 구성되어 있습니다:
46
+ ### 컴포넌트 아키텍처
21
47
 
22
48
  ```mermaid
23
- graph TD
24
- A[UploadAction] --> B[UploadRun]
25
- A --> C[UploadParams]
26
- B --> D[LogCode]
27
- B --> E[UploadStatus]
28
- A --> F[ExcelSecurityConfig]
29
- A --> G[ExcelMetadataUtils]
30
-
31
- style A fill:#e1f5fe80,stroke:#01579b,stroke-width:2px
32
- style B fill:#f3e5f580,stroke:#4a148c,stroke-width:2px
33
- style C fill:#e8f5e880,stroke:#1b5e20,stroke-width:2px
34
- style D fill:#fff3e080,stroke:#e65100,stroke-width:2px
35
- style E fill:#fff3e080,stroke:#e65100,stroke-width:2px
36
- style F fill:#fce4ec80,stroke:#880e4f,stroke-width:2px
37
- style G fill:#fce4ec80,stroke:#880e4f,stroke-width:2px
38
- ```
39
-
40
- ### 디렉터리 구조
41
-
42
- ```
43
- synapse_sdk/
44
- ├── plugins/
45
- │ └── categories/
46
- │ └── upload/
47
- │ └── actions/
48
- │ └── upload/
49
- │ ├── __init__.py # 공개 API 내보내기
50
- │ ├── action.py # 주요 UploadAction 클래스
51
- │ ├── run.py # UploadRun 클래스 및 실행 로직
52
- │ ├── models.py # 매개변수 모델 및 검증
53
- │ ├── enums.py # 열거형 및 상수
54
- │ ├── exceptions.py # 사용자 정의 예외
55
- │ └── utils.py # 유틸리티 클래스 및 헬퍼
49
+ classDiagram
50
+ %% Light/Dark mode compatible colors
51
+ classDef coreClass fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000000
52
+ classDef strategyClass fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000000
53
+ classDef stepClass fill:#fff9c4,stroke:#f57c00,stroke-width:2px,color:#000000
54
+ classDef contextClass fill:#ffebee,stroke:#d32f2f,stroke-width:2px,color:#000000
55
+
56
+ class UploadAction {
57
+ +name: str = "upload"
58
+ +category: PluginCategory.UPLOAD
59
+ +method: RunMethod.JOB
60
+ +run_class: UploadRun
61
+ +params_model: UploadParams
62
+ +strategy_factory: StrategyFactory
63
+ +step_registry: StepRegistry
64
+
65
+ +start() dict
66
+ +get_workflow_summary() dict
67
+ +_configure_workflow() None
68
+ +_configure_strategies() dict
69
+ }
70
+
71
+ class UploadOrchestrator {
72
+ +context: UploadContext
73
+ +step_registry: StepRegistry
74
+ +strategies: dict
75
+ +executed_steps: list
76
+ +current_step_index: int
77
+ +rollback_executed: bool
78
+
79
+ +execute() dict
80
+ +get_workflow_summary() dict
81
+ +get_executed_steps() list
82
+ +is_rollback_executed() bool
83
+ +_execute_step(step) StepResult
84
+ +_handle_step_failure(step, error) None
85
+ +_rollback_executed_steps() None
86
+ }
87
+
88
+ class UploadContext {
89
+ +params: dict
90
+ +run: UploadRun
91
+ +client: Any
92
+ +storage: Any
93
+ +pathlib_cwd: Path
94
+ +metadata: dict
95
+ +file_specifications: dict
96
+ +organized_files: list
97
+ +uploaded_files: list
98
+ +data_units: list
99
+ +metrics: dict
100
+ +errors: list
101
+ +strategies: dict
102
+ +rollback_data: dict
103
+
104
+ +update(result: StepResult) None
105
+ +get_result() dict
106
+ +has_errors() bool
107
+ +update_metrics(category, metrics) None
108
+ }
109
+
110
+ class StepRegistry {
111
+ +_steps: list
112
+ +register(step: BaseStep) None
113
+ +get_steps() list
114
+ +get_total_progress_weight() float
115
+ +clear() None
116
+ }
117
+
118
+ class StrategyFactory {
119
+ +create_validation_strategy(params, context) BaseValidationStrategy
120
+ +create_file_discovery_strategy(params, context) BaseFileDiscoveryStrategy
121
+ +create_metadata_strategy(params, context) BaseMetadataStrategy
122
+ +create_upload_strategy(params, context) BaseUploadStrategy
123
+ +create_data_unit_strategy(params, context) BaseDataUnitStrategy
124
+ +get_available_strategies() dict
125
+ }
126
+
127
+ class BaseStep {
128
+ <<abstract>>
129
+ +name: str
130
+ +progress_weight: float
131
+ +execute(context: UploadContext) StepResult
132
+ +can_skip(context: UploadContext) bool
133
+ +rollback(context: UploadContext) None
134
+ +create_success_result(data) StepResult
135
+ +create_error_result(error) StepResult
136
+ +create_skip_result() StepResult
137
+ }
138
+
139
+ class StepResult {
140
+ +success: bool
141
+ +data: dict
142
+ +error: str
143
+ +rollback_data: dict
144
+ +skipped: bool
145
+ +original_exception: Exception
146
+ +timestamp: datetime
147
+ }
148
+
149
+ %% Strategy Base Classes
150
+ class BaseValidationStrategy {
151
+ <<abstract>>
152
+ +validate_files(files, context) bool
153
+ +validate_security(file_path) bool
154
+ }
155
+
156
+ class BaseFileDiscoveryStrategy {
157
+ <<abstract>>
158
+ +discover_files(path, context) list
159
+ +organize_files(files, specs, context) list
160
+ }
161
+
162
+ class BaseMetadataStrategy {
163
+ <<abstract>>
164
+ +process_metadata(context) dict
165
+ +extract_metadata(file_path) dict
166
+ }
167
+
168
+ class BaseUploadStrategy {
169
+ <<abstract>>
170
+ +upload_files(files, context) list
171
+ +upload_batch(batch, context) list
172
+ }
173
+
174
+ class BaseDataUnitStrategy {
175
+ <<abstract>>
176
+ +generate_data_units(files, context) list
177
+ +create_data_unit_batch(batch, context) list
178
+ }
179
+
180
+ %% Workflow Steps
181
+ class InitializeStep {
182
+ +name = "initialize"
183
+ +progress_weight = 0.05
184
+ }
185
+
186
+ class ProcessMetadataStep {
187
+ +name = "process_metadata"
188
+ +progress_weight = 0.05
189
+ }
190
+
191
+ class AnalyzeCollectionStep {
192
+ +name = "analyze_collection"
193
+ +progress_weight = 0.05
194
+ }
195
+
196
+ class OrganizeFilesStep {
197
+ +name = "organize_files"
198
+ +progress_weight = 0.10
199
+ }
200
+
201
+ class ValidateFilesStep {
202
+ +name = "validate_files"
203
+ +progress_weight = 0.05
204
+ }
205
+
206
+ class UploadFilesStep {
207
+ +name = "upload_files"
208
+ +progress_weight = 0.30
209
+ }
210
+
211
+ class GenerateDataUnitsStep {
212
+ +name = "generate_data_units"
213
+ +progress_weight = 0.35
214
+ }
215
+
216
+ class CleanupStep {
217
+ +name = "cleanup"
218
+ +progress_weight = 0.05
219
+ }
220
+
221
+ %% Relationships
222
+ UploadAction --> UploadOrchestrator : creates and executes
223
+ UploadAction --> StrategyFactory : configures strategies
224
+ UploadAction --> StepRegistry : manages workflow steps
225
+ UploadOrchestrator --> UploadContext : coordinates state
226
+ UploadOrchestrator --> StepRegistry : executes steps from
227
+ UploadOrchestrator --> BaseStep : executes
228
+ BaseStep --> StepResult : returns
229
+ UploadContext --> StepResult : updates with
230
+ StrategyFactory --> BaseValidationStrategy : creates
231
+ StrategyFactory --> BaseFileDiscoveryStrategy : creates
232
+ StrategyFactory --> BaseMetadataStrategy : creates
233
+ StrategyFactory --> BaseUploadStrategy : creates
234
+ StrategyFactory --> BaseDataUnitStrategy : creates
235
+ StepRegistry --> BaseStep : contains
236
+
237
+ %% Step inheritance
238
+ InitializeStep --|> BaseStep : extends
239
+ ProcessMetadataStep --|> BaseStep : extends
240
+ AnalyzeCollectionStep --|> BaseStep : extends
241
+ OrganizeFilesStep --|> BaseStep : extends
242
+ ValidateFilesStep --|> BaseStep : extends
243
+ UploadFilesStep --|> BaseStep : extends
244
+ GenerateDataUnitsStep --|> BaseStep : extends
245
+ CleanupStep --|> BaseStep : extends
246
+
247
+ %% Note: Class styling defined above - Mermaid will apply based on classDef definitions
56
248
  ```
57
249
 
58
- ## 업로드 액션 아키텍처
250
+ ### 단계 기반 워크플로우 실행
251
+
252
+ 리팩토링된 아키텍처는 UploadOrchestrator에 의해 조정되는 단계 기반 워크플로우를 사용합니다. 각 단계는 정의된 책임과 진행률 가중치를 가집니다.
59
253
 
60
- ### 업로드 처리 흐름
254
+ #### 워크플로우 단계 개요
61
255
 
62
- 업로드 플러그인은 다음과 같은 단계별 처리 흐름을 따릅니다:
256
+ | 단계 | 이름 | 가중치 | 책임 |
257
+ | ---- | ------------------- | ------ | ---------------------------------- |
258
+ | 1 | Initialize | 5% | 스토리지, pathlib, 기본 검증 설정 |
259
+ | 2 | Process Metadata | 5% | 제공된 Excel 메타데이터 처리 |
260
+ | 3 | Analyze Collection | 5% | 데이터 컬렉션 사양 검색 및 검증 |
261
+ | 4 | Organize Files | 10% | 타입별 파일 발견 및 구성 |
262
+ | 5 | Validate Files | 5% | 보안 및 내용 검증 |
263
+ | 6 | Upload Files | 30% | 스토리지에 파일 업로드 |
264
+ | 7 | Generate Data Units | 35% | 업로드된 파일에서 데이터 단위 생성 |
265
+ | 8 | Cleanup | 5% | 임시 리소스 정리 |
266
+
267
+ #### 실행 플로우
63
268
 
64
269
  ```mermaid
65
270
  flowchart TD
66
- A[파라미터 검증] --> B[스토리지/컬렉션 존재 확인]
67
- B --> C[파일 발견 및 필터링]
68
- C --> D{Excel 파일 포함?}
69
- D -->|예| E[Excel 보안 검증]
70
- D -->|아니오| F[일반 파일 처리]
71
- E --> G[Excel 메타데이터 추출]
72
- G --> H[데이터 단위 생성]
73
- F --> H
74
- H --> I[배치 업로드 실행]
75
- I --> J[진행률 추적]
76
- J --> K[결과 반환]
77
-
78
- style A fill:#e3f2fd80,stroke:#1976d2,stroke-width:2px
79
- style B fill:#f3e5f580,stroke:#7b1fa2,stroke-width:2px
80
- style C fill:#e8f5e880,stroke:#388e3c,stroke-width:2px
81
- style D fill:#fff3e080,stroke:#f57c00,stroke-width:2px
82
- style E fill:#ffebee80,stroke:#d32f2f,stroke-width:2px
83
- style F fill:#e8f5e880,stroke:#388e3c,stroke-width:2px
84
- style G fill:#f3e5f580,stroke:#7b1fa2,stroke-width:2px
85
- style H fill:#e3f2fd80,stroke:#1976d2,stroke-width:2px
86
- style I fill:#fff3e080,stroke:#f57c00,stroke-width:2px
87
- style J fill:#e8f5e880,stroke:#388e3c,stroke-width:2px
88
- style K fill:#ffebee80,stroke:#d32f2f,stroke-width:2px
89
- ```
90
-
91
- 처리 흐름은 견고성과 보안을 보장하면서 다양한 파일 유형에 대해 최적화된 성능을 제공합니다.
92
-
93
- ## 빠른 시작
94
-
95
- ### 기본 사용법
96
-
97
- ```python
98
- from synapse_sdk.plugins.categories.upload.actions import upload
99
-
100
- # 업로드 매개변수 정의
101
- params = upload.UploadParams(
102
- source_path="/path/to/files",
103
- storage_id="your_storage_id",
104
- collection_id="your_collection_id",
105
- project_id="your_project_id"
106
- )
271
+ %% Start
272
+ A["🚀 업로드 액션 시작"] --> B["📋 UploadContext 생성"]
273
+ B --> C["⚙️ 전략 구성"]
274
+ C --> D["📝 워크플로우 단계 등록"]
275
+ D --> E["🎯 UploadOrchestrator 생성"]
276
+
277
+ %% Strategy Injection
278
+ E --> F["💉 컨텍스트에 전략 주입"]
279
+ F --> G["📊 진행률 추적 초기화"]
280
+
281
+ %% Step Execution Loop
282
+ G --> H["🔄 단계 실행 루프 시작"]
283
+ H --> I["📍 다음 단계 가져오기"]
284
+ I --> J{"🤔 단계를 건너뛸 수 있는가?"}
285
+ J -->|Yes| K["⏭️ 단계 건너뛰기"]
286
+ J -->|No| L["▶️ 단계 실행"]
287
+
288
+ %% Step Execution
289
+ L --> M{"✅ 단계 성공?"}
290
+ M -->|Yes| N["📈 진행률 업데이트"]
291
+ M -->|No| O["❌ 단계 실패 처리"]
292
+
293
+ %% Success Path
294
+ N --> P["💾 단계 결과 저장"]
295
+ P --> Q["📝 실행된 단계에 추가"]
296
+ Q --> R{"🏁 많은 단계?"}
297
+ R -->|Yes| I
298
+ R -->|No| S["🎉 워크플로우 완료"]
299
+
300
+ %% Skip Path
301
+ K --> T["📊 진행률 업데이트 (건너뛰기)"]
302
+ T --> R
303
+
304
+ %% Error Handling
305
+ O --> U["🔙 롤백 프로세스 시작"]
306
+ U --> V["⏪ 실행된 단계 롤백"]
307
+ V --> W["📝 롤백 결과 로그"]
308
+ W --> X["💥 예외 전파"]
309
+
310
+ %% Final Results
311
+ S --> Y["📊 최종 메트릭 수집"]
312
+ Y --> Z["📋 결과 요약 생성"]
313
+ Z --> AA["🔄 UploadAction으로 반환"]
314
+
315
+ %% Apply styles - Light/Dark mode compatible
316
+ classDef startNode fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000000
317
+ classDef processNode fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000000
318
+ classDef decisionNode fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000000
319
+ classDef successNode fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000000
320
+ classDef errorNode fill:#ffebee,stroke:#d32f2f,stroke-width:2px,color:#000000
321
+ classDef stepNode fill:#f0f4c3,stroke:#689f38,stroke-width:1px,color:#000000
322
+
323
+ class A,B,E startNode
324
+ class C,D,F,G,H,I,L,N,P,Q,T,Y,Z,AA processNode
325
+ class J,M,R decisionNode
326
+ class K,S successNode
327
+ class O,U,V,W,X errorNode
328
+ ```
107
329
 
108
- # 업로드 액션 실행
109
- action = upload.UploadAction(params=params)
110
- result = action.run()
330
+ #### 단계 실행 세부사항
111
331
 
112
- print(f"업로드 상태: {result.status}")
332
+ ```mermaid
333
+ flowchart TD
334
+ %% Individual Step Details
335
+ A["🚀 워크플로우 단계 개요"]
336
+
337
+ A --> B1["1. 🏗️ 초기화 단계<br/>가중치: 5%<br/>• 스토리지 연결 설정<br/>• pathlib 액세스 검증<br/>• 컨텍스트 상태 초기화"]
338
+ A --> B2["2. 📋 메타데이터 처리 단계<br/>가중치: 5%<br/>• Excel 메타데이터 파싱<br/>• 보안 제약 검증<br/>• 파일명 매핑 생성"]
339
+ A --> B3["3. 🔍 컬렉션 분석 단계<br/>가중치: 5%<br/>• 파일 사양 검색<br/>• 컬렉션 접근 검증<br/>• 구성 규칙 설정"]
340
+ A --> B4["4. 🗂️ 파일 구성 단계<br/>가중치: 10%<br/>• 파일 발견 (재귀/평면)<br/>• 파일 타입별 그룹화<br/>• 구성된 구조 생성"]
341
+ A --> B5["5. ✅ 파일 검증 단계<br/>가중치: 5%<br/>• 보안 검증<br/>• 크기 및 내용 검사<br/>• 검증 전략 적용"]
342
+ A --> B6["6. ⬆️ 파일 업로드 단계<br/>가중치: 30%<br/>• 배치 파일 업로드<br/>• 진행률 추적<br/>• 실패한 업로드 재시도"]
343
+ A --> B7["7. 🏗️ 데이터 단위 생성 단계<br/>가중치: 35%<br/>• 데이터 단위 생성<br/>• 배치 처리<br/>• 파일을 단위에 연결"]
344
+ A --> B8["8. 🧹 정리 단계<br/>가중치: 5%<br/>• 임시 파일 정리<br/>• 리소스 해제<br/>• 최종 검증"]
345
+
346
+ %% Step Details
347
+ B1 --> C1["책임사항:<br/>• 클라이언트에서 스토리지 가져오기<br/>• pathlib 작업 디렉터리 초기화<br/>• 기본 업로드 전제조건 검증"]
348
+ B2 --> C2["책임사항:<br/>• Excel 메타데이터 파일 로드 및 파싱<br/>• 보안 검증 적용<br/>• 파일명-메타데이터 매핑 생성"]
349
+ B3 --> C3["책임사항:<br/>• 데이터 컬렉션 사양 가져오기<br/>• 파일 타입 요구사항 검증<br/>• 구성 규칙 설정"]
350
+ B4 --> C4["책임사항:<br/>• 파일 발견 전략 사용<br/>• 파일 필터링 및 분류<br/>• 구성된 파일 구조 생성"]
351
+ B5 --> C5["책임사항:<br/>• 검증 전략 사용<br/>• 파일 보안 및 내용 검사<br/>• 비즈니스 검증 규칙 적용"]
352
+ B6 --> C6["책임사항:<br/>• 업로드 전략 사용<br/>• 구성 가능한 배치로 파일 처리<br/>• 진행률 추적 및 실패 처리"]
353
+ B7 --> C7["책임사항:<br/>• 데이터 단위 전략 사용<br/>• 업로드된 파일에서 데이터 단위 생성<br/>• 파일을 적절한 단위에 연결"]
354
+ B8 --> C8["책임사항:<br/>• 임시 파일 및 리소스 정리<br/>• 최종 시스템 검증 수행<br/>• 깨끗한 종료 보장"]
355
+
356
+ %% Apply styles - Light/Dark mode compatible
357
+ classDef overviewNode fill:#e3f2fd,stroke:#1976d2,stroke-width:3px,color:#000000
358
+ classDef stepNode fill:#fff9c4,stroke:#f57c00,stroke-width:2px,color:#000000
359
+ classDef detailNode fill:#f0f4c3,stroke:#689f38,stroke-width:1px,color:#000000
360
+
361
+ class A overviewNode
362
+ class B1,B2,B3,B4,B5,B6,B7,B8 stepNode
363
+ class C1,C2,C3,C4,C5,C6,C7,C8 detailNode
113
364
  ```
114
365
 
115
- ### Excel 파일 업로드
366
+ #### 전략 통합 지점
116
367
 
117
- ```python
118
- from synapse_sdk.plugins.categories.upload.actions import upload
368
+ 전략은 워크플로우의 특정 지점에서 주입됩니다:
119
369
 
120
- # Excel 파일에 대한 매개변수
121
- params = upload.UploadParams(
122
- source_path="/path/to/excel/files",
123
- storage_id="storage123",
124
- collection_id="collection456",
125
- project_id="project789",
126
- excel_security_config={
127
- "check_macros": True,
128
- "check_external_links": True,
129
- "max_file_size": 50 # MB
130
- }
131
- )
370
+ - **검증 전략**: ValidateFilesStep에서 사용
371
+ - **파일 발견 전략**: OrganizeFilesStep에서 사용
372
+ - **메타데이터 전략**: ProcessMetadataStep에서 사용
373
+ - **업로드 전략**: UploadFilesStep에서 사용
374
+ - **데이터 단위 전략**: GenerateDataUnitsStep에서 사용
375
+
376
+ #### 오류 처리 및 롤백
132
377
 
133
- action = upload.UploadAction(params=params)
134
- result = action.run()
378
+ 오케스트레이터는 자동 롤백 기능을 제공합니다:
379
+
380
+ 1. **예외 캡처**: 디버깅을 위해 원본 예외 보존
381
+ 2. **롤백 실행**: 성공적으로 실행된 모든 단계에서 역순으로 rollback() 호출
382
+ 3. **우아한 저하**: 개별 단계 롤백이 실패해도 롤백 계속 진행
383
+ 4. **상태 보존**: 실패 후 분석을 위한 실행 상태 유지
384
+
385
+ ## 개발 가이드
386
+
387
+ 이 섹션은 사용자 정의 전략과 워크플로우 단계로 업로드 액션을 확장하기 위한 포괄적인 가이드를 제공합니다.
388
+
389
+ ### 사용자 정의 전략 생성
390
+
391
+ 전략은 업로드 프로세스의 다양한 측면에 대한 특정 동작을 구현합니다. 각 전략 타입은 잘 정의된 인터페이스를 가지고 있습니다.
392
+
393
+ #### 사용자 정의 검증 전략
394
+
395
+ ```python
396
+ from synapse_sdk.plugins.categories.upload.actions.upload.strategies.validation.base import BaseValidationStrategy
397
+ from synapse_sdk.plugins.categories.upload.actions.upload.context import UploadContext
398
+
399
+ class CustomValidationStrategy(BaseValidationStrategy):
400
+ """고급 보안 검사를 포함한 사용자 정의 검증 전략."""
401
+
402
+ def validate_files(self, files: List[Path], context: UploadContext) -> bool:
403
+ """사용자 정의 비즈니스 규칙을 사용하여 파일 검증."""
404
+ for file_path in files:
405
+ # 사용자 정의 검증 로직
406
+ if not self._validate_custom_rules(file_path):
407
+ return False
408
+
409
+ # 보안 검증 호출
410
+ if not self.validate_security(file_path):
411
+ return False
412
+ return True
413
+
414
+ def validate_security(self, file_path: Path) -> bool:
415
+ """사용자 정의 보안 검증."""
416
+ # 사용자 정의 보안 검사 구현
417
+ if file_path.suffix in ['.exe', '.bat', '.sh']:
418
+ return False
419
+
420
+ # 파일 크기 검사
421
+ if file_path.stat().st_size > 100 * 1024 * 1024: # 100MB
422
+ return False
423
+
424
+ return True
425
+
426
+ def _validate_custom_rules(self, file_path: Path) -> bool:
427
+ """도메인별 검증 규칙 구현."""
428
+ # 사용자 정의 비즈니스 로직
429
+ return True
135
430
  ```
136
431
 
137
- ### 진행률 모니터링
432
+ #### 사용자 정의 파일 발견 전략
138
433
 
139
434
  ```python
140
- import asyncio
141
- from synapse_sdk.plugins.categories.upload.actions import upload
435
+ from synapse_sdk.plugins.categories.upload.actions.upload.strategies.file_discovery.base import BaseFileDiscoveryStrategy
436
+ from pathlib import Path
437
+ from typing import List, Dict, Any
438
+
439
+ class CustomFileDiscoveryStrategy(BaseFileDiscoveryStrategy):
440
+ """고급 필터링을 포함한 사용자 정의 파일 발견."""
441
+
442
+ def discover_files(self, path: Path, context: UploadContext) -> List[Path]:
443
+ """사용자 정의 필터링 규칙으로 파일 발견."""
444
+ files = []
445
+
446
+ if context.get_param('is_recursive', False):
447
+ files = list(path.rglob('*'))
448
+ else:
449
+ files = list(path.iterdir())
450
+
451
+ # 사용자 정의 필터링 적용
452
+ return self._apply_custom_filters(files, context)
453
+
454
+ def organize_files(self, files: List[Path], specs: Dict[str, Any], context: UploadContext) -> List[Dict[str, Any]]:
455
+ """사용자 정의 분류를 사용하여 파일 구성."""
456
+ organized = []
457
+
458
+ for file_path in files:
459
+ if file_path.is_file():
460
+ category = self._determine_category(file_path)
461
+ organized.append({
462
+ 'file_path': file_path,
463
+ 'category': category,
464
+ 'metadata': self._extract_file_metadata(file_path)
465
+ })
466
+
467
+ return organized
468
+
469
+ def _apply_custom_filters(self, files: List[Path], context: UploadContext) -> List[Path]:
470
+ """도메인별 파일 필터 적용."""
471
+ filtered = []
472
+ for file_path in files:
473
+ if self._should_include_file(file_path):
474
+ filtered.append(file_path)
475
+ return filtered
476
+
477
+ def _determine_category(self, file_path: Path) -> str:
478
+ """사용자 정의 로직을 사용하여 파일 카테고리 결정."""
479
+ # 사용자 정의 분류 로직
480
+ ext = file_path.suffix.lower()
481
+ if ext in ['.jpg', '.png', '.gif']:
482
+ return 'images'
483
+ elif ext in ['.pdf', '.doc', '.docx']:
484
+ return 'documents'
485
+ else:
486
+ return 'other'
487
+ ```
142
488
 
143
- async def upload_with_progress():
144
- params = upload.UploadParams(
145
- source_path="/path/to/large/dataset",
146
- storage_id="storage123",
147
- collection_id="collection456",
148
- project_id="project789"
149
- )
489
+ #### 사용자 정의 업로드 전략
490
+
491
+ ```python
492
+ from synapse_sdk.plugins.categories.upload.actions.upload.strategies.upload.base import BaseUploadStrategy
493
+ from typing import List, Dict, Any
494
+
495
+ class CustomUploadStrategy(BaseUploadStrategy):
496
+ """고급 재시도 로직을 포함한 사용자 정의 업로드 전략."""
497
+
498
+ def upload_files(self, files: List[Dict[str, Any]], context: UploadContext) -> List[Dict[str, Any]]:
499
+ """사용자 정의 배치 및 재시도 로직으로 파일 업로드."""
500
+ uploaded_files = []
501
+ batch_size = context.get_param('upload_batch_size', 10)
502
+
503
+ # 사용자 정의 배치로 처리
504
+ for i in range(0, len(files), batch_size):
505
+ batch = files[i:i + batch_size]
506
+ batch_results = self.upload_batch(batch, context)
507
+ uploaded_files.extend(batch_results)
508
+
509
+ return uploaded_files
510
+
511
+ def upload_batch(self, batch: List[Dict[str, Any]], context: UploadContext) -> List[Dict[str, Any]]:
512
+ """재시도 로직으로 파일 배치 업로드."""
513
+ results = []
514
+
515
+ for file_info in batch:
516
+ max_retries = 3
517
+ for attempt in range(max_retries):
518
+ try:
519
+ result = self._upload_single_file(file_info, context)
520
+ results.append(result)
521
+ break
522
+ except Exception as e:
523
+ if attempt == max_retries - 1:
524
+ # 최종 시도 실패
525
+ context.add_error(f"Failed to upload {file_info['file_path']}: {e}")
526
+ else:
527
+ # 재시도 전 대기
528
+ time.sleep(2 ** attempt)
529
+
530
+ return results
531
+
532
+ def _upload_single_file(self, file_info: Dict[str, Any], context: UploadContext) -> Dict[str, Any]:
533
+ """사용자 정의 로직으로 단일 파일 업로드."""
534
+ # 사용자 정의 업로드 구현
535
+ file_path = file_info['file_path']
536
+
537
+ # 컨텍스트에서 스토리지 사용
538
+ storage = context.storage
539
+
540
+ # 여기에 사용자 정의 업로드 로직
541
+ uploaded_file = {
542
+ 'file_path': str(file_path),
543
+ 'storage_path': f"uploads/{file_path.name}",
544
+ 'size': file_path.stat().st_size,
545
+ 'checksum': self._calculate_checksum(file_path)
546
+ }
547
+
548
+ return uploaded_file
549
+ ```
150
550
 
151
- action = upload.UploadAction(params=params)
551
+ ### 사용자 정의 워크플로우 단계 생성
152
552
 
153
- # 진행률 콜백으로 업로드 실행
154
- result = await action.run_async(
155
- progress_callback=lambda progress: print(f"진행률: {progress}%")
156
- )
553
+ 사용자 정의 워크플로우 단계는 기본 단계 클래스를 확장하고 필수 인터페이스를 구현합니다.
157
554
 
158
- return result
555
+ #### 사용자 정의 처리 단계
159
556
 
160
- # 비동기 업로드 실행
161
- result = asyncio.run(upload_with_progress())
557
+ ```python
558
+ from synapse_sdk.plugins.categories.upload.actions.upload.steps.base import BaseStep
559
+ from synapse_sdk.plugins.categories.upload.actions.upload.context import UploadContext, StepResult
560
+ from pathlib import Path
561
+
562
+ class CustomProcessingStep(BaseStep):
563
+ """특수 파일 처리를 위한 사용자 정의 처리 단계."""
564
+
565
+ @property
566
+ def name(self) -> str:
567
+ return 'custom_processing'
568
+
569
+ @property
570
+ def progress_weight(self) -> float:
571
+ return 0.15 # 전체 워크플로우의 15%
572
+
573
+ def execute(self, context: UploadContext) -> StepResult:
574
+ """사용자 정의 처리 로직 실행."""
575
+ try:
576
+ # 사용자 정의 처리 로직
577
+ processed_files = self._process_files(context)
578
+
579
+ # 결과로 컨텍스트 업데이트
580
+ return self.create_success_result({
581
+ 'processed_files': processed_files,
582
+ 'processing_stats': self._get_processing_stats()
583
+ })
584
+
585
+ except Exception as e:
586
+ return self.create_error_result(f'Custom processing failed: {str(e)}')
587
+
588
+ def can_skip(self, context: UploadContext) -> bool:
589
+ """단계를 건너뛸 수 있는지 결정."""
590
+ # 처리할 파일이 없으면 건너뛰기
591
+ return len(context.organized_files) == 0
592
+
593
+ def rollback(self, context: UploadContext) -> None:
594
+ """사용자 정의 처리 작업 롤백."""
595
+ # 처리 중에 생성된 리소스 정리
596
+ self._cleanup_processing_resources(context)
597
+
598
+ def _process_files(self, context: UploadContext) -> List[Dict]:
599
+ """사용자 정의 파일 처리 구현."""
600
+ processed = []
601
+
602
+ for file_info in context.organized_files:
603
+ # 사용자 정의 처리 로직
604
+ result = self._process_single_file(file_info)
605
+ processed.append(result)
606
+
607
+ return processed
608
+
609
+ def _process_single_file(self, file_info: Dict) -> Dict:
610
+ """단일 파일 처리."""
611
+ # 사용자 정의 처리 구현
612
+ return {
613
+ 'original': file_info,
614
+ 'processed': True,
615
+ 'timestamp': datetime.now()
616
+ }
162
617
  ```
163
618
 
164
- ## API 참조
619
+ ### 전략 팩토리 확장
620
+
621
+ 사용자 정의 전략을 사용 가능하게 하려면 StrategyFactory를 확장하세요:
622
+
623
+ ```python
624
+ from synapse_sdk.plugins.categories.upload.actions.upload.factory import StrategyFactory
625
+
626
+ class CustomStrategyFactory(StrategyFactory):
627
+ """사용자 정의 전략을 포함한 확장 팩토리."""
628
+
629
+ def create_validation_strategy(self, params: Dict, context=None):
630
+ """사용자 정의 옵션으로 검증 전략 생성."""
631
+ validation_type = params.get('custom_validation_type', 'default')
165
632
 
166
- ### UploadAction
633
+ if validation_type == 'strict':
634
+ return CustomValidationStrategy()
635
+ else:
636
+ return super().create_validation_strategy(params, context)
167
637
 
168
- 업로드 작업을 관리하는 주요 클래스입니다.
638
+ def create_file_discovery_strategy(self, params: Dict, context=None):
639
+ """사용자 정의 옵션으로 파일 발견 전략 생성."""
640
+ discovery_mode = params.get('discovery_mode', 'default')
641
+
642
+ if discovery_mode == 'advanced':
643
+ return CustomFileDiscoveryStrategy()
644
+ else:
645
+ return super().create_file_discovery_strategy(params, context)
646
+ ```
647
+
648
+ ### 사용자 정의 업로드 액션
649
+
650
+ 포괄적인 커스터마이제이션을 위해서는 UploadAction 자체를 확장하세요:
169
651
 
170
652
  ```python
171
- class UploadAction:
172
- def __init__(self, params: UploadParams, run: Optional[UploadRun] = None):
173
- """
174
- 업로드 액션을 초기화합니다.
653
+ from synapse_sdk.plugins.categories.upload.actions.upload.action import UploadAction
654
+ from synapse_sdk.plugins.categories.decorators import register_action
655
+
656
+ @register_action
657
+ class CustomUploadAction(UploadAction):
658
+ """확장 워크플로우를 포함한 사용자 정의 업로드 액션."""
659
+
660
+ name = 'custom_upload'
175
661
 
176
- Args:
177
- params: 업로드 매개변수
178
- run: 선택적 실행 인스턴스
179
- """
662
+ def __init__(self, *args, **kwargs):
663
+ super().__init__(*args, **kwargs)
664
+ # 사용자 정의 전략 팩토리 사용
665
+ self.strategy_factory = CustomStrategyFactory()
666
+
667
+ def _configure_workflow(self) -> None:
668
+ """추가 단계로 사용자 정의 워크플로우 구성."""
669
+ # 표준 단계 등록
670
+ super()._configure_workflow()
671
+
672
+ # 사용자 정의 처리 단계 추가
673
+ self.step_registry.register(CustomProcessingStep())
674
+
675
+ def _configure_strategies(self, context=None) -> Dict[str, Any]:
676
+ """사용자 정의 매개변수로 전략 구성."""
677
+ strategies = super()._configure_strategies(context)
678
+
679
+ # 사용자 정의 전략 추가
680
+ strategies['custom_processing'] = self._create_custom_processing_strategy()
681
+
682
+ return strategies
683
+
684
+ def _create_custom_processing_strategy(self):
685
+ """사용자 정의 처리 전략 생성."""
686
+ return CustomProcessingStrategy(self.params)
180
687
  ```
181
688
 
182
- #### 메서드
689
+ ### 사용자 정의 컴포넌트 테스트
183
690
 
184
- - `run() -> UploadResult`: 동기적으로 업로드를 실행합니다
185
- - `run_async() -> UploadResult`: 비동기적으로 업로드를 실행합니다
186
- - `validate_params() -> bool`: 업로드 매개변수를 검증합니다
187
- - `discover_files() -> List[str]`: 업로드할 파일을 발견합니다
188
- - `process_excel_metadata() -> Dict`: Excel 파일에서 메타데이터를 추출합니다
691
+ #### 사용자 정의 전략 테스트
189
692
 
190
- ### UploadParams
693
+ ```python
694
+ import pytest
695
+ from unittest.mock import Mock
696
+ from pathlib import Path
697
+
698
+ class TestCustomValidationStrategy:
699
+
700
+ def setup_method(self):
701
+ self.strategy = CustomValidationStrategy()
702
+ self.context = Mock()
703
+
704
+ def test_validate_files_success(self):
705
+ """성공적인 파일 검증 테스트."""
706
+ files = [Path('/test/file1.txt'), Path('/test/file2.jpg')]
707
+ result = self.strategy.validate_files(files, self.context)
708
+ assert result is True
709
+
710
+ def test_validate_files_security_failure(self):
711
+ """보안상 이유로 검증 실패 테스트."""
712
+ files = [Path('/test/malware.exe')]
713
+ result = self.strategy.validate_files(files, self.context)
714
+ assert result is False
715
+
716
+ def test_validate_large_file_failure(self):
717
+ """큰 파일에 대한 검증 실패 테스트."""
718
+ # 큰 크기를 반환하도록 파일 stat 모킹
719
+ large_file = Mock(spec=Path)
720
+ large_file.suffix = '.txt'
721
+ large_file.stat.return_value.st_size = 200 * 1024 * 1024 # 200MB
722
+
723
+ result = self.strategy.validate_security(large_file)
724
+ assert result is False
725
+ ```
191
726
 
192
- 업로드 구성을 위한 매개변수 모델입니다.
727
+ #### 사용자 정의 단계 테스트
193
728
 
194
729
  ```python
195
- class UploadParams(BaseModel):
196
- source_path: str
197
- storage_id: str
198
- collection_id: str
199
- project_id: str
200
- excel_security_config: Optional[ExcelSecurityConfig] = None
201
- batch_size: int = 100
202
- max_workers: int = 4
203
- include_patterns: Optional[List[str]] = None
204
- exclude_patterns: Optional[List[str]] = None
730
+ class TestCustomProcessingStep:
731
+
732
+ def setup_method(self):
733
+ self.step = CustomProcessingStep()
734
+ self.context = Mock()
735
+ self.context.organized_files = [
736
+ {'file_path': '/test/file1.txt'},
737
+ {'file_path': '/test/file2.jpg'}
738
+ ]
739
+
740
+ def test_execute_success(self):
741
+ """성공적인 단계 실행 테스트."""
742
+ result = self.step.execute(self.context)
743
+
744
+ assert result.success is True
745
+ assert 'processed_files' in result.data
746
+ assert len(result.data['processed_files']) == 2
747
+
748
+ def test_can_skip_with_no_files(self):
749
+ """단계 건너뛰기 로직 테스트."""
750
+ self.context.organized_files = []
751
+ assert self.step.can_skip(self.context) is True
752
+
753
+ def test_rollback_cleanup(self):
754
+ """롤백 정리 테스트."""
755
+ # 이것은 예외를 발생시키지 않아야 함
756
+ self.step.rollback(self.context)
205
757
  ```
206
758
 
207
- #### 필드
759
+ ## 업로드 매개변수
760
+
761
+ 업로드 액션은 포괄적인 매개변수 검증을 위해 `UploadParams`를 사용합니다:
762
+
763
+ ### 필수 매개변수
764
+
765
+ | 매개변수 | 타입 | 설명 | 검증 |
766
+ | ----------------- | ----- | ----------------------- | ---------------------- |
767
+ | `name` | `str` | 읽기 쉬운 업로드 이름 | 빈 값이 아니어야 함 |
768
+ | `path` | `str` | 소스 파일/디렉토리 경로 | 유효한 경로여야 함 |
769
+ | `storage` | `int` | 대상 스토리지 ID | API를 통해 존재해야 함 |
770
+ | `data_collection` | `int` | 데이터 컬렉션 ID | API를 통해 존재해야 함 |
208
771
 
209
- - **source_path**: 업로드할 파일의 소스 경로
210
- - **storage_id**: 대상 스토리지 식별자
211
- - **collection_id**: 대상 컬렉션 식별자
212
- - **project_id**: 대상 프로젝트 식별자
213
- - **excel_security_config**: Excel 보안 설정 (선택사항)
214
- - **batch_size**: 배치 처리 크기 (기본값: 100)
215
- - **max_workers**: 최대 작업자 (기본값: 4)
216
- - **include_patterns**: 포함할 파일 패턴 (선택사항)
217
- - **exclude_patterns**: 제외할 파일 패턴 (선택사항)
772
+ ### 선택적 매개변수
773
+
774
+ | 매개변수 | 타입 | 기본값 | 설명 |
775
+ | ------------------------------- | ------------- | ------- | --------------------------- |
776
+ | `description` | `str \| None` | `None` | 업로드 설명 |
777
+ | `project` | `int \| None` | `None` | 프로젝트 ID (제공시 검증됨) |
778
+ | `excel_metadata_path` | `str \| None` | `None` | Excel 메타데이터 파일 경로 |
779
+ | `is_recursive` | `bool` | `False` | 디렉토리를 재귀적으로 스캔 |
780
+ | `max_file_size_mb` | `int` | `50` | 최대 파일 크기 (MB) |
781
+ | `creating_data_unit_batch_size` | `int` | `100` | 데이터 단위 배치 크기 |
782
+ | `use_async_upload` | `bool` | `True` | 비동기 처리 사용 |
218
783
 
219
784
  ### 매개변수 검증
220
785
 
221
- UploadParams는 Pydantic 모델을 사용하여 포괄적인 검증을 제공합니다:
786
+ 시스템은 실시간 검증을 수행합니다:
222
787
 
223
788
  ```python
224
- # 필수 매개변수 검증
225
- params = upload.UploadParams(
226
- source_path="/valid/directory", # 존재하는 디렉터리여야 함
227
- storage_id="storage_123", # 비어있지 않은 문자열
228
- collection_id="collection_456", # 비어있지 않은 문자열
229
- project_id="project_789" # 비어있지 않은 문자열
230
- )
231
-
232
- # 스토리지 및 컬렉션 존재 검증
233
- try:
234
- action = upload.UploadAction(params=params)
235
- result = action.run()
236
- except ValidationError as e:
237
- print(f"매개변수 검증 실패: {e}")
789
+ # 스토리지 검증
790
+ @field_validator('storage', mode='before')
791
+ @classmethod
792
+ def check_storage_exists(cls, value: str, info) -> str:
793
+ action = info.context['action']
794
+ client = action.client
795
+ try:
796
+ client.get_storage(value)
797
+ except ClientError:
798
+ raise PydanticCustomError('client_error', 'Storage not found')
799
+ return value
238
800
  ```
239
801
 
240
802
  **검증 규칙:**
@@ -296,25 +858,13 @@ Excel 파일 보안 설정:
296
858
 
297
859
  ```python
298
860
  class ExcelSecurityConfig:
299
- check_macros: bool = True
300
- check_external_links: bool = True
301
- max_file_size: int = 50 # MB
302
- allowed_extensions: List[str] = [".xlsx", ".xls"]
303
- ```
304
-
305
- #### ExcelMetadataUtils
306
-
307
- Excel 메타데이터 추출을 위한 유틸리티:
308
-
309
- ```python
310
- class ExcelMetadataUtils:
311
- @staticmethod
312
- def extract_metadata(file_path: str) -> Dict:
313
- """Excel 파일에서 메타데이터를 추출합니다."""
861
+ max_file_size_mb: int = 10 # 파일 크기 제한 (MB)
862
+ max_rows: int = 100000 # 행 수 제한
863
+ max_columns: int = 50 # 열 수 제한
314
864
 
315
- @staticmethod
316
- def get_sheet_info(file_path: str) -> List[Dict]:
317
- """Excel 시트 정보를 가져옵니다."""
865
+ @classmethod
866
+ def from_action_config(cls, action_config) -> 'ExcelSecurityConfig':
867
+ """config.yaml에서 설정을 로드합니다."""
318
868
  ```
319
869
 
320
870
  #### PathAwareJSONEncoder
@@ -343,555 +893,1185 @@ class PathAwareJSONEncoder:
343
893
 
344
894
  ## Excel 메타데이터 처리
345
895
 
896
+ 업로드 플러그인은 포괄적인 파일명 매칭, 유연한 헤더 지원 및 최적화된 성능을 통한 고급 Excel 메타데이터 처리를 제공합니다:
897
+
346
898
  ### Excel 파일 형식
347
899
 
348
- 업로드 플러그인은 다음 Excel 형식을 지원합니다:
900
+ Excel 파일은 유연한 헤더 형식과 포괄적인 파일명 매칭을 지원합니다:
901
+
902
+ #### 지원되는 헤더 형식
903
+
904
+ 대소문자를 구분하지 않는 매칭으로 두 헤더 형식 모두 지원됩니다:
905
+
906
+ **옵션 1: "filename" 헤더**
907
+ | filename | category | description | custom_field |
908
+ | ---------- | -------- | ------------------ | ------------ |
909
+ | image1.jpg | nature | Mountain landscape | high_res |
910
+ | image2.png | urban | City skyline | processed |
911
+
912
+ **옵션 2: "file_name" 헤더**
913
+ | file_name | category | description | custom_field |
914
+ | ---------- | -------- | ------------------ | ------------ |
915
+ | image1.jpg | nature | Mountain landscape | high_res |
916
+ | image2.png | urban | City skyline | processed |
349
917
 
350
- - **XLSX**: Office Open XML 형식 (권장)
351
- - **XLS**: 레거시 Excel 형식 (제한적 지원)
918
+ #### 파일명 매칭 전략
919
+
920
+ 시스템은 파일과 메타데이터를 연결하기 위해 포괄적인 5단계 우선순위 매칭 알고리즘을 사용합니다:
921
+
922
+ 1. **정확한 stem 매칭** (최우선): `image1`이 `image1.jpg`와 매칭
923
+ 2. **정확한 파일명 매칭**: `image1.jpg`가 `image1.jpg`와 매칭
924
+ 3. **메타데이터 키 stem 매칭**: `path/image1.ext` stem이 `image1`과 매칭
925
+ 4. **부분 경로 매칭**: `/uploads/image1.jpg`에 `image1` 포함
926
+ 5. **전체 경로 매칭**: 복잡한 구조에 대한 완전한 경로 매칭
927
+
928
+ 이 강력한 매칭은 파일 구성이나 명명 규칙에 관계없이 메타데이터가 올바르게 연결되도록 보장합니다.
352
929
 
353
930
  ### 보안 검증
354
931
 
355
- Excel 파일에 대해 포괄적인 보안 검사를 수행합니다:
932
+ Excel 파일은 포괄적인 보안 검증을 받습니다:
356
933
 
357
934
  ```python
358
- # 보안 구성 예제
359
- security_config = {
360
- "check_macros": True, # 매크로 탐지
361
- "check_external_links": True, # 외부 링크 탐지
362
- "max_file_size": 50, # 최대 파일 크기 (MB)
363
- "max_memory_usage": 100, # 최대 메모리 사용량 (MB)
364
- "max_rows": 1000000, # 최대 행 수
365
- "max_columns": 16384, # 최대 열 수
366
- }
935
+ class ExcelSecurityConfig:
936
+ max_file_size_mb: int = 10 # 파일 크기 제한 (MB)
937
+ max_rows: int = 100000 # 수 제한
938
+ max_columns: int = 50 # 제한
367
939
  ```
368
940
 
369
- ### 환경 구성
941
+ #### 고급 보안 기능
370
942
 
371
- 환경 변수를 통해 Excel 처리 설정을 재정의할 있습니다:
943
+ - **파일 형식 검증**: Excel 파일 시그니처 확인 (.xlsx의 경우 PK, .xls의 경우 복합 문서)
944
+ - **메모리 추정**: 대용량 스프레드시트로 인한 메모리 고갈 방지
945
+ - **내용 정화**: 지나치게 긴 값의 자동 잘라내기
946
+ - **오류 복원력**: 손상되거나 접근할 수 없는 파일의 우아한 처리
372
947
 
373
- ```bash
374
- # 메모리 제한 설정
375
- export EXCEL_MAX_MEMORY_MB=200
948
+ ### config.yaml을 통한 구성
376
949
 
377
- # 파일 크기 제한 설정
378
- export EXCEL_MAX_FILE_SIZE_MB=100
950
+ 보안 제한 처리 옵션을 구성할 수 있습니다:
379
951
 
380
- # 행/열 제한 설정
381
- export EXCEL_MAX_ROWS=500000
382
- export EXCEL_MAX_COLUMNS=1000
952
+ ```yaml
953
+ actions:
954
+ upload:
955
+ excel_config:
956
+ max_file_size_mb: 10 # Excel 파일 최대 크기 (MB)
957
+ max_rows: 100000 # 허용되는 최대 행 수
958
+ max_columns: 50 # 허용되는 최대 열 수
383
959
  ```
384
960
 
385
- ### 메타데이터 처리 흐름
961
+ ### 성능 최적화
962
+
963
+ Excel 메타데이터 처리에는 여러 성능 향상 기능이 포함되어 있습니다:
386
964
 
387
- ```mermaid
388
- flowchart TD
389
- A[Excel 파일 발견] --> B[보안 검증]
390
- B --> C{검증 통과?}
391
- C -->|실패| D[보안 오류 발생]
392
- C -->|통과| E[시트 정보 추출]
393
- E --> F[행/열 수 확인]
394
- F --> G[메타데이터 수집]
395
- G --> H[JSON 직렬화]
396
- H --> I[데이터 단위 생성]
397
-
398
- style A fill:#e3f2fd80,stroke:#1976d2,stroke-width:2px
399
- style B fill:#ffebee80,stroke:#d32f2f,stroke-width:2px
400
- style C fill:#fff3e080,stroke:#f57c00,stroke-width:2px
401
- style D fill:#ffcdd280,stroke:#d32f2f,stroke-width:2px
402
- style E fill:#e8f5e880,stroke:#388e3c,stroke-width:2px
403
- style F fill:#f3e5f580,stroke:#7b1fa2,stroke-width:2px
404
- style G fill:#e3f2fd80,stroke:#1976d2,stroke-width:2px
405
- style H fill:#fff3e080,stroke:#f57c00,stroke-width:2px
406
- style I fill:#e8f5e880,stroke:#388e3c,stroke-width:2px
407
- ```
965
+ #### 메타데이터 인덱싱
408
966
 
409
- ## 파일 구성
967
+ - **O(1) 해시 검색**: 정확한 stem 및 파일명 매칭용
968
+ - **사전 구축된 인덱스**: 일반적인 매칭 패턴용
969
+ - **대체 알고리즘**: 복잡한 경로 매칭 시나리오용
970
+
971
+ #### 효율적인 처리
410
972
 
411
- ### 타입 탐지
973
+ - **최적화된 행 처리**: 빈 행을 조기에 건너뛰기
974
+ - **메모리 인식 작업**: 배치로 파일 처리
975
+ - **스마트 파일 발견**: 반복되는 변환을 피하기 위한 경로 문자열 캐시
412
976
 
413
- 업로드 플러그인은 파일 확장자와 MIME 타입을 기반으로 파일 유형을 자동 탐지합니다:
977
+ ### 메타데이터 처리 플로우
978
+
979
+ 1. **보안 검증**: 파일 크기, 형식 및 내용 제한
980
+ 2. **헤더 검증**: 대소문자를 구분하지 않는 매칭으로 "filename" 및 "file_name" 모두 지원
981
+ 3. **인덱스 구축**: 성능을 위한 O(1) 검색 구조 생성
982
+ 4. **내용 처리**: 최적화를 통한 행별 메타데이터 추출
983
+ 5. **데이터 정화**: 자동 잘라내기 및 검증
984
+ 6. **패턴 매칭**: 5단계 파일명 연결 알고리즘
985
+ 7. **매핑 생성**: 최적화된 파일명에서 메타데이터로의 매핑
986
+
987
+ ### Excel 메타데이터 매개변수
988
+
989
+ 사용자 정의 Excel 메타데이터 파일 경로를 지정할 수 있습니다:
414
990
 
415
991
  ```python
416
- # 지원되는 파일 유형
417
- SUPPORTED_EXTENSIONS = {
418
- '.xlsx': 'Excel 워크북',
419
- '.xls': 'Excel 레거시',
420
- '.csv': 'CSV 파일',
421
- '.txt': '텍스트 파일',
422
- '.json': 'JSON 데이터',
423
- '.jpg': 'JPEG 이미지',
424
- '.png': 'PNG 이미지',
425
- '.pdf': 'PDF 문서'
992
+ params = {
993
+ "name": "Excel 메타데이터 업로드",
994
+ "path": "/data/files",
995
+ "storage": 1,
996
+ "data_collection": 5,
997
+ "excel_metadata_path": "/data/custom_metadata.xlsx" # 사용자 정의 Excel 파일
426
998
  }
427
999
  ```
428
1000
 
429
- ### 디렉터리 구조
1001
+ #### 경로 해결
1002
+
1003
+ - **절대 경로**: 존재하고 접근 가능한 경우 직접 사용
1004
+ - **상대 경로**: 업로드 경로에 상대적으로 해결
1005
+ - **기본 발견**: 경로가 지정되지 않은 경우 자동으로 `meta.xlsx` 또는 `meta.xls` 검색
1006
+ - **스토리지 통합**: 적절한 경로 해결을 위해 스토리지 구성 사용
1007
+
1008
+ ### 오류 처리
1009
+
1010
+ 포괄적인 오류 처리로 강력한 작업을 보장합니다:
1011
+
1012
+ ```python
1013
+ # Excel 처리 오류는 우아하게 처리됩니다
1014
+ try:
1015
+ metadata = process_excel_metadata(excel_path)
1016
+ except ExcelSecurityError as e:
1017
+ # 보안 위반 - 파일이 너무 크거나 행이 너무 많음 등
1018
+ log_security_violation(e)
1019
+ except ExcelParsingError as e:
1020
+ # 파싱 실패 - 손상된 파일, 잘못된 형식 등
1021
+ log_parsing_error(e)
1022
+ ```
1023
+
1024
+ #### 오류 복구
1025
+
1026
+ - **우아한 성능 저하**: Excel이 실패하면 빈 메타데이터로 처리 계속
1027
+ - **상세 로깅**: 다양한 실패 유형에 대한 특정 오류 코드
1028
+ - **경로 검증**: 매개변수 처리 중 포괄적인 검증
1029
+ - **대체 동작**: 메타데이터를 처리할 수 없을 때 스마트 기본값
1030
+
1031
+ ## 파일 구성
1032
+
1033
+ 업로드 시스템은 파일을 타입에 따라 자동으로 구성합니다:
1034
+
1035
+ ### 타입 감지
1036
+
1037
+ 파일은 다음을 기반으로 분류됩니다:
430
1038
 
431
- 복잡한 디렉터리 구조를 효율적으로 처리합니다:
1039
+ - 파일 확장자 패턴
1040
+ - MIME 타입 감지
1041
+ - 내용 분석
1042
+ - 사용자 정의 타입 규칙
1043
+
1044
+ ### 디렉토리 구조
432
1045
 
433
1046
  ```
434
- source_directory/
435
- ├── excel_files/
436
- │ ├── data1.xlsx
437
- │ └── data2.xlsx
1047
+ upload_output/
438
1048
  ├── images/
439
- │ ├── photo1.jpg
440
- │ └── photo2.png
441
- └── documents/
442
- ├── report.pdf
443
- └── notes.txt
1049
+ │ ├── image1.jpg
1050
+ │ └── image2.png
1051
+ ├── documents/
1052
+ ├── report.pdf
1053
+ └── data.xlsx
1054
+ └── videos/
1055
+ └── presentation.mp4
444
1056
  ```
445
1057
 
446
1058
  ### 배치 처리
447
1059
 
448
- 파일들을 효율적인 배치로 그룹화합니다:
1060
+ 파일은 구성 가능한 배치로 처리됩니다:
449
1061
 
450
- - **유형별 그룹화**: 동일한 처리 로직을 사용하는 파일들을 함께 처리
451
- - **크기 기반 분할**: 메모리 사용량을 제어하기 위해 큰 파일들을 별도로 처리
452
- - **병렬 처리**: 독립적인 배치들을 동시에 처리
1062
+ ```python
1063
+ # 배치 크기 구성
1064
+ params = {
1065
+ "creating_data_unit_batch_size": 100,
1066
+ "use_async_upload": True
1067
+ }
1068
+ ```
453
1069
 
454
1070
  ## 진행률 추적 및 메트릭
455
1071
 
456
1072
  ### 진행률 카테고리
457
1073
 
458
- 업로드 과정에서 다음 진행률 카테고리를 추적합니다:
1074
+ 업로드 액션은 가지 주요 단계에서 진행률을 추적합니다:
459
1075
 
460
- ```python
461
- # 진행률 카테고리
462
- PROGRESS_CATEGORIES = {
463
- 'file_discovery': '파일 발견',
464
- 'validation': '검증',
465
- 'excel_processing': 'Excel 처리',
466
- 'metadata_extraction': '메타데이터 추출',
467
- 'upload_preparation': '업로드 준비',
468
- 'data_upload': '데이터 업로드',
469
- 'finalization': '완료 처리'
470
- }
471
- ```
1076
+ | 카테고리 | 비율 | 설명 |
1077
+ | --------------------- | ---- | ------------------------ |
1078
+ | `analyze_collection` | 2% | 매개변수 검증 및 설정 |
1079
+ | `upload_data_files` | 38% | 파일 업로드 처리 |
1080
+ | `generate_data_units` | 60% | 데이터 단위 생성 및 완료 |
472
1081
 
473
1082
  ### 메트릭 수집
474
1083
 
475
- 상세한 성능 메트릭을 수집하여 분석에 활용할 수 있습니다:
1084
+ 모니터링을 위해 실시간 메트릭이 수집됩니다:
476
1085
 
477
1086
  ```python
478
- # 수집되는 메트릭
479
- metrics = {
480
- "processing_time": {
481
- "file_discovery": 2.5, #
482
- "excel_processing": 15.2,
483
- "upload": 45.8
1087
+ metrics_categories = {
1088
+ 'data_files': {
1089
+ 'stand_by': 0, # 처리 대기 중인 파일
1090
+ 'failed': 0, # 업로드 실패한 파일
1091
+ 'success': 0, # 성공적으로 업로드된 파일
484
1092
  },
485
- "file_statistics": {
486
- "total_files": 150,
487
- "excel_files": 25,
488
- "image_files": 100,
489
- "other_files": 25
1093
+ 'data_units': {
1094
+ 'stand_by': 0, # 생성 대기 중인 단위
1095
+ 'failed': 0, # 생성 실패한 단위
1096
+ 'success': 0, # 성공적으로 생성된 단위
490
1097
  },
491
- "data_volume": {
492
- "total_size_mb": 2048,
493
- "excel_data_mb": 512,
494
- "media_data_mb": 1536
495
- }
496
1098
  }
497
1099
  ```
498
1100
 
499
1101
  ## 타입 안전 로깅
500
1102
 
1103
+ 업로드 시스템은 일관성을 위해 열거형 기반 로깅을 사용합니다:
1104
+
501
1105
  ### 로그 코드
502
1106
 
503
- 모든 로깅 작업에 타입 안전 코드를 사용합니다:
1107
+ ```python
1108
+ class LogCode(str, Enum):
1109
+ VALIDATION_FAILED = 'VALIDATION_FAILED'
1110
+ NO_FILES_FOUND = 'NO_FILES_FOUND'
1111
+ EXCEL_SECURITY_VIOLATION = 'EXCEL_SECURITY_VIOLATION'
1112
+ EXCEL_PARSING_ERROR = 'EXCEL_PARSING_ERROR'
1113
+ FILES_DISCOVERED = 'FILES_DISCOVERED'
1114
+ UPLOADING_DATA_FILES = 'UPLOADING_DATA_FILES'
1115
+ GENERATING_DATA_UNITS = 'GENERATING_DATA_UNITS'
1116
+ IMPORT_COMPLETED = 'IMPORT_COMPLETED'
1117
+ ```
1118
+
1119
+ ### 로깅 사용법
504
1120
 
505
1121
  ```python
506
- # 검증 관련 로그 코드
507
- VALIDATION_FAILED = "UPLOAD_001"
508
- STORAGE_VALIDATION_FAILED = "UPLOAD_002"
509
- COLLECTION_VALIDATION_FAILED = "UPLOAD_003"
1122
+ # 기본 로깅
1123
+ run.log_message_with_code(LogCode.FILES_DISCOVERED, file_count)
1124
+
1125
+ # 사용자 정의 레벨로
1126
+ run.log_message_with_code(
1127
+ LogCode.EXCEL_SECURITY_VIOLATION,
1128
+ filename,
1129
+ level=Context.DANGER
1130
+ )
1131
+
1132
+ # 업로드 특정 이벤트
1133
+ run.log_upload_event(LogCode.UPLOADING_DATA_FILES, batch_size)
1134
+ ```
1135
+
1136
+ ## 마이그레이션 가이드
1137
+
1138
+ ### 레거시에서 리팩토링된 아키텍처로
1139
+
1140
+ 업로드 액션은 **100% 하위 호환성**을 유지하면서 현대적인 디자인 패턴을 사용하여 리팩토링되었습니다. 기존 코드는 변경 없이 계속 작동합니다.
1141
+
1142
+ #### 주요 변경 사항
1143
+
1144
+ **이전 (레거시 모놀리식):**
1145
+
1146
+ - 모든 로직을 포함한 단일 900+ 줄 액션 클래스
1147
+ - 검증, 파일 발견 등에 대한 하드코딩된 동작
1148
+ - 확장성이나 커스터마이제이션 옵션 없음
1149
+ - 전반에 걸친 수동 오류 처리
510
1150
 
511
- # 파일 처리 관련 로그 코드
512
- NO_FILES_FOUND = "UPLOAD_004"
513
- FILES_DISCOVERED = "UPLOAD_005"
514
- FILE_FILTERED = "UPLOAD_006"
1151
+ **이후 (전략/파사드 패턴):**
515
1152
 
516
- # Excel 처리 관련 로그 코드
517
- EXCEL_SECURITY_VIOLATION = "UPLOAD_010"
518
- EXCEL_PARSING_ERROR = "UPLOAD_011"
519
- EXCEL_METADATA_EXTRACTED = "UPLOAD_012"
1153
+ - 8개 워크플로우 단계로 명확한 관심사 분리
1154
+ - 다양한 동작을 위한 플러그형 전략
1155
+ - 사용자 정의 구현을 위한 확장 가능한 아키텍처
1156
+ - 자동 롤백 및 포괄적인 오류 처리
520
1157
 
521
- # 진행률 관련 로그 코드
522
- UPLOADING_DATA_FILES = "UPLOAD_020"
523
- GENERATING_DATA_UNITS = "UPLOAD_021"
524
- UPLOAD_PROGRESS = "UPLOAD_022"
1158
+ #### 하위 호환성
1159
+
1160
+ ```python
1161
+ # 레거시 사용법은 동일하게 작동합니다
1162
+ from synapse_sdk.plugins.categories.upload.actions.upload.action import UploadAction
1163
+
1164
+ params = {
1165
+ "name": "My Upload",
1166
+ "path": "/data/files",
1167
+ "storage": 1,
1168
+ "data_collection": 5 # 'collection'에서 'data_collection'으로 변경
1169
+ }
1170
+
1171
+ action = UploadAction(params=params, plugin_config=config)
1172
+ result = action.start() # 이전과 동일하게 작동
525
1173
  ```
526
1174
 
527
- ### 로깅 사용법
1175
+ #### 향상된 기능
528
1176
 
529
- 구조화된 로깅으로 일관된 메시지 형식을 보장합니다:
1177
+ 리팩토링된 아키텍처는 새로운 기능을 제공합니다:
530
1178
 
531
1179
  ```python
532
- # 로그 메시지 예제
533
- run.log_message_with_code(
534
- LogCode.FILES_DISCOVERED,
535
- file_count=25,
536
- directory=source_path
537
- )
538
- # 출력: [UPLOAD_005] INFO: 25개 파일 발견: /path/to/files
1180
+ # 자세한 워크플로우 정보 가져오기
1181
+ action = UploadAction(params=params, plugin_config=config)
1182
+ workflow_info = action.get_workflow_summary()
1183
+ print(f"Configured with {workflow_info['step_count']} steps")
1184
+ print(f"Available strategies: {workflow_info['available_strategies']}")
1185
+
1186
+ # 실행하고 자세한 결과 가져오기
1187
+ result = action.start()
1188
+ print(f"Success: {result['success']}")
1189
+ print(f"Uploaded files: {result['uploaded_files_count']}")
1190
+ print(f"Generated data units: {result['generated_data_units_count']}")
1191
+ print(f"Errors: {result['errors']}")
1192
+ print(f"Metrics: {result['metrics']}")
539
1193
  ```
540
1194
 
541
- ## 고급 사용법
1195
+ #### 매개변수 변경
1196
+
1197
+ 하나의 매개변수 이름만 변경되었습니다:
1198
+
1199
+ | 레거시 | 리팩토링 | 상태 |
1200
+ | ------------------ | ----------------- | ------------- |
1201
+ | `collection` | `data_collection` | **필수 변경** |
1202
+ | 기타 모든 매개변수 | 변경 없음 | 완전 호환 |
1203
+
1204
+ #### 마이그레이션의 이점
1205
+
1206
+ - **더 나은 오류 처리**: 실패 시 자동 롤백
1207
+ - **진행률 추적**: 워크플로우 단계 전반의 자세한 진행률 메트릭
1208
+ - **확장성**: 사용자 정의 전략 및 단계 추가
1209
+ - **테스트**: 모킹 친화적인 아키텍처로 더 나은 테스트 가능성
1210
+ - **유지보수성**: 명확한 관심사 분리
1211
+ - **성능**: 더 효율적인 리소스 관리
1212
+
1213
+ ## 사용 예제
542
1214
 
543
- ### 사용자 정의 파일 필터링
1215
+ ### 기본 파일 업로드 (리팩토링된 아키텍처)
544
1216
 
545
1217
  ```python
546
- from synapse_sdk.plugins.categories.upload.actions import upload
1218
+ from synapse_sdk.plugins.categories.upload.actions.upload.action import UploadAction
1219
+
1220
+ # 새 아키텍처로 기본 업로드 구성
1221
+ params = {
1222
+ "name": "Dataset Upload",
1223
+ "description": "Training dataset for ML model",
1224
+ "path": "/data/training_images",
1225
+ "storage": 1,
1226
+ "data_collection": 5, # 참고: 'collection' 대신 'data_collection'
1227
+ "is_recursive": True,
1228
+ "max_file_size_mb": 100
1229
+ }
547
1230
 
548
- params = upload.UploadParams(
549
- source_path="/path/to/mixed/files",
550
- storage_id="storage123",
551
- collection_id="collection456",
552
- project_id="project789",
553
- include_patterns=["*.xlsx", "*.jpg", "*.png"],
554
- exclude_patterns=["*temp*", "*backup*"]
1231
+ action = UploadAction(
1232
+ params=params,
1233
+ plugin_config=plugin_config
555
1234
  )
556
1235
 
557
- action = upload.UploadAction(params=params)
558
- result = action.run()
1236
+ # 자동 단계 기반 워크플로우 및 롤백으로 실행
1237
+ result = action.start()
1238
+
1239
+ # 향상된 결과 정보
1240
+ print(f"Upload successful: {result['success']}")
1241
+ print(f"Uploaded {result['uploaded_files_count']} files")
1242
+ print(f"Generated {result['generated_data_units_count']} data units")
1243
+ print(f"Workflow errors: {result['errors']}")
1244
+
1245
+ # 자세한 메트릭에 액세스
1246
+ workflow_metrics = result['metrics'].get('workflow', {})
1247
+ print(f"Total steps executed: {workflow_metrics.get('current_step', 0)}")
1248
+ print(f"Progress completed: {workflow_metrics.get('progress_percentage', 0)}%")
559
1249
  ```
560
1250
 
561
- ### 배치 크기 최적화
1251
+ ### 진행률 추적이 포함된 Excel 메타데이터 업로드
562
1252
 
563
1253
  ```python
564
- # 대용량 파일에 대한 작은 배치 크기
565
- params = upload.UploadParams(
566
- source_path="/path/to/large/files",
567
- storage_id="storage123",
568
- collection_id="collection456",
569
- project_id="project789",
570
- batch_size=50, # 메모리 사용량 감소
571
- max_workers=2 # 리소스 사용량 감소
1254
+ # Excel 메타데이터 진행률 모니터링으로 업로드
1255
+ params = {
1256
+ "name": "Annotated Dataset Upload",
1257
+ "path": "/data/images",
1258
+ "storage": 1,
1259
+ "data_collection": 5,
1260
+ "excel_metadata_path": "/data/metadata.xlsx",
1261
+ "is_recursive": False,
1262
+ "creating_data_unit_batch_size": 50
1263
+ }
1264
+
1265
+ action = UploadAction(
1266
+ params=params,
1267
+ plugin_config=plugin_config
572
1268
  )
1269
+
1270
+ # 실행 전 워크플로우 요약 가져오기
1271
+ workflow_info = action.get_workflow_summary()
1272
+ print(f"Workflow configured with {workflow_info['step_count']} steps")
1273
+ print(f"Total progress weight: {workflow_info['total_progress_weight']}")
1274
+ print(f"Steps: {workflow_info['steps']}")
1275
+
1276
+ # 향상된 오류 처리로 실행
1277
+ try:
1278
+ result = action.start()
1279
+ if result['success']:
1280
+ print("Upload completed successfully!")
1281
+ print(f"Files: {result['uploaded_files_count']}")
1282
+ print(f"Data units: {result['generated_data_units_count']}")
1283
+ else:
1284
+ print("Upload failed with errors:")
1285
+ for error in result['errors']:
1286
+ print(f" - {error}")
1287
+ except Exception as e:
1288
+ print(f"Upload action failed: {e}")
573
1289
  ```
574
1290
 
575
- ### Excel 보안 구성
1291
+ ### 사용자 정의 전략 업로드
576
1292
 
577
1293
  ```python
578
- # 엄격한 보안 설정
579
- excel_config = upload.ExcelSecurityConfig(
580
- check_macros=True,
581
- check_external_links=True,
582
- max_file_size=25, # 25MB 제한
583
- allowed_extensions=[".xlsx"] # .xls 파일 제외
584
- )
1294
+ from synapse_sdk.plugins.categories.upload.actions.upload.action import UploadAction
1295
+ from my_custom_strategies import CustomValidationStrategy
1296
+
1297
+ # 사용자 정의 팩토리로 액션 생성
1298
+ class CustomUploadAction(UploadAction):
1299
+ def _configure_strategies(self, context=None):
1300
+ strategies = super()._configure_strategies(context)
1301
+
1302
+ # 사용자 정의 검증으로 오버라이드
1303
+ if self.params.get('use_strict_validation'):
1304
+ strategies['validation'] = CustomValidationStrategy()
1305
+
1306
+ return strategies
1307
+
1308
+ # 사용자 정의 액션 사용
1309
+ params = {
1310
+ "name": "Strict Validation Upload",
1311
+ "path": "/data/sensitive_files",
1312
+ "storage": 1,
1313
+ "data_collection": 5,
1314
+ "use_strict_validation": True,
1315
+ "max_file_size_mb": 10 # 더 엄격한 제한
1316
+ }
585
1317
 
586
- params = upload.UploadParams(
587
- source_path="/path/to/excel/files",
588
- storage_id="storage123",
589
- collection_id="collection456",
590
- project_id="project789",
591
- excel_security_config=excel_config
1318
+ action = CustomUploadAction(
1319
+ params=params,
1320
+ plugin_config=plugin_config
592
1321
  )
1322
+
1323
+ result = action.start()
593
1324
  ```
594
1325
 
595
- ## 오류 처리
1326
+ ### 사용자 정의 구성을 포함한 배치 처리
596
1327
 
597
- 업로드 플러그인은 다양한 오류 상황에 대한 포괄적인 오류 처리를 제공합니다:
1328
+ ```python
1329
+ import os
1330
+
1331
+ # Excel 처리 제한 구성
1332
+ os.environ['EXCEL_MAX_FILE_SIZE_MB'] = '20'
1333
+ os.environ['EXCEL_MAX_ROWS'] = '20000'
1334
+
1335
+ # 사용자 정의 설정을 포함한 대량 배치 업로드
1336
+ params = {
1337
+ "name": "Large Batch Upload",
1338
+ "path": "/data/large_dataset",
1339
+ "storage": 2,
1340
+ "data_collection": 10,
1341
+ "is_recursive": True,
1342
+ "max_file_size_mb": 500,
1343
+ "creating_data_unit_batch_size": 200,
1344
+ "use_async_upload": True
1345
+ }
1346
+
1347
+ action = UploadAction(
1348
+ params=params,
1349
+ plugin_config=plugin_config
1350
+ )
1351
+
1352
+ # 진행률 모니터링으로 실행
1353
+ result = action.start()
1354
+
1355
+ # 결과 분석
1356
+ print(f"Batch upload summary:")
1357
+ print(f" Success: {result['success']}")
1358
+ print(f" Files processed: {result['uploaded_files_count']}")
1359
+ print(f" Data units created: {result['generated_data_units_count']}")
1360
+
1361
+ # 카테고리별 메트릭 확인
1362
+ metrics = result['metrics']
1363
+ if 'data_files' in metrics:
1364
+ files_metrics = metrics['data_files']
1365
+ print(f" Files - Success: {files_metrics.get('success', 0)}")
1366
+ print(f" Files - Failed: {files_metrics.get('failed', 0)}")
1367
+
1368
+ if 'data_units' in metrics:
1369
+ units_metrics = metrics['data_units']
1370
+ print(f" Units - Success: {units_metrics.get('success', 0)}")
1371
+ print(f" Units - Failed: {units_metrics.get('failed', 0)}")
1372
+ ```
598
1373
 
599
- ### 일반적인 예외
1374
+ ### 오류 처리 및 롤백
600
1375
 
601
1376
  ```python
602
- from synapse_sdk.plugins.categories.upload.actions import upload
1377
+ # 자동 롤백을 포함한 향상된 오류 처리 시연
1378
+ params = {
1379
+ "name": "Error Recovery Example",
1380
+ "path": "/data/problematic_files",
1381
+ "storage": 1,
1382
+ "data_collection": 5,
1383
+ "is_recursive": True
1384
+ }
1385
+
1386
+ action = UploadAction(
1387
+ params=params,
1388
+ plugin_config=plugin_config
1389
+ )
603
1390
 
604
1391
  try:
605
- params = upload.UploadParams(
606
- source_path="/invalid/path",
607
- storage_id="storage123",
608
- collection_id="collection456",
609
- project_id="project789"
610
- )
611
-
612
- action = upload.UploadAction(params=params)
613
- result = action.run()
614
-
615
- except upload.UploadValidationError as e:
616
- print(f"검증 오류: {e}")
617
- except upload.UploadExecutionError as e:
618
- print(f"실행 오류: {e}")
619
- except upload.UploadSecurityError as e:
620
- print(f"보안 오류: {e}")
621
- except upload.ExcelParsingError as e:
622
- print(f"Excel 파싱 오류: {e}")
1392
+ result = action.start()
1393
+
1394
+ if not result['success']:
1395
+ print("Upload failed, but cleanup was automatic:")
1396
+ print(f"Errors encountered: {len(result['errors'])}")
1397
+ for i, error in enumerate(result['errors'], 1):
1398
+ print(f" {i}. {error}")
1399
+
1400
+ # 롤백이 수행되었는지 확인 (오케스트레이터 내부를 통해)
1401
+ workflow_metrics = result['metrics'].get('workflow', {})
1402
+ current_step = workflow_metrics.get('current_step', 0)
1403
+ total_steps = workflow_metrics.get('total_steps', 0)
1404
+ print(f"Workflow stopped at step {current_step} of {total_steps}")
1405
+
1406
+ except Exception as e:
1407
+ print(f"Critical upload failure: {e}")
1408
+ # 예외 전파 전에 롤백이 자동으로 수행됨
623
1409
  ```
624
1410
 
1411
+ ## 오류 처리
1412
+
625
1413
  ### 예외 타입
626
1414
 
627
- #### ExcelSecurityError
1415
+ 업로드 시스템은 특정 예외를 정의합니다:
628
1416
 
629
- Excel 파일이 보안 제약 조건을 위반할 때 발생합니다.
1417
+ ```python
1418
+ # 보안 위반
1419
+ try:
1420
+ action.start()
1421
+ except ExcelSecurityError as e:
1422
+ print(f"Excel security violation: {e}")
630
1423
 
631
- **일반적인 원인:**
1424
+ # 파싱 오류
1425
+ except ExcelParsingError as e:
1426
+ print(f"Excel parsing failed: {e}")
632
1427
 
633
- - 파일 크기가 제한을 초과
634
- - 메모리 사용량 추정치가 너무 높음
635
- - 콘텐츠 보안 위반
1428
+ # 일반 업로드 오류
1429
+ except ActionError as e:
1430
+ print(f"Upload action failed: {e}")
1431
+ ```
636
1432
 
637
- #### ExcelParsingError
1433
+ ### 검증 오류
638
1434
 
639
- Excel 파일을 파싱할 없을 때 발생합니다.
1435
+ 매개변수 검증은 자세한 오류 메시지를 제공합니다:
640
1436
 
641
- **일반적인 원인:**
1437
+ ```python
1438
+ from pydantic import ValidationError
642
1439
 
643
- - 파일 형식 손상
644
- - 잘못된 Excel 구조
645
- - 필수 누락
646
- - 콘텐츠 파싱 실패
1440
+ try:
1441
+ params = UploadParams(**invalid_params)
1442
+ except ValidationError as e:
1443
+ for error in e.errors():
1444
+ print(f"Field {error['loc']}: {error['msg']}")
1445
+ ```
647
1446
 
648
- ### 부분 실패 처리
1447
+ ## API 레퍼런스
649
1448
 
650
- ```python
651
- result = action.run()
1449
+ ### 핵심 컴포넌트
1450
+
1451
+ #### UploadAction
1452
+
1453
+ 파일 처리 작업을 위한 전략 및 파사드 패턴을 구현하는 메인 업로드 액션 클래스입니다.
1454
+
1455
+ **클래스 속성:**
1456
+
1457
+ - `name = 'upload'` - 액션 식별자
1458
+ - `category = PluginCategory.UPLOAD` - 플러그인 카테고리
1459
+ - `method = RunMethod.JOB` - 실행 방법
1460
+ - `run_class = UploadRun` - 전문 실행 관리
1461
+ - `params_model = UploadParams` - 매개변수 검증 모델
1462
+ - `strategy_factory: StrategyFactory` - 전략 구현 생성
1463
+ - `step_registry: StepRegistry` - 워크플로우 단계 관리
1464
+
1465
+ **주요 메서드:**
652
1466
 
653
- if result.status == upload.UploadStatus.FAILED:
654
- print(f"실패한 파일: {result.failed_files}")
655
- print(f"성공한 파일: {result.successful_files}")
1467
+ - `start() -> Dict[str, Any]` - 오케스트레이션된 업로드 워크플로우 실행
1468
+ - `get_workflow_summary() -> Dict[str, Any]` - 구성된 워크플로우 요약 가져오기
1469
+ - `_configure_workflow() -> None` - 실행 순서로 워크플로우 단계 등록
1470
+ - `_configure_strategies(context=None) -> Dict[str, Any]` - 전략 인스턴스 생성
656
1471
 
657
- # 실패한 파일만 재시도
658
- retry_params = upload.UploadParams(
659
- source_path=result.failed_files,
660
- storage_id=params.storage_id,
661
- collection_id=params.collection_id,
662
- project_id=params.project_id
663
- )
1472
+ **진행률 카테고리:**
1473
+
1474
+ ```python
1475
+ progress_categories = {
1476
+ 'analyze_collection': {'proportion': 2},
1477
+ 'upload_data_files': {'proportion': 38},
1478
+ 'generate_data_units': {'proportion': 60},
1479
+ }
664
1480
  ```
665
1481
 
666
- ## 성능 고려사항
1482
+ #### UploadOrchestrator
1483
+
1484
+ 자동 롤백을 포함한 완전한 업로드 워크플로우를 조정하는 파사드 컴포넌트입니다.
1485
+
1486
+ **클래스 속성:**
1487
+
1488
+ - `context: UploadContext` - 워크플로우 전반의 공유 상태
1489
+ - `step_registry: StepRegistry` - 워크플로우 단계 레지스트리
1490
+ - `strategies: Dict[str, Any]` - 전략 구현
1491
+ - `executed_steps: List[BaseStep]` - 성공적으로 실행된 단계
1492
+ - `current_step_index: int` - 워크플로우의 현재 위치
1493
+ - `rollback_executed: bool` - 롤백이 수행되었는지 여부
1494
+
1495
+ **주요 메서드:**
1496
+
1497
+ - `execute() -> Dict[str, Any]` - 오류 처리를 포함한 완전한 워크플로우 실행
1498
+ - `get_workflow_summary() -> Dict[str, Any]` - 실행 요약 및 메트릭 가져오기
1499
+ - `get_executed_steps() -> List[BaseStep]` - 성공적으로 실행된 단계 목록 가져오기
1500
+ - `is_rollback_executed() -> bool` - 롤백이 수행되었는지 확인
1501
+ - `_execute_step(step: BaseStep) -> StepResult` - 개별 워크플로우 단계 실행
1502
+ - `_handle_step_failure(step: BaseStep, error: Exception) -> None` - 단계 실패 처리
1503
+ - `_rollback_executed_steps() -> None` - 실행된 단계를 역순으로 롤백
1504
+
1505
+ #### UploadContext
1506
+
1507
+ 워크플로우 컴포넌트 간의 공유 상태 및 통신을 유지하는 컨텍스트 객체입니다.
1508
+
1509
+ **상태 속성:**
1510
+
1511
+ - `params: Dict` - 업로드 매개변수
1512
+ - `run: UploadRun` - 실행 관리 인스턴스
1513
+ - `client: Any` - 외부 작업을 위한 API 클라이언트
1514
+ - `storage: Any` - 스토리지 구성 객체
1515
+ - `pathlib_cwd: Path` - 현재 작업 디렉토리 경로
1516
+ - `metadata: Dict[str, Dict[str, Any]]` - 파일 메타데이터 매핑
1517
+ - `file_specifications: Dict[str, Any]` - 데이터 컬렉션 파일 사양
1518
+ - `organized_files: List[Dict[str, Any]]` - 구성된 파일 정보
1519
+ - `uploaded_files: List[Dict[str, Any]]` - 성공적으로 업로드된 파일
1520
+ - `data_units: List[Dict[str, Any]]` - 생성된 데이터 단위
1521
+
1522
+ **진행률 및 메트릭:**
1523
+
1524
+ - `metrics: Dict[str, Any]` - 워크플로우 메트릭 및 통계
1525
+ - `errors: List[str]` - 축적된 오류 메시지
1526
+ - `step_results: List[StepResult]` - 실행된 단계의 결과
1527
+
1528
+ **전략 및 롤백:**
1529
+
1530
+ - `strategies: Dict[str, Any]` - 주입된 전략 구현
1531
+ - `rollback_data: Dict[str, Any]` - 롤백 작업을 위한 데이터
1532
+
1533
+ **주요 메서드:**
1534
+
1535
+ - `update(result: StepResult) -> None` - 단계 결과로 컨텍스트 업데이트
1536
+ - `get_result() -> Dict[str, Any]` - 최종 결과 딕셔너리 생성
1537
+ - `has_errors() -> bool` - 축적된 오류 확인
1538
+ - `get_last_step_result() -> Optional[StepResult]` - 가장 최근 단계 결과 가져오기
1539
+ - `update_metrics(category: str, metrics: Dict[str, Any]) -> None` - 메트릭 업데이트
1540
+ - `add_error(error: str) -> None` - 컨텍스트에 오류 추가
1541
+ - `get_param(key: str, default: Any = None) -> Any` - 기본값이 있는 매개변수 가져오기
667
1542
 
668
- ### 메모리 사용량
1543
+ #### StepRegistry
669
1544
 
670
- - 대용량 Excel 파일의 경우 작은 `batch_size` 사용
671
- - 메모리 집약적인 작업을 위해 `max_workers` 제한
672
- - 스트리밍 처리를 위해 청크 업로드 사용
1545
+ 워크플로우 단계의 컬렉션 실행 순서를 관리하는 레지스트리입니다.
673
1546
 
674
- ### 네트워크 최적화
1547
+ **속성:**
675
1548
 
676
- - 네트워크 지연 시간이 높은 환경에서 배치 크기 증가
677
- - 신뢰할 수 없는 연결에 대해 재시도 메커니즘 구현
678
- - 대용량 파일에 대해 체크섬 검증 사용
1549
+ - `_steps: List[BaseStep]` - 실행 순서로 등록된 워크플로우 단계
679
1550
 
680
- ### 병렬 처리
1551
+ **주요 메서드:**
1552
+
1553
+ - `register(step: BaseStep) -> None` - 워크플로우 단계 등록
1554
+ - `get_steps() -> List[BaseStep]` - 순서대로 모든 등록된 단계 가져오기
1555
+ - `get_total_progress_weight() -> float` - 총 진행률 가중치 계산
1556
+ - `clear() -> None` - 모든 등록된 단계 지우기
1557
+ - `__len__() -> int` - 등록된 단계 수 가져오기
1558
+
1559
+ #### StrategyFactory
1560
+
1561
+ 매개변수를 기반으로 적절한 전략 구현을 생성하는 팩토리 컴포넌트입니다.
1562
+
1563
+ **주요 메서드:**
1564
+
1565
+ - `create_validation_strategy(params: Dict, context=None) -> BaseValidationStrategy` - 검증 전략 생성
1566
+ - `create_file_discovery_strategy(params: Dict, context=None) -> BaseFileDiscoveryStrategy` - 파일 발견 전략 생성
1567
+ - `create_metadata_strategy(params: Dict, context=None) -> BaseMetadataStrategy` - 메타데이터 처리 전략 생성
1568
+ - `create_upload_strategy(params: Dict, context: UploadContext) -> BaseUploadStrategy` - 업로드 전략 생성 (컨텍스트 필요)
1569
+ - `create_data_unit_strategy(params: Dict, context: UploadContext) -> BaseDataUnitStrategy` - 데이터 단위 전략 생성 (컨텍스트 필요)
1570
+ - `get_available_strategies() -> Dict[str, List[str]]` - 사용 가능한 전략 타입 및 구현 가져오기
1571
+
1572
+ ### 워크플로우 단계
1573
+
1574
+ #### BaseStep (추상)
1575
+
1576
+ 공통 인터페이스 및 유틸리티를 제공하는 모든 워크플로우 단계의 기본 클래스입니다.
1577
+
1578
+ **추상 속성:**
1579
+
1580
+ - `name: str` - 고유한 단계 식별자
1581
+ - `progress_weight: float` - 진행률 계산을 위한 가중치 (합은 1.0이어야 함)
1582
+
1583
+ **추상 메서드:**
1584
+
1585
+ - `execute(context: UploadContext) -> StepResult` - 단계 로직 실행
1586
+ - `can_skip(context: UploadContext) -> bool` - 단계를 건너뛸 수 있는지 결정
1587
+ - `rollback(context: UploadContext) -> None` - 단계 작업 롤백
1588
+
1589
+ **유틸리티 메서드:**
1590
+
1591
+ - `create_success_result(data: Dict = None) -> StepResult` - 성공 결과 생성
1592
+ - `create_error_result(error: str, original_exception: Exception = None) -> StepResult` - 오류 결과 생성
1593
+ - `create_skip_result() -> StepResult` - 건너뛰기 결과 생성
1594
+
1595
+ #### StepResult
1596
+
1597
+ 워크플로우 단계 실행에서 반환되는 결과 객체입니다.
1598
+
1599
+ **속성:**
1600
+
1601
+ - `success: bool` - 단계가 성공적으로 실행되었는지 여부
1602
+ - `data: Dict[str, Any]` - 단계 결과 데이터
1603
+ - `error: str` - 단계가 실패한 경우 오류 메시지
1604
+ - `rollback_data: Dict[str, Any]` - 롤백에 필요한 데이터
1605
+ - `skipped: bool` - 단계가 건너뛰어졌는지 여부
1606
+ - `original_exception: Optional[Exception]` - 디버깅을 위한 원본 예외
1607
+ - `timestamp: datetime` - 실행 타임스탬프
1608
+
1609
+ **사용법:**
681
1610
 
682
1611
  ```python
683
- # Ray를 사용한 분산 처리
684
- params = upload.UploadParams(
685
- source_path="/path/to/massive/dataset",
686
- storage_id="storage123",
687
- collection_id="collection456",
688
- project_id="project789",
689
- execution_method="ray", # Ray 클러스터 사용
690
- max_workers=10
691
- )
1612
+ # 불린 평가
1613
+ if step_result:
1614
+ # 단계가 성공함
1615
+ process_success(step_result.data)
692
1616
  ```
693
1617
 
694
- ## 로깅 및 모니터링
1618
+ #### 구체적인 단계
695
1619
 
696
- ### 로그 코드 사용
1620
+ **InitializeStep** (`name: "initialize"`, `weight: 0.05`)
697
1621
 
698
- 업로드 플러그인은 모든 작업에 대해 구조화된 로깅을 제공합니다:
1622
+ - 스토리지 연결 pathlib 작업 디렉토리 설정
1623
+ - 기본 업로드 전제조건 검증
699
1624
 
700
- ```python
701
- from synapse_sdk.plugins.categories.upload.actions import upload
1625
+ **ProcessMetadataStep** (`name: "process_metadata"`, `weight: 0.05`)
702
1626
 
703
- # 사용자 정의 로깅 핸들러
704
- def custom_log_handler(log_code: upload.LogCode, message: str, level: str):
705
- print(f"[{log_code.value}] {level}: {message}")
1627
+ - 제공된 Excel 메타데이터 처리
1628
+ - 메타데이터 보안 형식 검증
706
1629
 
707
- # 로그 핸들러와 함께 액션 실행
708
- action = upload.UploadAction(params=params)
709
- action.run.add_log_handler(custom_log_handler)
710
- result = action.run()
711
- ```
1630
+ **AnalyzeCollectionStep** (`name: "analyze_collection"`, `weight: 0.05`)
712
1631
 
713
- ### 메트릭 수집
1632
+ - 데이터 컬렉션 파일 사양 검색 및 검증
1633
+ - 파일 구성 규칙 설정
714
1634
 
715
- ```python
716
- # 업로드 메트릭 수집
717
- result = action.run()
1635
+ **OrganizeFilesStep** (`name: "organize_files"`, `weight: 0.10`)
718
1636
 
719
- metrics = {
720
- "total_files": result.total_files,
721
- "successful_files": len(result.successful_files),
722
- "failed_files": len(result.failed_files),
723
- "total_size": result.total_size_bytes,
724
- "duration": result.duration_seconds,
725
- "throughput": result.total_size_bytes / result.duration_seconds
726
- }
1637
+ - 파일 발견 전략을 사용한 파일 발견
1638
+ - 타입 및 사양별 파일 구성
727
1639
 
728
- print(f"업로드 메트릭: {metrics}")
729
- ```
1640
+ **ValidateFilesStep** (`name: "validate_files"`, `weight: 0.05`)
730
1641
 
731
- ## 테스팅
1642
+ - 검증 전략을 사용한 파일 검증
1643
+ - 보안 및 내용 검사 수행
732
1644
 
733
- 업로드 플러그인에는 포괄적인 테스트 스위트가 포함되어 있습니다:
1645
+ **UploadFilesStep** (`name: "upload_files"`, `weight: 0.30`)
734
1646
 
735
- ### 단위 테스트 실행
1647
+ - 업로드 전략을 사용한 파일 업로드
1648
+ - 배치 및 진행률 추적 처리
736
1649
 
737
- ```bash
738
- # 모든 업로드 테스트 실행
739
- python -m pytest tests/plugins/upload/ -v
1650
+ **GenerateDataUnitsStep** (`name: "generate_data_units"`, `weight: 0.35`)
740
1651
 
741
- # 특정 테스트 파일 실행
742
- python -m pytest tests/plugins/upload/test_action.py -v
1652
+ - 데이터 단위 전략을 사용한 데이터 단위 생성
1653
+ - 업로드된 파일을 데이터 단위에 연결
743
1654
 
744
- # 커버리지와 함께 테스트 실행
745
- python -m pytest tests/plugins/upload/ --cov=synapse_sdk.plugins.categories.upload
746
- ```
1655
+ **CleanupStep** (`name: "cleanup"`, `weight: 0.05`)
747
1656
 
748
- ### 테스트 케이스 예제
1657
+ - 임시 리소스 및 파일 정리
1658
+ - 최종 검증 수행
749
1659
 
750
- ```python
751
- import pytest
752
- from synapse_sdk.plugins.categories.upload.actions import upload
1660
+ ### 전략 기본 클래스
753
1661
 
754
- def test_upload_params_validation():
755
- """업로드 매개변수 검증을 테스트합니다."""
756
- # 유효한 매개변수
757
- params = upload.UploadParams(
758
- source_path="/valid/path",
759
- storage_id="storage123",
760
- collection_id="collection456",
761
- project_id="project789"
762
- )
763
- assert params.source_path == "/valid/path"
1662
+ #### BaseValidationStrategy (추상)
764
1663
 
765
- # 무효한 매개변수
766
- with pytest.raises(ValidationError):
767
- upload.UploadParams(
768
- source_path="", # 빈 경로
769
- storage_id="storage123",
770
- collection_id="collection456",
771
- project_id="project789"
772
- )
773
- ```
1664
+ 파일 검증 전략의 기본 클래스입니다.
774
1665
 
775
- ## 문제 해결
1666
+ **추상 메서드:**
776
1667
 
777
- ### 일반적인 문제
1668
+ - `validate_files(files: List[Path], context: UploadContext) -> bool` - 파일 컬렉션 검증
1669
+ - `validate_security(file_path: Path) -> bool` - 개별 파일 보안 검증
778
1670
 
779
- #### 1. 권한 오류
1671
+ #### BaseFileDiscoveryStrategy (추상)
780
1672
 
781
- ```
782
- UploadSecurityError: Access denied to source path
783
- ```
1673
+ 파일 발견 및 구성 전략의 기본 클래스입니다.
784
1674
 
785
- **해결책**: 소스 디렉터리와 대상 스토리지에 대한 적절한 권한이 있는지 확인하세요.
1675
+ **추상 메서드:**
786
1676
 
787
- #### 2. Excel 파일 처리 실패
1677
+ - `discover_files(path: Path, context: UploadContext) -> List[Path]` - 경로에서 파일 발견
1678
+ - `organize_files(files: List[Path], specs: Dict[str, Any], context: UploadContext) -> List[Dict[str, Any]]` - 발견된 파일 구성
788
1679
 
789
- ```
790
- UploadExecutionError: Failed to process Excel file - corrupted or protected
791
- ```
1680
+ #### BaseMetadataStrategy (추상)
792
1681
 
793
- **해결책**:
1682
+ 메타데이터 처리 전략의 기본 클래스입니다.
794
1683
 
795
- - Excel 파일이 손상되지 않았는지 확인
796
- - 암호로 보호된 파일에 대한 올바른 자격 증명 제공
797
- - Excel 보안 구성 검토
1684
+ **추상 메서드:**
798
1685
 
799
- #### 3. 메모리 부족 오류
1686
+ - `process_metadata(context: UploadContext) -> Dict[str, Any]` - 컨텍스트에서 메타데이터 처리
1687
+ - `extract_metadata(file_path: Path) -> Dict[str, Any]` - 파일에서 메타데이터 추출
800
1688
 
801
- ```
802
- MemoryError: Unable to allocate array
803
- ```
1689
+ #### BaseUploadStrategy (추상)
804
1690
 
805
- **해결책**:
1691
+ 파일 업로드 전략의 기본 클래스입니다.
806
1692
 
807
- - 배치 크기 줄이기 (`batch_size` 매개변수)
808
- - 최대 작업자 수 제한 (`max_workers` 매개변수)
809
- - 대용량 파일에 대해 스트리밍 업로드 사용
1693
+ **추상 메서드:**
810
1694
 
811
- ### 디버그 모드
1695
+ - `upload_files(files: List[Dict[str, Any]], context: UploadContext) -> List[Dict[str, Any]]` - 파일 컬렉션 업로드
1696
+ - `upload_batch(batch: List[Dict[str, Any]], context: UploadContext) -> List[Dict[str, Any]]` - 파일 배치 업로드
812
1697
 
813
- ```python
814
- import logging
1698
+ #### BaseDataUnitStrategy (추상)
815
1699
 
816
- # 디버그 로깅 활성화
817
- logging.basicConfig(level=logging.DEBUG)
1700
+ 데이터 단위 생성 전략의 기본 클래스입니다.
818
1701
 
819
- # 상세한 로깅과 함께 업로드 실행
820
- params = upload.UploadParams(
821
- source_path="/path/to/files",
822
- storage_id="storage123",
823
- collection_id="collection456",
824
- project_id="project789",
825
- debug_mode=True # 상세한 로깅 활성화
826
- )
1702
+ **추상 메서드:**
827
1703
 
828
- result = action.run()
829
- ```
1704
+ - `generate_data_units(files: List[Dict[str, Any]], context: UploadContext) -> List[Dict[str, Any]]` - 데이터 단위 생성
1705
+ - `create_data_unit_batch(batch: List[Dict[str, Any]], context: UploadContext) -> List[Dict[str, Any]]` - 데이터 단위 배치 생성
830
1706
 
831
- ## 기여하기
1707
+ ### 레거시 컴포넌트
832
1708
 
833
- 업로드 플러그인 개발에 기여하려면:
1709
+ #### UploadRun
834
1710
 
835
- 1. 개발 환경 설정:
1711
+ 업로드 작업을 위한 전문 실행 관리 (레거시에서 변경 없음).
836
1712
 
837
- ```bash
838
- git clone <repository-url>
839
- cd synapse-sdk
840
- pip install -e ".[dev]"
841
- ```
1713
+ **로깅 메서드:**
842
1714
 
843
- 2. 테스트 실행:
1715
+ - `log_message_with_code(code, *args, level=None)` - 타입 안전 로깅
1716
+ - `log_upload_event(code, *args, level=None)` - 업로드 특정 이벤트
844
1717
 
845
- ```bash
846
- python -m pytest tests/plugins/upload/ -v
847
- ```
1718
+ **중첩 모델:**
848
1719
 
849
- 3. 코드 스타일 확인:
1720
+ - `UploadEventLog` - 업로드 이벤트 로깅
1721
+ - `DataFileLog` - 데이터 파일 처리 로그
1722
+ - `DataUnitLog` - 데이터 단위 생성 로그
1723
+ - `TaskLog` - 작업 실행 로그
1724
+ - `MetricsRecord` - 메트릭 추적
850
1725
 
851
- ```bash
852
- black synapse_sdk/plugins/categories/upload/
853
- flake8 synapse_sdk/plugins/categories/upload/
854
- ```
1726
+ #### UploadParams
1727
+
1728
+ Pydantic 통합을 포함한 매개변수 검증 모델 (레거시에서 변경 없음).
1729
+
1730
+ **필수 매개변수:**
1731
+
1732
+ - `name: str` - 업로드 이름
1733
+ - `path: str` - 소스 경로
1734
+ - `storage: int` - 스토리지 ID
1735
+ - `data_collection: int` - 데이터 컬렉션 ID
1736
+
1737
+ **선택적 매개변수:**
1738
+
1739
+ - `description: str | None = None` - 업로드 설명
1740
+ - `project: int | None = None` - 프로젝트 ID
1741
+ - `excel_metadata_path: str | None = None` - Excel 메타데이터 파일 경로
1742
+ - `is_recursive: bool = False` - 재귀적 파일 발견
1743
+ - `max_file_size_mb: int = 50` - 최대 파일 크기
1744
+ - `creating_data_unit_batch_size: int = 100` - 데이터 단위 배치 크기
1745
+ - `use_async_upload: bool = True` - 비동기 업로드 처리
1746
+
1747
+ **검증 기능:**
855
1748
 
856
- 4. 새로운 기능 또는 버그 수정을 위한 풀 리퀘스트 생성
1749
+ - storage/data_collection/project에 대한 실시간 API 검증
1750
+ - 문자열 정화 및 길이 검증
1751
+ - 타입 검사 및 변환
1752
+ - 사용자 정의 검증자 메서드
857
1753
 
858
- ### 개발 지침
1754
+ ### 유틸리티 클래스
1755
+
1756
+ #### ExcelSecurityConfig
1757
+
1758
+ Excel 파일 처리를 위한 보안 구성입니다.
1759
+
1760
+ **구성 속성:**
1761
+
1762
+ - 파일 크기 및 메모리 제한
1763
+ - 행 및 열 개수 제한
1764
+ - 문자열 길이 제한
1765
+ - 환경 변수 오버라이드
1766
+
1767
+ #### ExcelMetadataUtils
1768
+
1769
+ Excel 메타데이터 처리를 위한 유틸리티 메서드입니다.
1770
+
1771
+ **주요 메서드:**
1772
+
1773
+ - `validate_and_truncate_string()` - 문자열 정화
1774
+ - `is_valid_filename_length()` - 파일명 검증
1775
+
1776
+ #### PathAwareJSONEncoder
1777
+
1778
+ Path 및 datetime 객체를 위한 사용자 정의 JSON 인코더입니다.
1779
+
1780
+ **지원되는 타입:**
1781
+
1782
+ - Path 객체 (문자열로 변환)
1783
+ - Datetime 객체 (ISO 형식)
1784
+ - 표준 JSON 직렬화 가능한 타입
1785
+
1786
+ ### 열거형
1787
+
1788
+ #### LogCode
1789
+
1790
+ 업로드 작업을 위한 타입 안전 로깅 코드입니다.
1791
+
1792
+ **카테고리:**
859
1793
 
860
- - 모든 새로운 기능에 대해 테스트 작성
861
- - 공개 API에 대한 docstring 업데이트
862
- - 타입 힌트 사용으로 코드 안전성 보장
863
- - 기존 로깅 패턴 및 LogCode 열거형 따르기
1794
+ - 검증 코드 (VALIDATION_FAILED, STORAGE_VALIDATION_FAILED)
1795
+ - 파일 처리 코드 (NO_FILES_FOUND, FILES_DISCOVERED)
1796
+ - Excel 처리 코드 (EXCEL_SECURITY_VIOLATION, EXCEL_PARSING_ERROR)
1797
+ - 진행률 코드 (UPLOADING_DATA_FILES, GENERATING_DATA_UNITS)
1798
+
1799
+ #### UploadStatus
1800
+
1801
+ 업로드 처리 상태 열거형입니다.
1802
+
1803
+ **값:**
1804
+
1805
+ - `SUCCESS = 'success'` - 작업이 성공적으로 완료됨
1806
+ - `FAILED = 'failed'` - 작업이 오류로 실패함
1807
+
1808
+ ### 예외
1809
+
1810
+ #### ExcelSecurityError
1811
+
1812
+ Excel 파일이 보안 제약을 위반할 때 발생합니다.
1813
+
1814
+ **일반적인 원인:**
1815
+
1816
+ - 파일 크기가 제한을 초과함
1817
+ - 메모리 사용량 추정이 너무 높음
1818
+ - 내용 보안 위반
1819
+
1820
+ #### ExcelParsingError
1821
+
1822
+ Excel 파일을 파싱할 수 없을 때 발생합니다.
1823
+
1824
+ **일반적인 원인:**
1825
+
1826
+ - 파일 형식 손상
1827
+ - 유효하지 않은 Excel 구조
1828
+ - 필요한 열 누락
1829
+ - 내용 파싱 실패
864
1830
 
865
1831
  ## 모범 사례
866
1832
 
1833
+ ### 아키텍처 패턴
1834
+
1835
+ 1. **전략 선택**: 사용 사례 요구사항에 따라 적절한 전략을 선택하세요
1836
+
1837
+ - 깊은 디렉토리 구조에는 `RecursiveFileDiscoveryStrategy` 사용
1838
+ - 표준 파일 검증에는 `BasicValidationStrategy` 사용
1839
+ - 큰 파일 세트에는 `AsyncUploadStrategy` 사용
1840
+
1841
+ 2. **단계 순서**: 논리적 단계 종속성을 유지하세요
1842
+
1843
+ - Initialize → Process Metadata → Analyze Collection → Organize Files → Validate → Upload → Generate Data Units → Cleanup
1844
+ - 사용자 정의 단계는 워크플로우의 적절한 지점에 삽입해야 함
1845
+
1846
+ 3. **컨텍스트 관리**: 상태 공유를 위해 UploadContext를 활용하세요
1847
+ - 다운스트림 단계를 위해 컨텍스트에 중간 결과 저장
1848
+ - 단계 간 통신에 컨텍스트 사용
1849
+ - 정리 작업을 위해 롤백 데이터 보존
1850
+
867
1851
  ### 성능 최적화
868
1852
 
869
- 1. **배치 처리**: 대용량 업로드를 위해 적절한 배치 크기 사용
870
- 2. **비동기 작업**: 더 나은 처리량을 위해 비동기 처리 활성화
871
- 3. **메모리 관리**: Excel 보안 제한을 적절히 구성
872
- 4. **진행률 모니터링**: 사용자 피드백을 위한 진행률 카테고리 추적
1853
+ 1. **배치 처리**: 시스템 리소스를 기반으로 최적의 배치 크기를 구성하세요
1854
+
1855
+ ```python
1856
+ params = {
1857
+ "creating_data_unit_batch_size": 200, # 메모리에 따라 조정
1858
+ "upload_batch_size": 10, # 업로드 전략을 위한 사용자 정의 매개변수
1859
+ }
1860
+ ```
1861
+
1862
+ 2. **비동기 작업**: I/O 바인딩 작업에 비동기 처리를 활성화하세요
1863
+
1864
+ ```python
1865
+ params = {
1866
+ "use_async_upload": True, # 네트워크 작업의 더 나은 처리량
1867
+ }
1868
+ ```
1869
+
1870
+ 3. **메모리 관리**: 사용자 정의 전략에서 메모리 사용량을 모니터링하세요
1871
+
1872
+ - 모든 파일을 메모리에 로드하지 말고 청크 단위로 처리
1873
+ - 큰 파일 컬렉션에 제너레이터 사용
1874
+ - Excel 보안 제한을 적절히 구성
1875
+
1876
+ 4. **진행률 모니터링**: 자세한 진행률 추적을 구현하세요
1877
+ ```python
1878
+ # 진행률 업데이트가 포함된 사용자 정의 단계
1879
+ def execute(self, context):
1880
+ total_files = len(context.organized_files)
1881
+ for i, file_info in enumerate(context.organized_files):
1882
+ # 파일 처리
1883
+ progress = (i + 1) / total_files * 100
1884
+ context.update_metrics('custom_step', {'progress': progress})
1885
+ ```
873
1886
 
874
1887
  ### 보안 고려사항
875
1888
 
876
- 1. **파일 검증**: 항상 파일 크기와 유형 검증
877
- 2. **Excel 보안**: 적절한 보안 제한 구성
878
- 3. **경로 삭제**: 파일 경로 검증 및 삭제
879
- 4. **콘텐츠 필터링**: 콘텐츠 기반 보안 검사 구현
1889
+ 1. **입력 검증**: 모든 입력 매개변수 및 파일 경로를 검증하세요
880
1890
 
881
- ### 오류 처리
1891
+ ```python
1892
+ # 전략에서 사용자 정의 검증
1893
+ def validate_files(self, files, context):
1894
+ for file_path in files:
1895
+ if not self._is_safe_path(file_path):
1896
+ return False
1897
+ return True
1898
+ ```
1899
+
1900
+ 2. **파일 내용 보안**: 내용 기반 보안 검사를 구현하세요
1901
+
1902
+ - 악성 파일 서명 스캔
1903
+ - 파일 헤더가 확장자와 일치하는지 검증
1904
+ - 임베디드 실행 파일 검사
1905
+
1906
+ 3. **Excel 보안**: 적절한 보안 제한을 구성하세요
1907
+
1908
+ ```python
1909
+ import os
1910
+ os.environ['EXCEL_MAX_FILE_SIZE_MB'] = '10'
1911
+ os.environ['EXCEL_MAX_MEMORY_MB'] = '30'
1912
+ ```
1913
+
1914
+ 4. **경로 정화**: 모든 파일 경로를 검증하고 정화하세요
1915
+ - 경로 순회 공격 방지
1916
+ - 파일 확장자 검증
1917
+ - 파일 권한 확인
1918
+
1919
+ ### 오류 처리 및 복구
1920
+
1921
+ 1. **우아한 저하**: 부분 실패 시나리오를 위해 설계하세요
1922
+
1923
+ ```python
1924
+ class RobustUploadStrategy(BaseUploadStrategy):
1925
+ def upload_files(self, files, context):
1926
+ successful_uploads = []
1927
+ failed_uploads = []
1928
+
1929
+ for file_info in files:
1930
+ try:
1931
+ result = self._upload_file(file_info)
1932
+ successful_uploads.append(result)
1933
+ except Exception as e:
1934
+ failed_uploads.append({'file': file_info, 'error': str(e)})
1935
+ # 완전히 실패하지 말고 다른 파일로 계속 진행
1936
+
1937
+ # 부분 결과로 컨텍스트 업데이트
1938
+ context.add_uploaded_files(successful_uploads)
1939
+ if failed_uploads:
1940
+ context.add_error(f"Failed to upload {len(failed_uploads)} files")
1941
+
1942
+ return successful_uploads
1943
+ ```
1944
+
1945
+ 2. **롤백 설계**: 포괄적인 롤백 전략을 구현하세요
1946
+
1947
+ ```python
1948
+ def rollback(self, context):
1949
+ # 작업의 역순으로 정리
1950
+ if hasattr(self, '_created_temp_files'):
1951
+ for temp_file in self._created_temp_files:
1952
+ try:
1953
+ temp_file.unlink()
1954
+ except Exception:
1955
+ pass # 정리 문제로 인한 롤백 실패 방지
1956
+ ```
1957
+
1958
+ 3. **자세한 로깅**: 디버깅을 위한 구조화된 로깅을 사용하세요
1959
+
1960
+ ```python
1961
+ def execute(self, context):
1962
+ try:
1963
+ context.run.log_message_with_code(
1964
+ 'CUSTOM_STEP_STARTED',
1965
+ {'step': self.name, 'file_count': len(context.organized_files)}
1966
+ )
1967
+ # 여기에 단계 로직
1968
+ except Exception as e:
1969
+ context.run.log_message_with_code(
1970
+ 'CUSTOM_STEP_FAILED',
1971
+ {'step': self.name, 'error': str(e)},
1972
+ level=Context.DANGER
1973
+ )
1974
+ raise
1975
+ ```
1976
+
1977
+ ### 개발 가이드라인
1978
+
1979
+ 1. **사용자 정의 전략 개발**: 확립된 패턴을 따르세요
1980
+
1981
+ ```python
1982
+ # 항상 적절한 기본 클래스를 확장
1983
+ class MyCustomStrategy(BaseValidationStrategy):
1984
+ def __init__(self, config=None):
1985
+ self.config = config or {}
1986
+
1987
+ def validate_files(self, files, context):
1988
+ # 검증 로직 구현
1989
+ return True
1990
+
1991
+ def validate_security(self, file_path):
1992
+ # 보안 검증 구현
1993
+ return True
1994
+ ```
1995
+
1996
+ 2. **테스트 전략**: 포괄적인 테스트 커버리지
1997
+
1998
+ ```python
1999
+ # 성공 및 실패 시나리오 모두 테스트
2000
+ class TestCustomStrategy:
2001
+ def test_success_case(self):
2002
+ strategy = MyCustomStrategy()
2003
+ result = strategy.validate_files([Path('valid_file.txt')], mock_context)
2004
+ assert result is True
2005
+
2006
+ def test_security_failure(self):
2007
+ strategy = MyCustomStrategy()
2008
+ result = strategy.validate_security(Path('malware.exe'))
2009
+ assert result is False
2010
+
2011
+ def test_rollback_cleanup(self):
2012
+ step = MyCustomStep()
2013
+ step.rollback(mock_context)
2014
+ # 정리가 수행되었는지 확인
2015
+ ```
2016
+
2017
+ 3. **확장 지점**: 확장성을 위해 팩토리 패턴 사용
2018
+
2019
+ ```python
2020
+ class CustomStrategyFactory(StrategyFactory):
2021
+ def create_validation_strategy(self, params, context=None):
2022
+ validation_type = params.get('validation_type', 'basic')
2023
+
2024
+ strategy_map = {
2025
+ 'basic': BasicValidationStrategy,
2026
+ 'strict': StrictValidationStrategy,
2027
+ 'custom': MyCustomValidationStrategy,
2028
+ }
2029
+
2030
+ strategy_class = strategy_map.get(validation_type, BasicValidationStrategy)
2031
+ return strategy_class(params)
2032
+ ```
2033
+
2034
+ 4. **구성 관리**: 환경 변수 및 매개변수 사용
2035
+
2036
+ ```python
2037
+ class ConfigurableStep(BaseStep):
2038
+ def __init__(self):
2039
+ # 런타임 구성 허용
2040
+ self.batch_size = int(os.getenv('STEP_BATCH_SIZE', '50'))
2041
+ self.timeout = int(os.getenv('STEP_TIMEOUT_SECONDS', '300'))
2042
+
2043
+ def execute(self, context):
2044
+ # 구성된 값 사용
2045
+ batch_size = context.get_param('step_batch_size', self.batch_size)
2046
+ timeout = context.get_param('step_timeout', self.timeout)
2047
+ ```
2048
+
2049
+ ### 피해야 할 안티패턴
2050
+
2051
+ 1. **강한 결합**: 전략을 특정 구현에 결합하지 마세요
2052
+ 2. **상태 변형**: update() 메서드 외부에서 컨텍스트 상태를 직접 수정하지 마세요
2053
+ 3. **예외 삼킴**: 적절한 처리 없이 예외를 잡아서 무시하지 마세요
2054
+ 4. **블로킹 작업**: 진행률 업데이트 없이 장시간 실행되는 동기 작업을 수행하지 마세요
2055
+ 5. **메모리 누수**: 단계 인스턴스에서 큰 객체에 대한 참조를 보유하지 마세요
2056
+
2057
+ ### 문제 해결 가이드
882
2058
 
883
- 1. **우아한 저하**: 부분 업로드 실패 처리
884
- 2. **상세한 로깅**: 일관된 로깅을 위해 LogCode 열거형 사용
885
- 3. **사용자 피드백**: 명확한 오류 메시지 제공
886
- 4. **복구 옵션**: 적절한 재시도 메커니즘 구현
2059
+ 1. **단계 실패**: 단계 실행 순서 및 종속성 확인
2060
+ 2. **전략 문제**: 전략 팩토리 구성 매개변수 전달 확인
2061
+ 3. **컨텍스트 문제**: 적절한 컨텍스트 업데이트 및 상태 관리 확인
2062
+ 4. **롤백 실패**: 멱등적 롤백 작업 설계
2063
+ 5. **성능 문제**: 배치 크기 및 비동기 작업 사용량 프로파일링
887
2064
 
888
- ### 개발 지침
2065
+ ### 마이그레이션 체크리스트
889
2066
 
890
- 1. **모듈 구조**: 확립된 모듈 패턴 따르기
891
- 2. **타입 안전성**: Pydantic 모델 및 열거형 로깅 사용
892
- 3. **테스팅**: 포괄적인 단위 테스트 커버리지
893
- 4. **문서화**: 사용자 정의 검증자와 메서드 문서화
2067
+ 레거시 구현에서 업그레이드할 때:
894
2068
 
895
- ## 라이센스
2069
+ - [ ] 매개변수 이름을 `collection`에서 `data_collection`으로 업데이트
2070
+ - [ ] 호환성을 위한 기존 워크플로우 테스트
2071
+ - [ ] 새 아키텍처 기회에 대한 사용자 정의 확장 검토
2072
+ - [ ] 새 롤백 기능을 활용하도록 오류 처리 업데이트
2073
+ - [ ] 특수 요구사항에 대한 사용자 정의 전략 구현 고려
2074
+ - [ ] 새 워크플로우 단계를 검증하도록 테스트 케이스 업데이트
2075
+ - [ ] 향상된 정보에 대한 로깅 및 메트릭 수집 검토
896
2076
 
897
- 플러그인은 Synapse SDK와 동일한 라이센스 하에 배포됩니다.
2077
+ BaseUploader 템플릿을 사용한 커스텀 업로드 플러그인 개발에 대한 자세한 정보는 [업로드 템플릿 개발하기](./developing-upload-template.md) 가이드를 참조하세요.