| 13 | |
| 14 | |
| 15 | class Client: |
| 16 | _SUPPORTED_METHODS = ('GET', 'POST', 'DELETE', 'PATCH') |
| 17 | ERROR_MESSAGES = { |
| 18 | 401: 'Unauthorized -- Your API key is wrong.', |
| 19 | 403: 'Forbidden -- The requested resource is hidden for administrators only.', |
| 20 | 404: 'Not Found -- The specified resource could not be found.', |
| 21 | 405: 'Method Not Allowed -- You tried to access a resource with an invalid method.', |
| 22 | 406: 'Not Acceptable -- You requested a format that isn\'t json.', |
| 23 | 410: 'Gone -- The requested resource has been removed from our servers.', |
| 24 | 418: 'I\'m a teapot.', |
| 25 | 429: 'Too Many Requests -- You\'re requesting too many resources! Slow down!', |
| 26 | 500: 'Internal Server Error -- We had a problem with our server. Try again later.', |
| 27 | 503: 'Service Unavailable -- We\'re temporarily offline for maintenance. Please try again later.', |
| 28 | } |
| 29 | |
| 30 | __slots__ = ( |
| 31 | "API_KEY", |
| 32 | "proxy", |
| 33 | "_headers", |
| 34 | "_connector", |
| 35 | "_session" |
| 36 | ) |
| 37 | |
| 38 | def __init__(self, api_key: str, proxy: str = None) -> None: |
| 39 | self.API_KEY = api_key |
| 40 | self.proxy = proxy |
| 41 | self._validate_proxy() |
| 42 | self._headers = { |
| 43 | 'Authorization': self.API_KEY |
| 44 | } |
| 45 | self._connector = ProxyConnector.from_url(self.proxy, ttl_dns_cache=300) if self.proxy else aiohttp.TCPConnector( |
| 46 | resolver=aiohttp.resolver.AsyncResolver(), |
| 47 | limit_per_host=50 |
| 48 | ) |
| 49 | self._session = aiohttp.ClientSession(connector=self._connector, headers=self._headers) |
| 50 | |
| 51 | async def __aenter__(self): |
| 52 | return self |
| 53 | |
| 54 | async def __aexit__(self, exc_type, exc, tb): |
| 55 | await self._session.close() |
| 56 | |
| 57 | async def close(self): |
| 58 | await self._session.close() |
| 59 | |
| 60 | def _validate_proxy(self) -> None: |
| 61 | """Validates the proxy URL format. |
| 62 | |
| 63 | Raises: |
| 64 | ValueError: If the proxy URL format is invalid or the port is out of range. |
| 65 | """ |
| 66 | if not self.proxy: |
| 67 | return # Proxy is not set, which is acceptable |
| 68 | # Regular expression to check the format: socks5://user:pass@host:port, etc. |
| 69 | pattern = r'^(socks5|socks4|http|https)://(\w+:\w+@)?[\w.-]+:\d+$' |
| 70 | if not re.match(pattern, self.proxy): |
| 71 | raise ValueError( |
| 72 | f"Invalid proxy URL format: {self.proxy}. Expected format like 'socks5://user:pass@host:port'") |
nothing calls this directly
no outgoing calls
no test coverage detected