api-key-manager 2.1.0__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.
- api_key_manager-2.1.0.dist-info/METADATA +709 -0
- api_key_manager-2.1.0.dist-info/RECORD +73 -0
- api_key_manager-2.1.0.dist-info/WHEEL +5 -0
- api_key_manager-2.1.0.dist-info/entry_points.txt +2 -0
- api_key_manager-2.1.0.dist-info/top_level.txt +1 -0
- key_manager/__init__.py +16 -0
- key_manager/__main__.py +5 -0
- key_manager/api_models.py +358 -0
- key_manager/checker.py +51 -0
- key_manager/cli.py +270 -0
- key_manager/config.py +61 -0
- key_manager/core.py +205 -0
- key_manager/detector.py +335 -0
- key_manager/errors.py +179 -0
- key_manager/i18n.py +142 -0
- key_manager/logger.py +207 -0
- key_manager/model_capabilities.py +412 -0
- key_manager/parser.py +153 -0
- key_manager/providers/__init__.py +283 -0
- key_manager/providers/ai302.py +109 -0
- key_manager/providers/anthropic.py +109 -0
- key_manager/providers/baichuan.py +97 -0
- key_manager/providers/base.py +312 -0
- key_manager/providers/cerebras.py +109 -0
- key_manager/providers/cohere.py +90 -0
- key_manager/providers/cstcloud.py +122 -0
- key_manager/providers/dashscope.py +120 -0
- key_manager/providers/dashscope_coding.py +122 -0
- key_manager/providers/deepseek.py +166 -0
- key_manager/providers/dmxapi.py +109 -0
- key_manager/providers/doubao.py +109 -0
- key_manager/providers/fireworks.py +109 -0
- key_manager/providers/google.py +99 -0
- key_manager/providers/grok.py +109 -0
- key_manager/providers/groq.py +109 -0
- key_manager/providers/huggingface.py +54 -0
- key_manager/providers/hyperbolic.py +109 -0
- key_manager/providers/infini.py +135 -0
- key_manager/providers/infini_coding.py +124 -0
- key_manager/providers/kimi.py +121 -0
- key_manager/providers/kimi_coding.py +124 -0
- key_manager/providers/longcat.py +123 -0
- key_manager/providers/mimo.py +109 -0
- key_manager/providers/mimo_plan.py +140 -0
- key_manager/providers/minimax.py +97 -0
- key_manager/providers/minimax_plan.py +122 -0
- key_manager/providers/mistral.py +109 -0
- key_manager/providers/models_registry.py +2901 -0
- key_manager/providers/modelscope.py +134 -0
- key_manager/providers/nvidia.py +109 -0
- key_manager/providers/ocoolai.py +109 -0
- key_manager/providers/openai.py +140 -0
- key_manager/providers/openrouter.py +119 -0
- key_manager/providers/perplexity.py +109 -0
- key_manager/providers/poe.py +109 -0
- key_manager/providers/ppio.py +109 -0
- key_manager/providers/replicate.py +54 -0
- key_manager/providers/siliconflow.py +121 -0
- key_manager/providers/stepfun.py +132 -0
- key_manager/providers/tencent_hunyuan.py +122 -0
- key_manager/providers/together.py +134 -0
- key_manager/providers/yi.py +97 -0
- key_manager/providers/zai.py +109 -0
- key_manager/providers/zhipu.py +127 -0
- key_manager/providers/zhipu_coding.py +124 -0
- key_manager/proxy.py +70 -0
- key_manager/ssrf.py +68 -0
- key_manager/storage.py +134 -0
- key_manager/tester.py +137 -0
- key_manager/url_override.py +5 -0
- key_manager/validator.py +185 -0
- key_manager/web.py +1512 -0
- key_manager/webhook.py +257 -0
key_manager/webhook.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Webhook system for API Key Manager.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
- Multiple event types
|
|
6
|
+
- Retry logic with exponential backoff
|
|
7
|
+
- HMAC-SHA256 signature verification
|
|
8
|
+
- Async HTTP delivery
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import hashlib
|
|
13
|
+
import hmac
|
|
14
|
+
import json
|
|
15
|
+
import time
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from typing import Any, Callable, Optional
|
|
20
|
+
|
|
21
|
+
import httpx
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WebhookEvent(str, Enum):
|
|
25
|
+
"""Supported webhook event types."""
|
|
26
|
+
KEY_IMPORTED = "key.imported"
|
|
27
|
+
KEY_CHECKED = "key.checked"
|
|
28
|
+
KEY_TESTED = "key.tested"
|
|
29
|
+
KEY_DELETED = "key.deleted"
|
|
30
|
+
BATCH_CHECK_COMPLETED = "batch.check.completed"
|
|
31
|
+
BATCH_TEST_COMPLETED = "batch.test.completed"
|
|
32
|
+
ERROR_OCCURRED = "error.occurred"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class WebhookConfig:
|
|
37
|
+
"""Webhook configuration."""
|
|
38
|
+
url: str
|
|
39
|
+
events: list[WebhookEvent] = field(default_factory=lambda: list(WebhookEvent))
|
|
40
|
+
secret: Optional[str] = None
|
|
41
|
+
active: bool = True
|
|
42
|
+
max_retries: int = 3
|
|
43
|
+
retry_delay: float = 1.0
|
|
44
|
+
timeout: float = 10.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class WebhookDelivery:
|
|
49
|
+
"""Record of a webhook delivery attempt."""
|
|
50
|
+
webhook_url: str
|
|
51
|
+
event: WebhookEvent
|
|
52
|
+
payload: dict
|
|
53
|
+
status_code: Optional[int] = None
|
|
54
|
+
success: bool = False
|
|
55
|
+
error: Optional[str] = None
|
|
56
|
+
attempts: int = 0
|
|
57
|
+
delivered_at: Optional[str] = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class WebhookManager:
|
|
61
|
+
"""Manages webhook registrations and deliveries."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, config: Optional[dict] = None):
|
|
64
|
+
self._webhooks: dict[str, WebhookConfig] = {}
|
|
65
|
+
self._delivery_log: list[WebhookDelivery] = []
|
|
66
|
+
self._max_log_size = 1000
|
|
67
|
+
self._config = config or {}
|
|
68
|
+
self._load_from_config()
|
|
69
|
+
|
|
70
|
+
def _load_from_config(self):
|
|
71
|
+
"""Load webhooks from configuration."""
|
|
72
|
+
webhooks = self._config.get("webhooks", [])
|
|
73
|
+
for wh in webhooks:
|
|
74
|
+
if wh.get("url"):
|
|
75
|
+
self.register(
|
|
76
|
+
url=wh["url"],
|
|
77
|
+
events=wh.get("events", [e.value for e in WebhookEvent]),
|
|
78
|
+
secret=wh.get("secret"),
|
|
79
|
+
active=wh.get("active", True),
|
|
80
|
+
max_retries=wh.get("max_retries", 3),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def register(
|
|
84
|
+
self,
|
|
85
|
+
url: str,
|
|
86
|
+
events: Optional[list[str]] = None,
|
|
87
|
+
secret: Optional[str] = None,
|
|
88
|
+
active: bool = True,
|
|
89
|
+
max_retries: int = 3,
|
|
90
|
+
) -> str:
|
|
91
|
+
"""Register a new webhook."""
|
|
92
|
+
webhook_id = hashlib.md5(url.encode()).hexdigest()[:8]
|
|
93
|
+
|
|
94
|
+
event_list = []
|
|
95
|
+
if events:
|
|
96
|
+
for e in events:
|
|
97
|
+
try:
|
|
98
|
+
event_list.append(WebhookEvent(e))
|
|
99
|
+
except ValueError:
|
|
100
|
+
pass
|
|
101
|
+
else:
|
|
102
|
+
event_list = list(WebhookEvent)
|
|
103
|
+
|
|
104
|
+
self._webhooks[webhook_id] = WebhookConfig(
|
|
105
|
+
url=url,
|
|
106
|
+
events=event_list,
|
|
107
|
+
secret=secret,
|
|
108
|
+
active=active,
|
|
109
|
+
max_retries=max_retries,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return webhook_id
|
|
113
|
+
|
|
114
|
+
def unregister(self, webhook_id: str) -> bool:
|
|
115
|
+
"""Unregister a webhook."""
|
|
116
|
+
if webhook_id in self._webhooks:
|
|
117
|
+
del self._webhooks[webhook_id]
|
|
118
|
+
return True
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def get(self, webhook_id: str) -> Optional[WebhookConfig]:
|
|
122
|
+
"""Get webhook configuration."""
|
|
123
|
+
return self._webhooks.get(webhook_id)
|
|
124
|
+
|
|
125
|
+
def list_all(self) -> dict[str, WebhookConfig]:
|
|
126
|
+
"""List all registered webhooks."""
|
|
127
|
+
return dict(self._webhooks)
|
|
128
|
+
|
|
129
|
+
def update(self, webhook_id: str, **kwargs) -> bool:
|
|
130
|
+
"""Update webhook configuration."""
|
|
131
|
+
wh = self._webhooks.get(webhook_id)
|
|
132
|
+
if not wh:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
for key, value in kwargs.items():
|
|
136
|
+
if hasattr(wh, key):
|
|
137
|
+
if key == "events" and isinstance(value, list):
|
|
138
|
+
event_list = []
|
|
139
|
+
for e in value:
|
|
140
|
+
try:
|
|
141
|
+
event_list.append(WebhookEvent(e) if isinstance(e, str) else e)
|
|
142
|
+
except ValueError:
|
|
143
|
+
pass
|
|
144
|
+
setattr(wh, key, event_list)
|
|
145
|
+
else:
|
|
146
|
+
setattr(wh, key, value)
|
|
147
|
+
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
def _sign_payload(self, payload: dict, secret: str) -> str:
|
|
151
|
+
"""Generate HMAC-SHA256 signature for payload."""
|
|
152
|
+
body = json.dumps(payload, separators=(",", ":"), sort_keys=True)
|
|
153
|
+
signature = hmac.new(
|
|
154
|
+
secret.encode("utf-8"),
|
|
155
|
+
body.encode("utf-8"),
|
|
156
|
+
hashlib.sha256,
|
|
157
|
+
).hexdigest()
|
|
158
|
+
return f"sha256={signature}"
|
|
159
|
+
|
|
160
|
+
async def dispatch(self, event: WebhookEvent, data: dict) -> list[WebhookDelivery]:
|
|
161
|
+
"""Dispatch an event to all registered webhooks."""
|
|
162
|
+
deliveries = []
|
|
163
|
+
|
|
164
|
+
payload = {
|
|
165
|
+
"event": event.value,
|
|
166
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
167
|
+
"data": data,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
tasks = []
|
|
171
|
+
for webhook_id, config in self._webhooks.items():
|
|
172
|
+
if not config.active:
|
|
173
|
+
continue
|
|
174
|
+
if event not in config.events:
|
|
175
|
+
continue
|
|
176
|
+
tasks.append(self._deliver(config, payload))
|
|
177
|
+
|
|
178
|
+
if tasks:
|
|
179
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
180
|
+
for result in results:
|
|
181
|
+
if isinstance(result, WebhookDelivery):
|
|
182
|
+
deliveries.append(result)
|
|
183
|
+
self._log_delivery(result)
|
|
184
|
+
|
|
185
|
+
return deliveries
|
|
186
|
+
|
|
187
|
+
async def _deliver(self, config: WebhookConfig, payload: dict) -> WebhookDelivery:
|
|
188
|
+
"""Deliver a webhook with retry logic."""
|
|
189
|
+
delivery = WebhookDelivery(
|
|
190
|
+
webhook_url=config.url,
|
|
191
|
+
event=WebhookEvent(payload["event"]),
|
|
192
|
+
payload=payload,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
headers = {
|
|
196
|
+
"Content-Type": "application/json",
|
|
197
|
+
"User-Agent": "API-Key-Manager/1.0",
|
|
198
|
+
"X-Webhook-Event": payload["event"],
|
|
199
|
+
"X-Webhook-Timestamp": payload["timestamp"],
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if config.secret:
|
|
203
|
+
headers["X-Webhook-Signature"] = self._sign_payload(payload, config.secret)
|
|
204
|
+
|
|
205
|
+
last_error = None
|
|
206
|
+
for attempt in range(config.max_retries):
|
|
207
|
+
delivery.attempts = attempt + 1
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
async with httpx.AsyncClient(timeout=config.timeout) as client:
|
|
211
|
+
response = await client.post(
|
|
212
|
+
config.url,
|
|
213
|
+
json=payload,
|
|
214
|
+
headers=headers,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
delivery.status_code = response.status_code
|
|
218
|
+
|
|
219
|
+
if 200 <= response.status_code < 300:
|
|
220
|
+
delivery.success = True
|
|
221
|
+
delivery.delivered_at = datetime.utcnow().isoformat() + "Z"
|
|
222
|
+
return delivery
|
|
223
|
+
|
|
224
|
+
last_error = f"HTTP {response.status_code}"
|
|
225
|
+
|
|
226
|
+
except httpx.TimeoutException:
|
|
227
|
+
last_error = "Timeout"
|
|
228
|
+
except httpx.ConnectError:
|
|
229
|
+
last_error = "Connection error"
|
|
230
|
+
except Exception as e:
|
|
231
|
+
last_error = str(e)
|
|
232
|
+
|
|
233
|
+
if attempt < config.max_retries - 1:
|
|
234
|
+
delay = config.retry_delay * (2 ** attempt)
|
|
235
|
+
await asyncio.sleep(delay)
|
|
236
|
+
|
|
237
|
+
delivery.error = last_error
|
|
238
|
+
delivery.delivered_at = datetime.utcnow().isoformat() + "Z"
|
|
239
|
+
return delivery
|
|
240
|
+
|
|
241
|
+
def _log_delivery(self, delivery: WebhookDelivery):
|
|
242
|
+
"""Log a delivery attempt."""
|
|
243
|
+
self._delivery_log.append(delivery)
|
|
244
|
+
if len(self._delivery_log) > self._max_log_size:
|
|
245
|
+
self._delivery_log = self._delivery_log[-self._max_log_size:]
|
|
246
|
+
|
|
247
|
+
def get_delivery_log(self, limit: int = 50) -> list[WebhookDelivery]:
|
|
248
|
+
"""Get recent delivery log."""
|
|
249
|
+
return self._delivery_log[-limit:]
|
|
250
|
+
|
|
251
|
+
def clear_delivery_log(self):
|
|
252
|
+
"""Clear delivery log."""
|
|
253
|
+
self._delivery_log.clear()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# Global webhook manager instance
|
|
257
|
+
webhook_manager = WebhookManager()
|