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.
Files changed (73) hide show
  1. api_key_manager-2.1.0.dist-info/METADATA +709 -0
  2. api_key_manager-2.1.0.dist-info/RECORD +73 -0
  3. api_key_manager-2.1.0.dist-info/WHEEL +5 -0
  4. api_key_manager-2.1.0.dist-info/entry_points.txt +2 -0
  5. api_key_manager-2.1.0.dist-info/top_level.txt +1 -0
  6. key_manager/__init__.py +16 -0
  7. key_manager/__main__.py +5 -0
  8. key_manager/api_models.py +358 -0
  9. key_manager/checker.py +51 -0
  10. key_manager/cli.py +270 -0
  11. key_manager/config.py +61 -0
  12. key_manager/core.py +205 -0
  13. key_manager/detector.py +335 -0
  14. key_manager/errors.py +179 -0
  15. key_manager/i18n.py +142 -0
  16. key_manager/logger.py +207 -0
  17. key_manager/model_capabilities.py +412 -0
  18. key_manager/parser.py +153 -0
  19. key_manager/providers/__init__.py +283 -0
  20. key_manager/providers/ai302.py +109 -0
  21. key_manager/providers/anthropic.py +109 -0
  22. key_manager/providers/baichuan.py +97 -0
  23. key_manager/providers/base.py +312 -0
  24. key_manager/providers/cerebras.py +109 -0
  25. key_manager/providers/cohere.py +90 -0
  26. key_manager/providers/cstcloud.py +122 -0
  27. key_manager/providers/dashscope.py +120 -0
  28. key_manager/providers/dashscope_coding.py +122 -0
  29. key_manager/providers/deepseek.py +166 -0
  30. key_manager/providers/dmxapi.py +109 -0
  31. key_manager/providers/doubao.py +109 -0
  32. key_manager/providers/fireworks.py +109 -0
  33. key_manager/providers/google.py +99 -0
  34. key_manager/providers/grok.py +109 -0
  35. key_manager/providers/groq.py +109 -0
  36. key_manager/providers/huggingface.py +54 -0
  37. key_manager/providers/hyperbolic.py +109 -0
  38. key_manager/providers/infini.py +135 -0
  39. key_manager/providers/infini_coding.py +124 -0
  40. key_manager/providers/kimi.py +121 -0
  41. key_manager/providers/kimi_coding.py +124 -0
  42. key_manager/providers/longcat.py +123 -0
  43. key_manager/providers/mimo.py +109 -0
  44. key_manager/providers/mimo_plan.py +140 -0
  45. key_manager/providers/minimax.py +97 -0
  46. key_manager/providers/minimax_plan.py +122 -0
  47. key_manager/providers/mistral.py +109 -0
  48. key_manager/providers/models_registry.py +2901 -0
  49. key_manager/providers/modelscope.py +134 -0
  50. key_manager/providers/nvidia.py +109 -0
  51. key_manager/providers/ocoolai.py +109 -0
  52. key_manager/providers/openai.py +140 -0
  53. key_manager/providers/openrouter.py +119 -0
  54. key_manager/providers/perplexity.py +109 -0
  55. key_manager/providers/poe.py +109 -0
  56. key_manager/providers/ppio.py +109 -0
  57. key_manager/providers/replicate.py +54 -0
  58. key_manager/providers/siliconflow.py +121 -0
  59. key_manager/providers/stepfun.py +132 -0
  60. key_manager/providers/tencent_hunyuan.py +122 -0
  61. key_manager/providers/together.py +134 -0
  62. key_manager/providers/yi.py +97 -0
  63. key_manager/providers/zai.py +109 -0
  64. key_manager/providers/zhipu.py +127 -0
  65. key_manager/providers/zhipu_coding.py +124 -0
  66. key_manager/proxy.py +70 -0
  67. key_manager/ssrf.py +68 -0
  68. key_manager/storage.py +134 -0
  69. key_manager/tester.py +137 -0
  70. key_manager/url_override.py +5 -0
  71. key_manager/validator.py +185 -0
  72. key_manager/web.py +1512 -0
  73. 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()