Merge branch 'dev-3.x' into dev-3.x

This commit is contained in:
Alex Root Junior 2020-05-13 22:33:46 +03:00 committed by GitHub
commit 02e6bd971b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 460 additions and 155 deletions

View file

@ -28,5 +28,5 @@ __all__ = (
"handler",
)
__version__ = '3.0.0a4'
__version__ = "3.0.0a4"
__api_version__ = "4.8"

View file

@ -3,7 +3,6 @@ from __future__ import annotations
from typing import (
Any,
AsyncGenerator,
Callable,
Dict,
Iterable,
List,
@ -15,11 +14,11 @@ from typing import (
cast,
)
from aiohttp import BasicAuth, ClientSession, ClientTimeout, FormData, TCPConnector
from aiohttp import BasicAuth, ClientSession, FormData, TCPConnector
from aiogram.api.methods import Request, TelegramMethod
from .base import PRODUCTION, BaseSession, TelegramAPIServer
from .base import BaseSession
T = TypeVar("T")
_ProxyBasic = Union[str, Tuple[str, BasicAuth]]
@ -72,34 +71,42 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"]
class AiohttpSession(BaseSession):
def __init__(
self,
api: TelegramAPIServer = PRODUCTION,
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, proxy=proxy
)
def __init__(self, proxy: Optional[_ProxyType] = None):
self._session: Optional[ClientSession] = None
self._connector_type: Type[TCPConnector] = TCPConnector
self._connector_init: Dict[str, Any] = {}
self._should_reset_connector = True # flag determines connector state
self._proxy: Optional[_ProxyType] = None
if self.proxy:
if proxy is not None:
try:
self._connector_type, self._connector_init = _prepare_connector(
cast(_ProxyType, self.proxy)
)
self._setup_proxy_connector(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
def _setup_proxy_connector(self, proxy: _ProxyType) -> None:
self._connector_type, self._connector_init = _prepare_connector(proxy)
self._proxy = proxy
@property
def proxy(self) -> Optional[_ProxyType]:
return self._proxy
@proxy.setter
def proxy(self, proxy: _ProxyType) -> None:
self._setup_proxy_connector(proxy)
self._should_reset_connector = True
async def create_session(self) -> ClientSession:
if self._should_reset_connector:
await self.close()
if self._session is None or self._session.closed:
self._session = ClientSession(connector=self._connector_type(**self._connector_init))
self._should_reset_connector = False
return self._session
@ -125,7 +132,9 @@ class AiohttpSession(BaseSession):
url = self.api.api_url(token=token, method=request.method)
form = self.build_form_data(request)
async with session.post(url, data=form) as resp:
async with session.post(
url, data=form, timeout=call.request_timeout or self.timeout
) as resp:
raw_result = await resp.json(loads=self.json_loads)
response = call.build_response(raw_result)
@ -136,9 +145,8 @@ class AiohttpSession(BaseSession):
self, url: str, timeout: int, chunk_size: int
) -> AsyncGenerator[bytes, None]:
session = await self.create_session()
client_timeout = ClientTimeout(total=timeout)
async with session.get(url, timeout=client_timeout) as resp:
async with session.get(url, timeout=timeout) as resp:
async for chunk in resp.content.iter_chunked(chunk_size):
yield chunk

View file

@ -4,7 +4,7 @@ import abc
import datetime
import json
from types import TracebackType
from typing import Any, AsyncGenerator, Callable, Optional, Type, TypeVar, Union
from typing import Any, AsyncGenerator, Callable, ClassVar, Optional, Type, TypeVar, Union
from aiogram.utils.exceptions import TelegramAPIError
@ -12,30 +12,58 @@ from ...methods import Response, TelegramMethod
from ..telegram import PRODUCTION, TelegramAPIServer
T = TypeVar("T")
PT = TypeVar("PT")
_JsonLoads = Callable[..., Any]
_JsonDumps = Callable[..., str]
class BaseSession(abc.ABC):
def __init__(
self,
api: Optional[TelegramAPIServer] = None,
json_loads: Optional[Callable[..., Any]] = None,
json_dumps: Optional[Callable[..., str]] = None,
proxy: Optional[PT] = None,
) -> None:
if api is None:
api = PRODUCTION
if json_loads is None:
json_loads = json.loads
if json_dumps is None:
json_dumps = json.dumps
# global session timeout
default_timeout: ClassVar[float] = 60.0
self.api = api
self.json_loads = json_loads
self.json_dumps = json_dumps
self.proxy = proxy
_api: TelegramAPIServer
_json_loads: _JsonLoads
_json_dumps: _JsonDumps
_timeout: float
def raise_for_status(self, response: Response[T]) -> None:
@property
def api(self) -> TelegramAPIServer:
return getattr(self, "_api", PRODUCTION) # type: ignore
@api.setter
def api(self, value: TelegramAPIServer) -> None:
self._api = value
@property
def json_loads(self) -> _JsonLoads:
return getattr(self, "_json_loads", json.loads) # type: ignore
@json_loads.setter
def json_loads(self, value: _JsonLoads) -> None:
self._json_loads = value # type: ignore
@property
def json_dumps(self) -> _JsonDumps:
return getattr(self, "_json_dumps", json.dumps) # type: ignore
@json_dumps.setter
def json_dumps(self, value: _JsonDumps) -> None:
self._json_dumps = value # type: ignore
@property
def timeout(self) -> float:
return getattr(self, "_timeout", self.__class__.default_timeout) # type: ignore
@timeout.setter
def timeout(self, value: float) -> None:
self._timeout = value
@timeout.deleter
def timeout(self) -> None:
if hasattr(self, "_timeout"):
del self._timeout
@classmethod
def raise_for_status(cls, response: Response[T]) -> None:
if response.ok:
return
raise TelegramAPIError(response.description)

View file

@ -55,6 +55,16 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[T]):
def build_request(self) -> Request: # pragma: no cover
pass
request_timeout: Optional[float] = None
def dict(self, **kwargs: Any) -> Any:
# override dict of pydantic.BaseModel to overcome exporting request_timeout field
exclude = kwargs.pop("exclude", set())
if isinstance(exclude, set):
exclude.add("request_timeout")
return super().dict(exclude=exclude, **kwargs)
def build_response(self, data: Dict[str, Any]) -> Response[T]:
# noinspection PyTypeChecker
return Response[self.__returning__](**data) # type: ignore

