mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
feat(proxy): proxy for aiohttp,base sessions (#284)
* feat(proxy): proxy for aiohttp,base sessions Add support for proxies in aiohttp session with aiohttp_socks library, edit BaseSession class to support proxies for other sessions in future. * fix(annotation): missing underscore before "private" typevar * chore: remove redundant of proxy_url schema for socks version * test: add missing test Add missing test, remove BaseSession.cfg and switch to implementing class' "private" traits, add aiohttp_socks in dependency list as optional and extra. * feat(session): Implement asyncio session for requests [wip] * feat(proxy chain): Chained proxy support in aiohttp session Add ChainProxyConnector support, !pin pydantic to "1.4", add documentation on aiohttp connector. * style(mypy): apply linter changes * tests(mock): remove await for magic mock * fix dangling dependency * refactor(generic): get rid of generic behaviour for base session
This commit is contained in:
parent
2553f5f19e
commit
15bcc0ba9f
8 changed files with 245 additions and 8 deletions
|
|
@ -121,7 +121,7 @@ class Bot(ContextInstanceMixin["Bot"]):
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, token: str, session: Optional[BaseSession] = None, parse_mode: Optional[str] = None
|
||||
self, token: str, session: Optional[BaseSession] = None, parse_mode: Optional[str] = None,
|
||||
) -> None:
|
||||
validate_token(token)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,105 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import AsyncGenerator, Callable, Optional, TypeVar, cast
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncGenerator,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from aiohttp import ClientSession, ClientTimeout, FormData
|
||||
from aiohttp import BasicAuth, ClientSession, ClientTimeout, FormData, TCPConnector
|
||||
|
||||
from aiogram.api.methods import Request, TelegramMethod
|
||||
|
||||
from .base import PRODUCTION, BaseSession, TelegramAPIServer
|
||||
|
||||
T = TypeVar("T")
|
||||
_ProxyBasic = Union[str, Tuple[str, BasicAuth]]
|
||||
_ProxyChain = Iterable[_ProxyBasic]
|
||||
_ProxyType = Union[_ProxyChain, _ProxyBasic]
|
||||
|
||||
|
||||
def _retrieve_basic(basic: _ProxyBasic) -> Dict[str, Any]:
|
||||
from aiohttp_socks.utils import parse_proxy_url # type: ignore
|
||||
|
||||
proxy_auth: Optional[BasicAuth] = None
|
||||
|
||||
if isinstance(basic, str):
|
||||
proxy_url = basic
|
||||
else:
|
||||
proxy_url, proxy_auth = basic
|
||||
|
||||
proxy_type, host, port, username, password = parse_proxy_url(proxy_url)
|
||||
if isinstance(proxy_auth, BasicAuth):
|
||||
username = proxy_auth.login
|
||||
password = proxy_auth.password
|
||||
|
||||
return dict(
|
||||
proxy_type=proxy_type,
|
||||
host=host,
|
||||
port=port,
|
||||
username=username,
|
||||
password=password,
|
||||
rdns=True,
|
||||
)
|
||||
|
||||
|
||||
def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"], Dict[str, Any]]:
|
||||
from aiohttp_socks import ProxyInfo, ProxyConnector, ChainProxyConnector # type: ignore
|
||||
|
||||
# since tuple is Iterable(compatible with _ProxyChain) object, we assume that
|
||||
# user wants chained proxies if tuple is a pair of string(url) and BasicAuth
|
||||
if isinstance(chain_or_plain, str) or (
|
||||
isinstance(chain_or_plain, tuple) and len(chain_or_plain) == 2
|
||||
):
|
||||
chain_or_plain = cast(_ProxyBasic, chain_or_plain)
|
||||
return ProxyConnector, _retrieve_basic(chain_or_plain)
|
||||
|
||||
chain_or_plain = cast(_ProxyChain, chain_or_plain)
|
||||
infos: List[ProxyInfo] = []
|
||||
for basic in chain_or_plain:
|
||||
infos.append(ProxyInfo(**_retrieve_basic(basic)))
|
||||
|
||||
return ChainProxyConnector, dict(proxy_infos=infos)
|
||||
|
||||
|
||||
class AiohttpSession(BaseSession):
|
||||
def __init__(
|
||||
self,
|
||||
api: TelegramAPIServer = PRODUCTION,
|
||||
json_loads: Optional[Callable[..., str]] = None,
|
||||
json_loads: Optional[Callable[..., Any]] = None,
|
||||
json_dumps: Optional[Callable[..., str]] = None,
|
||||
proxy: Optional[_ProxyType] = None,
|
||||
):
|
||||
super(AiohttpSession, self).__init__(api=api, json_loads=json_loads, json_dumps=json_dumps)
|
||||
super(AiohttpSession, self).__init__(
|
||||
api=api, json_loads=json_loads, json_dumps=json_dumps, proxy=proxy
|
||||
)
|
||||
self._session: Optional[ClientSession] = None
|
||||
self._connector_type: Type[TCPConnector] = TCPConnector
|
||||
self._connector_init: Dict[str, Any] = {}
|
||||
|
||||
if self.proxy:
|
||||
try:
|
||||
self._connector_type, self._connector_init = _prepare_connector(
|
||||
cast(_ProxyType, self.proxy)
|
||||
)
|
||||
except ImportError as exc: # pragma: no cover
|
||||
raise UserWarning(
|
||||
"In order to use aiohttp client for proxy requests, install "
|
||||
"https://pypi.org/project/aiohttp-socks/"
|
||||
) from exc
|
||||
|
||||
async def create_session(self) -> ClientSession:
|
||||
if self._session is None or self._session.closed:
|
||||
self._session = ClientSession()
|
||||
self._session = ClientSession(connector=self._connector_type(**self._connector_init))
|
||||
|
||||
return self._session
|
||||
|
||||
|
|
|
|||
|
|
@ -12,14 +12,16 @@ from ...methods import Response, TelegramMethod
|
|||
from ..telegram import PRODUCTION, TelegramAPIServer
|
||||
|
||||
T = TypeVar("T")
|
||||
PT = TypeVar("PT")
|
||||
|
||||
|
||||
class BaseSession(abc.ABC):
|
||||
def __init__(
|
||||
self,
|
||||
api: Optional[TelegramAPIServer] = None,
|
||||
json_loads: Optional[Callable[..., str]] = None,
|
||||
json_loads: Optional[Callable[..., Any]] = None,
|
||||
json_dumps: Optional[Callable[..., str]] = None,
|
||||
proxy: Optional[PT] = None,
|
||||
) -> None:
|
||||
if api is None:
|
||||
api = PRODUCTION
|
||||
|
|
@ -31,6 +33,7 @@ class BaseSession(abc.ABC):
|
|||
self.api = api
|
||||
self.json_loads = json_loads
|
||||
self.json_dumps = json_dumps
|
||||
self.proxy = proxy
|
||||
|
||||
def raise_for_status(self, response: Response[T]) -> None:
|
||||
if response.ok:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue