Dev 3.x i18n & improvements (#696)

* Added base code and make code improvements
* Auto-exclude coverage for `if TYPE_CHECKING:`
* Fixed current coverage
* Cover I18n module
* Update pipeline
* Fixed annotations
* Added docs
* Move exceptions
* Added tests for KeyboardBuilder and initial docs
* Remove help generator (removed from sources tree, requires rewrite)
* Added patch-notes #698, #699, #700, #701, #702, #703
This commit is contained in:
Alex Root Junior 2021-09-22 00:52:38 +03:00 committed by GitHub
parent 5bd1162f57
commit e4046095d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
223 changed files with 1909 additions and 1121 deletions

View file

@ -0,0 +1,5 @@
[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
@abstractmethod

View file

@ -12,7 +12,6 @@ jobs:
build:
strategy:
max-parallel: 9
fail-fast: true
matrix:
os:
- ubuntu-latest
@ -38,10 +37,12 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install and configure Poetry
uses: snok/install-poetry@v1.1.4
uses: snok/install-poetry@v1
with:
version: 1.1.8
virtualenvs-create: true
virtualenvs-in-project: true
installer-parallel: true
- name: Setup redis
if: ${{ matrix.os != 'windows-latest' }}
@ -59,7 +60,7 @@ jobs:
- name: Project dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: |
poetry install --no-interaction
poetry install --no-interaction -E fast -E redis -E proxy -E i18n -E docs
- name: Lint code
run: |

3
CHANGES/698.feature Normal file
View file

@ -0,0 +1,3 @@
Added support of local Bot API server files downloading
When Local API is enabled files can be downloaded via `bot.download`/`bot.download_file` methods.

1
CHANGES/699.misc Normal file
View file

@ -0,0 +1 @@
Covered by tests and docs KeyboardBuilder util

4
CHANGES/700.misc Normal file
View file

@ -0,0 +1,4 @@
**Breaking!!!**. Refactored and renamed exceptions.
- Exceptions module was moved from :code:`aiogram.utils.exceptions` to :code:`aiogram.exceptions`
- Added prefix `Telegram` for all error classes

1
CHANGES/701.feature Normal file
View file

@ -0,0 +1 @@
Implemented I18n & L10n support

1
CHANGES/702.misc Normal file
View file

@ -0,0 +1 @@
Replaced all :code:`pragma: no cover` marks via global :code:`.coveragerc` config

4
CHANGES/703.misc Normal file
View file

@ -0,0 +1,4 @@
Updated dependencies.
**Breaking for framework developers**
Now all optional dependencies should be installed as extra: `poetry install -E fast -E redis -E proxy -E i18n -E docs`

View file

@ -47,7 +47,7 @@ help:
.PHONY: install
install:
poetry install
poetry install -E fast -E redis -E proxy -E i18n -E docs
$(py) pre-commit install
.PHONY: clean

View file

@ -231,6 +231,14 @@ class Bot(ContextInstanceMixin["Bot"]):
async for chunk in stream:
await f.write(chunk)
@classmethod
async def __aiofiles_reader(
cls, file: str, chunk_size: int = 65536
) -> AsyncGenerator[bytes, None]:
async with aiofiles.open(file, "rb") as f:
while chunk := await f.read(chunk_size):
yield chunk
async def download_file(
self,
file_path: str,
@ -254,15 +262,26 @@ class Bot(ContextInstanceMixin["Bot"]):
if destination is None:
destination = io.BytesIO()
close_stream = False
if self.session.api.is_local:
stream = self.__aiofiles_reader(
self.session.api.wrap_local_file(file_path), chunk_size=chunk_size
)
close_stream = True
else:
url = self.session.api.file_url(self.__token, file_path)
stream = self.session.stream_content(url=url, timeout=timeout, chunk_size=chunk_size)
try:
if isinstance(destination, (str, pathlib.Path)):
return await self.__download_file(destination=destination, stream=stream)
else:
return await self.__download_file_binary_io(
destination=destination, seek=seek, stream=stream
)
finally:
if close_stream:
await stream.aclose()
async def download(
self,

View file

@ -19,11 +19,11 @@ from aiohttp import BasicAuth, ClientError, ClientSession, FormData, TCPConnecto
from aiogram.methods import Request, TelegramMethod
from ...exceptions import TelegramNetworkError
from ...methods.base import TelegramType
from ...utils.exceptions.network import NetworkError
from .base import UNSET, BaseSession
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..bot import Bot
_ProxyBasic = Union[str, Tuple[str, BasicAuth]]
@ -147,9 +147,9 @@ class AiohttpSession(BaseSession):
) as resp:
raw_result = await resp.text()
except asyncio.TimeoutError:
raise NetworkError(method=call, message="Request timeout error")
raise TelegramNetworkError(method=call, message="Request timeout error")
except ClientError as e:
raise NetworkError(method=call, message=f"{type(e).__name__}: {e}")
raise TelegramNetworkError(method=call, message=f"{type(e).__name__}: {e}")
response = self.check_response(method=call, status_code=resp.status, content=raw_result)
return cast(TelegramType, response.result)

View file

@ -20,22 +20,27 @@ from typing import (
cast,
)
from aiogram.utils.exceptions.base import TelegramAPIError
from aiogram.exceptions import (
RestartingTelegram,
TelegramAPIError,
TelegramBadRequest,
TelegramConflictError,
TelegramEntityTooLarge,
TelegramForbiddenError,
TelegramMigrateToChat,
TelegramNotFound,
TelegramRetryAfter,
TelegramServerError,
TelegramUnauthorizedError,
)
from aiogram.utils.helper import Default
from ...methods import Response, TelegramMethod
from ...methods.base import TelegramType
from ...types import UNSET, TelegramObject
from ...utils.exceptions.bad_request import BadRequest
from ...utils.exceptions.conflict import ConflictError
from ...utils.exceptions.network import EntityTooLarge
from ...utils.exceptions.not_found import NotFound
from ...utils.exceptions.server import RestartingTelegram, ServerError
from ...utils.exceptions.special import MigrateToChat, RetryAfter
from ...utils.exceptions.unauthorized import UnauthorizedError
from ..telegram import PRODUCTION, TelegramAPIServer
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..bot import Bot
_JsonLoads = Callable[..., Any]
@ -44,7 +49,7 @@ NextRequestMiddlewareType = Callable[
["Bot", TelegramMethod[TelegramObject]], Awaitable[Response[TelegramObject]]
]
RequestMiddlewareType = Callable[
["Bot", TelegramMethod[TelegramType], NextRequestMiddlewareType],
[NextRequestMiddlewareType, "Bot", TelegramMethod[TelegramType]],
Awaitable[Response[TelegramType]],
]
@ -79,29 +84,31 @@ class BaseSession(abc.ABC):
if parameters := response.parameters:
if parameters.retry_after:
raise RetryAfter(
raise TelegramRetryAfter(
method=method, message=description, retry_after=parameters.retry_after
)
if parameters.migrate_to_chat_id:
raise MigrateToChat(
raise TelegramMigrateToChat(
method=method,
message=description,
migrate_to_chat_id=parameters.migrate_to_chat_id,
)
if status_code == HTTPStatus.BAD_REQUEST:
raise BadRequest(method=method, message=description)
raise TelegramBadRequest(method=method, message=description)
if status_code == HTTPStatus.NOT_FOUND:
raise NotFound(method=method, message=description)
raise TelegramNotFound(method=method, message=description)
if status_code == HTTPStatus.CONFLICT:
raise ConflictError(method=method, message=description)
if status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
raise UnauthorizedError(method=method, message=description)
raise TelegramConflictError(method=method, message=description)
if status_code == HTTPStatus.UNAUTHORIZED:
raise TelegramUnauthorizedError(method=method, message=description)
if status_code == HTTPStatus.FORBIDDEN:
raise TelegramForbiddenError(method=method, message=description)
if status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
raise EntityTooLarge(method=method, message=description)
raise TelegramEntityTooLarge(method=method, message=description)
if status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
if "restart" in description:
raise RestartingTelegram(method=method, message=description)
raise ServerError(method=method, message=description)
raise TelegramServerError(method=method, message=description)
raise TelegramAPIError(
method=method,

View file

@ -1,4 +1,10 @@
from dataclasses import dataclass
from typing import Any, Protocol
class WrapLocalFileCallbackCallbackProtocol(Protocol): # pragma: no cover
def __call__(self, value: str) -> str:
pass
@dataclass(frozen=True)
@ -8,8 +14,13 @@ class TelegramAPIServer:
"""
base: str
"""Base URL"""
file: str
"""Files URL"""
is_local: bool = False
"""Mark this server is in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_."""
wrap_local_file: WrapLocalFileCallbackCallbackProtocol = lambda v: v
"""Callback to wrap files path in local mode"""
def api_url(self, token: str, method: str) -> str:
"""
@ -32,19 +43,18 @@ class TelegramAPIServer:
return self.file.format(token=token, path=path)
@classmethod
def from_base(cls, base: str, is_local: bool = False) -> "TelegramAPIServer":
def from_base(cls, base: str, **kwargs: Any) -> "TelegramAPIServer":
"""
Use this method to auto-generate TelegramAPIServer instance from base URL
:param base: Base URL
:param is_local: Mark this server is in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_.
:return: instance of :class:`TelegramAPIServer`
"""
base = base.rstrip("/")
return cls(
base=f"{base}/bot{{token}}/{{method}}",
file=f"{base}/file/bot{{token}}/{{path}}",
is_local=is_local,
**kwargs,
)

View file

@ -8,13 +8,11 @@ from typing import Any, AsyncGenerator, Dict, List, Optional, Union
from .. import loggers
from ..client.bot import Bot
from ..exceptions import TelegramAPIError, TelegramNetworkError, TelegramServerError
from ..methods import GetUpdates, TelegramMethod
from ..types import Update, User
from ..types.update import UpdateTypeLookupError
from ..utils.backoff import Backoff, BackoffConfig
from ..utils.exceptions.base import TelegramAPIError
from ..utils.exceptions.network import NetworkError
from ..utils.exceptions.server import ServerError
from .event.bases import UNHANDLED, SkipHandler
from .event.telegram import TelegramEventObserver
from .fsm.middleware import FSMContextMiddleware
@ -149,7 +147,7 @@ class Dispatcher(Router):
while True:
try:
updates = await bot(get_updates, **kwargs)
except (NetworkError, ServerError) as e:
except (TelegramNetworkError, TelegramServerError) as e:
# In cases when Telegram Bot API was inaccessible don't need to stop polling process
# because some of developers can't make auto-restarting of the script
loggers.dispatcher.error("Failed to fetch updates - %s: %s", type(e).__name__, e)

View file

@ -1,14 +1,19 @@
from __future__ import annotations
from typing import Any, Awaitable, Callable, Dict, NoReturn, Optional, Union
from typing import Any, Awaitable, Callable, Dict, NoReturn, Optional, TypeVar, Union
from unittest.mock import sentinel
from ...types import TelegramObject
from ..middlewares.base import BaseMiddleware
NextMiddlewareType = Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]]
MiddlewareEventType = TypeVar("MiddlewareEventType", bound=TelegramObject)
NextMiddlewareType = Callable[[MiddlewareEventType, Dict[str, Any]], Awaitable[Any]]
MiddlewareType = Union[
BaseMiddleware, Callable[[NextMiddlewareType, TelegramObject, Dict[str, Any]], Awaitable[Any]]
BaseMiddleware,
Callable[
[NextMiddlewareType[MiddlewareEventType], MiddlewareEventType, Dict[str, Any]],
Awaitable[Any],
],
]
UNHANDLED = sentinel.UNHANDLED

View file

@ -8,10 +8,17 @@ from pydantic import ValidationError
from ...types import TelegramObject
from ..filters.base import BaseFilter
from .bases import REJECTED, UNHANDLED, MiddlewareType, NextMiddlewareType, SkipHandler
from .bases import (
REJECTED,
UNHANDLED,
MiddlewareEventType,
MiddlewareType,
NextMiddlewareType,
SkipHandler,
)
from .handler import CallbackType, FilterObject, FilterType, HandlerObject, HandlerType
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from aiogram.dispatcher.router import Router
@ -29,8 +36,8 @@ class TelegramEventObserver:
self.handlers: List[HandlerObject] = []
self.filters: List[Type[BaseFilter]] = []
self.outer_middlewares: List[MiddlewareType] = []
self.middlewares: List[MiddlewareType] = []
self.outer_middlewares: List[MiddlewareType[TelegramObject]] = []
self.middlewares: List[MiddlewareType[TelegramObject]] = []
# Re-used filters check method from already implemented handler object
# with dummy callback which never will be used
@ -78,7 +85,7 @@ class TelegramEventObserver:
yield filter_
registry.append(filter_)
def _resolve_middlewares(self, *, outer: bool = False) -> List[MiddlewareType]:
def _resolve_middlewares(self, *, outer: bool = False) -> List[MiddlewareType[TelegramObject]]:
"""
Get all middlewares in a tree
:param *:
@ -137,8 +144,8 @@ class TelegramEventObserver:
@classmethod
def _wrap_middleware(
cls, middlewares: List[MiddlewareType], handler: HandlerType
) -> NextMiddlewareType:
cls, middlewares: List[MiddlewareType[MiddlewareEventType]], handler: HandlerType
) -> NextMiddlewareType[MiddlewareEventType]:
@functools.wraps(handler)
def mapper(event: TelegramObject, kwargs: Dict[str, Any]) -> Any:
return handler(event, **kwargs)
@ -194,8 +201,11 @@ class TelegramEventObserver:
def middleware(
self,
middleware: Optional[MiddlewareType] = None,
) -> Union[Callable[[MiddlewareType], MiddlewareType], MiddlewareType]:
middleware: Optional[MiddlewareType[TelegramObject]] = None,
) -> Union[
Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]],
MiddlewareType[TelegramObject],
]:
"""
Decorator for registering inner middlewares
@ -215,7 +225,7 @@ class TelegramEventObserver:
<event>.middleware(my_middleware) # via method
"""
def wrapper(m: MiddlewareType) -> MiddlewareType:
def wrapper(m: MiddlewareType[TelegramObject]) -> MiddlewareType[TelegramObject]:
self.middlewares.append(m)
return m
@ -225,8 +235,11 @@ class TelegramEventObserver:
def outer_middleware(
self,
middleware: Optional[MiddlewareType] = None,
) -> Union[Callable[[MiddlewareType], MiddlewareType], MiddlewareType]:
middleware: Optional[MiddlewareType[TelegramObject]] = None,
) -> Union[
Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]],
MiddlewareType[TelegramObject],
]:
"""
Decorator for registering outer middlewares
@ -246,7 +259,7 @@ class TelegramEventObserver:
<event>.outer_middleware(my_middleware) # via method
"""
def wrapper(m: MiddlewareType) -> MiddlewareType:
def wrapper(m: MiddlewareType[TelegramObject]) -> MiddlewareType[TelegramObject]:
self.outer_middlewares.append(m)
return m

View file

@ -4,6 +4,7 @@ from .base import BaseFilter
from .command import Command, CommandObject
from .content_types import ContentTypesFilter
from .exception import ExceptionMessageFilter, ExceptionTypeFilter
from .state import StateFilter
from .text import Text
__all__ = (
@ -15,21 +16,46 @@ __all__ = (
"ContentTypesFilter",
"ExceptionMessageFilter",
"ExceptionTypeFilter",
"StateFilter",
)
BUILTIN_FILTERS: Dict[str, Tuple[Type[BaseFilter], ...]] = {
"message": (Text, Command, ContentTypesFilter),
"edited_message": (Text, Command, ContentTypesFilter),
"channel_post": (Text, ContentTypesFilter),
"edited_channel_post": (Text, ContentTypesFilter),
"inline_query": (Text,),
"chosen_inline_result": (),
"callback_query": (Text,),
"shipping_query": (),
"pre_checkout_query": (),
"poll": (),
"poll_answer": (),
"my_chat_member": (),
"chat_member": (),
"message": (
Text,
Command,
ContentTypesFilter,
StateFilter,
),
"edited_message": (
Text,
Command,
ContentTypesFilter,
StateFilter,
),
"channel_post": (
Text,
ContentTypesFilter,
StateFilter,
),
"edited_channel_post": (
Text,
ContentTypesFilter,
StateFilter,
),
"inline_query": (
Text,
StateFilter,
),
"chosen_inline_result": (StateFilter,),
"callback_query": (
Text,
StateFilter,
),
"shipping_query": (StateFilter,),
"pre_checkout_query": (StateFilter,),
"poll": (StateFilter,),
"poll_answer": (StateFilter,),
"my_chat_member": (StateFilter,),
"chat_member": (StateFilter,),
"error": (ExceptionMessageFilter, ExceptionTypeFilter),
}

View file

@ -14,7 +14,7 @@ class BaseFilter(ABC, BaseModel):
the validators based on class attributes and custom validator.
"""
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
# This checking type-hint is needed because mypy checks validity of overrides and raises:
# error: Signature of "__call__" incompatible with supertype "BaseFilter" [override]
# https://mypy.readthedocs.io/en/latest/error_code_list.html#check-validity-of-overrides-override

View file

@ -22,7 +22,7 @@ class CallbackDataException(Exception):
class CallbackData(BaseModel):
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
sep: str
prefix: str

View file

@ -0,0 +1,47 @@
from inspect import isclass
from typing import Any, Dict, Optional, Sequence, Type, Union, cast, no_type_check
from pydantic import validator
from aiogram.dispatcher.filters import BaseFilter
from aiogram.dispatcher.fsm.state import State, StatesGroup
from aiogram.types import TelegramObject
StateType = Union[str, None, State, StatesGroup, Type[StatesGroup]]
class StateFilter(BaseFilter):
"""
State filter
"""
state: Union[StateType, Sequence[StateType]]
class Config:
arbitrary_types_allowed = True
@validator("state", always=True)
@no_type_check # issubclass breaks things
def _validate_state(cls, v: Union[StateType, Sequence[StateType]]) -> Sequence[StateType]:
if (
isinstance(v, (str, State, StatesGroup))
or (isclass(v) and issubclass(v, StatesGroup))
or v is None
):
return [v]
return v
async def __call__(
self, obj: Union[TelegramObject], raw_state: Optional[str] = None
) -> Union[bool, Dict[str, Any]]:
allowed_states = cast(Sequence[StateType], self.state)
for allowed_state in allowed_states:
if isinstance(allowed_state, str) or allowed_state is None:
if allowed_state == "*":
return True
return raw_state == allowed_state
elif isinstance(allowed_state, (State, StatesGroup)):
return allowed_state(event=obj, raw_state=raw_state)
elif isclass(allowed_state) and issubclass(allowed_state, StatesGroup):
return allowed_state()(event=obj, raw_state=raw_state)
return False

View file

@ -1,11 +1,14 @@
from typing import Any, Dict, Optional, Sequence, Union
from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Union
from pydantic import root_validator
from aiogram.dispatcher.filters import BaseFilter
from aiogram.types import CallbackQuery, InlineQuery, Message, Poll
TextType = str
if TYPE_CHECKING:
from aiogram.utils.i18n.lazy_proxy import LazyProxy
TextType = Union[str, "LazyProxy"]
class Text(BaseFilter):
@ -35,6 +38,9 @@ class Text(BaseFilter):
text_ignore_case: bool = False
"""Ignore case when checks"""
class Config:
arbitrary_types_allowed = True
@root_validator
def _validate_constraints(cls, values: Dict[str, Any]) -> Dict[str, Any]:
# Validate that only one text filter type is presented

View file

@ -5,10 +5,10 @@ from aiogram.dispatcher.fsm.context import FSMContext
from aiogram.dispatcher.fsm.storage.base import BaseStorage
from aiogram.dispatcher.fsm.strategy import FSMStrategy, apply_strategy
from aiogram.dispatcher.middlewares.base import BaseMiddleware
from aiogram.types import Update
from aiogram.types import TelegramObject
class FSMContextMiddleware(BaseMiddleware[Update]):
class FSMContextMiddleware(BaseMiddleware):
def __init__(
self,
storage: BaseStorage,
@ -21,8 +21,8 @@ class FSMContextMiddleware(BaseMiddleware[Update]):
async def __call__(
self,
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
event: Update,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
bot: Bot = cast(Bot, data["bot"])

View file

@ -129,8 +129,8 @@ class StatesGroup(metaclass=StatesGroupMeta):
return cls
return cls.__parent__.get_root()
def __call__(cls, event: TelegramObject, raw_state: Optional[str] = None) -> bool:
return raw_state in type(cls).__all_states_names__
def __call__(self, event: TelegramObject, raw_state: Optional[str] = None) -> bool:
return raw_state in type(self).__all_states_names__
def __str__(self) -> str:
return f"StatesGroup {type(self).__full_group_name__}"

View file

@ -8,7 +8,7 @@ T = TypeVar("T")
class BaseHandlerMixin(Generic[T]):
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
event: T
data: Dict[str, Any]

View file

@ -1,10 +1,12 @@
from abc import ABC, abstractmethod
from typing import Any, Awaitable, Callable, Dict, Generic, TypeVar
from typing import Any, Awaitable, Callable, Dict, TypeVar
from aiogram.types import TelegramObject
T = TypeVar("T")
class BaseMiddleware(ABC, Generic[T]):
class BaseMiddleware(ABC):
"""
Generic middleware class
"""
@ -12,8 +14,8 @@ class BaseMiddleware(ABC, Generic[T]):
@abstractmethod
async def __call__(
self,
handler: Callable[[T, Dict[str, Any]], Awaitable[Any]],
event: T,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any: # pragma: no cover
"""

View file

@ -2,22 +2,22 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict
from ...types import Update
from ...types import TelegramObject
from ..event.bases import UNHANDLED, CancelHandler, SkipHandler
from .base import BaseMiddleware
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..router import Router
class ErrorsMiddleware(BaseMiddleware[Update]):
class ErrorsMiddleware(BaseMiddleware):
def __init__(self, router: Router):
self.router = router
async def __call__(
self,
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
event: Any,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
try:

View file

@ -2,16 +2,18 @@ from contextlib import contextmanager
from typing import Any, Awaitable, Callable, Dict, Iterator, Optional, Tuple
from aiogram.dispatcher.middlewares.base import BaseMiddleware
from aiogram.types import Chat, Update, User
from aiogram.types import Chat, TelegramObject, Update, User
class UserContextMiddleware(BaseMiddleware[Update]):
class UserContextMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
event: Update,
handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: Dict[str, Any],
) -> Any:
if not isinstance(event, Update):
raise RuntimeError("UserContextMiddleware got an unexpected event type!")
chat, user = self.resolve_event_context(event=event)
with self.context(chat=chat, user=user):
if user is not None:

View file

@ -1,9 +1,35 @@
from typing import Optional
from aiogram.methods import TelegramMethod
from aiogram.methods.base import TelegramType
from aiogram.utils.exceptions.base import TelegramAPIError
class RetryAfter(TelegramAPIError):
class TelegramAPIError(Exception):
url: Optional[str] = None
def __init__(
self,
method: TelegramMethod[TelegramType],
message: str,
) -> None:
self.method = method
self.message = message
def render_description(self) -> str:
return self.message
def __str__(self) -> str:
message = [self.render_description()]
if self.url:
message.append(f"(background on this error at: {self.url})")
return "\n".join(message)
class TelegramNetworkError(TelegramAPIError):
pass
class TelegramRetryAfter(TelegramAPIError):
url = "https://core.telegram.org/bots/faq#my-bot-is-hitting-limits-how-do-i-avoid-this"
def __init__(
@ -23,7 +49,7 @@ class RetryAfter(TelegramAPIError):
return description
class MigrateToChat(TelegramAPIError):
class TelegramMigrateToChat(TelegramAPIError):
url = "https://core.telegram.org/bots/api#responseparameters"
def __init__(
@ -42,3 +68,35 @@ class MigrateToChat(TelegramAPIError):
if chat_id := getattr(self.method, "chat_id", None):
description += f" from {chat_id}"
return description
class TelegramBadRequest(TelegramAPIError):
pass
class TelegramNotFound(TelegramAPIError):
pass
class TelegramConflictError(TelegramAPIError):
pass
class TelegramUnauthorizedError(TelegramAPIError):
pass
class TelegramForbiddenError(TelegramAPIError):
pass
class TelegramServerError(TelegramAPIError):
pass
class RestartingTelegram(TelegramServerError):
pass
class TelegramEntityTooLarge(TelegramNetworkError):
url = "https://core.telegram.org/bots/api#sending-files"

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import InputFile, MaskPosition
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import InlineQueryResult
from .base import Request, TelegramMethod, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import ShippingOption
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -9,7 +9,7 @@ from pydantic.generics import GenericModel
from ..types import UNSET, InputFile, ResponseParameters
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot
TelegramType = TypeVar("TelegramType", bound=Any)

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -13,7 +13,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import ChatInviteLink
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import InputFile, MaskPosition
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
from ..types import BotCommandScope
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import ChatInviteLink
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from ..types import UNSET, InlineKeyboardMarkup, Message, MessageEntity
from .base import Request, TelegramMethod, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import InlineKeyboardMarkup, Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import InlineKeyboardMarkup, InputFile, InputMedia, Message
from .base import Request, TelegramMethod, prepare_media_file, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import InlineKeyboardMarkup, Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from ..types import UNSET, InlineKeyboardMarkup, Message, MessageEntity
from .base import Request, TelegramMethod, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from ..types import Chat
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -12,7 +12,7 @@ from ..types import (
)
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -12,7 +12,7 @@ from ..types import (
)
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict
from ..types import File
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import GameHighScore
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict
from ..types import User
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import BotCommand, BotCommandScope
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict
from ..types import StickerSet
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import Update
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
from ..types import UserProfilePhotos
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict
from ..types import WebhookInfo
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import ChatPermissions
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from ..types import ChatInviteLink
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -14,7 +14,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_file, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -14,7 +14,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_file, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -11,7 +11,7 @@ from ..types import (
)
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -11,7 +11,7 @@ from ..types import (
)
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -14,7 +14,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_file, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional
from ..types import InlineKeyboardMarkup, Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from ..types import InlineKeyboardMarkup, LabeledPrice, Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -11,7 +11,7 @@ from ..types import (
)
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -12,7 +12,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_input_media, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -13,7 +13,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -14,7 +14,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_file, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -14,7 +14,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -12,7 +12,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -11,7 +11,7 @@ from ..types import (
)
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -14,7 +14,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_file, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -12,7 +12,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -14,7 +14,7 @@ from ..types import (
)
from .base import Request, TelegramMethod, prepare_file, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from ..types import ChatPermissions
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from ..types import InputFile
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import BotCommand, BotCommandScope
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

View file

@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List
from ..types import PassportElementError
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from ..client.bot import Bot

Some files were not shown because too many files have changed in this diff Show more