View file

@ -57,7 +57,7 @@ class Dispatcher(Router):
Bot.set_current(bot)
try:
async for result in self.update_handler.trigger(update, bot=bot, **kwargs):
async for result in self.update.trigger(update, bot=bot, **kwargs):
handled = True
yield result
finally:

View file

@ -24,31 +24,25 @@ class Router:
self.sub_routers: List[Router] = []
# Observers
self.update_handler = TelegramEventObserver(router=self, event_name="update")
self.message_handler = TelegramEventObserver(router=self, event_name="message")
self.edited_message_handler = TelegramEventObserver(
router=self, event_name="edited_message"
)
self.channel_post_handler = TelegramEventObserver(router=self, event_name="channel_post")
self.edited_channel_post_handler = TelegramEventObserver(
self.update = TelegramEventObserver(router=self, event_name="update")
self.message = TelegramEventObserver(router=self, event_name="message")
self.edited_message = TelegramEventObserver(router=self, event_name="edited_message")
self.channel_post = TelegramEventObserver(router=self, event_name="channel_post")
self.edited_channel_post = TelegramEventObserver(
router=self, event_name="edited_channel_post"
)
self.inline_query_handler = TelegramEventObserver(router=self, event_name="inline_query")
self.chosen_inline_result_handler = TelegramEventObserver(
self.inline_query = TelegramEventObserver(router=self, event_name="inline_query")
self.chosen_inline_result = TelegramEventObserver(
router=self, event_name="chosen_inline_result"
)
self.callback_query_handler = TelegramEventObserver(
router=self, event_name="callback_query"
)
self.shipping_query_handler = TelegramEventObserver(
router=self, event_name="shipping_query"
)
self.pre_checkout_query_handler = TelegramEventObserver(
self.callback_query = TelegramEventObserver(router=self, event_name="callback_query")
self.shipping_query = TelegramEventObserver(router=self, event_name="shipping_query")
self.pre_checkout_query = TelegramEventObserver(
router=self, event_name="pre_checkout_query"
)
self.poll_handler = TelegramEventObserver(router=self, event_name="poll")
self.poll_answer_handler = TelegramEventObserver(router=self, event_name="poll_answer")
self.errors_handler = TelegramEventObserver(router=self, event_name="error")
self.poll = TelegramEventObserver(router=self, event_name="poll")
self.poll_answer = TelegramEventObserver(router=self, event_name="poll_answer")
self.errors = TelegramEventObserver(router=self, event_name="error")
self.middleware = MiddlewareManager(router=self)
@ -56,23 +50,23 @@ class Router:
self.shutdown = EventObserver()
self.observers: Dict[str, TelegramEventObserver] = {
"update": self.update_handler,
"message": self.message_handler,
"edited_message": self.edited_message_handler,
"channel_post": self.channel_post_handler,
"edited_channel_post": self.edited_channel_post_handler,
"inline_query": self.inline_query_handler,
"chosen_inline_result": self.chosen_inline_result_handler,
"callback_query": self.callback_query_handler,
"shipping_query": self.shipping_query_handler,
"pre_checkout_query": self.pre_checkout_query_handler,
"poll": self.poll_handler,
"poll_answer": self.poll_answer_handler,
"error": self.errors_handler,
"update": self.update,
"message": self.message,
"edited_message": self.edited_message,
"channel_post": self.channel_post,
"edited_channel_post": self.edited_channel_post,
"inline_query": self.inline_query,
"chosen_inline_result": self.chosen_inline_result,
"callback_query": self.callback_query,
"shipping_query": self.shipping_query,
"pre_checkout_query": self.pre_checkout_query,
"poll": self.poll,
"poll_answer": self.poll_answer,
"error": self.errors,
}
# Root handler
self.update_handler.register(self._listen_update)
self.update.register(self._listen_update)
# Builtin filters
if use_builtin_filters:
@ -299,7 +293,7 @@ class Router:
raise
except Exception as e:
async for result in self.errors_handler.trigger(e, **kwargs):
async for result in self.errors.trigger(e, **kwargs):
return result
raise
@ -336,3 +330,146 @@ class Router:
pass
for router in self.sub_routers:
await router.emit_shutdown(*args, **kwargs)
@property
def update_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.update_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.update(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.update
@property
def message_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.message_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.message(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.message
@property
def edited_message_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.edited_message_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.edited_message(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.edited_message
@property
def channel_post_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.channel_post_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.channel_post(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.channel_post
@property
def edited_channel_post_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.edited_channel_post_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.edited_channel_post(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.edited_channel_post
@property
def inline_query_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.inline_query_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.inline_query(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.inline_query
@property
def chosen_inline_result_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.chosen_inline_result_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.chosen_inline_result(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.chosen_inline_result
@property
def callback_query_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.callback_query_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.callback_query(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.callback_query
@property
def shipping_query_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.shipping_query_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.shipping_query(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.shipping_query
@property
def pre_checkout_query_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.pre_checkout_query_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.pre_checkout_query(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.pre_checkout_query
@property
def poll_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.poll_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.poll(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.poll
@property
def poll_answer_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.poll_answer_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.poll_answer(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.poll_answer
@property
def errors_handler(self) -> TelegramEventObserver:
warnings.warn(
"`Router.errors_handler(...)` is deprecated and will be removed in version 3.2 "
"use `Router.errors(...)`",
DeprecationWarning,
stacklevel=2,
)
return self.errors

View file

@ -1,6 +1,6 @@
# Aiohttp session
AiohttpSession represents a wrapper-class around `ClientSession` from [aiohttp]('https://pypi.org/project/aiohttp/')
AiohttpSession represents a wrapper-class around `ClientSession` from [aiohttp](https://pypi.org/project/aiohttp/ "PyPi repository"){target=_blank}
Currently `AiohttpSession` is a default session used in `aiogram.Bot`
@ -17,7 +17,7 @@ Bot('token', session=session)
## Proxy requests in AiohttpSession
In order to use AiohttpSession with proxy connector you have to install [aiohttp-socks]('https://pypi.org/project/aiohttp-socks/')
In order to use AiohttpSession with proxy connector you have to install [aiohttp-socks](https://pypi.org/project/aiohttp-socks/ "PyPi repository"){target=_blank}
Binding session to bot:
```python

View file

@ -8,7 +8,7 @@ from aiogram.handlers import CallbackQueryHandler
...
@router.callback_query_handler()
@router.callback_query()
class MyHandler(CallbackQueryHandler):
async def handle(self) -> Any: ...
@ -26,4 +26,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some
- [BaseHandler](basics.md#basehandler)
- [CallbackQuery](../../api/types/callback_query.md)
- [Router.callback_query_handler](../router.md#callback-query)
- [Router.callback_query](../router.md#callback-query)

View file

@ -8,7 +8,7 @@ from aiogram.handlers import ChosenInlineResultHandler
...
@router.chosen_inline_result_handler()
@router.chosen_inline_result()
class MyHandler(ChosenInlineResultHandler):
async def handle(self) -> Any: ...
@ -25,4 +25,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some
- [BaseHandler](basics.md#basehandler)
- [ChosenInlineResult](../../api/types/chosen_inline_result.md)
- [Router.chosen_inline_result_handler](../router.md#chosen-inline-query)
- [Router.chosen_inline_result](../router.md#chosen-inline-query)

View file

@ -8,7 +8,7 @@ from aiogram.handlers import ErrorHandler
...
@router.errors_handler()
@router.errors()
class MyHandler(ErrorHandler):
async def handle(self) -> Any:
log.exception(
@ -28,5 +28,5 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some
## Related pages
- [BaseHandler](basics.md#basehandler)
- [Router.errors_handler](../router.md#errors)
- [Router.errors](../router.md#errors)
- [Filters](../filters/exception.md)

View file

@ -7,7 +7,7 @@ from aiogram.handlers import InlineQueryHandler
...
@router.inline_query_handler()
@router.inline_query()
class MyHandler(InlineQueryHandler):
async def handle(self) -> Any: ...

View file

@ -8,7 +8,7 @@ from aiogram.handlers import MessageHandler
...
@router.message_handler()
@router.message()
class MyHandler(MessageHandler):
async def handle(self) -> Any:
return SendMessage(chat_id=self.chat.id, text="PASS")
@ -26,7 +26,7 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some
- [BaseHandler](basics.md#basehandler)
- [Message](../../api/types/message.md)
- [Router.message_handler](../router.md#message)
- [Router.edited_message_handler](../router.md#edited-message)
- [Router.channel_post_handler](../router.md#channel-post)
- [Router.edited_channel_post_handler](../router.md#edited-channel-post)
- [Router.message](../router.md#message)
- [Router.edited_message](../router.md#edited-message)
- [Router.channel_post](../router.md#channel-post)
- [Router.edited_channel_post](../router.md#edited-channel-post)

View file

@ -8,7 +8,7 @@ from aiogram.handlers import PollHandler
...
@router.poll_handler()
@router.poll()
class MyHandler(PollHandler):
async def handle(self) -> Any: ...
@ -25,4 +25,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some
- [BaseHandler](basics.md#basehandler)
- [Poll](../../api/types/poll.md)
- [Router.poll_handler](../router.md#poll)
- [Router.poll](../router.md#poll)

View file

@ -8,7 +8,7 @@ from aiogram.handlers import PreCheckoutQueryHandler
...
@router.pre_checkout_query_handler()
@router.pre_checkout_query()
class MyHandler(PreCheckoutQueryHandler):
async def handle(self) -> Any: ...
@ -24,4 +24,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some
- [BaseHandler](basics.md#basehandler)
- [PreCheckoutQuery](../../api/types/pre_checkout_query.md)
- [Router.pre_checkout_query_handler](../router.md#pre-checkout-query)
- [Router.pre_checkout_query](../router.md#pre-checkout-query)

View file

@ -8,7 +8,7 @@ from aiogram.handlers import ShippingQueryHandler
...
@router.shipping_query_handler()
@router.shipping_query()
class MyHandler(ShippingQueryHandler):
async def handle(self) -> Any: ...
@ -24,4 +24,4 @@ This base handler is subclass of [BaseHandler](basics.md#basehandler) with some
- [BaseHandler](basics.md#basehandler)
- [ShippingQuery](../../api/types/shipping_query.md)
- [Router.shipping_query_handler](../router.md#shipping-query)
- [Router.shipping_query](../router.md#shipping-query)

View file

@ -21,7 +21,7 @@ Example:
```python3
dp = Dispatcher()
@dp.message_handler()
@dp.message()
async def message_handler(message: types.Message) -> None:
await SendMessage(chat_id=message.from_user.id, text=message.text)
```

View file

@ -19,7 +19,7 @@ Works only with [Message](../../api/types/message.md) events which have the `tex
1. Handle command by regexp pattern: `#!python3 Command(commands=[re.compile(r"item_(\d+)")])`
1. Match command by multiple variants: `#!python3 Command(commands=["item", re.compile(r"item_(\d+)")])`
1. Handle commands in public chats intended for other bots: `#!python3 Command(commands=["command"], commands)`
1. As keyword argument in registerer: `#!python3 @router.message_handler(commands=["help"])`
1. As keyword argument in registerer: `#!python3 @router.message(commands=["help"])`
!!! warning
Command cannot include spaces or any whitespace

View file

@ -53,9 +53,9 @@ class MyText(BaseFilter):
return message.text == self.my_text
router.message_handler.bind_filter(MyText)
router.message.bind_filter(MyText)
@router.message_handler(my_text="hello")
@router.message(my_text="hello")
async def my_handler(message: Message): ...
```

View file

@ -34,91 +34,91 @@ Here is list of available observers and examples how to register handlers (In ex
### Update
```python3
@router.update_handler()
@router.update()
async def message_handler(update: types.Update) -> Any: pass
```
Should be used for handling [updates](../api/types/update.md). By default Router is already have an update handler which route all event types to another observers.
### Message
```python3
@router.message_handler()
@router.message()
async def message_handler(message: types.Message) -> Any: pass
```
Is useful for handling [message](../api/types/message.md)
### Edited message
```python3
@router.edited_message_handler()
@router.edited_message()
async def edited_message_handler(edited_message: types.Message) -> Any: pass
```
Is useful for handling [edited messages](../api/types/message.md)
### Channel post
```python3
@router.channel_post_handler()
@router.channel_post()
async def channel_post_handler(channel_post: types.Message) -> Any: pass
```
Is useful for handling [channel posts](../api/types/message.md)
### Edited channel post
```python3
@router.edited_channel_post_handler()
@router.edited_channel_post()
async def edited_channel_post_handler(edited_channel_post: types.Message) -> Any: pass
```
Is useful for handling [edited channel posts](../api/types/message.md)
### Inline query
```python3
@router.inline_query_handler()
@router.inline_query()
async def inline_query_handler(inline_query: types.Message) -> Any: pass
```
Is useful for handling [inline query](../api/types/inline_query.md)
### Chosen inline query
```python3
@router.chosen_inline_result_handler()
@router.chosen_inline_result()
async def chosen_inline_result_handler(chosen_inline_result: types.ChosenInlineResult) -> Any: pass
```
Is useful for handling [chosen inline query](../api/types/chosen_inline_result.md)
### Callback query
```python3
@router.callback_query_handler()
@router.callback_query()
async def callback_query_handler(callback_query: types.CallbackQuery) -> Any: pass
```
Is useful for handling [callback query's](../api/types/callback_query.md)
### Shipping query
```python3
@router.shipping_query_handler()
@router.shipping_query()
async def shipping_query_handler(shipping_query: types.ShippingQuery) -> Any: pass
```
Is useful for handling [shipping query](../api/types/shipping_query.md)
### Pre checkout query
```python3
@router.pre_checkout_query_handler()
@router.pre_checkout_query()
async def pre_checkout_query_handler(pre_checkout_query: types.PreCheckoutQuery) -> Any: pass
```
Is useful for handling [pre-checkout query](../api/types/pre_checkout_query.md)
### Poll
```python3
@router.poll_handler()
@router.poll()
async def poll_handler(poll: types.Poll) -> Any: pass
```
Is useful for handling [polls](../api/types/poll.md)
### Poll answer
```python3
@router.poll_answer_handler()
@router.poll_answer()
async def poll_answer_handler(poll_answer: types.PollAnswer) -> Any: pass
```
Is useful for handling [polls answers](../api/types/poll_answer.md)
### Errors
```python3
@router.errors_handler()
@router.errors()
async def error_handler(exception: Exception) -> Any: pass
```
Is useful for handling errors from other handlers

View file

@ -44,7 +44,7 @@ TOKEN = "42:TOKEN"
dp = Dispatcher()
@dp.message_handler(commands=["start"])
@dp.message(commands=["start"])
class MyHandler(MessageHandler):
"""
This handler receive messages with `/start` command
@ -54,7 +54,7 @@ class MyHandler(MessageHandler):
await self.event.answer(f"<b>Hello, {self.from_user.full_name}!</b>")
@dp.message_handler(content_types=[types.ContentType.ANY])
@dp.message(content_types=[types.ContentType.ANY])
async def echo_handler(message: types.Message, bot: Bot):
"""
Handler will forward received message back to the sender

View file

@ -35,6 +35,7 @@ markdown_extensions:
- pymdownx.inlinehilite
- markdown_include.include:
base_path: docs
- attr_list
nav:
- index.md

3
poetry.lock generated
View file

@ -1031,7 +1031,8 @@ fast = ["uvloop"]
proxy = ["aiohttp-socks"]
[metadata]
content-hash = "0b325d34fff06035598724438d816a4bd1aba992e21b2fdef10ab5fafa59ff2c"
content-hash = "57137b60a539ba01e8df533db976e2f3eadec37e717cbefbe775dc021a8c2714"
python-versions = "^3.7"
[metadata.files]

View file

@ -67,6 +67,7 @@ ipython = "^7.10"
markdown-include = "^0.5.1"
aiohttp-socks = "^0.3.4"
pre-commit = "^2.3.0"
packaging = "^20.3"
[tool.poetry.extras]
fast = ["uvloop"]

26
tests/deprecated.py Normal file
View file

@ -0,0 +1,26 @@
from contextlib import contextmanager
from typing import Type
import pytest
from packaging import version
import aiogram
@contextmanager
def check_deprecated(
max_version: str, exception: Type[Exception], warning: Type[Warning] = DeprecationWarning,
) -> None:
"""
Should be used for modules that are being deprecated or already removed from aiogram
"""
parsed_max_version = version.parse(max_version)
current_version = version.parse(aiogram.__version__)
if parsed_max_version <= current_version:
with pytest.raises(exception):
yield
else:
with pytest.warns(warning, match=max_version):
yield

View file

@ -83,6 +83,22 @@ class TestAiohttpSession:
aiohttp_session = await session.create_session()
assert isinstance(aiohttp_session.connector, aiohttp_socks.ChainProxyConnector)
@pytest.mark.asyncio
async def test_reset_connector(self):
session = AiohttpSession()
assert session._should_reset_connector
await session.create_session()
assert session._should_reset_connector is False
await session.close()
assert session._should_reset_connector is False
assert session.proxy is None
session.proxy = "socks5://auth:auth@proxy.url/"
assert session._should_reset_connector
await session.create_session()
assert session._should_reset_connector is False
await session.close()
@pytest.mark.asyncio
async def test_close_session(self):
session = AiohttpSession()

View file

@ -1,4 +1,5 @@
import datetime
import json
from typing import AsyncContextManager, AsyncGenerator
import pytest
@ -35,13 +36,56 @@ class TestBaseSession:
session = CustomSession()
assert session.api == PRODUCTION
def test_default_props(self):
session = CustomSession()
assert session.api == PRODUCTION
assert session.json_loads == json.loads
assert session.json_dumps == json.dumps
def custom_loads(*_):
return json.loads
def custom_dumps(*_):
return json.dumps
session.json_dumps = custom_dumps
assert session.json_dumps == custom_dumps == session._json_dumps
session.json_loads = custom_loads
assert session.json_loads == custom_loads == session._json_loads
different_session = CustomSession()
assert all(
not hasattr(different_session, attr) for attr in ("_json_loads", "_json_dumps", "_api")
)
def test_timeout(self):
session = CustomSession()
assert session.timeout == session.default_timeout == CustomSession.default_timeout
session.default_timeout = float(65.0_0) # mypy will complain
assert session.timeout != session.default_timeout
CustomSession.default_timeout = float(68.0_0)
assert session.timeout == CustomSession.default_timeout
session.timeout = float(71.0_0)
assert session.timeout != session.default_timeout
del session.timeout
CustomSession.default_timeout = session.default_timeout + 100
assert (
session.timeout != BaseSession.default_timeout
and session.timeout == CustomSession.default_timeout
)
def test_init_custom_api(self):
api = TelegramAPIServer(
base="http://example.com/{token}/{method}",
file="http://example.com/{token}/file/{path{",
file="http://example.com/{token}/file/{path}",
)
session = CustomSession(api=api)
session = CustomSession()
session.api = api
assert session.api == api
assert "example.com" in session.api.base
def test_prepare_value(self):
session = CustomSession()

View file

@ -1,4 +1,5 @@
import pytest
from aiogram.api.types import ReplyKeyboardRemove

View file

@ -0,0 +1,32 @@
import pytest
from aiogram.dispatcher.event.observer import TelegramEventObserver
from aiogram.dispatcher.router import Router
from tests.deprecated import check_deprecated
OBSERVERS = {
"callback_query",
"channel_post",
"chosen_inline_result",
"edited_channel_post",
"edited_message",
"errors",
"inline_query",
"message",
"poll",
"poll_answer",
"pre_checkout_query",
"shipping_query",
"update",
}
DEPRECATED_OBSERVERS = {observer + "_handler" for observer in OBSERVERS}
@pytest.mark.parametrize("observer_name", DEPRECATED_OBSERVERS)
def test_deprecated_handlers_name(observer_name: str):
router = Router()
with check_deprecated("3.2", exception=AttributeError):
observer = getattr(router, observer_name)
assert isinstance(observer, TelegramEventObserver)

View file

@ -55,7 +55,7 @@ class TestDispatcher:
dp = Dispatcher()
bot = Bot("42:TEST")
@dp.message_handler()
@dp.message()
async def my_handler(message: Message, **kwargs):
assert "bot" in kwargs
assert isinstance(kwargs["bot"], Bot)
@ -86,7 +86,7 @@ class TestDispatcher:
dp = Dispatcher()
bot = Bot("42:TEST")
@dp.message_handler()
@dp.message()
async def my_handler(message: Message):
assert message.text == "test"
return message.text
@ -142,7 +142,7 @@ class TestDispatcher:
async def test_process_update_handled(self, bot: MockedBot):
dispatcher = Dispatcher()
@dispatcher.update_handler()
@dispatcher.update()
async def update_handler(update: Update):
pass
@ -152,7 +152,7 @@ class TestDispatcher:
async def test_process_update_call_request(self, bot: MockedBot):
dispatcher = Dispatcher()
@dispatcher.update_handler()
@dispatcher.update()
async def update_handler(update: Update):
return GetMe()
@ -167,7 +167,7 @@ class TestDispatcher:
async def test_process_update_exception(self, bot: MockedBot, caplog):
dispatcher = Dispatcher()
@dispatcher.update_handler()
@dispatcher.update()
async def update_handler(update: Update):
raise Exception("Kaboom!")
@ -229,7 +229,7 @@ class TestDispatcher:
@pytest.mark.asyncio
async def test_feed_webhook_update_fast_process(self, bot: MockedBot):
dispatcher = Dispatcher()
dispatcher.message_handler.register(simple_message_handler)
dispatcher.message.register(simple_message_handler)
response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=2)
assert isinstance(response, dict)
@ -251,7 +251,7 @@ class TestDispatcher:
warnings.simplefilter("always")
dispatcher = Dispatcher()
dispatcher.message_handler.register(simple_message_handler)
dispatcher.message.register(simple_message_handler)
with patch(
"aiogram.dispatcher.dispatcher.Dispatcher._silent_call_request",
@ -267,7 +267,7 @@ class TestDispatcher:
warnings.simplefilter("always")
dispatcher = Dispatcher()
dispatcher.message_handler.register(invalid_message_handler)
dispatcher.message.register(invalid_message_handler)
response = await dispatcher.feed_webhook_update(bot, RAW_UPDATE, _timeout=1)
assert response is None

View file

@ -109,14 +109,14 @@ class TestTelegramEventObserver:
router1.include_router(router2)
router2.include_router(router3)
router1.message_handler.bind_filter(MyFilter1)
router1.message_handler.bind_filter(MyFilter2)
router2.message_handler.bind_filter(MyFilter2)
router3.message_handler.bind_filter(MyFilter3)
router1.message.bind_filter(MyFilter1)
router1.message.bind_filter(MyFilter2)
router2.message.bind_filter(MyFilter2)
router3.message.bind_filter(MyFilter3)
filters_chain1 = list(router1.message_handler._resolve_filters_chain())
filters_chain2 = list(router2.message_handler._resolve_filters_chain())
filters_chain3 = list(router3.message_handler._resolve_filters_chain())
filters_chain1 = list(router1.message._resolve_filters_chain())
filters_chain2 = list(router2.message._resolve_filters_chain())
filters_chain3 = list(router3.message._resolve_filters_chain())
assert MyFilter1 in filters_chain1
assert MyFilter1 in filters_chain2
@ -128,7 +128,7 @@ class TestTelegramEventObserver:
def test_resolve_filters(self):
router = Router(use_builtin_filters=False)
observer = router.message_handler
observer = router.message
observer.bind_filter(MyFilter1)
resolved = observer.resolve_filters({"test": "PASS"})
@ -149,7 +149,7 @@ class TestTelegramEventObserver:
def test_register(self):
router = Router(use_builtin_filters=False)
observer = router.message_handler
observer = router.message
observer.bind_filter(MyFilter1)
assert observer.register(my_handler) == my_handler
@ -174,7 +174,7 @@ class TestTelegramEventObserver:
def test_register_decorator(self):
router = Router(use_builtin_filters=False)
observer = router.message_handler
observer = router.message
@observer()
async def my_handler(event: Any):
@ -186,7 +186,7 @@ class TestTelegramEventObserver:
@pytest.mark.asyncio
async def test_trigger(self):
router = Router(use_builtin_filters=False)
observer = router.message_handler
observer = router.message
observer.bind_filter(MyFilter1)
observer.register(my_handler, test="ok")
@ -211,7 +211,7 @@ class TestTelegramEventObserver:
)
def test_register_filters_via_decorator(self, count, handler, filters):
router = Router(use_builtin_filters=False)
observer = router.message_handler
observer = router.message
for index in range(count):
wrapped_handler = functools.partial(handler, index=index)
@ -227,7 +227,7 @@ class TestTelegramEventObserver:
@pytest.mark.asyncio
async def test_trigger_right_context_in_handlers(self):
router = Router(use_builtin_filters=False)
observer = router.message_handler
observer = router.message
observer.register(
pipe_handler, lambda event: {"a": 1}, lambda event: False
) # {"a": 1} should not be in result

View file

@ -79,18 +79,18 @@ class TestRouter:
def test_observers_config(self):
router = Router()
assert router.update_handler.handlers
assert router.update_handler.handlers[0].callback == router._listen_update
assert router.observers["message"] == router.message_handler
assert router.observers["edited_message"] == router.edited_message_handler
assert router.observers["channel_post"] == router.channel_post_handler
assert router.observers["edited_channel_post"] == router.edited_channel_post_handler
assert router.observers["inline_query"] == router.inline_query_handler
assert router.observers["chosen_inline_result"] == router.chosen_inline_result_handler
assert router.observers["callback_query"] == router.callback_query_handler
assert router.observers["shipping_query"] == router.shipping_query_handler
assert router.observers["pre_checkout_query"] == router.pre_checkout_query_handler
assert router.observers["poll"] == router.poll_handler
assert router.update.handlers
assert router.update.handlers[0].callback == router._listen_update
assert router.observers["message"] == router.message
assert router.observers["edited_message"] == router.edited_message
assert router.observers["channel_post"] == router.channel_post
assert router.observers["edited_channel_post"] == router.edited_channel_post
assert router.observers["inline_query"] == router.inline_query
assert router.observers["chosen_inline_result"] == router.chosen_inline_result
assert router.observers["callback_query"] == router.callback_query
assert router.observers["shipping_query"] == router.shipping_query
assert router.observers["pre_checkout_query"] == router.pre_checkout_query
assert router.observers["poll"] == router.poll
@pytest.mark.asyncio
@pytest.mark.parametrize(
@ -341,7 +341,7 @@ class TestRouter:
router3 = Router()
router1.include_router(router2)
router1.include_router(router3)
observer = router3.message_handler
observer = router3.message
@observer()
async def my_handler(event: Message, **kwargs: Any):
@ -429,7 +429,7 @@ class TestRouter:
router = Router()
root_router.include_router(router)
@router.message_handler()
@router.message()
async def message_handler(message: Message):
raise Exception("KABOOM")
@ -452,7 +452,7 @@ class TestRouter:
chat=update.message.chat,
)
@root_router.errors_handler()
@root_router.errors()
async def root_error_handler(exception: Exception):
return exception
@ -466,7 +466,7 @@ class TestRouter:
assert isinstance(response, Exception)
assert str(response) == "KABOOM"
@router.errors_handler()
@router.errors()
async def error_handler(exception: Exception):
return "KABOOM"