yeonjae-universal-http-api-client 1.0.1__tar.gz

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.
Files changed (18) hide show
  1. yeonjae_universal_http_api_client-1.0.1/PKG-INFO +77 -0
  2. yeonjae_universal_http_api_client-1.0.1/README.md +38 -0
  3. yeonjae_universal_http_api_client-1.0.1/pyproject.toml +73 -0
  4. yeonjae_universal_http_api_client-1.0.1/setup.cfg +4 -0
  5. yeonjae_universal_http_api_client-1.0.1/src/universal_http_api_client/__init__.py +35 -0
  6. yeonjae_universal_http_api_client-1.0.1/src/universal_http_api_client/adapters.py +314 -0
  7. yeonjae_universal_http_api_client-1.0.1/src/universal_http_api_client/client.py +572 -0
  8. yeonjae_universal_http_api_client-1.0.1/src/universal_http_api_client/exceptions.py +209 -0
  9. yeonjae_universal_http_api_client-1.0.1/src/universal_http_api_client/models.py +200 -0
  10. yeonjae_universal_http_api_client-1.0.1/src/universal_http_api_client/py.typed +0 -0
  11. yeonjae_universal_http_api_client-1.0.1/src/universal_http_api_client/utils.py +40 -0
  12. yeonjae_universal_http_api_client-1.0.1/src/yeonjae_universal_http_api_client.egg-info/PKG-INFO +77 -0
  13. yeonjae_universal_http_api_client-1.0.1/src/yeonjae_universal_http_api_client.egg-info/SOURCES.txt +16 -0
  14. yeonjae_universal_http_api_client-1.0.1/src/yeonjae_universal_http_api_client.egg-info/dependency_links.txt +1 -0
  15. yeonjae_universal_http_api_client-1.0.1/src/yeonjae_universal_http_api_client.egg-info/requires.txt +13 -0
  16. yeonjae_universal_http_api_client-1.0.1/src/yeonjae_universal_http_api_client.egg-info/top_level.txt +1 -0
  17. yeonjae_universal_http_api_client-1.0.1/tests/test_basic.py +47 -0
  18. yeonjae_universal_http_api_client-1.0.1/tests/test_http_api_client.py +339 -0
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.4
2
+ Name: yeonjae-universal-http-api-client
3
+ Version: 1.0.1
4
+ Summary: Universal HTTP API client supporting GitHub, GitLab, Bitbucket and other platforms
5
+ Author-email: "CodePing.AI Team" <contact@codeping.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/yeonjae-work/universal-modules
8
+ Project-URL: Repository, https://github.com/yeonjae-work/universal-modules
9
+ Project-URL: Issues, https://github.com/yeonjae-work/universal-modules/issues
10
+ Project-URL: Changelog, https://github.com/yeonjae-work/universal-modules/blob/main/packages/yeonjae-universal-http-api-client/CHANGELOG.md
11
+ Keywords: http,api,client,github,gitlab,slack,notion,universal
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
24
+ Classifier: Topic :: System :: Networking
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ Requires-Dist: httpx>=0.25.0
28
+ Requires-Dist: requests>=2.28.0
29
+ Requires-Dist: pydantic>=2.0.0
30
+ Requires-Dist: urllib3>=1.26.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
33
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
34
+ Requires-Dist: black>=23.0.0; extra == "dev"
35
+ Requires-Dist: isort>=5.12.0; extra == "dev"
36
+ Requires-Dist: flake8>=6.0.0; extra == "dev"
37
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
38
+ Requires-Dist: requests-mock>=1.9.0; extra == "dev"
39
+
40
+ # Universal HTTP API Client
41
+
42
+ Universal HTTP API client with platform-specific adapters for GitHub, GitLab, Slack, and Notion.
43
+
44
+ ## Features
45
+
46
+ - **Multi-Platform Support**: GitHub, GitLab, Slack, Notion APIs
47
+ - **Smart Caching**: Automatic request caching with configurable TTL
48
+ - **Rate Limiting**: Built-in rate limiting with platform-specific rules
49
+ - **Error Handling**: Comprehensive error handling with custom exceptions
50
+ - **Async Support**: Full async/await support for all operations
51
+ - **Type Safety**: Complete type annotations with mypy support
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install universal-http-api-client
57
+ ```
58
+
59
+ ## Quick Start
60
+
61
+ ```python
62
+ from universal_http_api_client import HTTPAPIClient, Platform
63
+
64
+ # GitHub API
65
+ client = HTTPAPIClient(Platform.GITHUB, "your-token")
66
+ repo = client.get_repository("owner/repo")
67
+ client.close()
68
+
69
+ # Slack API
70
+ slack_client = HTTPAPIClient(Platform.SLACK, "your-slack-token")
71
+ response = slack_client.send_message("channel", "Hello World!")
72
+ slack_client.close()
73
+ ```
74
+
75
+ ## License
76
+
77
+ MIT License
@@ -0,0 +1,38 @@
1
+ # Universal HTTP API Client
2
+
3
+ Universal HTTP API client with platform-specific adapters for GitHub, GitLab, Slack, and Notion.
4
+
5
+ ## Features
6
+
7
+ - **Multi-Platform Support**: GitHub, GitLab, Slack, Notion APIs
8
+ - **Smart Caching**: Automatic request caching with configurable TTL
9
+ - **Rate Limiting**: Built-in rate limiting with platform-specific rules
10
+ - **Error Handling**: Comprehensive error handling with custom exceptions
11
+ - **Async Support**: Full async/await support for all operations
12
+ - **Type Safety**: Complete type annotations with mypy support
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ pip install universal-http-api-client
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```python
23
+ from universal_http_api_client import HTTPAPIClient, Platform
24
+
25
+ # GitHub API
26
+ client = HTTPAPIClient(Platform.GITHUB, "your-token")
27
+ repo = client.get_repository("owner/repo")
28
+ client.close()
29
+
30
+ # Slack API
31
+ slack_client = HTTPAPIClient(Platform.SLACK, "your-slack-token")
32
+ response = slack_client.send_message("channel", "Hello World!")
33
+ slack_client.close()
34
+ ```
35
+
36
+ ## License
37
+
38
+ MIT License
@@ -0,0 +1,73 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "yeonjae-universal-http-api-client"
7
+ version = "1.0.1"
8
+ description = "Universal HTTP API client supporting GitHub, GitLab, Bitbucket and other platforms"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ authors = [
12
+ {name = "CodePing.AI Team", email = "contact@codeping.ai"}
13
+ ]
14
+ keywords = ["http", "api", "client", "github", "gitlab", "slack", "notion", "universal"]
15
+ classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
28
+ "Topic :: System :: Networking",
29
+ ]
30
+ requires-python = ">=3.8"
31
+ dependencies = [
32
+ "httpx>=0.25.0",
33
+ "requests>=2.28.0",
34
+ "pydantic>=2.0.0",
35
+ "urllib3>=1.26.0",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ dev = [
40
+ "pytest>=7.0.0",
41
+ "pytest-asyncio>=0.21.0",
42
+ "black>=23.0.0",
43
+ "isort>=5.12.0",
44
+ "flake8>=6.0.0",
45
+ "mypy>=1.0.0",
46
+ "requests-mock>=1.9.0",
47
+ ]
48
+
49
+ [project.urls]
50
+ Homepage = "https://github.com/yeonjae-work/universal-modules"
51
+ Repository = "https://github.com/yeonjae-work/universal-modules"
52
+ Issues = "https://github.com/yeonjae-work/universal-modules/issues"
53
+ Changelog = "https://github.com/yeonjae-work/universal-modules/blob/main/packages/yeonjae-universal-http-api-client/CHANGELOG.md"
54
+
55
+ [tool.setuptools.packages.find]
56
+ where = ["src"]
57
+
58
+ [tool.setuptools.package-data]
59
+ "*" = ["py.typed"]
60
+
61
+ [tool.black]
62
+ line-length = 88
63
+ target-version = ['py38']
64
+
65
+ [tool.isort]
66
+ profile = "black"
67
+ line_length = 88
68
+
69
+ [tool.mypy]
70
+ python_version = "3.8"
71
+ warn_return_any = true
72
+ warn_unused_configs = true
73
+ disallow_untyped_defs = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ """
2
+ Universal HTTP API Client - 범용 HTTP API 클라이언트
3
+
4
+ 이 모듈은 GitHub, GitLab 등 다양한 플랫폼의 API를 호출하는 범용 클라이언트입니다.
5
+ 재사용성을 고려하여 설계되었으며, 인증, 재시도, 캐싱 등의 기능을 제공합니다.
6
+ """
7
+
8
+ __version__ = "1.0.0"
9
+
10
+ from .client import HTTPAPIClient, AsyncHTTPAPIClient
11
+ from .adapters import PlatformAPIAdapter, GitHubAdapter, GitLabAdapter
12
+ from .models import APIRequest, APIResponse, PlatformConfig, Platform, HTTPMethod
13
+ from .exceptions import APIError, RateLimitError, AuthenticationError, NetworkError, TimeoutError
14
+ from .utils import ModuleIOLogger, setup_logging
15
+
16
+ __all__ = [
17
+ "HTTPAPIClient",
18
+ "AsyncHTTPAPIClient",
19
+ "PlatformAPIAdapter",
20
+ "GitHubAdapter",
21
+ "GitLabAdapter",
22
+ "APIRequest",
23
+ "APIResponse",
24
+ "PlatformConfig",
25
+ "Platform",
26
+ "HTTPMethod",
27
+ "APIError",
28
+ "RateLimitError",
29
+ "AuthenticationError",
30
+ "NetworkError",
31
+ "TimeoutError",
32
+ "ModuleIOLogger",
33
+ "setup_logging",
34
+ "__version__",
35
+ ]
@@ -0,0 +1,314 @@
1
+ """
2
+ 플랫폼별 API 어댑터 구현
3
+ """
4
+
5
+ import hashlib
6
+ import time
7
+ from abc import ABC, abstractmethod
8
+ from typing import Dict, Any, Optional, Callable
9
+ from datetime import datetime
10
+
11
+ from .models import Platform, PlatformConfig, APIRequest, APIResponse, RateLimitInfo, PLATFORM_CONFIGS
12
+ from .exceptions import RateLimitError, PlatformNotSupportedError
13
+
14
+
15
+ class PlatformAPIAdapter(ABC):
16
+ """플랫폼 API 어댑터 기본 클래스"""
17
+
18
+ def __init__(self, config: PlatformConfig, auth_token: str):
19
+ self.config = config
20
+ self.auth_token = auth_token
21
+
22
+ @abstractmethod
23
+ def build_url(self, endpoint: str) -> str:
24
+ """API URL 구성"""
25
+ pass
26
+
27
+ @abstractmethod
28
+ def get_auth_headers(self) -> Dict[str, str]:
29
+ """인증 헤더 반환"""
30
+ pass
31
+
32
+ @abstractmethod
33
+ def parse_rate_limit(self, headers: Dict[str, str]) -> Optional[RateLimitInfo]:
34
+ """Rate limit 정보 파싱"""
35
+ pass
36
+
37
+ @abstractmethod
38
+ def parse_response(self, response_data: Dict[str, Any], operation: str) -> Dict[str, Any]:
39
+ """응답 데이터 파싱"""
40
+ pass
41
+
42
+ def get_default_headers(self) -> Dict[str, str]:
43
+ """기본 헤더 반환"""
44
+ headers = self.config.default_headers.copy()
45
+ headers.update(self.get_auth_headers())
46
+ return headers
47
+
48
+ def get_cache_key(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> str:
49
+ """캐시 키 생성"""
50
+ params_str = ""
51
+ if params:
52
+ # 파라미터를 정렬하여 일관된 해시 생성
53
+ sorted_params = sorted(params.items())
54
+ params_str = str(sorted_params)
55
+
56
+ cache_data = f"{self.config.name}:{endpoint}:{params_str}"
57
+ return hashlib.md5(cache_data.encode()).hexdigest()
58
+
59
+
60
+ class GitHubAdapter(PlatformAPIAdapter):
61
+ """GitHub API 어댑터"""
62
+
63
+ def build_url(self, endpoint: str) -> str:
64
+ """GitHub API URL 구성"""
65
+ # endpoint가 이미 전체 URL이면 그대로 반환
66
+ if endpoint.startswith('http'):
67
+ return endpoint
68
+
69
+ # 앞에 슬래시가 없으면 추가
70
+ if not endpoint.startswith('/'):
71
+ endpoint = f"/{endpoint}"
72
+
73
+ return f"{self.config.base_url}{endpoint}"
74
+
75
+ def get_auth_headers(self) -> Dict[str, str]:
76
+ """GitHub 인증 헤더"""
77
+ return self.config.get_auth_header(self.auth_token)
78
+
79
+ def parse_rate_limit(self, headers: Dict[str, str]) -> Optional[RateLimitInfo]:
80
+ """GitHub Rate limit 정보 파싱"""
81
+ try:
82
+ remaining = int(headers.get('X-RateLimit-Remaining', 0))
83
+ limit = int(headers.get('X-RateLimit-Limit', 5000))
84
+ reset_timestamp = int(headers.get('X-RateLimit-Reset', 0))
85
+ reset_time = datetime.fromtimestamp(reset_timestamp)
86
+
87
+ return RateLimitInfo(
88
+ remaining=remaining,
89
+ limit=limit,
90
+ reset_time=reset_time
91
+ )
92
+ except (ValueError, TypeError):
93
+ return None
94
+
95
+ def parse_response(self, response_data: Dict[str, Any], operation: str) -> Dict[str, Any]:
96
+ """GitHub 응답 데이터 파싱"""
97
+ if operation == "get_commit":
98
+ return self._parse_commit_response(response_data)
99
+ elif operation == "get_diff":
100
+ return self._parse_diff_response(response_data)
101
+ elif operation == "get_repository":
102
+ return self._parse_repository_response(response_data)
103
+ else:
104
+ return response_data
105
+
106
+ def _parse_commit_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
107
+ """커밋 응답 파싱"""
108
+ return {
109
+ "sha": data.get("sha"),
110
+ "message": data.get("commit", {}).get("message"),
111
+ "author": data.get("commit", {}).get("author", {}),
112
+ "committer": data.get("commit", {}).get("committer", {}),
113
+ "stats": data.get("stats", {}),
114
+ "files": data.get("files", []),
115
+ "parents": data.get("parents", []),
116
+ "url": data.get("html_url")
117
+ }
118
+
119
+ def _parse_diff_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
120
+ """Diff 응답 파싱"""
121
+ return {
122
+ "files": data.get("files", []),
123
+ "stats": data.get("stats", {}),
124
+ "total_additions": sum(f.get("additions", 0) for f in data.get("files", [])),
125
+ "total_deletions": sum(f.get("deletions", 0) for f in data.get("files", [])),
126
+ "total_changes": sum(f.get("changes", 0) for f in data.get("files", []))
127
+ }
128
+
129
+ def _parse_repository_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
130
+ """저장소 응답 파싱"""
131
+ return {
132
+ "id": data.get("id"),
133
+ "name": data.get("name"),
134
+ "full_name": data.get("full_name"),
135
+ "owner": data.get("owner", {}),
136
+ "private": data.get("private"),
137
+ "description": data.get("description"),
138
+ "language": data.get("language"),
139
+ "size": data.get("size"),
140
+ "stars": data.get("stargazers_count"),
141
+ "forks": data.get("forks_count"),
142
+ "created_at": data.get("created_at"),
143
+ "updated_at": data.get("updated_at"),
144
+ "url": data.get("html_url")
145
+ }
146
+
147
+
148
+ class GitLabAdapter(PlatformAPIAdapter):
149
+ """GitLab API 어댑터"""
150
+
151
+ def build_url(self, endpoint: str) -> str:
152
+ """GitLab API URL 구성"""
153
+ if endpoint.startswith('http'):
154
+ return endpoint
155
+
156
+ if not endpoint.startswith('/'):
157
+ endpoint = f"/{endpoint}"
158
+
159
+ return f"{self.config.base_url}{endpoint}"
160
+
161
+ def get_auth_headers(self) -> Dict[str, str]:
162
+ """GitLab 인증 헤더"""
163
+ return self.config.get_auth_header(self.auth_token)
164
+
165
+ def parse_rate_limit(self, headers: Dict[str, str]) -> Optional[RateLimitInfo]:
166
+ """GitLab Rate limit 정보 파싱"""
167
+ try:
168
+ remaining = int(headers.get('RateLimit-Remaining', 0))
169
+ limit = int(headers.get('RateLimit-Limit', 300))
170
+ reset_timestamp = int(headers.get('RateLimit-Reset', 0))
171
+ reset_time = datetime.fromtimestamp(reset_timestamp)
172
+
173
+ return RateLimitInfo(
174
+ remaining=remaining,
175
+ limit=limit,
176
+ reset_time=reset_time
177
+ )
178
+ except (ValueError, TypeError):
179
+ return None
180
+
181
+ def parse_response(self, response_data: Dict[str, Any], operation: str) -> Dict[str, Any]:
182
+ """GitLab 응답 데이터 파싱"""
183
+ if operation == "get_commit":
184
+ return self._parse_commit_response(response_data)
185
+ elif operation == "get_diff":
186
+ return self._parse_diff_response(response_data)
187
+ elif operation == "get_project":
188
+ return self._parse_project_response(response_data)
189
+ else:
190
+ return response_data
191
+
192
+ def _parse_commit_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
193
+ """커밋 응답 파싱"""
194
+ return {
195
+ "sha": data.get("id"),
196
+ "message": data.get("message"),
197
+ "author": {
198
+ "name": data.get("author_name"),
199
+ "email": data.get("author_email"),
200
+ "date": data.get("authored_date")
201
+ },
202
+ "committer": {
203
+ "name": data.get("committer_name"),
204
+ "email": data.get("committer_email"),
205
+ "date": data.get("committed_date")
206
+ },
207
+ "stats": data.get("stats", {}),
208
+ "parent_ids": data.get("parent_ids", []),
209
+ "url": data.get("web_url")
210
+ }
211
+
212
+ def _parse_diff_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
213
+ """Diff 응답 파싱"""
214
+ # GitLab의 diff 응답은 파일별로 분리되어 있음
215
+ files = []
216
+ total_additions = 0
217
+ total_deletions = 0
218
+
219
+ if isinstance(data, list):
220
+ for file_diff in data:
221
+ files.append(file_diff)
222
+ # GitLab diff에서 통계 정보 추출
223
+ diff_text = file_diff.get("diff", "")
224
+ additions = diff_text.count("\n+") if diff_text else 0
225
+ deletions = diff_text.count("\n-") if diff_text else 0
226
+ total_additions += additions
227
+ total_deletions += deletions
228
+
229
+ return {
230
+ "files": files,
231
+ "total_additions": total_additions,
232
+ "total_deletions": total_deletions,
233
+ "total_changes": total_additions + total_deletions
234
+ }
235
+
236
+ def _parse_project_response(self, data: Dict[str, Any]) -> Dict[str, Any]:
237
+ """프로젝트 응답 파싱"""
238
+ return {
239
+ "id": data.get("id"),
240
+ "name": data.get("name"),
241
+ "full_name": data.get("path_with_namespace"),
242
+ "owner": data.get("owner", {}),
243
+ "private": data.get("visibility") == "private",
244
+ "description": data.get("description"),
245
+ "language": None, # GitLab API에서는 별도 조회 필요
246
+ "size": data.get("repository_size"),
247
+ "stars": data.get("star_count"),
248
+ "forks": data.get("forks_count"),
249
+ "created_at": data.get("created_at"),
250
+ "updated_at": data.get("last_activity_at"),
251
+ "url": data.get("web_url")
252
+ }
253
+
254
+
255
+ class BitbucketAdapter(PlatformAPIAdapter):
256
+ """Bitbucket API 어댑터"""
257
+
258
+ def build_url(self, endpoint: str) -> str:
259
+ """Bitbucket API URL 구성"""
260
+ if endpoint.startswith('http'):
261
+ return endpoint
262
+
263
+ if not endpoint.startswith('/'):
264
+ endpoint = f"/{endpoint}"
265
+
266
+ return f"{self.config.base_url}{endpoint}"
267
+
268
+ def get_auth_headers(self) -> Dict[str, str]:
269
+ """Bitbucket 인증 헤더"""
270
+ return self.config.get_auth_header(self.auth_token)
271
+
272
+ def parse_rate_limit(self, headers: Dict[str, str]) -> Optional[RateLimitInfo]:
273
+ """Bitbucket Rate limit 정보 파싱"""
274
+ # Bitbucket은 특별한 rate limit 헤더가 없음
275
+ return None
276
+
277
+ def parse_response(self, response_data: Dict[str, Any], operation: str) -> Dict[str, Any]:
278
+ """Bitbucket 응답 데이터 파싱"""
279
+ # Bitbucket API 응답 구조에 맞게 파싱
280
+ return response_data
281
+
282
+
283
+ class AdapterFactory:
284
+ """어댑터 팩토리"""
285
+
286
+ _adapters = {
287
+ Platform.GITHUB: GitHubAdapter,
288
+ Platform.GITLAB: GitLabAdapter,
289
+ Platform.BITBUCKET: BitbucketAdapter
290
+ }
291
+
292
+ @classmethod
293
+ def create_adapter(cls, platform: Platform, auth_token: str) -> PlatformAPIAdapter:
294
+ """플랫폼에 맞는 어댑터 생성"""
295
+ if platform not in cls._adapters:
296
+ supported = [p.value for p in cls._adapters.keys()]
297
+ raise PlatformNotSupportedError(platform.value, supported)
298
+
299
+ config = PLATFORM_CONFIGS.get(platform)
300
+ if not config:
301
+ raise PlatformNotSupportedError(platform.value)
302
+
303
+ adapter_class = cls._adapters[platform]
304
+ return adapter_class(config, auth_token)
305
+
306
+ @classmethod
307
+ def register_adapter(cls, platform: Platform, adapter_class: type):
308
+ """새로운 어댑터 등록"""
309
+ cls._adapters[platform] = adapter_class
310
+
311
+ @classmethod
312
+ def get_supported_platforms(cls) -> list:
313
+ """지원하는 플랫폼 목록 반환"""
314
+ return [platform.value for platform in cls._adapters.keys()]