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
@@ -0,0 +1,185 @@
1
+ import asyncio
2
+ import json
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+
6
+ import httpx
7
+
8
+ from .providers import PROVIDERS
9
+ from .logger import KeyLogger
10
+
11
+
12
+ async def validate_keys(keys_file: str = "./data/keys.json",
13
+ results_file: str = "./data/check_results.json",
14
+ logs_dir: str = "./data/logs",
15
+ concurrency: int = 100,
16
+ timeout: int = 30,
17
+ proxy: str = None,
18
+ provider_filter: str = None,
19
+ status_filter: str = None,
20
+ single_key: str = None,
21
+ progress_callback=None) -> dict:
22
+ logger = KeyLogger(logs_dir, "check")
23
+
24
+ with open(keys_file, "r", encoding="utf-8") as f:
25
+ data = json.load(f)
26
+
27
+ keys_to_check = []
28
+ for key, info in data["keys"].items():
29
+ if single_key and key != single_key:
30
+ continue
31
+ if provider_filter and info["provider"].lower() != provider_filter.lower():
32
+ continue
33
+ if status_filter and info["status"] != status_filter:
34
+ continue
35
+ keys_to_check.append((key, info))
36
+
37
+ total = len(keys_to_check)
38
+ results = {
39
+ "run_at": datetime.utcnow().isoformat() + "Z",
40
+ "total": total,
41
+ "summary": {
42
+ "valid": {"count": 0, "keys": []},
43
+ "invalid": {"count": 0, "keys": []},
44
+ "error": {"count": 0, "keys": []}
45
+ },
46
+ "details": [],
47
+ "by_provider": {}
48
+ }
49
+
50
+ # Initialize progress
51
+ if progress_callback:
52
+ progress_callback(0, total)
53
+
54
+ semaphore = asyncio.Semaphore(concurrency)
55
+ completed_count = 0
56
+
57
+ async def check_one(client: httpx.AsyncClient, key: str, info: dict):
58
+ nonlocal completed_count
59
+ async with semaphore:
60
+ provider_name = info["provider"].lower()
61
+ provider = PROVIDERS.get(provider_name)
62
+ if not provider:
63
+ from src.providers.base import CheckResult
64
+ result = CheckResult(valid=False, status_code=None, latency_ms=0, error=f"unknown provider: {provider_name}")
65
+ else:
66
+ result = await provider.check(client, key)
67
+
68
+ # Get balance if provider supports it and key is valid
69
+ balance_info = None
70
+ if result.valid and hasattr(provider, 'get_balance'):
71
+ try:
72
+ balance_result = await provider.get_balance(client, key)
73
+ if balance_result.supported and balance_result.balance is not None:
74
+ balance_info = {
75
+ "balance": balance_result.balance,
76
+ "currency": balance_result.currency
77
+ }
78
+ except Exception:
79
+ pass # Balance fetch failed, continue without it
80
+
81
+ # Determine error_type based on status code and error message
82
+ error_type = None
83
+ if not result.valid:
84
+ error_msg = getattr(result, 'error', None) or ""
85
+ status_code = result.status_code
86
+ if status_code in (401, 403):
87
+ error_type = "invalid_key"
88
+ elif status_code == 429:
89
+ error_type = "rate_limited"
90
+ elif status_code == 402:
91
+ error_type = "insufficient_balance"
92
+ elif status_code and status_code >= 500:
93
+ error_type = "server_error"
94
+ elif any(kw in error_msg.lower() for kw in ["balance", "quota"]):
95
+ error_type = "insufficient_balance"
96
+ else:
97
+ error_type = "unknown"
98
+
99
+ # Store balance and error_type in result for later use
100
+ result.balance = balance_info
101
+ result.error_type = error_type
102
+
103
+ completed_count += 1
104
+ if progress_callback:
105
+ progress_callback(completed_count, total)
106
+
107
+ return key, info, result
108
+
109
+ async with httpx.AsyncClient(timeout=timeout, proxy=proxy, follow_redirects=False) as client:
110
+ tasks = [check_one(client, k, i) for k, i in keys_to_check]
111
+ completed = await asyncio.gather(*tasks, return_exceptions=True)
112
+
113
+ for item in completed:
114
+ if isinstance(item, Exception):
115
+ # Handle exceptions from gather
116
+ logger.log("CHECK", "unknown", "unknown", "ERROR", str(item), 0)
117
+ continue
118
+
119
+ key, info, result = item
120
+ key_masked = info["key_masked"]
121
+ provider = info["provider"]
122
+
123
+ # Ensure result has error field
124
+ error_msg = getattr(result, 'error', None) or None
125
+
126
+ if result.valid:
127
+ status = "valid"
128
+ results["summary"]["valid"]["count"] += 1
129
+ results["summary"]["valid"]["keys"].append(key_masked)
130
+ elif result.status_code in (401, 403):
131
+ status = "invalid"
132
+ # Make sure we have an error message for invalid keys
133
+ if not error_msg:
134
+ error_msg = "invalid key"
135
+ results["summary"]["invalid"]["count"] += 1
136
+ results["summary"]["invalid"]["keys"].append(key_masked)
137
+ else:
138
+ status = "error"
139
+ # Make sure we have an error message for errors
140
+ if not error_msg:
141
+ if result.status_code:
142
+ error_msg = f"HTTP {result.status_code}"
143
+ else:
144
+ error_msg = "connection failed"
145
+ results["summary"]["error"]["count"] += 1
146
+ results["summary"]["error"]["keys"].append(key_masked)
147
+
148
+ detail = error_msg or str(result.status_code)
149
+ logger.log("CHECK", provider, key_masked, status.upper(), detail, result.latency_ms / 1000)
150
+
151
+ results["details"].append({
152
+ "key_masked": key_masked,
153
+ "provider": provider,
154
+ "status": status,
155
+ "code": result.status_code,
156
+ "latency_ms": result.latency_ms,
157
+ "error": error_msg,
158
+ "error_type": getattr(result, 'error_type', None),
159
+ "balance": getattr(result, 'balance', None)
160
+ })
161
+
162
+ if provider not in results["by_provider"]:
163
+ results["by_provider"][provider] = {"valid": 0, "invalid": 0, "error": 0}
164
+ results["by_provider"][provider][status] += 1
165
+
166
+ data["keys"][key]["status"] = status
167
+ data["keys"][key]["checks"].append({
168
+ "timestamp": datetime.utcnow().isoformat() + "Z",
169
+ "status": status,
170
+ "status_code": result.status_code,
171
+ "latency_ms": result.latency_ms,
172
+ "error": error_msg,
173
+ "error_type": getattr(result, 'error_type', None),
174
+ "balance": getattr(result, 'balance', None)
175
+ })
176
+ data["keys"][key]["last_checked"] = datetime.utcnow().isoformat() + "Z"
177
+
178
+ with open(keys_file, "w", encoding="utf-8") as f:
179
+ json.dump(data, f, indent=2, ensure_ascii=False)
180
+
181
+ with open(results_file, "w", encoding="utf-8") as f:
182
+ json.dump(results, f, indent=2, ensure_ascii=False)
183
+
184
+ logger.flush()
185
+ return results