yeonjae-universal-http-api-client 1.0.1__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.
@@ -0,0 +1,209 @@
1
+ """
2
+ HTTPAPIClient 모듈의 예외 클래스 정의
3
+ """
4
+
5
+ from typing import Optional, Dict, Any
6
+
7
+
8
+ class APIError(Exception):
9
+ """API 호출 관련 기본 예외"""
10
+
11
+ def __init__(
12
+ self,
13
+ message: str,
14
+ status_code: Optional[int] = None,
15
+ response_data: Optional[Dict[str, Any]] = None,
16
+ platform: Optional[str] = None
17
+ ):
18
+ super().__init__(message)
19
+ self.message = message
20
+ self.status_code = status_code
21
+ self.response_data = response_data or {}
22
+ self.platform = platform
23
+
24
+ def __str__(self) -> str:
25
+ parts = [self.message]
26
+ if self.platform:
27
+ parts.append(f"Platform: {self.platform}")
28
+ if self.status_code:
29
+ parts.append(f"Status: {self.status_code}")
30
+ return " | ".join(parts)
31
+
32
+
33
+ class RateLimitError(APIError):
34
+ """Rate Limit 초과 예외"""
35
+
36
+ def __init__(
37
+ self,
38
+ message: str = "Rate limit exceeded",
39
+ retry_after: Optional[int] = None,
40
+ remaining: int = 0,
41
+ reset_time: Optional[int] = None,
42
+ platform: Optional[str] = None
43
+ ):
44
+ super().__init__(message, 429, platform=platform)
45
+ self.retry_after = retry_after
46
+ self.remaining = remaining
47
+ self.reset_time = reset_time
48
+
49
+ def __str__(self) -> str:
50
+ parts = [self.message]
51
+ if self.platform:
52
+ parts.append(f"Platform: {self.platform}")
53
+ if self.retry_after:
54
+ parts.append(f"Retry after: {self.retry_after}s")
55
+ if self.remaining is not None:
56
+ parts.append(f"Remaining: {self.remaining}")
57
+ return " | ".join(parts)
58
+
59
+
60
+ class AuthenticationError(APIError):
61
+ """인증 관련 예외"""
62
+
63
+ def __init__(
64
+ self,
65
+ message: str = "Authentication failed",
66
+ platform: Optional[str] = None
67
+ ):
68
+ super().__init__(message, 401, platform=platform)
69
+
70
+
71
+ class AuthorizationError(APIError):
72
+ """권한 관련 예외"""
73
+
74
+ def __init__(
75
+ self,
76
+ message: str = "Insufficient permissions",
77
+ platform: Optional[str] = None
78
+ ):
79
+ super().__init__(message, 403, platform=platform)
80
+
81
+
82
+ class NotFoundError(APIError):
83
+ """리소스를 찾을 수 없음 예외"""
84
+
85
+ def __init__(
86
+ self,
87
+ message: str = "Resource not found",
88
+ platform: Optional[str] = None
89
+ ):
90
+ super().__init__(message, 404, platform=platform)
91
+
92
+
93
+ class ServerError(APIError):
94
+ """서버 에러 예외"""
95
+
96
+ def __init__(
97
+ self,
98
+ message: str = "Internal server error",
99
+ status_code: int = 500,
100
+ platform: Optional[str] = None
101
+ ):
102
+ super().__init__(message, status_code, platform=platform)
103
+
104
+
105
+ class NetworkError(APIError):
106
+ """네트워크 관련 예외"""
107
+
108
+ def __init__(
109
+ self,
110
+ message: str = "Network error occurred",
111
+ platform: Optional[str] = None
112
+ ):
113
+ super().__init__(message, platform=platform)
114
+
115
+
116
+ class TimeoutError(APIError):
117
+ """타임아웃 예외"""
118
+
119
+ def __init__(
120
+ self,
121
+ message: str = "Request timeout",
122
+ timeout: Optional[float] = None,
123
+ platform: Optional[str] = None
124
+ ):
125
+ super().__init__(message, platform=platform)
126
+ self.timeout = timeout
127
+
128
+ def __str__(self) -> str:
129
+ parts = [self.message]
130
+ if self.platform:
131
+ parts.append(f"Platform: {self.platform}")
132
+ if self.timeout:
133
+ parts.append(f"Timeout: {self.timeout}s")
134
+ return " | ".join(parts)
135
+
136
+
137
+ class ValidationError(APIError):
138
+ """데이터 검증 예외"""
139
+
140
+ def __init__(
141
+ self,
142
+ message: str = "Validation error",
143
+ field: Optional[str] = None,
144
+ value: Optional[Any] = None
145
+ ):
146
+ super().__init__(message)
147
+ self.field = field
148
+ self.value = value
149
+
150
+ def __str__(self) -> str:
151
+ parts = [self.message]
152
+ if self.field:
153
+ parts.append(f"Field: {self.field}")
154
+ if self.value is not None:
155
+ parts.append(f"Value: {self.value}")
156
+ return " | ".join(parts)
157
+
158
+
159
+ class CacheError(APIError):
160
+ """캐시 관련 예외"""
161
+
162
+ def __init__(
163
+ self,
164
+ message: str = "Cache operation failed",
165
+ operation: Optional[str] = None
166
+ ):
167
+ super().__init__(message)
168
+ self.operation = operation
169
+
170
+ def __str__(self) -> str:
171
+ parts = [self.message]
172
+ if self.operation:
173
+ parts.append(f"Operation: {self.operation}")
174
+ return " | ".join(parts)
175
+
176
+
177
+ class PlatformNotSupportedError(APIError):
178
+ """지원하지 않는 플랫폼 예외"""
179
+
180
+ def __init__(
181
+ self,
182
+ platform: str,
183
+ supported_platforms: Optional[list] = None
184
+ ):
185
+ message = f"Platform '{platform}' is not supported"
186
+ super().__init__(message, platform=platform)
187
+ self.supported_platforms = supported_platforms or []
188
+
189
+ def __str__(self) -> str:
190
+ parts = [self.message]
191
+ if self.supported_platforms:
192
+ parts.append(f"Supported: {', '.join(self.supported_platforms)}")
193
+ return " | ".join(parts)
194
+
195
+
196
+ def handle_http_error(status_code: int, message: str, platform: Optional[str] = None) -> APIError:
197
+ """HTTP 상태 코드에 따른 적절한 예외 반환"""
198
+ if status_code == 401:
199
+ return AuthenticationError(message, platform)
200
+ elif status_code == 403:
201
+ return AuthorizationError(message, platform)
202
+ elif status_code == 404:
203
+ return NotFoundError(message, platform)
204
+ elif status_code == 429:
205
+ return RateLimitError(message, platform=platform)
206
+ elif 500 <= status_code < 600:
207
+ return ServerError(message, status_code, platform)
208
+ else:
209
+ return APIError(message, status_code, platform=platform)
@@ -0,0 +1,200 @@
1
+ """
2
+ HTTPAPIClient 모듈의 데이터 모델 정의
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict, Any, Optional, Union, List
7
+ from datetime import datetime
8
+ from enum import Enum
9
+
10
+
11
+ class HTTPMethod(Enum):
12
+ """HTTP 메서드 정의"""
13
+ GET = "GET"
14
+ POST = "POST"
15
+ PUT = "PUT"
16
+ DELETE = "DELETE"
17
+ PATCH = "PATCH"
18
+
19
+
20
+ class Platform(Enum):
21
+ """지원하는 플랫폼 정의"""
22
+ GITHUB = "github"
23
+ GITLAB = "gitlab"
24
+ BITBUCKET = "bitbucket"
25
+ GENERIC = "generic"
26
+
27
+
28
+ @dataclass
29
+ class PlatformConfig:
30
+ """플랫폼별 설정"""
31
+ name: str
32
+ base_url: str
33
+ auth_header: str
34
+ auth_prefix: str = ""
35
+ rate_limit_per_hour: int = 5000
36
+ rate_limit_per_minute: Optional[int] = None
37
+ default_headers: Dict[str, str] = field(default_factory=dict)
38
+ timeout: int = 30
39
+
40
+ def get_auth_header(self, token: str) -> Dict[str, str]:
41
+ """인증 헤더 생성"""
42
+ if self.auth_prefix:
43
+ auth_value = f"{self.auth_prefix} {token}"
44
+ else:
45
+ auth_value = token
46
+ return {self.auth_header: auth_value}
47
+
48
+
49
+ @dataclass
50
+ class APIRequest:
51
+ """API 요청 정보"""
52
+ platform: Platform
53
+ method: HTTPMethod
54
+ endpoint: str
55
+ headers: Optional[Dict[str, str]] = None
56
+ params: Optional[Dict[str, Any]] = None
57
+ data: Optional[Dict[str, Any]] = None
58
+ timeout: Optional[int] = None
59
+
60
+ def to_dict(self) -> Dict[str, Any]:
61
+ """딕셔너리 변환"""
62
+ return {
63
+ "platform": self.platform.value,
64
+ "method": self.method.value,
65
+ "endpoint": self.endpoint,
66
+ "headers": self.headers or {},
67
+ "params": self.params or {},
68
+ "data": self.data or {},
69
+ "timeout": self.timeout
70
+ }
71
+
72
+
73
+ @dataclass
74
+ class APIResponse:
75
+ """API 응답 정보"""
76
+ status_code: int
77
+ data: Dict[str, Any]
78
+ headers: Dict[str, str]
79
+ success: bool
80
+ error_message: Optional[str] = None
81
+ response_time: Optional[float] = None
82
+ cached: bool = False
83
+
84
+ @classmethod
85
+ def success_response(
86
+ cls,
87
+ status_code: int,
88
+ data: Dict[str, Any],
89
+ headers: Dict[str, str],
90
+ response_time: Optional[float] = None,
91
+ cached: bool = False
92
+ ) -> 'APIResponse':
93
+ """성공 응답 생성"""
94
+ return cls(
95
+ status_code=status_code,
96
+ data=data,
97
+ headers=headers,
98
+ success=True,
99
+ response_time=response_time,
100
+ cached=cached
101
+ )
102
+
103
+ @classmethod
104
+ def error_response(
105
+ cls,
106
+ status_code: int,
107
+ error_message: str,
108
+ headers: Optional[Dict[str, str]] = None
109
+ ) -> 'APIResponse':
110
+ """에러 응답 생성"""
111
+ return cls(
112
+ status_code=status_code,
113
+ data={},
114
+ headers=headers or {},
115
+ success=False,
116
+ error_message=error_message
117
+ )
118
+
119
+
120
+ @dataclass
121
+ class RateLimitInfo:
122
+ """Rate Limit 정보"""
123
+ remaining: int
124
+ limit: int
125
+ reset_time: datetime
126
+ retry_after: Optional[int] = None
127
+
128
+ @property
129
+ def is_exhausted(self) -> bool:
130
+ """Rate limit 고갈 여부"""
131
+ return self.remaining <= 0
132
+
133
+ def is_near_limit(self, threshold: int = 10) -> bool:
134
+ """Rate limit 임계값 근접 여부"""
135
+ return self.remaining <= threshold
136
+
137
+
138
+ @dataclass
139
+ class CacheKey:
140
+ """캐시 키 정보"""
141
+ platform: str
142
+ endpoint: str
143
+ params_hash: str
144
+
145
+ def __str__(self) -> str:
146
+ return f"{self.platform}:{self.endpoint}:{self.params_hash}"
147
+
148
+
149
+ @dataclass
150
+ class CachedResponse:
151
+ """캐시된 응답 정보"""
152
+ response: APIResponse
153
+ cached_at: datetime
154
+ ttl: int
155
+
156
+ @property
157
+ def is_expired(self) -> bool:
158
+ """캐시 만료 여부"""
159
+ from datetime import datetime, timedelta, timezone
160
+ return datetime.now(timezone.utc) > self.cached_at + timedelta(seconds=self.ttl)
161
+
162
+
163
+ # 플랫폼별 설정 정의
164
+ PLATFORM_CONFIGS = {
165
+ Platform.GITHUB: PlatformConfig(
166
+ name="GitHub",
167
+ base_url="https://api.github.com",
168
+ auth_header="Authorization",
169
+ auth_prefix="token",
170
+ rate_limit_per_hour=5000,
171
+ rate_limit_per_minute=None,
172
+ default_headers={
173
+ "Accept": "application/vnd.github.v3+json",
174
+ "User-Agent": "CodePing-HTTPAPIClient/1.0"
175
+ }
176
+ ),
177
+ Platform.GITLAB: PlatformConfig(
178
+ name="GitLab",
179
+ base_url="https://gitlab.com/api/v4",
180
+ auth_header="PRIVATE-TOKEN",
181
+ auth_prefix="",
182
+ rate_limit_per_hour=None,
183
+ rate_limit_per_minute=300,
184
+ default_headers={
185
+ "Content-Type": "application/json",
186
+ "User-Agent": "CodePing-HTTPAPIClient/1.0"
187
+ }
188
+ ),
189
+ Platform.BITBUCKET: PlatformConfig(
190
+ name="Bitbucket",
191
+ base_url="https://api.bitbucket.org/2.0",
192
+ auth_header="Authorization",
193
+ auth_prefix="Bearer",
194
+ rate_limit_per_hour=1000,
195
+ default_headers={
196
+ "Accept": "application/json",
197
+ "User-Agent": "CodePing-HTTPAPIClient/1.0"
198
+ }
199
+ )
200
+ }
File without changes
@@ -0,0 +1,40 @@
1
+ """
2
+ 독립적인 유틸리티 모듈
3
+ """
4
+ import logging
5
+ import time
6
+ from typing import Any, Dict, Optional
7
+
8
+
9
+ class ModuleIOLogger:
10
+ """모듈 입출력 로거 - shared.utils.logging.ModuleIOLogger 대체"""
11
+
12
+ def __init__(self, module_name: str):
13
+ self.module_name = module_name
14
+ self.logger = logging.getLogger(f"ModuleIO.{module_name}")
15
+
16
+ def log_input(self, operation: str, **kwargs):
17
+ """입력 로깅"""
18
+ self.logger.debug(f"[{self.module_name}] INPUT {operation}: {kwargs}")
19
+
20
+ def log_output(self, operation: str, result: Any, execution_time: Optional[float] = None):
21
+ """출력 로깅"""
22
+ log_msg = f"[{self.module_name}] OUTPUT {operation}: success"
23
+ if execution_time:
24
+ log_msg += f" (took {execution_time:.3f}s)"
25
+ self.logger.debug(log_msg)
26
+
27
+ def log_error(self, operation: str, error: Exception, execution_time: Optional[float] = None):
28
+ """에러 로깅"""
29
+ log_msg = f"[{self.module_name}] ERROR {operation}: {type(error).__name__}: {error}"
30
+ if execution_time:
31
+ log_msg += f" (took {execution_time:.3f}s)"
32
+ self.logger.error(log_msg)
33
+
34
+
35
+ def setup_logging(level: str = "INFO") -> None:
36
+ """로깅 설정"""
37
+ logging.basicConfig(
38
+ level=getattr(logging, level.upper()),
39
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
40
+ )
@@ -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,11 @@
1
+ universal_http_api_client/__init__.py,sha256=sSioVC1Alh4IBNijvZiEShy8mbh3Xdqimnq5m1F--nI,1056
2
+ universal_http_api_client/adapters.py,sha256=S_5zyH6NIy-dg_w7v1EMLPBi_KrIRixFMPRxELLZfLc,11699
3
+ universal_http_api_client/client.py,sha256=lq9jFsO5bku4kkTT0VnqEi5GP2Zx9xqsoeOvk-YlfZQ,21774
4
+ universal_http_api_client/exceptions.py,sha256=uuABwSQIMnHL92Rdo4Ojw4BNYEfERNsQnVICVzW5tec,5848
5
+ universal_http_api_client/models.py,sha256=FaOQUitSBEuzbO6KXNiNRqGpYctWX-bBtN2bQB47224,5162
6
+ universal_http_api_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ universal_http_api_client/utils.py,sha256=PV3Xv-9VOc3OJ9vXHKsVagKOXbE1H66OyOvehHX1gp0,1407
8
+ yeonjae_universal_http_api_client-1.0.1.dist-info/METADATA,sha256=NkYCWrCWs5R5oEkWPqX4kNsQ2hLVfLawF8EI0HeAdNw,2898
9
+ yeonjae_universal_http_api_client-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ yeonjae_universal_http_api_client-1.0.1.dist-info/top_level.txt,sha256=eiCeozAaUmZUn2d3cu5PPa3fJShjaa1SsxU9wnBmAiI,26
11
+ yeonjae_universal_http_api_client-1.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ universal_http_api_client