synapse-sdk 1.0.0b22__py3-none-any.whl → 1.0.0b23__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.
- synapse_sdk/devtools/docs/docs/plugins/upload-plugins.md +680 -0
- synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md +897 -0
- synapse_sdk/devtools/docs/sidebars.ts +1 -0
- synapse_sdk/plugins/README.md +934 -0
- synapse_sdk/plugins/categories/upload/actions/upload/__init__.py +20 -0
- synapse_sdk/plugins/categories/upload/actions/upload/action.py +623 -0
- synapse_sdk/plugins/categories/upload/actions/upload/enums.py +221 -0
- synapse_sdk/plugins/categories/upload/actions/upload/exceptions.py +36 -0
- synapse_sdk/plugins/categories/upload/actions/upload/models.py +149 -0
- synapse_sdk/plugins/categories/upload/actions/upload/run.py +178 -0
- synapse_sdk/plugins/categories/upload/actions/upload/utils.py +139 -0
- synapse_sdk/plugins/categories/upload/templates/plugin/upload.py +6 -1
- synapse_sdk/plugins/models.py +13 -7
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/METADATA +1 -1
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/RECORD +19 -10
- synapse_sdk/plugins/categories/upload/actions/upload.py +0 -1368
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/WHEEL +0 -0
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/licenses/LICENSE +0 -0
- {synapse_sdk-1.0.0b22.dist-info → synapse_sdk-1.0.0b23.dist-info}/top_level.txt +0 -0
synapse_sdk/devtools/docs/i18n/ko/docusaurus-plugin-content-docs/current/plugins/upload-plugins.md
ADDED
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
# 업로드 플러그인
|
|
2
|
+
|
|
3
|
+
업로드 플러그인은 다양한 데이터 소스에서 Synapse 데이터셋으로 파일을 업로드하기 위한 강력하고 유연한 시스템입니다. 로컬 파일, Excel 스프레드시트, 이미지 컬렉션을 위한 포괄적인 업로드 기능을 제공합니다.
|
|
4
|
+
|
|
5
|
+
## 개요
|
|
6
|
+
|
|
7
|
+
업로드 플러그인은 메타데이터 추출, 보안 검증, 진행률 추적을 포함하여 파일 업로드 과정의 모든 측면을 처리합니다. 대용량 파일 배치와 복잡한 디렉터리 구조를 효율적으로 관리하도록 설계되었습니다.
|
|
8
|
+
|
|
9
|
+
### 주요 기능
|
|
10
|
+
|
|
11
|
+
- **다중 파일 형식 지원**: Excel, 이미지, 텍스트 파일 등
|
|
12
|
+
- **메타데이터 추출**: Excel 파일에서 자동 메타데이터 수집
|
|
13
|
+
- **보안 검증**: 파일 액세스 제어 및 권한 검사
|
|
14
|
+
- **진행률 추적**: 실시간 업로드 진행률 모니터링
|
|
15
|
+
- **배치 처리**: 여러 파일의 효율적인 배치 업로드
|
|
16
|
+
- **분산 실행**: Ray를 사용한 확장 가능한 처리
|
|
17
|
+
|
|
18
|
+
## 아키텍처
|
|
19
|
+
|
|
20
|
+
업로드 플러그인은 각각 특정한 책임을 가진 여러 모듈로 구성되어 있습니다:
|
|
21
|
+
|
|
22
|
+
```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 # 유틸리티 클래스 및 헬퍼
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 업로드 액션 아키텍처
|
|
59
|
+
|
|
60
|
+
### 업로드 처리 흐름
|
|
61
|
+
|
|
62
|
+
업로드 플러그인은 다음과 같은 단계별 처리 흐름을 따릅니다:
|
|
63
|
+
|
|
64
|
+
```mermaid
|
|
65
|
+
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
|
+
)
|
|
107
|
+
|
|
108
|
+
# 업로드 액션 실행
|
|
109
|
+
action = upload.UploadAction(params=params)
|
|
110
|
+
result = action.run()
|
|
111
|
+
|
|
112
|
+
print(f"업로드 상태: {result.status}")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Excel 파일 업로드
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from synapse_sdk.plugins.categories.upload.actions import upload
|
|
119
|
+
|
|
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
|
+
)
|
|
132
|
+
|
|
133
|
+
action = upload.UploadAction(params=params)
|
|
134
|
+
result = action.run()
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 진행률 모니터링
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import asyncio
|
|
141
|
+
from synapse_sdk.plugins.categories.upload.actions import upload
|
|
142
|
+
|
|
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
|
+
)
|
|
150
|
+
|
|
151
|
+
action = upload.UploadAction(params=params)
|
|
152
|
+
|
|
153
|
+
# 진행률 콜백으로 업로드 실행
|
|
154
|
+
result = await action.run_async(
|
|
155
|
+
progress_callback=lambda progress: print(f"진행률: {progress}%")
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
# 비동기 업로드 실행
|
|
161
|
+
result = asyncio.run(upload_with_progress())
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## API 참조
|
|
165
|
+
|
|
166
|
+
### UploadAction
|
|
167
|
+
|
|
168
|
+
업로드 작업을 관리하는 주요 클래스입니다.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
class UploadAction:
|
|
172
|
+
def __init__(self, params: UploadParams, run: Optional[UploadRun] = None):
|
|
173
|
+
"""
|
|
174
|
+
업로드 액션을 초기화합니다.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
params: 업로드 매개변수
|
|
178
|
+
run: 선택적 실행 인스턴스
|
|
179
|
+
"""
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### 메서드
|
|
183
|
+
|
|
184
|
+
- `run() -> UploadResult`: 동기적으로 업로드를 실행합니다
|
|
185
|
+
- `run_async() -> UploadResult`: 비동기적으로 업로드를 실행합니다
|
|
186
|
+
- `validate_params() -> bool`: 업로드 매개변수를 검증합니다
|
|
187
|
+
- `discover_files() -> List[str]`: 업로드할 파일을 발견합니다
|
|
188
|
+
- `process_excel_metadata() -> Dict`: Excel 파일에서 메타데이터를 추출합니다
|
|
189
|
+
|
|
190
|
+
### UploadParams
|
|
191
|
+
|
|
192
|
+
업로드 구성을 위한 매개변수 모델입니다.
|
|
193
|
+
|
|
194
|
+
```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
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### 필드
|
|
208
|
+
|
|
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**: 제외할 파일 패턴 (선택사항)
|
|
218
|
+
|
|
219
|
+
### 매개변수 검증
|
|
220
|
+
|
|
221
|
+
UploadParams는 Pydantic 모델을 사용하여 포괄적인 검증을 제공합니다:
|
|
222
|
+
|
|
223
|
+
```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}")
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**검증 규칙:**
|
|
241
|
+
|
|
242
|
+
- `source_path`: 읽기 가능한 디렉터리 또는 파일이어야 함
|
|
243
|
+
- 모든 ID 필드: 비어있지 않은 문자열이어야 함
|
|
244
|
+
- `batch_size`: 1 이상의 정수
|
|
245
|
+
- `max_workers`: 1 이상의 정수
|
|
246
|
+
- `include_patterns`/`exclude_patterns`: 유효한 glob 패턴 목록
|
|
247
|
+
|
|
248
|
+
### UploadRun
|
|
249
|
+
|
|
250
|
+
업로드 실행과 로깅을 관리합니다.
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
class UploadRun(BaseRun):
|
|
254
|
+
def __init__(self, action_name: str = "upload"):
|
|
255
|
+
"""업로드 실행을 초기화합니다."""
|
|
256
|
+
|
|
257
|
+
def log_message_with_code(self, log_code: LogCode, **kwargs):
|
|
258
|
+
"""로그 코드를 사용하여 메시지를 로깅합니다."""
|
|
259
|
+
|
|
260
|
+
def log_upload_event(self, event_type: str, details: Dict):
|
|
261
|
+
"""업로드 이벤트를 로깅합니다."""
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 열거형
|
|
265
|
+
|
|
266
|
+
#### LogCode
|
|
267
|
+
|
|
268
|
+
업로드 작업에 대한 타입 안전 로깅 코드:
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
class LogCode(Enum):
|
|
272
|
+
UPLOAD_STARTED = "UPLOAD_001"
|
|
273
|
+
FILE_DISCOVERED = "UPLOAD_002"
|
|
274
|
+
FILE_PROCESSED = "UPLOAD_003"
|
|
275
|
+
BATCH_COMPLETED = "UPLOAD_004"
|
|
276
|
+
UPLOAD_COMPLETED = "UPLOAD_005"
|
|
277
|
+
UPLOAD_FAILED = "UPLOAD_006"
|
|
278
|
+
# ... 총 36개 코드
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### UploadStatus
|
|
282
|
+
|
|
283
|
+
업로드 작업 상태:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
class UploadStatus(Enum):
|
|
287
|
+
SUCCESS = "success"
|
|
288
|
+
FAILED = "failed"
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 유틸리티 클래스
|
|
292
|
+
|
|
293
|
+
#### ExcelSecurityConfig
|
|
294
|
+
|
|
295
|
+
Excel 파일 보안 설정:
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
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 파일에서 메타데이터를 추출합니다."""
|
|
314
|
+
|
|
315
|
+
@staticmethod
|
|
316
|
+
def get_sheet_info(file_path: str) -> List[Dict]:
|
|
317
|
+
"""Excel 시트 정보를 가져옵니다."""
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### PathAwareJSONEncoder
|
|
321
|
+
|
|
322
|
+
Path 및 datetime 객체를 위한 사용자 정의 JSON 인코더:
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
class PathAwareJSONEncoder:
|
|
326
|
+
"""JSON 직렬화를 위한 사용자 정의 인코더"""
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def default(obj):
|
|
330
|
+
"""지원되는 타입을 JSON 호환 형식으로 변환"""
|
|
331
|
+
if isinstance(obj, Path):
|
|
332
|
+
return str(obj)
|
|
333
|
+
elif isinstance(obj, datetime):
|
|
334
|
+
return obj.isoformat()
|
|
335
|
+
return json.JSONEncoder.default(obj)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**지원 타입:**
|
|
339
|
+
|
|
340
|
+
- Path 객체 (문자열로 변환)
|
|
341
|
+
- Datetime 객체 (ISO 형식)
|
|
342
|
+
- 표준 JSON 직렬화 가능 타입
|
|
343
|
+
|
|
344
|
+
## Excel 메타데이터 처리
|
|
345
|
+
|
|
346
|
+
### Excel 파일 형식
|
|
347
|
+
|
|
348
|
+
업로드 플러그인은 다음 Excel 형식을 지원합니다:
|
|
349
|
+
|
|
350
|
+
- **XLSX**: Office Open XML 형식 (권장)
|
|
351
|
+
- **XLS**: 레거시 Excel 형식 (제한적 지원)
|
|
352
|
+
|
|
353
|
+
### 보안 검증
|
|
354
|
+
|
|
355
|
+
Excel 파일에 대해 포괄적인 보안 검사를 수행합니다:
|
|
356
|
+
|
|
357
|
+
```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
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### 환경 구성
|
|
370
|
+
|
|
371
|
+
환경 변수를 통해 Excel 처리 설정을 재정의할 수 있습니다:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# 메모리 제한 설정
|
|
375
|
+
export EXCEL_MAX_MEMORY_MB=200
|
|
376
|
+
|
|
377
|
+
# 파일 크기 제한 설정
|
|
378
|
+
export EXCEL_MAX_FILE_SIZE_MB=100
|
|
379
|
+
|
|
380
|
+
# 행/열 제한 설정
|
|
381
|
+
export EXCEL_MAX_ROWS=500000
|
|
382
|
+
export EXCEL_MAX_COLUMNS=1000
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### 메타데이터 처리 흐름
|
|
386
|
+
|
|
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
|
+
```
|
|
408
|
+
|
|
409
|
+
## 파일 구성
|
|
410
|
+
|
|
411
|
+
### 타입 탐지
|
|
412
|
+
|
|
413
|
+
업로드 플러그인은 파일 확장자와 MIME 타입을 기반으로 파일 유형을 자동 탐지합니다:
|
|
414
|
+
|
|
415
|
+
```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 문서'
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### 디렉터리 구조
|
|
430
|
+
|
|
431
|
+
복잡한 디렉터리 구조를 효율적으로 처리합니다:
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
source_directory/
|
|
435
|
+
├── excel_files/
|
|
436
|
+
│ ├── data1.xlsx
|
|
437
|
+
│ └── data2.xlsx
|
|
438
|
+
├── images/
|
|
439
|
+
│ ├── photo1.jpg
|
|
440
|
+
│ └── photo2.png
|
|
441
|
+
└── documents/
|
|
442
|
+
├── report.pdf
|
|
443
|
+
└── notes.txt
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 배치 처리
|
|
447
|
+
|
|
448
|
+
파일들을 효율적인 배치로 그룹화합니다:
|
|
449
|
+
|
|
450
|
+
- **유형별 그룹화**: 동일한 처리 로직을 사용하는 파일들을 함께 처리
|
|
451
|
+
- **크기 기반 분할**: 메모리 사용량을 제어하기 위해 큰 파일들을 별도로 처리
|
|
452
|
+
- **병렬 처리**: 독립적인 배치들을 동시에 처리
|
|
453
|
+
|
|
454
|
+
## 진행률 추적 및 메트릭
|
|
455
|
+
|
|
456
|
+
### 진행률 카테고리
|
|
457
|
+
|
|
458
|
+
업로드 과정에서 다음 진행률 카테고리를 추적합니다:
|
|
459
|
+
|
|
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
|
+
```
|
|
472
|
+
|
|
473
|
+
### 메트릭 수집
|
|
474
|
+
|
|
475
|
+
상세한 성능 메트릭을 수집하여 분석에 활용할 수 있습니다:
|
|
476
|
+
|
|
477
|
+
```python
|
|
478
|
+
# 수집되는 메트릭
|
|
479
|
+
metrics = {
|
|
480
|
+
"processing_time": {
|
|
481
|
+
"file_discovery": 2.5, # 초
|
|
482
|
+
"excel_processing": 15.2,
|
|
483
|
+
"upload": 45.8
|
|
484
|
+
},
|
|
485
|
+
"file_statistics": {
|
|
486
|
+
"total_files": 150,
|
|
487
|
+
"excel_files": 25,
|
|
488
|
+
"image_files": 100,
|
|
489
|
+
"other_files": 25
|
|
490
|
+
},
|
|
491
|
+
"data_volume": {
|
|
492
|
+
"total_size_mb": 2048,
|
|
493
|
+
"excel_data_mb": 512,
|
|
494
|
+
"media_data_mb": 1536
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
## 타입 안전 로깅
|
|
500
|
+
|
|
501
|
+
### 로그 코드
|
|
502
|
+
|
|
503
|
+
모든 로깅 작업에 타입 안전 코드를 사용합니다:
|
|
504
|
+
|
|
505
|
+
```python
|
|
506
|
+
# 검증 관련 로그 코드
|
|
507
|
+
VALIDATION_FAILED = "UPLOAD_001"
|
|
508
|
+
STORAGE_VALIDATION_FAILED = "UPLOAD_002"
|
|
509
|
+
COLLECTION_VALIDATION_FAILED = "UPLOAD_003"
|
|
510
|
+
|
|
511
|
+
# 파일 처리 관련 로그 코드
|
|
512
|
+
NO_FILES_FOUND = "UPLOAD_004"
|
|
513
|
+
FILES_DISCOVERED = "UPLOAD_005"
|
|
514
|
+
FILE_FILTERED = "UPLOAD_006"
|
|
515
|
+
|
|
516
|
+
# Excel 처리 관련 로그 코드
|
|
517
|
+
EXCEL_SECURITY_VIOLATION = "UPLOAD_010"
|
|
518
|
+
EXCEL_PARSING_ERROR = "UPLOAD_011"
|
|
519
|
+
EXCEL_METADATA_EXTRACTED = "UPLOAD_012"
|
|
520
|
+
|
|
521
|
+
# 진행률 관련 로그 코드
|
|
522
|
+
UPLOADING_DATA_FILES = "UPLOAD_020"
|
|
523
|
+
GENERATING_DATA_UNITS = "UPLOAD_021"
|
|
524
|
+
UPLOAD_PROGRESS = "UPLOAD_022"
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### 로깅 사용법
|
|
528
|
+
|
|
529
|
+
구조화된 로깅으로 일관된 메시지 형식을 보장합니다:
|
|
530
|
+
|
|
531
|
+
```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
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## 고급 사용법
|
|
542
|
+
|
|
543
|
+
### 사용자 정의 파일 필터링
|
|
544
|
+
|
|
545
|
+
```python
|
|
546
|
+
from synapse_sdk.plugins.categories.upload.actions import upload
|
|
547
|
+
|
|
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*"]
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
action = upload.UploadAction(params=params)
|
|
558
|
+
result = action.run()
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### 배치 크기 최적화
|
|
562
|
+
|
|
563
|
+
```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 # 리소스 사용량 감소
|
|
572
|
+
)
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### Excel 보안 구성
|
|
576
|
+
|
|
577
|
+
```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
|
+
)
|
|
585
|
+
|
|
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
|
|
592
|
+
)
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
## 오류 처리
|
|
596
|
+
|
|
597
|
+
업로드 플러그인은 다양한 오류 상황에 대한 포괄적인 오류 처리를 제공합니다:
|
|
598
|
+
|
|
599
|
+
### 일반적인 예외
|
|
600
|
+
|
|
601
|
+
```python
|
|
602
|
+
from synapse_sdk.plugins.categories.upload.actions import upload
|
|
603
|
+
|
|
604
|
+
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}")
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### 예외 타입
|
|
626
|
+
|
|
627
|
+
#### ExcelSecurityError
|
|
628
|
+
|
|
629
|
+
Excel 파일이 보안 제약 조건을 위반할 때 발생합니다.
|
|
630
|
+
|
|
631
|
+
**일반적인 원인:**
|
|
632
|
+
|
|
633
|
+
- 파일 크기가 제한을 초과
|
|
634
|
+
- 메모리 사용량 추정치가 너무 높음
|
|
635
|
+
- 콘텐츠 보안 위반
|
|
636
|
+
|
|
637
|
+
#### ExcelParsingError
|
|
638
|
+
|
|
639
|
+
Excel 파일을 파싱할 수 없을 때 발생합니다.
|
|
640
|
+
|
|
641
|
+
**일반적인 원인:**
|
|
642
|
+
|
|
643
|
+
- 파일 형식 손상
|
|
644
|
+
- 잘못된 Excel 구조
|
|
645
|
+
- 필수 열 누락
|
|
646
|
+
- 콘텐츠 파싱 실패
|
|
647
|
+
|
|
648
|
+
### 부분 실패 처리
|
|
649
|
+
|
|
650
|
+
```python
|
|
651
|
+
result = action.run()
|
|
652
|
+
|
|
653
|
+
if result.status == upload.UploadStatus.FAILED:
|
|
654
|
+
print(f"실패한 파일: {result.failed_files}")
|
|
655
|
+
print(f"성공한 파일: {result.successful_files}")
|
|
656
|
+
|
|
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
|
+
)
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
## 성능 고려사항
|
|
667
|
+
|
|
668
|
+
### 메모리 사용량
|
|
669
|
+
|
|
670
|
+
- 대용량 Excel 파일의 경우 더 작은 `batch_size` 사용
|
|
671
|
+
- 메모리 집약적인 작업을 위해 `max_workers` 제한
|
|
672
|
+
- 스트리밍 처리를 위해 청크 업로드 사용
|
|
673
|
+
|
|
674
|
+
### 네트워크 최적화
|
|
675
|
+
|
|
676
|
+
- 네트워크 지연 시간이 높은 환경에서 배치 크기 증가
|
|
677
|
+
- 신뢰할 수 없는 연결에 대해 재시도 메커니즘 구현
|
|
678
|
+
- 대용량 파일에 대해 체크섬 검증 사용
|
|
679
|
+
|
|
680
|
+
### 병렬 처리
|
|
681
|
+
|
|
682
|
+
```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
|
+
)
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
## 로깅 및 모니터링
|
|
695
|
+
|
|
696
|
+
### 로그 코드 사용
|
|
697
|
+
|
|
698
|
+
업로드 플러그인은 모든 작업에 대해 구조화된 로깅을 제공합니다:
|
|
699
|
+
|
|
700
|
+
```python
|
|
701
|
+
from synapse_sdk.plugins.categories.upload.actions import upload
|
|
702
|
+
|
|
703
|
+
# 사용자 정의 로깅 핸들러
|
|
704
|
+
def custom_log_handler(log_code: upload.LogCode, message: str, level: str):
|
|
705
|
+
print(f"[{log_code.value}] {level}: {message}")
|
|
706
|
+
|
|
707
|
+
# 로그 핸들러와 함께 액션 실행
|
|
708
|
+
action = upload.UploadAction(params=params)
|
|
709
|
+
action.run.add_log_handler(custom_log_handler)
|
|
710
|
+
result = action.run()
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### 메트릭 수집
|
|
714
|
+
|
|
715
|
+
```python
|
|
716
|
+
# 업로드 메트릭 수집
|
|
717
|
+
result = action.run()
|
|
718
|
+
|
|
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
|
+
}
|
|
727
|
+
|
|
728
|
+
print(f"업로드 메트릭: {metrics}")
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
## 테스팅
|
|
732
|
+
|
|
733
|
+
업로드 플러그인에는 포괄적인 테스트 스위트가 포함되어 있습니다:
|
|
734
|
+
|
|
735
|
+
### 단위 테스트 실행
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
# 모든 업로드 테스트 실행
|
|
739
|
+
python -m pytest tests/plugins/upload/ -v
|
|
740
|
+
|
|
741
|
+
# 특정 테스트 파일 실행
|
|
742
|
+
python -m pytest tests/plugins/upload/test_action.py -v
|
|
743
|
+
|
|
744
|
+
# 커버리지와 함께 테스트 실행
|
|
745
|
+
python -m pytest tests/plugins/upload/ --cov=synapse_sdk.plugins.categories.upload
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
### 테스트 케이스 예제
|
|
749
|
+
|
|
750
|
+
```python
|
|
751
|
+
import pytest
|
|
752
|
+
from synapse_sdk.plugins.categories.upload.actions import upload
|
|
753
|
+
|
|
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"
|
|
764
|
+
|
|
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
|
+
```
|
|
774
|
+
|
|
775
|
+
## 문제 해결
|
|
776
|
+
|
|
777
|
+
### 일반적인 문제
|
|
778
|
+
|
|
779
|
+
#### 1. 권한 오류
|
|
780
|
+
|
|
781
|
+
```
|
|
782
|
+
UploadSecurityError: Access denied to source path
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
**해결책**: 소스 디렉터리와 대상 스토리지에 대한 적절한 권한이 있는지 확인하세요.
|
|
786
|
+
|
|
787
|
+
#### 2. Excel 파일 처리 실패
|
|
788
|
+
|
|
789
|
+
```
|
|
790
|
+
UploadExecutionError: Failed to process Excel file - corrupted or protected
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
**해결책**:
|
|
794
|
+
|
|
795
|
+
- Excel 파일이 손상되지 않았는지 확인
|
|
796
|
+
- 암호로 보호된 파일에 대한 올바른 자격 증명 제공
|
|
797
|
+
- Excel 보안 구성 검토
|
|
798
|
+
|
|
799
|
+
#### 3. 메모리 부족 오류
|
|
800
|
+
|
|
801
|
+
```
|
|
802
|
+
MemoryError: Unable to allocate array
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
**해결책**:
|
|
806
|
+
|
|
807
|
+
- 배치 크기 줄이기 (`batch_size` 매개변수)
|
|
808
|
+
- 최대 작업자 수 제한 (`max_workers` 매개변수)
|
|
809
|
+
- 대용량 파일에 대해 스트리밍 업로드 사용
|
|
810
|
+
|
|
811
|
+
### 디버그 모드
|
|
812
|
+
|
|
813
|
+
```python
|
|
814
|
+
import logging
|
|
815
|
+
|
|
816
|
+
# 디버그 로깅 활성화
|
|
817
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
818
|
+
|
|
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
|
+
)
|
|
827
|
+
|
|
828
|
+
result = action.run()
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
## 기여하기
|
|
832
|
+
|
|
833
|
+
업로드 플러그인 개발에 기여하려면:
|
|
834
|
+
|
|
835
|
+
1. 개발 환경 설정:
|
|
836
|
+
|
|
837
|
+
```bash
|
|
838
|
+
git clone <repository-url>
|
|
839
|
+
cd synapse-sdk
|
|
840
|
+
pip install -e ".[dev]"
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
2. 테스트 실행:
|
|
844
|
+
|
|
845
|
+
```bash
|
|
846
|
+
python -m pytest tests/plugins/upload/ -v
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
3. 코드 스타일 확인:
|
|
850
|
+
|
|
851
|
+
```bash
|
|
852
|
+
black synapse_sdk/plugins/categories/upload/
|
|
853
|
+
flake8 synapse_sdk/plugins/categories/upload/
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
4. 새로운 기능 또는 버그 수정을 위한 풀 리퀘스트 생성
|
|
857
|
+
|
|
858
|
+
### 개발 지침
|
|
859
|
+
|
|
860
|
+
- 모든 새로운 기능에 대해 테스트 작성
|
|
861
|
+
- 공개 API에 대한 docstring 업데이트
|
|
862
|
+
- 타입 힌트 사용으로 코드 안전성 보장
|
|
863
|
+
- 기존 로깅 패턴 및 LogCode 열거형 따르기
|
|
864
|
+
|
|
865
|
+
## 모범 사례
|
|
866
|
+
|
|
867
|
+
### 성능 최적화
|
|
868
|
+
|
|
869
|
+
1. **배치 처리**: 대용량 업로드를 위해 적절한 배치 크기 사용
|
|
870
|
+
2. **비동기 작업**: 더 나은 처리량을 위해 비동기 처리 활성화
|
|
871
|
+
3. **메모리 관리**: Excel 보안 제한을 적절히 구성
|
|
872
|
+
4. **진행률 모니터링**: 사용자 피드백을 위한 진행률 카테고리 추적
|
|
873
|
+
|
|
874
|
+
### 보안 고려사항
|
|
875
|
+
|
|
876
|
+
1. **파일 검증**: 항상 파일 크기와 유형 검증
|
|
877
|
+
2. **Excel 보안**: 적절한 보안 제한 구성
|
|
878
|
+
3. **경로 삭제**: 파일 경로 검증 및 삭제
|
|
879
|
+
4. **콘텐츠 필터링**: 콘텐츠 기반 보안 검사 구현
|
|
880
|
+
|
|
881
|
+
### 오류 처리
|
|
882
|
+
|
|
883
|
+
1. **우아한 저하**: 부분 업로드 실패 처리
|
|
884
|
+
2. **상세한 로깅**: 일관된 로깅을 위해 LogCode 열거형 사용
|
|
885
|
+
3. **사용자 피드백**: 명확한 오류 메시지 제공
|
|
886
|
+
4. **복구 옵션**: 적절한 재시도 메커니즘 구현
|
|
887
|
+
|
|
888
|
+
### 개발 지침
|
|
889
|
+
|
|
890
|
+
1. **모듈 구조**: 확립된 모듈 패턴 따르기
|
|
891
|
+
2. **타입 안전성**: Pydantic 모델 및 열거형 로깅 사용
|
|
892
|
+
3. **테스팅**: 포괄적인 단위 테스트 커버리지
|
|
893
|
+
4. **문서화**: 사용자 정의 검증자와 메서드 문서화
|
|
894
|
+
|
|
895
|
+
## 라이센스
|
|
896
|
+
|
|
897
|
+
이 플러그인은 Synapse SDK와 동일한 라이센스 하에 배포됩니다.
|