Обновлены возвращаемые типы

This commit is contained in:
2025-10-26 14:24:58 +03:00
parent e380eda03a
commit 107729ec8b
14 changed files with 38 additions and 130 deletions

View File

@ -1,6 +1,6 @@
[project] [project]
name = "aiohttpx" name = "aiohttpx"
version = "0.1.0" version = "0.2.0"
description = "Custom HTTPX client with aiohttp transport, rate limiter and caching" description = "Custom HTTPX client with aiohttp transport, rate limiter and caching"
readme = "README.md" readme = "README.md"
authors = [ authors = [

View File

@ -1 +1 @@
__version__: str = '0.1.0' __version__: str = '0.2.0'

View File

@ -33,7 +33,7 @@ class AioHTTPXClient(AsyncHTTPXClient):
redis_url: str | None = None, redis_url: str | None = None,
key: str | None = None, key: str | None = None,
limit: int | None = None, limit: int | None = None,
): ) -> None:
super().__init__( super().__init__(
auth=auth, auth=auth,
params=params, params=params,

View File

@ -5,5 +5,5 @@ from orjson import loads
class Response(HTTPXResponse): class Response(HTTPXResponse):
def json(self, **kwargs: Any): def json(self, **kwargs: Any) -> Any:
return loads(self.content, **kwargs) return loads(self.content, **kwargs)

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
from logging import getLogger from logging import getLogger
from types import TracebackType from types import TracebackType
from typing import Any from typing import Any, Self
import aiohttp import aiohttp
import httpx import httpx
@ -32,12 +32,27 @@ class AiohttpTransport(httpx.AsyncBaseTransport):
def __init__( def __init__(
self, self,
session: aiohttp.ClientSession | None = None, session: aiohttp.ClientSession | None = None,
): ) -> None:
self.logger = getLogger(__name__) self.logger = getLogger(__name__)
self._session = session or aiohttp.ClientSession() self._session = session or aiohttp.ClientSession()
self._closed = False self._closed = False
def map_aiohttp_exception(self, exc: Exception): def map_aiohttp_exception(
self, exc: Exception
) -> (
httpx.ConnectError
| httpx.DecodingError
| httpx.HTTPStatusError
| httpx.InvalidURL
| httpx.NetworkError
| httpx.ProtocolError
| httpx.ProxyError
| httpx.ReadError
| httpx.RequestError
| httpx.TooManyRedirects
| httpx.TimeoutException
| httpx.HTTPError
):
for aiohttp_exc, httpx_exc in EXCEPTIONS.items(): for aiohttp_exc, httpx_exc in EXCEPTIONS.items():
if isinstance(exc, aiohttp_exc): if isinstance(exc, aiohttp_exc):
return httpx_exc(message=str(exc)) # type: ignore return httpx_exc(message=str(exc)) # type: ignore
@ -47,7 +62,7 @@ class AiohttpTransport(httpx.AsyncBaseTransport):
return httpx.HTTPError(f'Unknown error: {exc!s}') return httpx.HTTPError(f'Unknown error: {exc!s}')
async def __aenter__(self): async def __aenter__(self) -> Self:
await self._session.__aenter__() await self._session.__aenter__()
return self return self
@ -56,16 +71,16 @@ class AiohttpTransport(httpx.AsyncBaseTransport):
exc_type: type[BaseException] | None = None, exc_type: type[BaseException] | None = None,
exc_value: BaseException | None = None, exc_value: BaseException | None = None,
traceback: TracebackType | None = None, traceback: TracebackType | None = None,
): ) -> None:
await self._session.__aexit__(exc_type, exc_value, traceback) await self._session.__aexit__(exc_type, exc_value, traceback)
self._closed = True self._closed = True
async def aclose(self): async def aclose(self) -> None:
if not self._closed: if not self._closed:
self._closed = True self._closed = True
await self._session.close() await self._session.close()
async def handle_async_request(self, request: httpx.Request): async def handle_async_request(self, request: httpx.Request) -> Response:
headers = dict(request.headers) headers = dict(request.headers)
method = request.method method = request.method
url = str(request.url) url = str(request.url)
@ -102,7 +117,7 @@ class AiohttpTransport(httpx.AsyncBaseTransport):
async def make_request( async def make_request(
self, method: str, url: str, headers: dict[str, Any], data: bytes self, method: str, url: str, headers: dict[str, Any], data: bytes
): ) -> Response:
if self._closed: if self._closed:
msg = 'Cannot make request: Transport session is closed' msg = 'Cannot make request: Transport session is closed'
raise RuntimeError(msg) raise RuntimeError(msg)

View File

@ -7,7 +7,7 @@ from aiohttpx.responses import Response
from aiohttpx.transports.rate_limiter import AsyncRateLimit, Redis from aiohttpx.transports.rate_limiter import AsyncRateLimit, Redis
def generate_cache_key(request: Request): def generate_cache_key(request: Request) -> str:
request_data = { request_data = {
'method': request.method, 'method': request.method,
'url': str(request.url), 'url': str(request.url),
@ -28,7 +28,7 @@ def cache_response(
client.set(cache_key, serialized_response, ex=ttl) client.set(cache_key, serialized_response, ex=ttl)
def get_ttl_from_headers(headers: m.Headers): def get_ttl_from_headers(headers: m.Headers) -> int | None:
if 'X-Cache-TTL' in headers: if 'X-Cache-TTL' in headers:
try: try:
return int(headers['X-Cache-TTL']) return int(headers['X-Cache-TTL'])
@ -36,7 +36,9 @@ def get_ttl_from_headers(headers: m.Headers):
return None return None
def get_cached_response(client: Redis[bytes], cache_key: str): def get_cached_response(
client: Redis[bytes], cache_key: str
) -> Response | None:
cached_data = client.get(cache_key) cached_data = client.get(cache_key)
if cached_data: if cached_data:
@ -45,7 +47,7 @@ def get_cached_response(client: Redis[bytes], cache_key: str):
return None return None
def serialize_response(response: Response | HTTPXResponse): def serialize_response(response: Response | HTTPXResponse) -> bytes:
response_data = { response_data = {
'status_code': response.status_code, 'status_code': response.status_code,
'headers': dict(response.headers), 'headers': dict(response.headers),
@ -55,7 +57,7 @@ def serialize_response(response: Response | HTTPXResponse):
return dumps(response_data) return dumps(response_data)
def deserialize_response(serialized_response: bytes): def deserialize_response(serialized_response: bytes) -> Response:
response_data = loads(serialized_response) response_data = loads(serialized_response)
return Response( return Response(
@ -68,7 +70,7 @@ def deserialize_response(serialized_response: bytes):
class AsyncCacheTransport(AsyncRateLimit): class AsyncCacheTransport(AsyncRateLimit):
def __init__( def __init__(
self, redis_url: str | None, key: str | None, limit: int | None self, redis_url: str | None, key: str | None, limit: int | None
): ) -> None:
if redis_url: if redis_url:
self.client = Redis.from_url(redis_url) self.client = Redis.from_url(redis_url)
else: else:
@ -76,7 +78,7 @@ class AsyncCacheTransport(AsyncRateLimit):
self.transport = AsyncRateLimit(self.client, key, limit) self.transport = AsyncRateLimit(self.client, key, limit)
async def handle_async_request(self, request: Request): async def handle_async_request(self, request: Request) -> Response:
if not self.client: if not self.client:
return await self.transport.handle_async_request(request) return await self.transport.handle_async_request(request)

View File

@ -10,7 +10,7 @@ from aiohttpx.transports.aio import AiohttpTransport
class AsyncRateLimit(AiohttpTransport): class AsyncRateLimit(AiohttpTransport):
def __init__( def __init__(
self, redis: Redis[bytes] | None, key: str | None, limit: int | None self, redis: Redis[bytes] | None, key: str | None, limit: int | None
): ) -> None:
self.transport = AiohttpTransport() self.transport = AiohttpTransport()
self.client = redis self.client = redis
self.key = key self.key = key
@ -25,7 +25,7 @@ class AsyncRateLimit(AiohttpTransport):
msg = 'Incorrectly configured for rate limiting' msg = 'Incorrectly configured for rate limiting'
raise Exception(msg) raise Exception(msg)
async def request_is_limited(self): async def request_is_limited(self) -> bool:
if self.client and self.key and self.limit: if self.client and self.key and self.limit:
t: int = int(self.client.time()[0]) # type: ignore t: int = int(self.client.time()[0]) # type: ignore
separation = round(60 / self.limit) separation = round(60 / self.limit)

View File

@ -1 +0,0 @@
__version__: str = ...

View File

@ -1,33 +0,0 @@
from collections.abc import Callable, Mapping
from ssl import SSLContext
from typing import Any
from httpx import URL, Limits
from httpx import AsyncClient as AsyncHTTPXClient
from httpx import _types as t # type: ignore
class AioHTTPXClient(AsyncHTTPXClient):
def __init__(
self,
*,
auth: t.AuthTypes | None = ...,
params: t.QueryParamTypes | None = ...,
headers: t.HeaderTypes | None = ...,
cookies: t.CookieTypes | None = ...,
verify: SSLContext | str | bool = ...,
cert: t.CertTypes | None = ...,
proxy: t.ProxyTypes | None = ...,
timeout: t.TimeoutTypes = ...,
follow_redirects: bool = ...,
limits: Limits = ...,
max_redirects: int = ...,
event_hooks: Mapping[str, list[Callable[..., Any]]] | None = ...,
base_url: URL | str = ...,
trust_env: bool = ...,
default_encoding: str | Callable[[bytes], str] = ...,
redis_url: str | None = ...,
key: str | None = ...,
limit: int | None = ...,
) -> None: ...
__all__ = ['AioHTTPXClient']

View File

@ -1,6 +0,0 @@
from typing import Any
from httpx import Response as HTTPXResponse
class Response(HTTPXResponse):
def json(self, **kwargs: Any) -> Any: ...

View File

@ -1,31 +0,0 @@
from types import TracebackType
from typing import Any, Self
import aiohttp
import httpx
from aiohttpx.responses import Response
EXCEPTIONS = ...
class AiohttpTransport(httpx.AsyncBaseTransport):
def __init__(
self, session: aiohttp.ClientSession | None = ...
) -> None: ...
def map_aiohttp_exception(
self, exc: Exception
) -> httpx.TimeoutException | httpx.HTTPError: ...
async def __aenter__(self) -> Self: ...
async def __aexit__(
self,
exc_type: type[BaseException] | None = ...,
exc_value: BaseException | None = ...,
traceback: TracebackType | None = ...,
) -> None: ...
async def aclose(self) -> None: ...
async def handle_async_request(
self, request: httpx.Request
) -> Response: ...
async def make_request(
self, method: str, url: str, headers: dict[str, Any], data: bytes
) -> Response: ...

View File

@ -1,26 +0,0 @@
from httpx import Request
from httpx import Response as HTTPXResponse
from httpx import _models as m # type: ignore
from aiohttpx.responses import Response
from aiohttpx.transports.rate_limiter import AsyncRateLimit, Redis
def generate_cache_key(request: Request) -> str: ...
def cache_response(
client: Redis[bytes],
cache_key: str,
request: Request,
response: Response | HTTPXResponse,
) -> None: ...
def get_ttl_from_headers(headers: m.Headers) -> int | None: ...
def get_cached_response(
client: Redis[bytes], cache_key: str
) -> Response | None: ...
def serialize_response(response: Response | HTTPXResponse) -> bytes: ...
def deserialize_response(serialized_response: bytes) -> Response: ...
class AsyncCacheTransport(AsyncRateLimit):
def __init__(
self, redis_url: str | None, key: str | None, limit: int | None
) -> None: ...
async def handle_async_request(self, request: Request) -> Response: ...

View File

@ -1,12 +0,0 @@
from httpx import Request
from redis import Redis
from aiohttpx.responses import Response
from aiohttpx.transports.aio import AiohttpTransport
class AsyncRateLimit(AiohttpTransport):
def __init__(
self, redis: Redis[bytes] | None, key: str | None, limit: int | None
) -> None: ...
async def request_is_limited(self) -> bool: ...
async def handle_async_request(self, request: Request) -> Response: ...