diff --git a/.coveragerc b/.coveragerc index e69de29b..9feee202 100644 --- a/.coveragerc +++ b/.coveragerc @@ -0,0 +1,5 @@ +[report] +exclude_lines = + pragma: no cover + if TYPE_CHECKING: + @abstractmethod diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index db6b0cc1..d26ad636 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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: | diff --git a/CHANGES/698.feature b/CHANGES/698.feature new file mode 100644 index 00000000..1fb43042 --- /dev/null +++ b/CHANGES/698.feature @@ -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. diff --git a/CHANGES/699.misc b/CHANGES/699.misc new file mode 100644 index 00000000..6721c9f1 --- /dev/null +++ b/CHANGES/699.misc @@ -0,0 +1 @@ +Covered by tests and docs KeyboardBuilder util diff --git a/CHANGES/700.misc b/CHANGES/700.misc new file mode 100644 index 00000000..0cafaf16 --- /dev/null +++ b/CHANGES/700.misc @@ -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 diff --git a/CHANGES/701.feature b/CHANGES/701.feature new file mode 100644 index 00000000..4e7e3224 --- /dev/null +++ b/CHANGES/701.feature @@ -0,0 +1 @@ +Implemented I18n & L10n support diff --git a/CHANGES/702.misc b/CHANGES/702.misc new file mode 100644 index 00000000..23d613f7 --- /dev/null +++ b/CHANGES/702.misc @@ -0,0 +1 @@ +Replaced all :code:`pragma: no cover` marks via global :code:`.coveragerc` config diff --git a/CHANGES/703.misc b/CHANGES/703.misc new file mode 100644 index 00000000..0d269d99 --- /dev/null +++ b/CHANGES/703.misc @@ -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` diff --git a/Makefile b/Makefile index f1148de8..887766bf 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index 592a83f8..7e0fe0d1 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -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() - url = self.session.api.file_url(self.__token, file_path) - stream = self.session.stream_content(url=url, timeout=timeout, chunk_size=chunk_size) - - 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 + 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, diff --git a/aiogram/client/session/aiohttp.py b/aiogram/client/session/aiohttp.py index a25e705c..ec586fea 100644 --- a/aiogram/client/session/aiohttp.py +++ b/aiogram/client/session/aiohttp.py @@ -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) diff --git a/aiogram/client/session/base.py b/aiogram/client/session/base.py index 0a8c2973..3a845cfd 100644 --- a/aiogram/client/session/base.py +++ b/aiogram/client/session/base.py @@ -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, diff --git a/aiogram/client/telegram.py b/aiogram/client/telegram.py index b8e89a89..2363e24e 100644 --- a/aiogram/client/telegram.py +++ b/aiogram/client/telegram.py @@ -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 `_.""" + 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 `_. :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, ) diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index fffe0262..fa848547 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -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) @@ -331,7 +329,7 @@ class Dispatcher(Router): try: try: await waiter - except CancelledError: # pragma: nocover + except CancelledError: # pragma: no cover process_updates.remove_done_callback(release_waiter) process_updates.cancel() raise diff --git a/aiogram/dispatcher/event/bases.py b/aiogram/dispatcher/event/bases.py index 8e5937ec..1765683a 100644 --- a/aiogram/dispatcher/event/bases.py +++ b/aiogram/dispatcher/event/bases.py @@ -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 diff --git a/aiogram/dispatcher/event/telegram.py b/aiogram/dispatcher/event/telegram.py index 424ffeb3..1d90d3d2 100644 --- a/aiogram/dispatcher/event/telegram.py +++ b/aiogram/dispatcher/event/telegram.py @@ -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: .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: .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 diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py index a0456429..d0a28492 100644 --- a/aiogram/dispatcher/filters/__init__.py +++ b/aiogram/dispatcher/filters/__init__.py @@ -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), } diff --git a/aiogram/dispatcher/filters/base.py b/aiogram/dispatcher/filters/base.py index c9c3fc15..769e887c 100644 --- a/aiogram/dispatcher/filters/base.py +++ b/aiogram/dispatcher/filters/base.py @@ -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 diff --git a/aiogram/dispatcher/filters/callback_data.py b/aiogram/dispatcher/filters/callback_data.py index 4a1cd392..d220da70 100644 --- a/aiogram/dispatcher/filters/callback_data.py +++ b/aiogram/dispatcher/filters/callback_data.py @@ -22,7 +22,7 @@ class CallbackDataException(Exception): class CallbackData(BaseModel): - if TYPE_CHECKING: # pragma: no cover + if TYPE_CHECKING: sep: str prefix: str diff --git a/aiogram/dispatcher/filters/state.py b/aiogram/dispatcher/filters/state.py new file mode 100644 index 00000000..294c1ada --- /dev/null +++ b/aiogram/dispatcher/filters/state.py @@ -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 diff --git a/aiogram/dispatcher/filters/text.py b/aiogram/dispatcher/filters/text.py index 55e7fe5e..3dd36fbd 100644 --- a/aiogram/dispatcher/filters/text.py +++ b/aiogram/dispatcher/filters/text.py @@ -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 diff --git a/aiogram/dispatcher/fsm/middleware.py b/aiogram/dispatcher/fsm/middleware.py index 734c5825..0384371b 100644 --- a/aiogram/dispatcher/fsm/middleware.py +++ b/aiogram/dispatcher/fsm/middleware.py @@ -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"]) diff --git a/aiogram/dispatcher/fsm/state.py b/aiogram/dispatcher/fsm/state.py index a034e003..6b29833b 100644 --- a/aiogram/dispatcher/fsm/state.py +++ b/aiogram/dispatcher/fsm/state.py @@ -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__}" diff --git a/aiogram/dispatcher/handler/base.py b/aiogram/dispatcher/handler/base.py index 6e26250b..a2da355d 100644 --- a/aiogram/dispatcher/handler/base.py +++ b/aiogram/dispatcher/handler/base.py @@ -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] diff --git a/aiogram/dispatcher/middlewares/base.py b/aiogram/dispatcher/middlewares/base.py index 18611176..15b0b4a3 100644 --- a/aiogram/dispatcher/middlewares/base.py +++ b/aiogram/dispatcher/middlewares/base.py @@ -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 """ diff --git a/aiogram/dispatcher/middlewares/error.py b/aiogram/dispatcher/middlewares/error.py index bd3cdcd4..4a026575 100644 --- a/aiogram/dispatcher/middlewares/error.py +++ b/aiogram/dispatcher/middlewares/error.py @@ -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: diff --git a/aiogram/dispatcher/middlewares/user_context.py b/aiogram/dispatcher/middlewares/user_context.py index c6e64878..c238332a 100644 --- a/aiogram/dispatcher/middlewares/user_context.py +++ b/aiogram/dispatcher/middlewares/user_context.py @@ -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: diff --git a/aiogram/utils/exceptions/special.py b/aiogram/exceptions.py similarity index 53% rename from aiogram/utils/exceptions/special.py rename to aiogram/exceptions.py index d2044ec2..6f60fa64 100644 --- a/aiogram/utils/exceptions/special.py +++ b/aiogram/exceptions.py @@ -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" diff --git a/aiogram/methods/add_sticker_to_set.py b/aiogram/methods/add_sticker_to_set.py index 12120964..43499324 100644 --- a/aiogram/methods/add_sticker_to_set.py +++ b/aiogram/methods/add_sticker_to_set.py @@ -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 diff --git a/aiogram/methods/answer_callback_query.py b/aiogram/methods/answer_callback_query.py index 7ab79207..f56e6704 100644 --- a/aiogram/methods/answer_callback_query.py +++ b/aiogram/methods/answer_callback_query.py @@ -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 diff --git a/aiogram/methods/answer_inline_query.py b/aiogram/methods/answer_inline_query.py index 78511403..2e76bba3 100644 --- a/aiogram/methods/answer_inline_query.py +++ b/aiogram/methods/answer_inline_query.py @@ -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 diff --git a/aiogram/methods/answer_pre_checkout_query.py b/aiogram/methods/answer_pre_checkout_query.py index fd47fa15..d6532675 100644 --- a/aiogram/methods/answer_pre_checkout_query.py +++ b/aiogram/methods/answer_pre_checkout_query.py @@ -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 diff --git a/aiogram/methods/answer_shipping_query.py b/aiogram/methods/answer_shipping_query.py index 6c3c7b6d..794466d2 100644 --- a/aiogram/methods/answer_shipping_query.py +++ b/aiogram/methods/answer_shipping_query.py @@ -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 diff --git a/aiogram/methods/ban_chat_member.py b/aiogram/methods/ban_chat_member.py index 457ca68b..6237b279 100644 --- a/aiogram/methods/ban_chat_member.py +++ b/aiogram/methods/ban_chat_member.py @@ -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 diff --git a/aiogram/methods/base.py b/aiogram/methods/base.py index 334beac0..6b1d9619 100644 --- a/aiogram/methods/base.py +++ b/aiogram/methods/base.py @@ -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) diff --git a/aiogram/methods/close.py b/aiogram/methods/close.py index 18f00eac..78e8f07d 100644 --- a/aiogram/methods/close.py +++ b/aiogram/methods/close.py @@ -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 diff --git a/aiogram/methods/copy_message.py b/aiogram/methods/copy_message.py index be94e4cc..c2a193e3 100644 --- a/aiogram/methods/copy_message.py +++ b/aiogram/methods/copy_message.py @@ -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 diff --git a/aiogram/methods/create_chat_invite_link.py b/aiogram/methods/create_chat_invite_link.py index c476df02..af152444 100644 --- a/aiogram/methods/create_chat_invite_link.py +++ b/aiogram/methods/create_chat_invite_link.py @@ -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 diff --git a/aiogram/methods/create_new_sticker_set.py b/aiogram/methods/create_new_sticker_set.py index 9c0750e2..baa29ad8 100644 --- a/aiogram/methods/create_new_sticker_set.py +++ b/aiogram/methods/create_new_sticker_set.py @@ -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 diff --git a/aiogram/methods/delete_chat_photo.py b/aiogram/methods/delete_chat_photo.py index dac07e6f..db67e00c 100644 --- a/aiogram/methods/delete_chat_photo.py +++ b/aiogram/methods/delete_chat_photo.py @@ -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 diff --git a/aiogram/methods/delete_chat_sticker_set.py b/aiogram/methods/delete_chat_sticker_set.py index fb9f92d0..f28fa4fe 100644 --- a/aiogram/methods/delete_chat_sticker_set.py +++ b/aiogram/methods/delete_chat_sticker_set.py @@ -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 diff --git a/aiogram/methods/delete_message.py b/aiogram/methods/delete_message.py index 7fe18d55..2e01cca4 100644 --- a/aiogram/methods/delete_message.py +++ b/aiogram/methods/delete_message.py @@ -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 diff --git a/aiogram/methods/delete_my_commands.py b/aiogram/methods/delete_my_commands.py index b0b9ebaa..0e475625 100644 --- a/aiogram/methods/delete_my_commands.py +++ b/aiogram/methods/delete_my_commands.py @@ -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 diff --git a/aiogram/methods/delete_sticker_from_set.py b/aiogram/methods/delete_sticker_from_set.py index 25499a7e..cb40c366 100644 --- a/aiogram/methods/delete_sticker_from_set.py +++ b/aiogram/methods/delete_sticker_from_set.py @@ -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 diff --git a/aiogram/methods/delete_webhook.py b/aiogram/methods/delete_webhook.py index 4672645f..a68bf78d 100644 --- a/aiogram/methods/delete_webhook.py +++ b/aiogram/methods/delete_webhook.py @@ -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 diff --git a/aiogram/methods/edit_chat_invite_link.py b/aiogram/methods/edit_chat_invite_link.py index d9380f20..ff5fe3af 100644 --- a/aiogram/methods/edit_chat_invite_link.py +++ b/aiogram/methods/edit_chat_invite_link.py @@ -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 diff --git a/aiogram/methods/edit_message_caption.py b/aiogram/methods/edit_message_caption.py index 6e7a1661..d9f6f402 100644 --- a/aiogram/methods/edit_message_caption.py +++ b/aiogram/methods/edit_message_caption.py @@ -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 diff --git a/aiogram/methods/edit_message_live_location.py b/aiogram/methods/edit_message_live_location.py index 99207cf3..e8a34f19 100644 --- a/aiogram/methods/edit_message_live_location.py +++ b/aiogram/methods/edit_message_live_location.py @@ -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 diff --git a/aiogram/methods/edit_message_media.py b/aiogram/methods/edit_message_media.py index be34133c..d77777ad 100644 --- a/aiogram/methods/edit_message_media.py +++ b/aiogram/methods/edit_message_media.py @@ -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 diff --git a/aiogram/methods/edit_message_reply_markup.py b/aiogram/methods/edit_message_reply_markup.py index eb16e43c..0304012c 100644 --- a/aiogram/methods/edit_message_reply_markup.py +++ b/aiogram/methods/edit_message_reply_markup.py @@ -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 diff --git a/aiogram/methods/edit_message_text.py b/aiogram/methods/edit_message_text.py index 9bd578ff..2fdc11ab 100644 --- a/aiogram/methods/edit_message_text.py +++ b/aiogram/methods/edit_message_text.py @@ -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 diff --git a/aiogram/methods/export_chat_invite_link.py b/aiogram/methods/export_chat_invite_link.py index 9321b5c6..1250ee3a 100644 --- a/aiogram/methods/export_chat_invite_link.py +++ b/aiogram/methods/export_chat_invite_link.py @@ -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 diff --git a/aiogram/methods/forward_message.py b/aiogram/methods/forward_message.py index e18e4cf6..6ea5b233 100644 --- a/aiogram/methods/forward_message.py +++ b/aiogram/methods/forward_message.py @@ -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 diff --git a/aiogram/methods/get_chat.py b/aiogram/methods/get_chat.py index ef84aa1e..925e2638 100644 --- a/aiogram/methods/get_chat.py +++ b/aiogram/methods/get_chat.py @@ -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 diff --git a/aiogram/methods/get_chat_administrators.py b/aiogram/methods/get_chat_administrators.py index 9ca31884..adc7ed6e 100644 --- a/aiogram/methods/get_chat_administrators.py +++ b/aiogram/methods/get_chat_administrators.py @@ -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 diff --git a/aiogram/methods/get_chat_member.py b/aiogram/methods/get_chat_member.py index 60d508bf..faf5dece 100644 --- a/aiogram/methods/get_chat_member.py +++ b/aiogram/methods/get_chat_member.py @@ -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 diff --git a/aiogram/methods/get_chat_member_count.py b/aiogram/methods/get_chat_member_count.py index b6bd67a4..7b492998 100644 --- a/aiogram/methods/get_chat_member_count.py +++ b/aiogram/methods/get_chat_member_count.py @@ -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 diff --git a/aiogram/methods/get_chat_members_count.py b/aiogram/methods/get_chat_members_count.py index cec4929d..2eeadff8 100644 --- a/aiogram/methods/get_chat_members_count.py +++ b/aiogram/methods/get_chat_members_count.py @@ -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 diff --git a/aiogram/methods/get_file.py b/aiogram/methods/get_file.py index 57d84c3a..163d484d 100644 --- a/aiogram/methods/get_file.py +++ b/aiogram/methods/get_file.py @@ -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 diff --git a/aiogram/methods/get_game_high_scores.py b/aiogram/methods/get_game_high_scores.py index 66427ed9..18a4eb2d 100644 --- a/aiogram/methods/get_game_high_scores.py +++ b/aiogram/methods/get_game_high_scores.py @@ -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 diff --git a/aiogram/methods/get_me.py b/aiogram/methods/get_me.py index 4b0c07c9..60d2f0c2 100644 --- a/aiogram/methods/get_me.py +++ b/aiogram/methods/get_me.py @@ -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 diff --git a/aiogram/methods/get_my_commands.py b/aiogram/methods/get_my_commands.py index 2e4e683d..61481a86 100644 --- a/aiogram/methods/get_my_commands.py +++ b/aiogram/methods/get_my_commands.py @@ -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 diff --git a/aiogram/methods/get_sticker_set.py b/aiogram/methods/get_sticker_set.py index 30a8f35b..35af44bf 100644 --- a/aiogram/methods/get_sticker_set.py +++ b/aiogram/methods/get_sticker_set.py @@ -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 diff --git a/aiogram/methods/get_updates.py b/aiogram/methods/get_updates.py index e5c99378..ebed1d7a 100644 --- a/aiogram/methods/get_updates.py +++ b/aiogram/methods/get_updates.py @@ -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 diff --git a/aiogram/methods/get_user_profile_photos.py b/aiogram/methods/get_user_profile_photos.py index b37f8a0e..2bcc881a 100644 --- a/aiogram/methods/get_user_profile_photos.py +++ b/aiogram/methods/get_user_profile_photos.py @@ -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 diff --git a/aiogram/methods/get_webhook_info.py b/aiogram/methods/get_webhook_info.py index 60c8ef52..6544cf83 100644 --- a/aiogram/methods/get_webhook_info.py +++ b/aiogram/methods/get_webhook_info.py @@ -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 diff --git a/aiogram/methods/kick_chat_member.py b/aiogram/methods/kick_chat_member.py index e854ff11..8f455747 100644 --- a/aiogram/methods/kick_chat_member.py +++ b/aiogram/methods/kick_chat_member.py @@ -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 diff --git a/aiogram/methods/leave_chat.py b/aiogram/methods/leave_chat.py index f247caa0..748c25c6 100644 --- a/aiogram/methods/leave_chat.py +++ b/aiogram/methods/leave_chat.py @@ -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 diff --git a/aiogram/methods/log_out.py b/aiogram/methods/log_out.py index c2bbe6d3..02c76ed2 100644 --- a/aiogram/methods/log_out.py +++ b/aiogram/methods/log_out.py @@ -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 diff --git a/aiogram/methods/pin_chat_message.py b/aiogram/methods/pin_chat_message.py index 51726dd5..60d2795d 100644 --- a/aiogram/methods/pin_chat_message.py +++ b/aiogram/methods/pin_chat_message.py @@ -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 diff --git a/aiogram/methods/promote_chat_member.py b/aiogram/methods/promote_chat_member.py index 22347dcd..84dd113b 100644 --- a/aiogram/methods/promote_chat_member.py +++ b/aiogram/methods/promote_chat_member.py @@ -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 diff --git a/aiogram/methods/restrict_chat_member.py b/aiogram/methods/restrict_chat_member.py index dec75404..0021d42e 100644 --- a/aiogram/methods/restrict_chat_member.py +++ b/aiogram/methods/restrict_chat_member.py @@ -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 diff --git a/aiogram/methods/revoke_chat_invite_link.py b/aiogram/methods/revoke_chat_invite_link.py index ee684f6d..1673c9e8 100644 --- a/aiogram/methods/revoke_chat_invite_link.py +++ b/aiogram/methods/revoke_chat_invite_link.py @@ -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 diff --git a/aiogram/methods/send_animation.py b/aiogram/methods/send_animation.py index 2399ee0d..8a374396 100644 --- a/aiogram/methods/send_animation.py +++ b/aiogram/methods/send_animation.py @@ -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 diff --git a/aiogram/methods/send_audio.py b/aiogram/methods/send_audio.py index 58899f81..43606db7 100644 --- a/aiogram/methods/send_audio.py +++ b/aiogram/methods/send_audio.py @@ -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 diff --git a/aiogram/methods/send_chat_action.py b/aiogram/methods/send_chat_action.py index 2db43414..5f0996af 100644 --- a/aiogram/methods/send_chat_action.py +++ b/aiogram/methods/send_chat_action.py @@ -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 diff --git a/aiogram/methods/send_contact.py b/aiogram/methods/send_contact.py index 226a091d..182654d2 100644 --- a/aiogram/methods/send_contact.py +++ b/aiogram/methods/send_contact.py @@ -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 diff --git a/aiogram/methods/send_dice.py b/aiogram/methods/send_dice.py index 65b148cd..be3d567c 100644 --- a/aiogram/methods/send_dice.py +++ b/aiogram/methods/send_dice.py @@ -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 diff --git a/aiogram/methods/send_document.py b/aiogram/methods/send_document.py index 5cb155c2..aa7c6ea7 100644 --- a/aiogram/methods/send_document.py +++ b/aiogram/methods/send_document.py @@ -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 diff --git a/aiogram/methods/send_game.py b/aiogram/methods/send_game.py index 690bf101..d4957707 100644 --- a/aiogram/methods/send_game.py +++ b/aiogram/methods/send_game.py @@ -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 diff --git a/aiogram/methods/send_invoice.py b/aiogram/methods/send_invoice.py index 97d569ea..88a1bc5a 100644 --- a/aiogram/methods/send_invoice.py +++ b/aiogram/methods/send_invoice.py @@ -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 diff --git a/aiogram/methods/send_location.py b/aiogram/methods/send_location.py index f9646468..31385884 100644 --- a/aiogram/methods/send_location.py +++ b/aiogram/methods/send_location.py @@ -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 diff --git a/aiogram/methods/send_media_group.py b/aiogram/methods/send_media_group.py index 46da358e..6a35e934 100644 --- a/aiogram/methods/send_media_group.py +++ b/aiogram/methods/send_media_group.py @@ -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 diff --git a/aiogram/methods/send_message.py b/aiogram/methods/send_message.py index bbacb6af..19ae433a 100644 --- a/aiogram/methods/send_message.py +++ b/aiogram/methods/send_message.py @@ -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 diff --git a/aiogram/methods/send_photo.py b/aiogram/methods/send_photo.py index 46b2a808..25b8b52e 100644 --- a/aiogram/methods/send_photo.py +++ b/aiogram/methods/send_photo.py @@ -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 diff --git a/aiogram/methods/send_poll.py b/aiogram/methods/send_poll.py index c9a6fdd0..4ffe4a69 100644 --- a/aiogram/methods/send_poll.py +++ b/aiogram/methods/send_poll.py @@ -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 diff --git a/aiogram/methods/send_sticker.py b/aiogram/methods/send_sticker.py index 05f0cf8d..c4435e77 100644 --- a/aiogram/methods/send_sticker.py +++ b/aiogram/methods/send_sticker.py @@ -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 diff --git a/aiogram/methods/send_venue.py b/aiogram/methods/send_venue.py index cdadf416..dc62b2d0 100644 --- a/aiogram/methods/send_venue.py +++ b/aiogram/methods/send_venue.py @@ -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 diff --git a/aiogram/methods/send_video.py b/aiogram/methods/send_video.py index 0105beaa..81d15da6 100644 --- a/aiogram/methods/send_video.py +++ b/aiogram/methods/send_video.py @@ -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 diff --git a/aiogram/methods/send_video_note.py b/aiogram/methods/send_video_note.py index 4e475222..99e3651a 100644 --- a/aiogram/methods/send_video_note.py +++ b/aiogram/methods/send_video_note.py @@ -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 diff --git a/aiogram/methods/send_voice.py b/aiogram/methods/send_voice.py index abd16217..7c765e8c 100644 --- a/aiogram/methods/send_voice.py +++ b/aiogram/methods/send_voice.py @@ -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 diff --git a/aiogram/methods/set_chat_administrator_custom_title.py b/aiogram/methods/set_chat_administrator_custom_title.py index cb570940..7522a237 100644 --- a/aiogram/methods/set_chat_administrator_custom_title.py +++ b/aiogram/methods/set_chat_administrator_custom_title.py @@ -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 diff --git a/aiogram/methods/set_chat_description.py b/aiogram/methods/set_chat_description.py index 109aa270..ab4eff13 100644 --- a/aiogram/methods/set_chat_description.py +++ b/aiogram/methods/set_chat_description.py @@ -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 diff --git a/aiogram/methods/set_chat_permissions.py b/aiogram/methods/set_chat_permissions.py index 75181e88..58cd77b2 100644 --- a/aiogram/methods/set_chat_permissions.py +++ b/aiogram/methods/set_chat_permissions.py @@ -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 diff --git a/aiogram/methods/set_chat_photo.py b/aiogram/methods/set_chat_photo.py index 2aaa6cca..62d857bb 100644 --- a/aiogram/methods/set_chat_photo.py +++ b/aiogram/methods/set_chat_photo.py @@ -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 diff --git a/aiogram/methods/set_chat_sticker_set.py b/aiogram/methods/set_chat_sticker_set.py index 91a8896b..639ac5dc 100644 --- a/aiogram/methods/set_chat_sticker_set.py +++ b/aiogram/methods/set_chat_sticker_set.py @@ -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 diff --git a/aiogram/methods/set_chat_title.py b/aiogram/methods/set_chat_title.py index f86ace4f..dfe2a7d5 100644 --- a/aiogram/methods/set_chat_title.py +++ b/aiogram/methods/set_chat_title.py @@ -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 diff --git a/aiogram/methods/set_game_score.py b/aiogram/methods/set_game_score.py index 9965dced..92063d7d 100644 --- a/aiogram/methods/set_game_score.py +++ b/aiogram/methods/set_game_score.py @@ -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 diff --git a/aiogram/methods/set_my_commands.py b/aiogram/methods/set_my_commands.py index 451ce509..3dcebbb3 100644 --- a/aiogram/methods/set_my_commands.py +++ b/aiogram/methods/set_my_commands.py @@ -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 diff --git a/aiogram/methods/set_passport_data_errors.py b/aiogram/methods/set_passport_data_errors.py index e3c215ce..1f781b08 100644 --- a/aiogram/methods/set_passport_data_errors.py +++ b/aiogram/methods/set_passport_data_errors.py @@ -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 diff --git a/aiogram/methods/set_sticker_position_in_set.py b/aiogram/methods/set_sticker_position_in_set.py index 8c60c0ea..c607c0d5 100644 --- a/aiogram/methods/set_sticker_position_in_set.py +++ b/aiogram/methods/set_sticker_position_in_set.py @@ -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 diff --git a/aiogram/methods/set_sticker_set_thumb.py b/aiogram/methods/set_sticker_set_thumb.py index ab97d663..5ab66cd5 100644 --- a/aiogram/methods/set_sticker_set_thumb.py +++ b/aiogram/methods/set_sticker_set_thumb.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, 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 diff --git a/aiogram/methods/set_webhook.py b/aiogram/methods/set_webhook.py index 1c1ffd13..2b80dd17 100644 --- a/aiogram/methods/set_webhook.py +++ b/aiogram/methods/set_webhook.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional 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 diff --git a/aiogram/methods/stop_message_live_location.py b/aiogram/methods/stop_message_live_location.py index 2565eb0d..1909b57e 100644 --- a/aiogram/methods/stop_message_live_location.py +++ b/aiogram/methods/stop_message_live_location.py @@ -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 diff --git a/aiogram/methods/stop_poll.py b/aiogram/methods/stop_poll.py index 027241bf..f36f18de 100644 --- a/aiogram/methods/stop_poll.py +++ b/aiogram/methods/stop_poll.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Union from ..types import InlineKeyboardMarkup, Poll from .base import Request, TelegramMethod -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from ..client.bot import Bot diff --git a/aiogram/methods/unban_chat_member.py b/aiogram/methods/unban_chat_member.py index 993335cf..e9938a84 100644 --- a/aiogram/methods/unban_chat_member.py +++ b/aiogram/methods/unban_chat_member.py @@ -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 diff --git a/aiogram/methods/unpin_all_chat_messages.py b/aiogram/methods/unpin_all_chat_messages.py index b37677f9..13b06fe6 100644 --- a/aiogram/methods/unpin_all_chat_messages.py +++ b/aiogram/methods/unpin_all_chat_messages.py @@ -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 diff --git a/aiogram/methods/unpin_chat_message.py b/aiogram/methods/unpin_chat_message.py index 736f6472..b7ff0c48 100644 --- a/aiogram/methods/unpin_chat_message.py +++ b/aiogram/methods/unpin_chat_message.py @@ -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 diff --git a/aiogram/methods/upload_sticker_file.py b/aiogram/methods/upload_sticker_file.py index 7d5bc86b..3d887abb 100644 --- a/aiogram/methods/upload_sticker_file.py +++ b/aiogram/methods/upload_sticker_file.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Dict from ..types import File, InputFile from .base import Request, TelegramMethod, prepare_file -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from ..client.bot import Bot diff --git a/aiogram/types/animation.py b/aiogram/types/animation.py index c97fb548..5cc59ff5 100644 --- a/aiogram/types/animation.py +++ b/aiogram/types/animation.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .photo_size import PhotoSize diff --git a/aiogram/types/audio.py b/aiogram/types/audio.py index cbb468f3..9fb4b1b8 100644 --- a/aiogram/types/audio.py +++ b/aiogram/types/audio.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .photo_size import PhotoSize diff --git a/aiogram/types/callback_query.py b/aiogram/types/callback_query.py index 90786e8f..08fddc8a 100644 --- a/aiogram/types/callback_query.py +++ b/aiogram/types/callback_query.py @@ -6,7 +6,7 @@ from pydantic import Field from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from ..methods import AnswerCallbackQuery from .message import Message from .user import User diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 4c0db8c9..7f17d9b5 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .chat_location import ChatLocation from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto diff --git a/aiogram/types/chat_invite_link.py b/aiogram/types/chat_invite_link.py index 4d39a116..0d610424 100644 --- a/aiogram/types/chat_invite_link.py +++ b/aiogram/types/chat_invite_link.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/chat_location.py b/aiogram/types/chat_location.py index 77c5a45b..89b28d7c 100644 --- a/aiogram/types/chat_location.py +++ b/aiogram/types/chat_location.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .location import Location diff --git a/aiogram/types/chat_member_administrator.py b/aiogram/types/chat_member_administrator.py index f25818c2..4678284a 100644 --- a/aiogram/types/chat_member_administrator.py +++ b/aiogram/types/chat_member_administrator.py @@ -6,7 +6,7 @@ from pydantic import Field from .chat_member import ChatMember -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/chat_member_banned.py b/aiogram/types/chat_member_banned.py index 3b828247..d1004825 100644 --- a/aiogram/types/chat_member_banned.py +++ b/aiogram/types/chat_member_banned.py @@ -7,7 +7,7 @@ from pydantic import Field from .chat_member import ChatMember -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/chat_member_left.py b/aiogram/types/chat_member_left.py index d95cd320..02df05de 100644 --- a/aiogram/types/chat_member_left.py +++ b/aiogram/types/chat_member_left.py @@ -6,7 +6,7 @@ from pydantic import Field from .chat_member import ChatMember -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/chat_member_member.py b/aiogram/types/chat_member_member.py index 2c55ea62..db5d5731 100644 --- a/aiogram/types/chat_member_member.py +++ b/aiogram/types/chat_member_member.py @@ -6,7 +6,7 @@ from pydantic import Field from .chat_member import ChatMember -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/chat_member_owner.py b/aiogram/types/chat_member_owner.py index dcc766a5..3494af80 100644 --- a/aiogram/types/chat_member_owner.py +++ b/aiogram/types/chat_member_owner.py @@ -6,7 +6,7 @@ from pydantic import Field from .chat_member import ChatMember -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/chat_member_restricted.py b/aiogram/types/chat_member_restricted.py index 6860b8d0..d2b694a7 100644 --- a/aiogram/types/chat_member_restricted.py +++ b/aiogram/types/chat_member_restricted.py @@ -7,7 +7,7 @@ from pydantic import Field from .chat_member import ChatMember -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/chat_member_updated.py b/aiogram/types/chat_member_updated.py index e7a2ce92..e4e4a340 100644 --- a/aiogram/types/chat_member_updated.py +++ b/aiogram/types/chat_member_updated.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .chat import Chat from .chat_invite_link import ChatInviteLink from .chat_member_administrator import ChatMemberAdministrator diff --git a/aiogram/types/chosen_inline_result.py b/aiogram/types/chosen_inline_result.py index 445e81fd..2144c0ca 100644 --- a/aiogram/types/chosen_inline_result.py +++ b/aiogram/types/chosen_inline_result.py @@ -6,7 +6,7 @@ from pydantic import Field from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .location import Location from .user import User diff --git a/aiogram/types/document.py b/aiogram/types/document.py index 788c47c1..a2eaa34b 100644 --- a/aiogram/types/document.py +++ b/aiogram/types/document.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .photo_size import PhotoSize diff --git a/aiogram/types/encrypted_passport_element.py b/aiogram/types/encrypted_passport_element.py index b17ae382..7b958f29 100644 --- a/aiogram/types/encrypted_passport_element.py +++ b/aiogram/types/encrypted_passport_element.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .passport_file import PassportFile diff --git a/aiogram/types/game.py b/aiogram/types/game.py index e8d41cc0..f9b03bd1 100644 --- a/aiogram/types/game.py +++ b/aiogram/types/game.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .animation import Animation from .message_entity import MessageEntity from .photo_size import PhotoSize diff --git a/aiogram/types/game_high_score.py b/aiogram/types/game_high_score.py index 848306f8..30ec941a 100644 --- a/aiogram/types/game_high_score.py +++ b/aiogram/types/game_high_score.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/inline_keyboard_button.py b/aiogram/types/inline_keyboard_button.py index 62ab33e9..28cebcaa 100644 --- a/aiogram/types/inline_keyboard_button.py +++ b/aiogram/types/inline_keyboard_button.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import MutableTelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .callback_game import CallbackGame from .login_url import LoginUrl diff --git a/aiogram/types/inline_keyboard_markup.py b/aiogram/types/inline_keyboard_markup.py index 21fdce6f..0bbdee37 100644 --- a/aiogram/types/inline_keyboard_markup.py +++ b/aiogram/types/inline_keyboard_markup.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List from .base import MutableTelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_button import InlineKeyboardButton diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py index 60bb67d7..89602803 100644 --- a/aiogram/types/inline_query.py +++ b/aiogram/types/inline_query.py @@ -6,7 +6,7 @@ from pydantic import Field from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from ..methods import AnswerInlineQuery from .inline_query_result import InlineQueryResult from .location import Location diff --git a/aiogram/types/inline_query_result_article.py b/aiogram/types/inline_query_result_article.py index a4f22bed..27164254 100644 --- a/aiogram/types/inline_query_result_article.py +++ b/aiogram/types/inline_query_result_article.py @@ -6,7 +6,7 @@ from pydantic import Field from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent diff --git a/aiogram/types/inline_query_result_audio.py b/aiogram/types/inline_query_result_audio.py index d124f5fa..97141268 100644 --- a/aiogram/types/inline_query_result_audio.py +++ b/aiogram/types/inline_query_result_audio.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_cached_audio.py b/aiogram/types/inline_query_result_cached_audio.py index 81817051..d9f12668 100644 --- a/aiogram/types/inline_query_result_cached_audio.py +++ b/aiogram/types/inline_query_result_cached_audio.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_cached_document.py b/aiogram/types/inline_query_result_cached_document.py index ef0cfe7e..bb4f1349 100644 --- a/aiogram/types/inline_query_result_cached_document.py +++ b/aiogram/types/inline_query_result_cached_document.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_cached_gif.py b/aiogram/types/inline_query_result_cached_gif.py index 393f3c87..053dfd43 100644 --- a/aiogram/types/inline_query_result_cached_gif.py +++ b/aiogram/types/inline_query_result_cached_gif.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_cached_mpeg4_gif.py b/aiogram/types/inline_query_result_cached_mpeg4_gif.py index 10e779b0..7076c187 100644 --- a/aiogram/types/inline_query_result_cached_mpeg4_gif.py +++ b/aiogram/types/inline_query_result_cached_mpeg4_gif.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_cached_photo.py b/aiogram/types/inline_query_result_cached_photo.py index ca9d8356..07ab3a50 100644 --- a/aiogram/types/inline_query_result_cached_photo.py +++ b/aiogram/types/inline_query_result_cached_photo.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_cached_sticker.py b/aiogram/types/inline_query_result_cached_sticker.py index 2d6911df..80685ff2 100644 --- a/aiogram/types/inline_query_result_cached_sticker.py +++ b/aiogram/types/inline_query_result_cached_sticker.py @@ -6,7 +6,7 @@ from pydantic import Field from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent diff --git a/aiogram/types/inline_query_result_cached_video.py b/aiogram/types/inline_query_result_cached_video.py index 1e23b7ae..334eafbf 100644 --- a/aiogram/types/inline_query_result_cached_video.py +++ b/aiogram/types/inline_query_result_cached_video.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_cached_voice.py b/aiogram/types/inline_query_result_cached_voice.py index 43c55551..3da9abf8 100644 --- a/aiogram/types/inline_query_result_cached_voice.py +++ b/aiogram/types/inline_query_result_cached_voice.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_contact.py b/aiogram/types/inline_query_result_contact.py index c54c0d2e..dcbda121 100644 --- a/aiogram/types/inline_query_result_contact.py +++ b/aiogram/types/inline_query_result_contact.py @@ -6,7 +6,7 @@ from pydantic import Field from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent diff --git a/aiogram/types/inline_query_result_document.py b/aiogram/types/inline_query_result_document.py index d817eb01..4e28ba91 100644 --- a/aiogram/types/inline_query_result_document.py +++ b/aiogram/types/inline_query_result_document.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_game.py b/aiogram/types/inline_query_result_game.py index 8ed9862d..4ece5b95 100644 --- a/aiogram/types/inline_query_result_game.py +++ b/aiogram/types/inline_query_result_game.py @@ -6,7 +6,7 @@ from pydantic import Field from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup diff --git a/aiogram/types/inline_query_result_gif.py b/aiogram/types/inline_query_result_gif.py index 2688cd30..3832aec4 100644 --- a/aiogram/types/inline_query_result_gif.py +++ b/aiogram/types/inline_query_result_gif.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_location.py b/aiogram/types/inline_query_result_location.py index 01d501a2..b7e45919 100644 --- a/aiogram/types/inline_query_result_location.py +++ b/aiogram/types/inline_query_result_location.py @@ -6,7 +6,7 @@ from pydantic import Field from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent diff --git a/aiogram/types/inline_query_result_mpeg4_gif.py b/aiogram/types/inline_query_result_mpeg4_gif.py index ea6d4ab2..c301668d 100644 --- a/aiogram/types/inline_query_result_mpeg4_gif.py +++ b/aiogram/types/inline_query_result_mpeg4_gif.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_photo.py b/aiogram/types/inline_query_result_photo.py index 9e2935b1..158f1914 100644 --- a/aiogram/types/inline_query_result_photo.py +++ b/aiogram/types/inline_query_result_photo.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_venue.py b/aiogram/types/inline_query_result_venue.py index f5c5c3ca..e27e6cd0 100644 --- a/aiogram/types/inline_query_result_venue.py +++ b/aiogram/types/inline_query_result_venue.py @@ -6,7 +6,7 @@ from pydantic import Field from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent diff --git a/aiogram/types/inline_query_result_video.py b/aiogram/types/inline_query_result_video.py index c1494d69..a1ed726e 100644 --- a/aiogram/types/inline_query_result_video.py +++ b/aiogram/types/inline_query_result_video.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/inline_query_result_voice.py b/aiogram/types/inline_query_result_voice.py index eac64546..2097f6b7 100644 --- a/aiogram/types/inline_query_result_voice.py +++ b/aiogram/types/inline_query_result_voice.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .inline_query_result import InlineQueryResult -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .inline_keyboard_markup import InlineKeyboardMarkup from .input_message_content import InputMessageContent from .message_entity import MessageEntity diff --git a/aiogram/types/input_invoice_message_content.py b/aiogram/types/input_invoice_message_content.py index b001bfdf..301fbd81 100644 --- a/aiogram/types/input_invoice_message_content.py +++ b/aiogram/types/input_invoice_message_content.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List, Optional from .input_message_content import InputMessageContent -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .labeled_price import LabeledPrice diff --git a/aiogram/types/input_media_animation.py b/aiogram/types/input_media_animation.py index a18e1e88..4f1f25b0 100644 --- a/aiogram/types/input_media_animation.py +++ b/aiogram/types/input_media_animation.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .input_media import InputMedia -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .input_file import InputFile from .message_entity import MessageEntity diff --git a/aiogram/types/input_media_audio.py b/aiogram/types/input_media_audio.py index 6252dbaf..0265f67a 100644 --- a/aiogram/types/input_media_audio.py +++ b/aiogram/types/input_media_audio.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .input_media import InputMedia -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .input_file import InputFile from .message_entity import MessageEntity diff --git a/aiogram/types/input_media_document.py b/aiogram/types/input_media_document.py index 0e3df5fe..72552ab6 100644 --- a/aiogram/types/input_media_document.py +++ b/aiogram/types/input_media_document.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .input_media import InputMedia -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .input_file import InputFile from .message_entity import MessageEntity diff --git a/aiogram/types/input_media_photo.py b/aiogram/types/input_media_photo.py index f32be92f..09aa2918 100644 --- a/aiogram/types/input_media_photo.py +++ b/aiogram/types/input_media_photo.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .input_media import InputMedia -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .input_file import InputFile from .message_entity import MessageEntity diff --git a/aiogram/types/input_media_video.py b/aiogram/types/input_media_video.py index b6328736..cd32ee2c 100644 --- a/aiogram/types/input_media_video.py +++ b/aiogram/types/input_media_video.py @@ -7,7 +7,7 @@ from pydantic import Field from .base import UNSET from .input_media import InputMedia -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .input_file import InputFile from .message_entity import MessageEntity diff --git a/aiogram/types/input_text_message_content.py b/aiogram/types/input_text_message_content.py index 5d0950a1..2b502765 100644 --- a/aiogram/types/input_text_message_content.py +++ b/aiogram/types/input_text_message_content.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, List, Optional from .base import UNSET from .input_message_content import InputMessageContent -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .message_entity import MessageEntity diff --git a/aiogram/types/keyboard_button.py b/aiogram/types/keyboard_button.py index a5d83cbc..c1e3e5f5 100644 --- a/aiogram/types/keyboard_button.py +++ b/aiogram/types/keyboard_button.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import MutableTelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .keyboard_button_poll_type import KeyboardButtonPollType diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 1f2fd996..7f05941f 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -9,7 +9,7 @@ from aiogram.utils import helper from .base import UNSET, TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from ..methods import ( CopyMessage, DeleteMessage, diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index 2fb6623a..ddaac506 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Optional from ..utils.text_decorations import add_surrogates, remove_surrogates from .base import MutableTelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/order_info.py b/aiogram/types/order_info.py index 5283a967..bf354b5e 100644 --- a/aiogram/types/order_info.py +++ b/aiogram/types/order_info.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .shipping_address import ShippingAddress diff --git a/aiogram/types/passport_data.py b/aiogram/types/passport_data.py index 028334b2..96608226 100644 --- a/aiogram/types/passport_data.py +++ b/aiogram/types/passport_data.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .encrypted_credentials import EncryptedCredentials from .encrypted_passport_element import EncryptedPassportElement diff --git a/aiogram/types/poll.py b/aiogram/types/poll.py index bcb80085..70142353 100644 --- a/aiogram/types/poll.py +++ b/aiogram/types/poll.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, List, Optional, Union from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .message_entity import MessageEntity from .poll_option import PollOption diff --git a/aiogram/types/poll_answer.py b/aiogram/types/poll_answer.py index 2f394451..c2cd7456 100644 --- a/aiogram/types/poll_answer.py +++ b/aiogram/types/poll_answer.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/pre_checkout_query.py b/aiogram/types/pre_checkout_query.py index fdac4033..a95fb5f4 100644 --- a/aiogram/types/pre_checkout_query.py +++ b/aiogram/types/pre_checkout_query.py @@ -6,7 +6,7 @@ from pydantic import Field from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from ..methods import AnswerPreCheckoutQuery from .order_info import OrderInfo from .user import User diff --git a/aiogram/types/proximity_alert_triggered.py b/aiogram/types/proximity_alert_triggered.py index 95b707ec..8275cd26 100644 --- a/aiogram/types/proximity_alert_triggered.py +++ b/aiogram/types/proximity_alert_triggered.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/types/reply_keyboard_markup.py b/aiogram/types/reply_keyboard_markup.py index dfbd46ed..6b8a65e3 100644 --- a/aiogram/types/reply_keyboard_markup.py +++ b/aiogram/types/reply_keyboard_markup.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List, Optional from .base import MutableTelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .keyboard_button import KeyboardButton diff --git a/aiogram/types/shipping_option.py b/aiogram/types/shipping_option.py index dca70afd..6caa84a5 100644 --- a/aiogram/types/shipping_option.py +++ b/aiogram/types/shipping_option.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .labeled_price import LabeledPrice diff --git a/aiogram/types/shipping_query.py b/aiogram/types/shipping_query.py index 8e2d2a31..df00e38d 100644 --- a/aiogram/types/shipping_query.py +++ b/aiogram/types/shipping_query.py @@ -6,7 +6,7 @@ from pydantic import Field from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from ..methods import AnswerShippingQuery from ..types import ShippingOption from .shipping_address import ShippingAddress diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py index ad5486b4..be5fc10a 100644 --- a/aiogram/types/sticker.py +++ b/aiogram/types/sticker.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .mask_position import MaskPosition from .photo_size import PhotoSize diff --git a/aiogram/types/sticker_set.py b/aiogram/types/sticker_set.py index 9132daf0..d26d206d 100644 --- a/aiogram/types/sticker_set.py +++ b/aiogram/types/sticker_set.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .photo_size import PhotoSize from .sticker import Sticker diff --git a/aiogram/types/successful_payment.py b/aiogram/types/successful_payment.py index d87acf10..d6e1ded7 100644 --- a/aiogram/types/successful_payment.py +++ b/aiogram/types/successful_payment.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .order_info import OrderInfo diff --git a/aiogram/types/update.py b/aiogram/types/update.py index 7b54195c..e567894f 100644 --- a/aiogram/types/update.py +++ b/aiogram/types/update.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Optional, cast from ..utils.mypy_hacks import lru_cache from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .callback_query import CallbackQuery from .chat_member_updated import ChatMemberUpdated from .chosen_inline_result import ChosenInlineResult diff --git a/aiogram/types/user_profile_photos.py b/aiogram/types/user_profile_photos.py index 8741bfd8..ad1197bf 100644 --- a/aiogram/types/user_profile_photos.py +++ b/aiogram/types/user_profile_photos.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .photo_size import PhotoSize diff --git a/aiogram/types/venue.py b/aiogram/types/venue.py index 8641ec0d..49caceff 100644 --- a/aiogram/types/venue.py +++ b/aiogram/types/venue.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .location import Location diff --git a/aiogram/types/video.py b/aiogram/types/video.py index 151abc58..de0a22c3 100644 --- a/aiogram/types/video.py +++ b/aiogram/types/video.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .photo_size import PhotoSize diff --git a/aiogram/types/video_note.py b/aiogram/types/video_note.py index 99f0dd47..6356bfdc 100644 --- a/aiogram/types/video_note.py +++ b/aiogram/types/video_note.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .photo_size import PhotoSize diff --git a/aiogram/types/voice_chat_participants_invited.py b/aiogram/types/voice_chat_participants_invited.py index 191b414f..b24ef91d 100644 --- a/aiogram/types/voice_chat_participants_invited.py +++ b/aiogram/types/voice_chat_participants_invited.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, List, Optional from .base import TelegramObject -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from .user import User diff --git a/aiogram/utils/exceptions/__init__.py b/aiogram/utils/__init__.py similarity index 100% rename from aiogram/utils/exceptions/__init__.py rename to aiogram/utils/__init__.py diff --git a/aiogram/utils/deprecated.py b/aiogram/utils/deprecated.py deleted file mode 100644 index 149440b4..00000000 --- a/aiogram/utils/deprecated.py +++ /dev/null @@ -1,140 +0,0 @@ -import asyncio -import functools -import inspect -import warnings -from typing import Any, Callable, Type - - -def deprecated(reason: str, stacklevel: int = 2) -> Callable[..., Any]: - """ - This is a decorator which can be used to mark functions - as deprecated. It will result in a warning being emitted - when the function is used. - - Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically - """ - - if isinstance(reason, str): - - # The @deprecated is used with a 'reason'. - # - # .. code-block:: python - # - # @deprecated("please, use another function") - # def old_function(x, y): - # pass - - def decorator(func: Callable[..., Any]) -> Callable[..., Any]: - - if inspect.isclass(func): - msg = "Call to deprecated class {name} ({reason})." - else: - msg = "Call to deprecated function {name} ({reason})." - - @functools.wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Any: - warn_deprecated( - msg.format(name=func.__name__, reason=reason), stacklevel=stacklevel - ) - warnings.simplefilter("default", DeprecationWarning) - return func(*args, **kwargs) - - return wrapper - - return decorator - - if inspect.isclass(reason) or inspect.isfunction(reason): - - # The @deprecated is used without any 'reason'. - # - # .. code-block:: python - # - # @deprecated - # def old_function(x, y): - # pass - - func1 = reason - - if inspect.isclass(func1): - msg1 = "Call to deprecated class {name}." - else: - msg1 = "Call to deprecated function {name}." - - @functools.wraps(func1) - def wrapper1(*args, **kwargs): - warn_deprecated(msg1.format(name=func1.__name__), stacklevel=stacklevel) - return func1(*args, **kwargs) - - return wrapper1 - - raise TypeError(repr(type(reason))) - - -def warn_deprecated( - message: str, warning: Type[Warning] = DeprecationWarning, stacklevel: int = 2 -) -> None: - warnings.simplefilter("always", warning) - warnings.warn(message, category=warning, stacklevel=stacklevel) - warnings.simplefilter("default", warning) - - -def renamed_argument( - old_name: str, new_name: str, until_version: str, stacklevel: int = 3 -) -> Callable[..., Any]: - """ - A meta-decorator to mark an argument as deprecated. - - .. code-block:: python3 - - @renamed_argument("chat", "chat_id", "3.0") # stacklevel=3 by default - @renamed_argument("user", "user_id", "3.0", stacklevel=4) - def some_function(user_id, chat_id=None): - print(f"user_id={user_id}, chat_id={chat_id}") - - some_function(user=123) # prints 'user_id=123, chat_id=None' with warning - some_function(123) # prints 'user_id=123, chat_id=None' without warning - some_function(user_id=123) # prints 'user_id=123, chat_id=None' without warning - - - :param old_name: - :param new_name: - :param until_version: the version in which the argument is scheduled to be removed - :param stacklevel: leave it to default if it's the first decorator used. - Increment with any new decorator used. - :return: decorator - """ - - def decorator(func: Callable[..., Any]) -> Callable[..., Any]: - if asyncio.iscoroutinefunction(func): - - @functools.wraps(func) - async def wrapped(*args: Any, **kwargs: Any) -> Any: - if old_name in kwargs: - warn_deprecated( - f"In coroutine '{func.__name__}' argument '{old_name}' " - f"is renamed to '{new_name}' " - f"and will be removed in aiogram {until_version}", - stacklevel=stacklevel, - ) - kwargs.update({new_name: kwargs[old_name]}) - kwargs.pop(old_name) - return await func(*args, **kwargs) - - else: - - @functools.wraps(func) - def wrapped(*args: Any, **kwargs: Any) -> Any: - if old_name in kwargs: - warn_deprecated( - f"In function `{func.__name__}` argument `{old_name}` " - f"is renamed to `{new_name}` " - f"and will be removed in aiogram {until_version}", - stacklevel=stacklevel, - ) - kwargs.update({new_name: kwargs[old_name]}) - kwargs.pop(old_name) - return func(*args, **kwargs) - - return wrapped - - return decorator diff --git a/aiogram/utils/exceptions/bad_request.py b/aiogram/utils/exceptions/bad_request.py deleted file mode 100644 index de1a0e2d..00000000 --- a/aiogram/utils/exceptions/bad_request.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.utils.exceptions.base import TelegramAPIError - - -class BadRequest(TelegramAPIError): - pass diff --git a/aiogram/utils/exceptions/base.py b/aiogram/utils/exceptions/base.py deleted file mode 100644 index fefd3db8..00000000 --- a/aiogram/utils/exceptions/base.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Optional, TypeVar - -from aiogram.methods import TelegramMethod -from aiogram.methods.base import TelegramType - -ErrorType = TypeVar("ErrorType") - - -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) diff --git a/aiogram/utils/exceptions/conflict.py b/aiogram/utils/exceptions/conflict.py deleted file mode 100644 index 965b328c..00000000 --- a/aiogram/utils/exceptions/conflict.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.utils.exceptions.base import TelegramAPIError - - -class ConflictError(TelegramAPIError): - pass diff --git a/aiogram/utils/exceptions/exceptions.py b/aiogram/utils/exceptions/exceptions.py deleted file mode 100644 index de1a0e2d..00000000 --- a/aiogram/utils/exceptions/exceptions.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.utils.exceptions.base import TelegramAPIError - - -class BadRequest(TelegramAPIError): - pass diff --git a/aiogram/utils/exceptions/network.py b/aiogram/utils/exceptions/network.py deleted file mode 100644 index b802ce69..00000000 --- a/aiogram/utils/exceptions/network.py +++ /dev/null @@ -1,9 +0,0 @@ -from aiogram.utils.exceptions.base import TelegramAPIError - - -class NetworkError(TelegramAPIError): - pass - - -class EntityTooLarge(NetworkError): - url = "https://core.telegram.org/bots/api#sending-files" diff --git a/aiogram/utils/exceptions/not_found.py b/aiogram/utils/exceptions/not_found.py deleted file mode 100644 index 6fa87a06..00000000 --- a/aiogram/utils/exceptions/not_found.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.utils.exceptions.base import TelegramAPIError - - -class NotFound(TelegramAPIError): - pass diff --git a/aiogram/utils/exceptions/server.py b/aiogram/utils/exceptions/server.py deleted file mode 100644 index c45c9f01..00000000 --- a/aiogram/utils/exceptions/server.py +++ /dev/null @@ -1,9 +0,0 @@ -from aiogram.utils.exceptions.base import TelegramAPIError - - -class ServerError(TelegramAPIError): - pass - - -class RestartingTelegram(ServerError): - pass diff --git a/aiogram/utils/exceptions/unauthorized.py b/aiogram/utils/exceptions/unauthorized.py deleted file mode 100644 index 789574a5..00000000 --- a/aiogram/utils/exceptions/unauthorized.py +++ /dev/null @@ -1,5 +0,0 @@ -from aiogram.utils.exceptions.base import TelegramAPIError - - -class UnauthorizedError(TelegramAPIError): - pass diff --git a/aiogram/utils/help/engine.py b/aiogram/utils/help/engine.py deleted file mode 100644 index c91c8899..00000000 --- a/aiogram/utils/help/engine.py +++ /dev/null @@ -1,48 +0,0 @@ -from abc import ABC, abstractmethod -from collections import Generator -from typing import Dict, List - -from aiogram.utils.help.record import CommandRecord - - -class BaseHelpBackend(ABC): - @abstractmethod - def add(self, record: CommandRecord) -> None: - pass - - @abstractmethod - def search(self, value: str) -> CommandRecord: - pass - - @abstractmethod - def all(self) -> Generator[CommandRecord, None, None]: - pass - - def __getitem__(self, item: str) -> CommandRecord: - return self.search(item) - - def __iter__(self) -> Generator[CommandRecord, None, None]: - return self.all() - - -class MappingBackend(BaseHelpBackend): - def __init__(self, search_empty_prefix: bool = True) -> None: - self._records: List[CommandRecord] = [] - self._mapping: Dict[str, CommandRecord] = {} - self.search_empty_prefix = search_empty_prefix - - def search(self, value: str) -> CommandRecord: - return self._mapping[value] - - def add(self, record: CommandRecord) -> None: - new_records = {} - for key in record.as_keys(with_empty_prefix=self.search_empty_prefix): - if key in self._mapping: - raise ValueError(f"Key '{key}' is already indexed") - new_records[key] = record - self._mapping.update(new_records) - self._records.append(record) - self._records.sort(key=lambda rec: (rec.priority, rec.commands[0])) - - def all(self) -> Generator[CommandRecord, None, None]: - yield from self._records diff --git a/aiogram/utils/help/manager.py b/aiogram/utils/help/manager.py deleted file mode 100644 index b9aefb0e..00000000 --- a/aiogram/utils/help/manager.py +++ /dev/null @@ -1,113 +0,0 @@ -from typing import Any, Optional, Tuple - -from aiogram import Bot, Router -from aiogram.dispatcher.filters import Command, CommandObject -from aiogram.types import BotCommand, Message -from aiogram.utils.help.engine import BaseHelpBackend, MappingBackend -from aiogram.utils.help.record import DEFAULT_PREFIXES, CommandRecord -from aiogram.utils.help.render import BaseHelpRenderer, SimpleRenderer - - -class HelpManager: - def __init__( - self, - backend: Optional[BaseHelpBackend] = None, - renderer: Optional[BaseHelpRenderer] = None, - ) -> None: - if backend is None: - backend = MappingBackend() - if renderer is None: - renderer = SimpleRenderer() - self._backend = backend - self._renderer = renderer - - def add( - self, - *commands: str, - help: str, - description: Optional[str] = None, - prefix: str = DEFAULT_PREFIXES, - ignore_case: bool = False, - ignore_mention: bool = False, - priority: int = 0, - ) -> CommandRecord: - record = CommandRecord( - commands=commands, - help=help, - description=description, - prefix=prefix, - ignore_case=ignore_case, - ignore_mention=ignore_mention, - priority=priority, - ) - self._backend.add(record) - return record - - def command( - self, - *commands: str, - help: str, - description: Optional[str] = None, - prefix: str = DEFAULT_PREFIXES, - ignore_case: bool = False, - ignore_mention: bool = False, - priority: int = 0, - ) -> Command: - record = self.add( - *commands, - help=help, - description=description, - prefix=prefix, - ignore_case=ignore_case, - ignore_mention=ignore_mention, - priority=priority, - ) - return record.as_filter() - - def mount_help( - self, - router: Router, - *commands: str, - prefix: str = "/", - help: str = "Help", - description: str = "Show help for the commands\n" - "Also you can use '/help command' for get help for specific command", - as_reply: bool = True, - filters: Tuple[Any, ...] = (), - **kw_filters: Any, - ) -> Any: - if not commands: - commands = ("help",) - help_filter = self.command(*commands, prefix=prefix, help=help, description=description) - - async def handle(message: Message, command: CommandObject, **kwargs: Any) -> Any: - return await self._handle_help( - message=message, command=command, as_reply=as_reply, **kwargs - ) - - return router.message.register(handle, help_filter, *filters, **kw_filters) - - async def _handle_help( - self, - message: Message, - bot: Bot, - command: CommandObject, - as_reply: bool = True, - **kwargs: Any, - ) -> Any: - lines = self._renderer.render(backend=self._backend, command=command, **kwargs) - text = "\n".join(line or "" for line in lines) - return await bot.send_message( - chat_id=message.chat.id, - text=text, - reply_to_message_id=message.message_id if as_reply else None, - ) - - async def set_bot_commands(self, bot: Bot) -> bool: - return await bot.set_my_commands( - commands=[ - BotCommand(command=record.commands[0], description=record.help) - for record in self._backend - if "/" in record.prefix - ] - ) diff --git a/aiogram/utils/help/record.py b/aiogram/utils/help/record.py deleted file mode 100644 index beb00468..00000000 --- a/aiogram/utils/help/record.py +++ /dev/null @@ -1,33 +0,0 @@ -from dataclasses import dataclass -from itertools import product -from typing import Generator, Optional, Sequence - -from aiogram.dispatcher.filters import Command - -DEFAULT_PREFIXES = "/" - - -@dataclass -class CommandRecord: - commands: Sequence[str] - help: str - description: Optional[str] = None - prefix: str = DEFAULT_PREFIXES - ignore_case: bool = False - ignore_mention: bool = False - priority: int = 0 - - def as_filter(self) -> Command: - return Command(commands=self.commands, commands_prefix=self.prefix) - - def as_keys(self, with_empty_prefix: bool = False) -> Generator[str, None, None]: - for command in self.commands: - yield command - for prefix in self.prefix: - yield f"{prefix}{command}" - - def as_command(self) -> str: - return f"{self.prefix[0]}{self.commands[0]}" - - def as_aliases(self) -> str: - return ", ".join(f"{p}{c}" for c, p in product(self.commands, self.prefix)) diff --git a/aiogram/utils/help/render.py b/aiogram/utils/help/render.py deleted file mode 100644 index 8b15d0af..00000000 --- a/aiogram/utils/help/render.py +++ /dev/null @@ -1,64 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Any, Generator, Optional - -from aiogram.dispatcher.filters import CommandObject -from aiogram.utils.help.engine import BaseHelpBackend - - -class BaseHelpRenderer(ABC): - @abstractmethod - def render( - self, backend: BaseHelpBackend, command: CommandObject, **kwargs: Any - ) -> Generator[Optional[str], None, None]: - pass - - -class SimpleRenderer(BaseHelpRenderer): - def __init__( - self, - help_title: str = "Commands list:", - help_footer: str = "", - aliases_line: str = "Aliases", - command_title: str = "Help for command:", - unknown_command: str = "Command not found", - ): - self.help_title = help_title - self.help_footer = help_footer - self.aliases_line = aliases_line - self.command_title = command_title - self.unknown_command = unknown_command - - def render_help(self, backend: BaseHelpBackend) -> Generator[Optional[str], None, None]: - yield self.help_title - - for command in backend: - yield f"{command.prefix[0]}{command.commands[0]} - {command.help}" - - if self.help_footer: - yield None - yield self.help_footer - - def render_command_help( - self, backend: BaseHelpBackend, target: str - ) -> Generator[Optional[str], None, None]: - try: - record = backend[target] - except KeyError: - yield f"{self.command_title} {target}" - yield self.unknown_command - return - - yield f"{self.command_title} {record.as_command()}" - if len(record.commands) > 1 or len(record.prefix) > 1: - yield f"{self.aliases_line}: {record.as_aliases()}" - yield record.help - yield None - yield record.description - - def render( - self, backend: BaseHelpBackend, command: CommandObject, **kwargs: Any - ) -> Generator[Optional[str], None, None]: - if command.args: - yield from self.render_command_help(backend=backend, target=command.args) - else: - yield from self.render_help(backend=backend) diff --git a/aiogram/utils/helper.py b/aiogram/utils/helper.py index 3ceff6aa..9b4cd039 100644 --- a/aiogram/utils/helper.py +++ b/aiogram/utils/helper.py @@ -216,8 +216,7 @@ class OrderedHelperMeta(type): setattr(cls, PROPS_KEYS_ATTR_NAME, props_keys) - # ref: https://gitter.im/python/typing?at=5da98cc5fa637359fc9cbfe1 - return cast(OrderedHelperMeta, cls) + return cls class OrderedHelper(Helper, metaclass=OrderedHelperMeta): diff --git a/aiogram/utils/i18n/__init__.py b/aiogram/utils/i18n/__init__.py new file mode 100644 index 00000000..e48a4c7f --- /dev/null +++ b/aiogram/utils/i18n/__init__.py @@ -0,0 +1,21 @@ +from .context import get_i18n, gettext, lazy_gettext, lazy_ngettext, ngettext +from .core import I18n +from .middleware import ( + ConstI18nMiddleware, + FSMI18nMiddleware, + I18nMiddleware, + SimpleI18nMiddleware, +) + +__all__ = ( + "I18n", + "I18nMiddleware", + "SimpleI18nMiddleware", + "ConstI18nMiddleware", + "FSMI18nMiddleware", + "gettext", + "lazy_gettext", + "ngettext", + "lazy_ngettext", + "get_i18n", +) diff --git a/aiogram/utils/i18n/context.py b/aiogram/utils/i18n/context.py new file mode 100644 index 00000000..7060a7a9 --- /dev/null +++ b/aiogram/utils/i18n/context.py @@ -0,0 +1,26 @@ +from contextvars import ContextVar +from typing import Any, Optional + +from aiogram.utils.i18n.core import I18n +from aiogram.utils.i18n.lazy_proxy import LazyProxy + +ctx_i18n: ContextVar[Optional[I18n]] = ContextVar("aiogram_ctx_i18n", default=None) + + +def get_i18n() -> I18n: + i18n = ctx_i18n.get() + if i18n is None: + raise LookupError("I18n context is not set") + return i18n + + +def gettext(*args: Any, **kwargs: Any) -> str: + return get_i18n().gettext(*args, **kwargs) + + +def lazy_gettext(*args: Any, **kwargs: Any) -> LazyProxy: + return LazyProxy(gettext, *args, **kwargs) + + +ngettext = gettext +lazy_ngettext = lazy_gettext diff --git a/aiogram/utils/i18n/core.py b/aiogram/utils/i18n/core.py new file mode 100644 index 00000000..d234ffea --- /dev/null +++ b/aiogram/utils/i18n/core.py @@ -0,0 +1,97 @@ +import gettext +import os +from contextvars import ContextVar +from pathlib import Path +from typing import Dict, Optional, Tuple, Union + +from aiogram.utils.i18n.lazy_proxy import LazyProxy + + +class I18n: + def __init__( + self, + *, + path: Union[str, Path], + locale: str = "en", + domain: str = "messages", + ) -> None: + self.path = path + self.locale = locale + self.domain = domain + self.ctx_locale = ContextVar("aiogram_ctx_locale", default=locale) + self.locales = self.find_locales() + + @property + def current_locale(self) -> str: + return self.ctx_locale.get() + + @current_locale.setter + def current_locale(self, value: str) -> None: + self.ctx_locale.set(value) + + def find_locales(self) -> Dict[str, gettext.GNUTranslations]: + """ + Load all compiled locales from path + + :return: dict with locales + """ + translations: Dict[str, gettext.GNUTranslations] = {} + + for name in os.listdir(self.path): + if not os.path.isdir(os.path.join(self.path, name)): + continue + mo_path = os.path.join(self.path, name, "LC_MESSAGES", self.domain + ".mo") + + if os.path.exists(mo_path): + with open(mo_path, "rb") as fp: + translations[name] = gettext.GNUTranslations(fp) # type: ignore + elif os.path.exists(mo_path[:-2] + "po"): # pragma: no cover + raise RuntimeError(f"Found locale '{name}' but this language is not compiled!") + + return translations + + def reload(self) -> None: + """ + Hot reload locales + """ + self.locales = self.find_locales() + + @property + def available_locales(self) -> Tuple[str, ...]: + """ + list of loaded locales + + :return: + """ + return tuple(self.locales.keys()) + + def gettext( + self, singular: str, plural: Optional[str] = None, n: int = 1, locale: Optional[str] = None + ) -> str: + """ + Get text + + :param singular: + :param plural: + :param n: + :param locale: + :return: + """ + if locale is None: + locale = self.current_locale + + if locale not in self.locales: + if n == 1: + return singular + return plural if plural else singular + + translator = self.locales[locale] + + if plural is None: + return translator.gettext(singular) + return translator.ngettext(singular, plural, n) + + def lazy_gettext( + self, singular: str, plural: Optional[str] = None, n: int = 1, locale: Optional[str] = None + ) -> LazyProxy: + return LazyProxy(self.gettext, singular=singular, plural=plural, n=n, locale=locale) diff --git a/aiogram/utils/i18n/lazy_proxy.py b/aiogram/utils/i18n/lazy_proxy.py new file mode 100644 index 00000000..6852540d --- /dev/null +++ b/aiogram/utils/i18n/lazy_proxy.py @@ -0,0 +1,13 @@ +from typing import Any + +try: + from babel.support import LazyProxy +except ImportError: # pragma: no cover + + class LazyProxy: # type: ignore + def __init__(self, func: Any, *args: Any, **kwargs: Any) -> None: + raise RuntimeError( + "LazyProxy can be used only when Babel installed\n" + "Just install Babel (`pip install Babel`) " + "or aiogram with i18n support (`pip install aiogram[i18n]`)" + ) diff --git a/aiogram/utils/i18n/middleware.py b/aiogram/utils/i18n/middleware.py new file mode 100644 index 00000000..3c810256 --- /dev/null +++ b/aiogram/utils/i18n/middleware.py @@ -0,0 +1,182 @@ +from abc import ABC, abstractmethod +from typing import Any, Awaitable, Callable, Dict, Optional, Set, cast + +try: + from babel import Locale +except ImportError: # pragma: no cover + Locale = None + +from aiogram import BaseMiddleware, Router +from aiogram.dispatcher.fsm.context import FSMContext +from aiogram.types import TelegramObject, User +from aiogram.utils.i18n.context import ctx_i18n +from aiogram.utils.i18n.core import I18n + + +class I18nMiddleware(BaseMiddleware, ABC): + """ + Abstract I18n middleware. + """ + + def __init__( + self, + i18n: I18n, + i18n_key: Optional[str] = "i18n", + middleware_key: str = "i18n_middleware", + ) -> None: + """ + Create an instance of middleware + + :param i18n: instance of I18n + :param i18n_key: context key for I18n instance + :param middleware_key: context key for this middleware + """ + self.i18n = i18n + self.i18n_key = i18n_key + self.middleware_key = middleware_key + + def setup( + self: BaseMiddleware, router: Router, exclude: Optional[Set[str]] = None + ) -> BaseMiddleware: + """ + Register middleware for all events in the Router + + :param router: + :param exclude: + :return: + """ + if exclude is None: + exclude = set() + exclude_events = {"update", "error", *exclude} + for event_name, observer in router.observers.items(): + if event_name in exclude_events: + continue + observer.outer_middleware(self) + return self + + async def __call__( + self, + handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: Dict[str, Any], + ) -> Any: + self.i18n.current_locale = await self.get_locale(event=event, data=data) + + if self.i18n_key: + data[self.i18n_key] = self.i18n + if self.middleware_key: + data[self.middleware_key] = self + token = ctx_i18n.set(self.i18n) + try: + return await handler(event, data) + finally: + ctx_i18n.reset(token) + + @abstractmethod + async def get_locale(self, event: TelegramObject, data: Dict[str, Any]) -> str: + """ + Detect current user locale based on event and context. + + **This method must be defined in child classes** + + :param event: + :param data: + :return: + """ + pass + + +class SimpleI18nMiddleware(I18nMiddleware): + """ + Simple I18n middleware. + + Chooses language code from the User object received in event + """ + + def __init__( + self, + i18n: I18n, + i18n_key: Optional[str] = "i18n", + middleware_key: str = "i18n_middleware", + ) -> None: + super().__init__(i18n=i18n, i18n_key=i18n_key, middleware_key=middleware_key) + + if Locale is None: # pragma: no cover + raise RuntimeError( + f"{type(self).__name__} can be used only when Babel installed\n" + "Just install Babel (`pip install Babel`) " + "or aiogram with i18n support (`pip install aiogram[i18n]`)" + ) + + async def get_locale(self, event: TelegramObject, data: Dict[str, Any]) -> str: + if Locale is None: # pragma: no cover + raise RuntimeError( + f"{type(self).__name__} can be used only when Babel installed\n" + "Just install Babel (`pip install Babel`) " + "or aiogram with i18n support (`pip install aiogram[i18n]`)" + ) + + event_from_user: Optional[User] = data.get("event_from_user", None) + if event_from_user is None: + return self.i18n.locale + locale = Locale.parse(event_from_user.language_code, sep="-") + if locale.language not in self.i18n.available_locales: + return self.i18n.locale + return cast(str, locale.language) + + +class ConstI18nMiddleware(I18nMiddleware): + """ + Const middleware chooses statically defined locale + """ + + def __init__( + self, + locale: str, + i18n: I18n, + i18n_key: Optional[str] = "i18n", + middleware_key: str = "i18n_middleware", + ) -> None: + super().__init__(i18n=i18n, i18n_key=i18n_key, middleware_key=middleware_key) + self.locale = locale + + async def get_locale(self, event: TelegramObject, data: Dict[str, Any]) -> str: + return self.locale + + +class FSMI18nMiddleware(SimpleI18nMiddleware): + """ + This middleware stores locale in the FSM storage + """ + + def __init__( + self, + i18n: I18n, + key: str = "locale", + i18n_key: Optional[str] = "i18n", + middleware_key: str = "i18n_middleware", + ) -> None: + super().__init__(i18n=i18n, i18n_key=i18n_key, middleware_key=middleware_key) + self.key = key + + async def get_locale(self, event: TelegramObject, data: Dict[str, Any]) -> str: + fsm_context: Optional[FSMContext] = data.get("state") + locale = None + if fsm_context: + fsm_data = await fsm_context.get_data() + locale = fsm_data.get(self.key, None) + if not locale: + locale = await super().get_locale(event=event, data=data) + if fsm_context: + await fsm_context.update_data(data={self.key: locale}) + return locale + + async def set_locale(self, state: FSMContext, locale: str) -> None: + """ + Write new locale to the storage + + :param state: instance of FSMContext + :param locale: new locale + """ + await state.update_data(data={self.key: locale}) + self.i18n.current_locale = locale diff --git a/aiogram/utils/keyboard.py b/aiogram/utils/keyboard.py index 56c5f1ff..34d660b0 100644 --- a/aiogram/utils/keyboard.py +++ b/aiogram/utils/keyboard.py @@ -1,5 +1,6 @@ from __future__ import annotations +from copy import deepcopy from itertools import chain from itertools import cycle as repeat_all from typing import ( @@ -149,7 +150,7 @@ class KeyboardBuilder(Generic[ButtonType]): :return: """ - return self._markup.copy() + return deepcopy(self._markup) def add(self, *buttons: ButtonType) -> "KeyboardBuilder[ButtonType]": """ @@ -241,7 +242,8 @@ def repeat_last(items: Iterable[T]) -> Generator[T, None, None]: items_iter = iter(items) try: value = next(items_iter) - except StopIteration: + except StopIteration: # pragma: no cover + # Possible case but not in place where this function is used return yield value finished = False @@ -255,7 +257,7 @@ def repeat_last(items: Iterable[T]) -> Generator[T, None, None]: class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]): - if TYPE_CHECKING: # pragma: no cover + if TYPE_CHECKING: @no_type_check def button( @@ -275,12 +277,20 @@ class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]): def as_markup(self, **kwargs: Any) -> InlineKeyboardMarkup: ... - def __init__(self) -> None: - super().__init__(InlineKeyboardButton) + def __init__(self, markup: Optional[List[List[InlineKeyboardButton]]] = None) -> None: + super().__init__(button_type=InlineKeyboardButton, markup=markup) + + def copy(self: "InlineKeyboardBuilder") -> "InlineKeyboardBuilder": + """ + Make full copy of current builder with markup + + :return: + """ + return InlineKeyboardBuilder(markup=self.export()) class ReplyKeyboardBuilder(KeyboardBuilder[KeyboardButton]): - if TYPE_CHECKING: # pragma: no cover + if TYPE_CHECKING: @no_type_check def button( @@ -296,5 +306,13 @@ class ReplyKeyboardBuilder(KeyboardBuilder[KeyboardButton]): def as_markup(self, **kwargs: Any) -> ReplyKeyboardMarkup: ... - def __init__(self) -> None: - super().__init__(KeyboardButton) + def __init__(self, markup: Optional[List[List[KeyboardButton]]] = None) -> None: + super().__init__(button_type=KeyboardButton, markup=markup) + + def copy(self: "ReplyKeyboardBuilder") -> "ReplyKeyboardBuilder": + """ + Make full copy of current builder with markup + + :return: + """ + return ReplyKeyboardBuilder(markup=self.export()) diff --git a/aiogram/utils/mixins.py b/aiogram/utils/mixins.py index 156339d6..80f5afe9 100644 --- a/aiogram/utils/mixins.py +++ b/aiogram/utils/mixins.py @@ -3,7 +3,7 @@ from __future__ import annotations import contextvars from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generic, Optional, TypeVar, cast, overload -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from typing_extensions import Literal __all__ = ("ContextInstanceMixin", "DataMixin") diff --git a/aiogram/utils/text_decorations.py b/aiogram/utils/text_decorations.py index 23c9c2a7..bd45fae8 100644 --- a/aiogram/utils/text_decorations.py +++ b/aiogram/utils/text_decorations.py @@ -5,7 +5,7 @@ import re from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Generator, List, Optional, Pattern, cast -if TYPE_CHECKING: # pragma: no cover +if TYPE_CHECKING: from aiogram.types import MessageEntity __all__ = ( diff --git a/docs/_static/stylesheets/extra.css b/docs/_static/stylesheets/extra.css index 5b653b0c..b35d291c 100644 --- a/docs/_static/stylesheets/extra.css +++ b/docs/_static/stylesheets/extra.css @@ -10,13 +10,3 @@ code, kbd, pre { font-family: "JetBrainsMono", "Roboto Mono", "Courier New", Courier, monospace; } - -.highlight * { - background: #f0f0f0; -} - -@media (prefers-color-scheme: dark) { - .highlight * { - background: #424242; - } -} diff --git a/docs/index.rst b/docs/index.rst index 0016a02e..ffd29dbb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -93,4 +93,5 @@ Contents install api/index dispatcher/index + utils/index changelog diff --git a/docs/utils/i18n.rst b/docs/utils/i18n.rst new file mode 100644 index 00000000..2c36c85d --- /dev/null +++ b/docs/utils/i18n.rst @@ -0,0 +1,190 @@ +=========== +Translation +=========== + +In order to make you bot translatable you have to add a minimal number of hooks to your Python code. + +These hooks are called translation strings. + +The aiogram translation utils is build on top of `GNU gettext Python module `_ +and `Babel library `_. + +Installation +============ + +Babel is required to make simple way to extract translation strings from your code + +Can be installed from pip directly: + +.. code-block:: bash + + pip install Babel + + +or as `aiogram` extra dependency: + +.. code-block:: bash + + pip install aiogram[i18n] + + +Make messages translatable +========================== + +In order to gettext need to know what the strings should be translated you will need to write translation strings. + +For example: + +.. code-block:: python + :emphasize-lines: 6-8 + + from aiogram import html + from aiogram.utils.i18n import gettext as _ + + async def my_handler(message: Message) -> None: + await message.answer( + _("Hello, {name}!").format( + name=html.quote(message.from_user.full_name) + ) + ) + + +.. danger:: + + f-strings can't be used as translations string because any dynamic variables should be added to message after getting translated message + + +Also if you want to use translated string in keyword- or magic- filters you will need to use lazy gettext calls: + + +.. code-block:: python + :emphasize-lines: 4 + + from aiogram import F + from aiogram.utils.i18n import lazy_gettext as __ + + @router.message(F.text.lower() == __("My menu entry")) + ... + + +.. danger:: + + Lazy gettext calls should always be used when the current language is not know at the moment + + +.. danger:: + + Lazy gettext can't be used as value for API methods or any Telegram Object (like :class:`aiogram.types.inline_keyboard_button.InlineKeyboardButton` or etc.) + +Configuring engine +================== + +After you messages is already done to use gettext your bot should know how to detect user language + +On top of your application the instance of :class:`aiogram.utils.i18n.code.I18n` should be created + + +.. code-block:: + + i18n = I18n(path="locales", language="en", domain="messages") + + +After that you will need to choose one of builtin I18n middleware or write your own. + +Builtin middlewares: + + +SimpleI18nMiddleware +~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: aiogram.utils.i18n.middleware.SimpleI18nMiddleware + :members: __init__ + +ConstI18nMiddleware +~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: aiogram.utils.i18n.middleware.ConstI18nMiddleware + :members: __init__ + +FSMI18nMiddleware +~~~~~~~~~~~~~~~~~ + +.. autoclass:: aiogram.utils.i18n.middleware.FSMI18nMiddleware + :members: __init__,set_locale + + +I18nMiddleware +~~~~~~~~~~~~~~ + +or define you own based on abstract I18nMiddleware middleware: + +.. autoclass:: aiogram.utils.i18n.middleware.FSMI18nMiddleware + :members: __init__,setup,get_locale + + +Deal with Babel +=============== + +Step 1: Extract messages +------------------- + +.. code-block:: bash + + pybabel extract --input-dirs=. -o locales/messages.pot + + +Here is :code:`--input-dirs=.` - path to code and the :code:`locales/messages.pot` +is template where messages will be extracted and `messages` is translation domain. + +.. note:: + + Some useful options: + + - Extract texts with pluralization support :code:`-k __:1,2` + - Add comments for translators, you can use another tag if you want (TR) :code:`--add-comments=NOTE` + - Disable comments with string location in code :code:`--no-location` + - Set project name :code:`--project=MySuperBot` + - Set version :code:`--version=2.2` + + +Step 2: Init language +---------------- + +.. code-block:: bash + + pybabel init -i locales/messages.pot -d locales -D messages -l en + +- :code:`-i locales/messages.pot` - pre-generated template +- :code:`-d locales` - translations directory +- :code:`-D messages` - translations domain +- :code:`-l en` - language. Can be changed to any other valid language code (For example :code:`-l uk` for ukrainian language) + + +Step 3: Translate texts +----------------------- + +To open .po file you can use basic text editor or any PO editor, e.g. `Poedit `_ + +Just open the file named :code:`locales/{language}/LC_MESSAGES/messages.po` and write translations + +Step 4: Compile translations +---------------------------- + +.. code-block:: bash + + pybabel compile -d locales -D messages + + +Step 5: Updating messages +------------------------- + +When you change the code of your bot you need to update po & mo files + +- Step 5.1: regenerate pot file: command from step 1 +- Step 5.2: update po files + .. code-block:: + + pybabel update -d locales -D messages -i locales/messages.pot + +- Step 5.3: update your translations: location and tools you know from step 3 +- Step 5.4: compile mo files: command from step 4 diff --git a/docs/utils/index.rst b/docs/utils/index.rst new file mode 100644 index 00000000..424ac0d9 --- /dev/null +++ b/docs/utils/index.rst @@ -0,0 +1,8 @@ +===== +Utils +===== + +.. toctree:: + + i18n + keyboard diff --git a/docs/utils/keyboard.rst b/docs/utils/keyboard.rst new file mode 100644 index 00000000..98681e9f --- /dev/null +++ b/docs/utils/keyboard.rst @@ -0,0 +1,24 @@ +================ +Keyboard builder +================ + +Inline Keyboard +=============== + +.. autoclass:: aiogram.utils.keyboard.InlineKeyboardBuilder + :members: __init__, buttons, copy, export, add, row, adjust, button, as_markup + :undoc-members: True + +Reply Keyboard +============== + +.. autoclass:: aiogram.utils.keyboard.ReplyKeyboardBuilder + :members: __init__, buttons, copy, export, add, row, adjust, button, as_markup + :undoc-members: True + + +Base builder +============ +.. autoclass:: aiogram.utils.keyboard.ReplyKeyboardBuilder + :members: __init__, buttons, copy, export, add, row, adjust, button, as_markup + :undoc-members: True diff --git a/mypy.ini b/mypy.ini index a75c96cb..0d85fa48 100644 --- a/mypy.ini +++ b/mypy.ini @@ -32,3 +32,6 @@ ignore_missing_imports = True [mypy-aioredis] ignore_missing_imports = True + +[mypy-babel.*] +ignore_missing_imports = True diff --git a/poetry.lock b/poetry.lock index fa16765f..b4ef5c38 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,10 @@ [[package]] name = "aiofiles" -version = "0.6.0" +version = "0.7.0" description = "File support for asyncio." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6,<4.0" [[package]] name = "aiohttp" @@ -30,7 +30,7 @@ name = "aiohttp-socks" version = "0.5.5" description = "Proxy connector for aiohttp" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -43,7 +43,7 @@ name = "aioredis" version = "2.0.0" description = "asyncio (PEP 3156) Redis support" category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.dependencies] @@ -58,15 +58,7 @@ name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false +optional = true python-versions = "*" [[package]] @@ -140,7 +132,7 @@ name = "babel" version = "2.9.1" description = "Internationalization utilities" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] @@ -154,16 +146,28 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "backports.entry-points-selectable" +version = "1.1.0" +description = "Compatibility shim providing selectable entry points for older implementations" +category = "dev" +optional = false +python-versions = ">=2.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=4.6)", "pytest-flake8", "pytest-cov", "pytest-black (>=0.3.7)", "pytest-mypy", "pytest-checkdocs (>=2.4)", "pytest-enabler (>=1.0.1)"] + [[package]] name = "beautifulsoup4" -version = "4.9.3" +version = "4.10.0" description = "Screen-scraping library" category = "main" -optional = false -python-versions = "*" +optional = true +python-versions = ">3.0.0" [package.dependencies] -soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""} +soupsieve = ">1.2" [package.extras] html5lib = ["html5lib"] @@ -171,29 +175,42 @@ lxml = ["lxml"] [[package]] name = "black" -version = "21.6b0" +version = "21.9b0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" click = ">=7.1.2" mypy-extensions = ">=0.4.3" -pathspec = ">=0.8.1,<1" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" regex = ">=2020.1.8" -toml = ">=0.10.1" +tomli = ">=0.2.6,<2.0.0" +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] python2 = ["typed-ast (>=1.4.2)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "certifi" +version = "2021.5.30" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = true +python-versions = "*" + [[package]] name = "cfgv" -version = "3.3.0" +version = "3.3.1" description = "Validate configuration and produce human readable error messages." category = "dev" optional = false @@ -207,6 +224,17 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "charset-normalizer" +version = "2.0.6" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = true +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + [[package]] name = "click" version = "8.0.1" @@ -222,8 +250,8 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "click-default-group" version = "1.2.2" description = "Extends click.Group to invoke a command without explicit subcommand name" -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" [package.dependencies] @@ -250,24 +278,12 @@ toml = ["toml"] [[package]] name = "decorator" -version = "5.0.9" +version = "5.1.0" description = "Decorators for Humans" category = "dev" optional = false python-versions = ">=3.5" -[[package]] -name = "diagrams" -version = "0.20.0" -description = "Diagram as Code" -category = "dev" -optional = false -python-versions = ">=3.6,<4.0" - -[package.dependencies] -graphviz = ">=0.13.2,<0.17.0" -jinja2 = ">=2.10,<3.0" - [[package]] name = "distlib" version = "0.3.2" @@ -281,7 +297,7 @@ name = "docutils" version = "0.17.1" description = "Docutils -- Python Documentation Utilities" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] @@ -321,36 +337,23 @@ pygments = ">=2.2.0" [[package]] name = "furo" -version = "2021.6.18b36" +version = "2021.9.8" description = "A clean customisable Sphinx documentation theme." category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.dependencies] beautifulsoup4 = "*" -sphinx = ">=3.0,<5.0" +sphinx = ">=4.0,<5.0" [package.extras] -doc = ["myst-parser", "sphinx-copybutton", "sphinx-inline-tabs", "docutils (!=0.17)"] +doc = ["myst-parser", "sphinx-copybutton", "sphinx-design", "sphinx-inline-tabs"] test = ["pytest", "pytest-cov", "pytest-xdist"] -[[package]] -name = "graphviz" -version = "0.16" -description = "Simple Python interface for Graphviz" -category = "dev" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*" - -[package.extras] -dev = ["tox (>=3)", "flake8", "pep8-naming", "wheel", "twine"] -docs = ["sphinx (>=1.8)", "sphinx-rtd-theme"] -test = ["mock (>=3)", "pytest (>=4)", "pytest-mock (>=2)", "pytest-cov"] - [[package]] name = "identify" -version = "2.2.10" +version = "2.2.15" description = "File identification library for Python" category = "dev" optional = false @@ -372,12 +375,12 @@ name = "imagesize" version = "1.2.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.5.0" +version = "4.8.1" description = "Read metadata from Python packages" category = "dev" optional = false @@ -388,14 +391,15 @@ zipp = ">=0.5" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +perf = ["ipython"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] name = "incremental" version = "21.3.0" description = "A small library that versions your Python projects." -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" [package.extras] @@ -411,7 +415,7 @@ python-versions = "*" [[package]] name = "ipython" -version = "7.24.1" +version = "7.27.0" description = "IPython: Productive Interactive Computing" category = "dev" optional = false @@ -441,26 +445,19 @@ parallel = ["ipyparallel"] qtconsole = ["qtconsole"] test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] -[[package]] -name = "ipython-genutils" -version = "0.2.0" -description = "Vestigial utilities from IPython" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "isort" -version = "5.8.0" +version = "5.9.3" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6,<4.0" +python-versions = ">=3.6.1,<4.0" [package.extras] pipfile_deprecated_finder = ["pipreqs", "requirementslib"] requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] [[package]] name = "jedi" @@ -479,24 +476,24 @@ testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] [[package]] name = "jinja2" -version = "2.11.3" +version = "3.0.1" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -MarkupSafe = ">=0.23" +MarkupSafe = ">=2.0" [package.extras] -i18n = ["Babel (>=0.8)"] +i18n = ["Babel (>=2.7)"] [[package]] name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -505,7 +502,7 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "magic-filter" -version = "1.0.0" +version = "1.0.2" description = "This package provides magic filter based on dynamic attribute getter" category = "main" optional = false @@ -515,8 +512,8 @@ python-versions = ">=3.6.2,<4.0.0" name = "markdown" version = "3.3.4" description = "Python implementation of Markdown." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [package.extras] @@ -526,8 +523,8 @@ testing = ["coverage", "pyyaml"] name = "markdown-include" version = "0.6.0" description = "This is an extension to Python-Markdown which provides an \"include\" function, similar to that found in LaTeX (and also the C pre-processor and Fortran). I originally wrote it for my FORD Fortran auto-documentation generator." -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" [package.dependencies] @@ -543,7 +540,7 @@ python-versions = ">=3.6" [[package]] name = "matplotlib-inline" -version = "0.1.2" +version = "0.1.3" description = "Inline Matplotlib backend for Jupyter" category = "dev" optional = false @@ -570,7 +567,7 @@ python-versions = ">=3.6" [[package]] name = "mypy" -version = "0.812" +version = "0.910" description = "Optional static typing for Python" category = "dev" optional = false @@ -578,11 +575,12 @@ python-versions = ">=3.5" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" -typed-ast = ">=1.4.0,<1.5.0" +toml = "*" typing-extensions = ">=3.7.4" [package.extras] dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<1.5.0)"] [[package]] name = "mypy-extensions" @@ -625,11 +623,11 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.8.1" +version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pexpect" @@ -650,16 +648,29 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "platformdirs" +version = "2.3.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.extras] dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" @@ -679,11 +690,11 @@ virtualenv = ">=20.0.8" [[package]] name = "prompt-toolkit" -version = "3.0.18" +version = "3.0.20" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.6.2" [package.dependencies] wcwidth = "*" @@ -737,7 +748,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.9.0" +version = "2.10.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -747,8 +758,8 @@ python-versions = ">=3.5" name = "pymdown-extensions" version = "8.2" description = "Extension pack for Python Markdown." -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.6" [package.dependencies] @@ -764,7 +775,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -776,7 +787,7 @@ attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -883,7 +894,7 @@ name = "python-socks" version = "1.2.4" description = "Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -899,7 +910,7 @@ name = "pytz" version = "2021.1" description = "World timezone definitions, modern and historical" category = "main" -optional = false +optional = true python-versions = "*" [[package]] @@ -912,7 +923,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "regex" -version = "2021.4.4" +version = "2021.8.28" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -920,15 +931,21 @@ python-versions = "*" [[package]] name = "requests" -version = "2.15.1" +version = "2.26.0" description = "Python HTTP for Humans." category = "main" -optional = false -python-versions = "*" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "six" @@ -943,7 +960,7 @@ name = "snowballstemmer" version = "2.1.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "main" -optional = false +optional = true python-versions = "*" [[package]] @@ -951,22 +968,22 @@ name = "soupsieve" version = "2.2.1" description = "A modern CSS selector implementation for Beautiful Soup." category = "main" -optional = false +optional = true python-versions = ">=3.6" [[package]] name = "sphinx" -version = "3.5.3" +version = "4.2.0" description = "Python documentation generator" category = "main" -optional = false -python-versions = ">=3.5" +optional = true +python-versions = ">=3.6" [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.12" +docutils = ">=0.14,<0.18" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" @@ -975,25 +992,26 @@ requests = ">=2.5.0" snowballstemmer = ">=1.1" sphinxcontrib-applehelp = "*" sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.800)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.900)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] name = "sphinx-autobuild" -version = "2020.9.1" +version = "2021.3.14" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.dependencies] +colorama = "*" livereload = "*" sphinx = "*" @@ -1002,10 +1020,10 @@ test = ["pytest", "pytest-cov"] [[package]] name = "sphinx-copybutton" -version = "0.3.2" +version = "0.4.0" description = "Add a copy button to each of your code cells." category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.dependencies] @@ -1013,13 +1031,14 @@ sphinx = ">=1.8" [package.extras] code_style = ["pre-commit (==2.12.1)"] +rtd = ["sphinx", "ipython", "sphinx-book-theme"] [[package]] name = "sphinx-intl" version = "2.0.1" description = "Sphinx utility that make it easy to translate and to apply translation." category = "main" -optional = false +optional = true python-versions = ">=3.5" [package.dependencies] @@ -1033,10 +1052,10 @@ transifex = ["transifex_client (>=0.11)"] [[package]] name = "sphinx-prompt" -version = "1.4.0" +version = "1.5.0" description = "Sphinx directive to add unselectable prompt" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -1048,7 +1067,7 @@ name = "sphinx-substitution-extensions" version = "2020.9.30.0" description = "Extensions for Sphinx which allow for substitutions." category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -1064,7 +1083,7 @@ name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" category = "main" -optional = false +optional = true python-versions = ">=3.5" [package.extras] @@ -1076,7 +1095,7 @@ name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." category = "main" -optional = false +optional = true python-versions = ">=3.5" [package.extras] @@ -1088,7 +1107,7 @@ name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" category = "main" -optional = false +optional = true python-versions = ">=3.6" [package.extras] @@ -1100,7 +1119,7 @@ name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" category = "main" -optional = false +optional = true python-versions = ">=3.5" [package.extras] @@ -1111,7 +1130,7 @@ name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." category = "main" -optional = false +optional = true python-versions = ">=3.5" [package.extras] @@ -1123,7 +1142,7 @@ name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." category = "main" -optional = false +optional = true python-versions = ">=3.5" [package.extras] @@ -1134,24 +1153,32 @@ test = ["pytest"] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "1.2.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "tornado" version = "6.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." category = "main" -optional = false +optional = true python-versions = ">= 3.5" [[package]] name = "towncrier" version = "21.3.0" description = "Building newsfiles for your project." -category = "dev" -optional = false +category = "main" +optional = true python-versions = "*" [package.dependencies] @@ -1166,64 +1193,67 @@ dev = ["packaging"] [[package]] name = "traitlets" -version = "5.0.5" +version = "5.1.0" description = "Traitlets Python configuration system" category = "dev" optional = false python-versions = ">=3.7" -[package.dependencies] -ipython-genutils = "*" - [package.extras] test = ["pytest"] -[[package]] -name = "typed-ast" -version = "1.4.3" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "typing-extensions" -version = "3.10.0.0" +version = "3.10.0.2" description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false python-versions = "*" +[[package]] +name = "urllib3" +version = "1.26.6" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + [[package]] name = "uvloop" -version = "0.15.2" +version = "0.16.0" description = "Fast implementation of asyncio event loop on top of libuv" -category = "dev" -optional = false +category = "main" +optional = true python-versions = ">=3.7" [package.extras] -dev = ["Cython (>=0.29.20,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)", "aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] -docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"] -test = ["aiohttp", "flake8 (>=3.8.4,<3.9.0)", "psutil", "pycodestyle (>=2.6.0,<2.7.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] [[package]] name = "virtualenv" -version = "20.4.7" +version = "20.8.0" description = "Virtual Python Environment builder" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -appdirs = ">=1.4.3,<2" +"backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.0.0,<4" +platformdirs = ">=2,<3" six = ">=1.9.0,<2" [package.extras] docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)", "xonsh (>=0.9.16)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] [[package]] name = "wcwidth" @@ -1247,7 +1277,7 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.4.1" +version = "3.5.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false @@ -1255,23 +1285,24 @@ python-versions = ">=3.6" [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [extras] -docs = ["sphinx", "sphinx-intl", "sphinx-autobuild", "sphinx-copybutton", "furo", "sphinx-prompt", "Sphinx-Substitution-Extensions"] -fast = [] +docs = ["Sphinx", "sphinx-intl", "sphinx-autobuild", "sphinx-copybutton", "furo", "sphinx-prompt", "Sphinx-Substitution-Extensions", "towncrier", "pygments", "pymdown-extensions", "markdown-include"] +fast = ["uvloop"] +i18n = ["Babel"] proxy = ["aiohttp-socks"] redis = ["aioredis"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "558effa794bd627becae89d8416465bf2d11bd991da715b2b67a8eda6682b60c" +content-hash = "68e420156c8460179788d4e4828a348753606d8b2c16013b3784e7b7f36f47c3" [metadata.files] aiofiles = [ - {file = "aiofiles-0.6.0-py3-none-any.whl", hash = "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27"}, - {file = "aiofiles-0.6.0.tar.gz", hash = "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092"}, + {file = "aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"}, + {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, ] aiohttp = [ {file = "aiohttp-3.7.4.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5"}, @@ -1324,10 +1355,6 @@ alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] appnope = [ {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, @@ -1363,23 +1390,34 @@ backcall = [ {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, ] +"backports.entry-points-selectable" = [ + {file = "backports.entry_points_selectable-1.1.0-py2.py3-none-any.whl", hash = "sha256:a6d9a871cde5e15b4c4a53e3d43ba890cc6861ec1332c9c2428c92f977192acc"}, + {file = "backports.entry_points_selectable-1.1.0.tar.gz", hash = "sha256:988468260ec1c196dab6ae1149260e2f5472c9110334e5d51adcb77867361f6a"}, +] beautifulsoup4 = [ - {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, - {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, - {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, + {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"}, + {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, ] black = [ - {file = "black-21.6b0-py3-none-any.whl", hash = "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"}, - {file = "black-21.6b0.tar.gz", hash = "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04"}, + {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, + {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, +] +certifi = [ + {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, + {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"}, ] cfgv = [ - {file = "cfgv-3.3.0-py2.py3-none-any.whl", hash = "sha256:b449c9c6118fe8cca7fa5e00b9ec60ba08145d281d52164230a69211c5d597a1"}, - {file = "cfgv-3.3.0.tar.gz", hash = "sha256:9e600479b3b99e8af981ecdfc80a0296104ee610cab48a5ae4ffd0b668650eb1"}, + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] chardet = [ {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, ] +charset-normalizer = [ + {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"}, + {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"}, +] click = [ {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, @@ -1446,12 +1484,8 @@ coverage = [ {file = "coverage-5.5.tar.gz", hash = "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c"}, ] decorator = [ - {file = "decorator-5.0.9-py3-none-any.whl", hash = "sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323"}, - {file = "decorator-5.0.9.tar.gz", hash = "sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"}, -] -diagrams = [ - {file = "diagrams-0.20.0-py3-none-any.whl", hash = "sha256:395391663b4d3f2d3e3614797402ca99494e00baf3926f5c9e72856d34cafedd"}, - {file = "diagrams-0.20.0.tar.gz", hash = "sha256:a50743ed9274e194e7898820f69aa12868ae217003580ef9e7d0285132c9674a"}, + {file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"}, + {file = "decorator-5.1.0.tar.gz", hash = "sha256:e59913af105b9860aa2c8d3272d9de5a56a4e608db9a2f167a8480b323d529a7"}, ] distlib = [ {file = "distlib-0.3.2-py2.py3-none-any.whl", hash = "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c"}, @@ -1474,16 +1508,12 @@ flake8-html = [ {file = "flake8_html-0.4.1-py2.py3-none-any.whl", hash = "sha256:17324eb947e7006807e4184ee26953e67baf421b3cf9e646a38bfec34eec5a94"}, ] furo = [ - {file = "furo-2021.6.18b36-py3-none-any.whl", hash = "sha256:a4c00634afeb5896a34d141a5dffb62f20c5eca7831b78269823a8cd8b09a5e4"}, - {file = "furo-2021.6.18b36.tar.gz", hash = "sha256:46a30bc597a9067088d39d730e7d9bf6c1a1d71967e4af062f796769f66b3bdb"}, -] -graphviz = [ - {file = "graphviz-0.16-py2.py3-none-any.whl", hash = "sha256:3cad5517c961090dfc679df6402a57de62d97703e2880a1a46147bb0dc1639eb"}, - {file = "graphviz-0.16.zip", hash = "sha256:d2d25af1c199cad567ce4806f0449cb74eb30cf451fd7597251e1da099ac6e57"}, + {file = "furo-2021.9.8-py3-none-any.whl", hash = "sha256:182d2ac70118191cb750caa763b997974a2c19f6f5fb7dfce458ce518dce80ae"}, + {file = "furo-2021.9.8.tar.gz", hash = "sha256:eeaca6997c3a91240c5841d4f743d43196c4ef464b1236ff9b192d25f3e0d2ab"}, ] identify = [ - {file = "identify-2.2.10-py2.py3-none-any.whl", hash = "sha256:18d0c531ee3dbc112fa6181f34faa179de3f57ea57ae2899754f16a7e0ff6421"}, - {file = "identify-2.2.10.tar.gz", hash = "sha256:5b41f71471bc738e7b586308c3fca172f78940195cb3bf6734c1e66fdac49306"}, + {file = "identify-2.2.15-py2.py3-none-any.whl", hash = "sha256:de83a84d774921669774a2000bf87ebba46b4d1c04775f4a5d37deff0cf39f73"}, + {file = "identify-2.2.15.tar.gz", hash = "sha256:528a88021749035d5a39fe2ba67c0642b8341aaf71889da0e1ed669a429b87f0"}, ] idna = [ {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, @@ -1494,8 +1524,8 @@ imagesize = [ {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.5.0-py3-none-any.whl", hash = "sha256:833b26fb89d5de469b24a390e9df088d4e52e4ba33b01dc5e0e4f41b81a16c00"}, - {file = "importlib_metadata-4.5.0.tar.gz", hash = "sha256:b142cc1dd1342f31ff04bb7d022492b09920cb64fed867cd3ea6f80fe3ebd139"}, + {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, + {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] incremental = [ {file = "incremental-21.3.0-py2.py3-none-any.whl", hash = "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"}, @@ -1506,31 +1536,27 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] ipython = [ - {file = "ipython-7.24.1-py3-none-any.whl", hash = "sha256:d513e93327cf8657d6467c81f1f894adc125334ffe0e4ddd1abbb1c78d828703"}, - {file = "ipython-7.24.1.tar.gz", hash = "sha256:9bc24a99f5d19721fb8a2d1408908e9c0520a17fff2233ffe82620847f17f1b6"}, -] -ipython-genutils = [ - {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, - {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, + {file = "ipython-7.27.0-py3-none-any.whl", hash = "sha256:75b5e060a3417cf64f138e0bb78e58512742c57dc29db5a5058a2b1f0c10df02"}, + {file = "ipython-7.27.0.tar.gz", hash = "sha256:58b55ebfdfa260dad10d509702dc2857cb25ad82609506b070cf2d7b7df5af13"}, ] isort = [ - {file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"}, - {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, + {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, + {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, ] jedi = [ {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, ] jinja2 = [ - {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, - {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, + {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, + {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, ] livereload = [ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] magic-filter = [ - {file = "magic-filter-1.0.0.tar.gz", hash = "sha256:6c1e8d185cd540606555a07a7c78d9c36bf0c97b9cd6e0a00da65dd38d56026f"}, - {file = "magic_filter-1.0.0-py3-none-any.whl", hash = "sha256:37f6c67144cbd087dcc1879f684b3640e13d5c73196544a5a00a6180c5edaa2e"}, + {file = "magic-filter-1.0.2.tar.gz", hash = "sha256:2b4a6769dd25d3fdab8d74f8ab5d471973a81176b5c54f79bbca106ba6e0cc36"}, + {file = "magic_filter-1.0.2-py3-none-any.whl", hash = "sha256:cfe7e0debe62d270b49c1e574eae451a802e93a7584cfcb3881dc478846f825d"}, ] markdown = [ {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, @@ -1596,8 +1622,8 @@ markupsafe = [ {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, ] matplotlib-inline = [ - {file = "matplotlib-inline-0.1.2.tar.gz", hash = "sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e"}, - {file = "matplotlib_inline-0.1.2-py3-none-any.whl", hash = "sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811"}, + {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, + {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -1643,28 +1669,29 @@ multidict = [ {file = "multidict-5.1.0.tar.gz", hash = "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5"}, ] mypy = [ - {file = "mypy-0.812-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49"}, - {file = "mypy-0.812-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c"}, - {file = "mypy-0.812-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521"}, - {file = "mypy-0.812-cp35-cp35m-win_amd64.whl", hash = "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"}, - {file = "mypy-0.812-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a"}, - {file = "mypy-0.812-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c"}, - {file = "mypy-0.812-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6"}, - {file = "mypy-0.812-cp36-cp36m-win_amd64.whl", hash = "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064"}, - {file = "mypy-0.812-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56"}, - {file = "mypy-0.812-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8"}, - {file = "mypy-0.812-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7"}, - {file = "mypy-0.812-cp37-cp37m-win_amd64.whl", hash = "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564"}, - {file = "mypy-0.812-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506"}, - {file = "mypy-0.812-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5"}, - {file = "mypy-0.812-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66"}, - {file = "mypy-0.812-cp38-cp38-win_amd64.whl", hash = "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e"}, - {file = "mypy-0.812-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a"}, - {file = "mypy-0.812-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a"}, - {file = "mypy-0.812-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97"}, - {file = "mypy-0.812-cp39-cp39-win_amd64.whl", hash = "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df"}, - {file = "mypy-0.812-py3-none-any.whl", hash = "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4"}, - {file = "mypy-0.812.tar.gz", hash = "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119"}, + {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"}, + {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"}, + {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"}, + {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"}, + {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"}, + {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"}, + {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"}, + {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"}, + {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"}, + {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"}, + {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"}, + {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"}, + {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"}, + {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"}, + {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"}, + {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"}, + {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"}, + {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"}, + {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"}, + {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"}, + {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"}, + {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"}, + {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1683,8 +1710,8 @@ parso = [ {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, ] pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pexpect = [ {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, @@ -1694,17 +1721,21 @@ pickleshare = [ {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] +platformdirs = [ + {file = "platformdirs-2.3.0-py3-none-any.whl", hash = "sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"}, + {file = "platformdirs-2.3.0.tar.gz", hash = "sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f"}, +] pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] pre-commit = [ {file = "pre_commit-2.15.0-py2.py3-none-any.whl", hash = "sha256:a4ed01000afcb484d9eb8d504272e642c4c4099bbad3a6b27e519bd6a3e928a6"}, {file = "pre_commit-2.15.0.tar.gz", hash = "sha256:3c25add78dbdfb6a28a651780d5c311ac40dd17f160eb3954a0c59da40a505a7"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.18-py3-none-any.whl", hash = "sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04"}, - {file = "prompt_toolkit-3.0.18.tar.gz", hash = "sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"}, + {file = "prompt_toolkit-3.0.20-py3-none-any.whl", hash = "sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c"}, + {file = "prompt_toolkit-3.0.20.tar.gz", hash = "sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -1747,8 +1778,8 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pygments = [ - {file = "Pygments-2.9.0-py3-none-any.whl", hash = "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"}, - {file = "Pygments-2.9.0.tar.gz", hash = "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f"}, + {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, + {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, ] pymdown-extensions = [ {file = "pymdown-extensions-8.2.tar.gz", hash = "sha256:b6daa94aad9e1310f9c64c8b1f01e4ce82937ab7eb53bfc92876a97aca02a6f4"}, @@ -1759,8 +1790,8 @@ pyparsing = [ {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, ] pytest-asyncio = [ {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"}, @@ -1830,51 +1861,51 @@ pyyaml = [ {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, ] regex = [ - {file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"}, - {file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"}, - {file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"}, - {file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"}, - {file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"}, - {file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"}, - {file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"}, - {file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"}, - {file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"}, - {file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"}, - {file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"}, - {file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"}, - {file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"}, - {file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"}, - {file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"}, - {file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"}, - {file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"}, + {file = "regex-2021.8.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a"}, + {file = "regex-2021.8.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308"}, + {file = "regex-2021.8.28-cp310-cp310-win32.whl", hash = "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed"}, + {file = "regex-2021.8.28-cp310-cp310-win_amd64.whl", hash = "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8"}, + {file = "regex-2021.8.28-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1"}, + {file = "regex-2021.8.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f"}, + {file = "regex-2021.8.28-cp36-cp36m-win32.whl", hash = "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354"}, + {file = "regex-2021.8.28-cp36-cp36m-win_amd64.whl", hash = "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645"}, + {file = "regex-2021.8.28-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"}, + {file = "regex-2021.8.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906"}, + {file = "regex-2021.8.28-cp37-cp37m-win32.whl", hash = "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a"}, + {file = "regex-2021.8.28-cp37-cp37m-win_amd64.whl", hash = "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc"}, + {file = "regex-2021.8.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b"}, + {file = "regex-2021.8.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e"}, + {file = "regex-2021.8.28-cp38-cp38-win32.whl", hash = "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d"}, + {file = "regex-2021.8.28-cp38-cp38-win_amd64.whl", hash = "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2"}, + {file = "regex-2021.8.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8"}, + {file = "regex-2021.8.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed"}, + {file = "regex-2021.8.28-cp39-cp39-win32.whl", hash = "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374"}, + {file = "regex-2021.8.28-cp39-cp39-win_amd64.whl", hash = "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73"}, + {file = "regex-2021.8.28.tar.gz", hash = "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1"}, ] requests = [ - {file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"}, - {file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"}, + {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, + {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, @@ -1889,24 +1920,23 @@ soupsieve = [ {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"}, ] sphinx = [ - {file = "Sphinx-3.5.3-py3-none-any.whl", hash = "sha256:3f01732296465648da43dec8fb40dc451ba79eb3e2cc5c6d79005fd98197107d"}, - {file = "Sphinx-3.5.3.tar.gz", hash = "sha256:ce9c228456131bab09a3d7d10ae58474de562a6f79abb3dc811ae401cf8c1abc"}, + {file = "Sphinx-4.2.0-py3-none-any.whl", hash = "sha256:98a535c62a4fcfcc362528592f69b26f7caec587d32cd55688db580be0287ae0"}, + {file = "Sphinx-4.2.0.tar.gz", hash = "sha256:94078db9184491e15bce0a56d9186e0aec95f16ac20b12d00e06d4e36f1058a6"}, ] sphinx-autobuild = [ - {file = "sphinx-autobuild-2020.9.1.tar.gz", hash = "sha256:4b184a7db893f2100bbd831991ae54ca89167a2b9ce68faea71eaa9e37716aed"}, - {file = "sphinx_autobuild-2020.9.1-py3-none-any.whl", hash = "sha256:df5c72cb8b8fc9b31279c4619780c4e95029be6de569ff60a8bb2e99d20f63dd"}, + {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, + {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, ] sphinx-copybutton = [ - {file = "sphinx-copybutton-0.3.2.tar.gz", hash = "sha256:f901f17e7dadc063bcfca592c5160f9113ec17501a59e046af3edb82b7527656"}, - {file = "sphinx_copybutton-0.3.2-py3-none-any.whl", hash = "sha256:f16f8ed8dfc60f2b34a58cb69bfa04722e24be2f6d7e04db5554c32cde4df815"}, + {file = "sphinx-copybutton-0.4.0.tar.gz", hash = "sha256:8daed13a87afd5013c3a9af3575cc4d5bec052075ccd3db243f895c07a689386"}, + {file = "sphinx_copybutton-0.4.0-py3-none-any.whl", hash = "sha256:4340d33c169dac6dd82dce2c83333412aa786a42dd01a81a8decac3b130dc8b0"}, ] sphinx-intl = [ {file = "sphinx-intl-2.0.1.tar.gz", hash = "sha256:b25a6ec169347909e8d983eefe2d8adecb3edc2f27760db79b965c69950638b4"}, {file = "sphinx_intl-2.0.1-py3.8.egg", hash = "sha256:2ff97cba0e4e43249e339a3c29dd2f5b63c25ce794050aabca320ad95f5c5b55"}, ] sphinx-prompt = [ - {file = "sphinx-prompt-1.4.0.tar.gz", hash = "sha256:70c8e66d1f36def1e295ed961c7562bbf8fc697eea5191b92b41e4784c264ef5"}, - {file = "sphinx_prompt-1.4.0-py3-none-any.whl", hash = "sha256:ac54b204c3e0ff75851d92060672b65407ff67f8942bde2eb6ba318b8e7ca595"}, + {file = "sphinx_prompt-1.5.0-py3-none-any.whl", hash = "sha256:fa4e90d8088b5a996c76087d701fc7e31175f8b9dc4aab03a507e45051067162"}, ] sphinx-substitution-extensions = [ {file = "Sphinx Substitution Extensions-2020.9.30.0.tar.gz", hash = "sha256:578afc04eb4f701d9a922f8b75f678d3a1a897fa7a172a9226b92f17553f177a"}, @@ -1940,6 +1970,10 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tomli = [ + {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, + {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, +] tornado = [ {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, {file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"}, @@ -1988,61 +2022,39 @@ towncrier = [ {file = "towncrier-21.3.0.tar.gz", hash = "sha256:6eed0bc924d72c98c000cb8a64de3bd566e5cb0d11032b73fcccf8a8f956ddfe"}, ] traitlets = [ - {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, - {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, -] -typed-ast = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, + {file = "traitlets-5.1.0-py3-none-any.whl", hash = "sha256:03f172516916220b58c9f19d7f854734136dd9528103d04e9bf139a92c9f54c4"}, + {file = "traitlets-5.1.0.tar.gz", hash = "sha256:bd382d7ea181fbbcce157c133db9a829ce06edffe097bcf3ab945b435452b46d"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, - {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, - {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, +] +urllib3 = [ + {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"}, + {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"}, ] uvloop = [ - {file = "uvloop-0.15.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69"}, - {file = "uvloop-0.15.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7"}, - {file = "uvloop-0.15.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d"}, - {file = "uvloop-0.15.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c"}, - {file = "uvloop-0.15.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47"}, - {file = "uvloop-0.15.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c"}, - {file = "uvloop-0.15.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc"}, - {file = "uvloop-0.15.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760"}, - {file = "uvloop-0.15.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c"}, - {file = "uvloop-0.15.2.tar.gz", hash = "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01"}, + {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, + {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"}, + {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"}, + {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9"}, + {file = "uvloop-0.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638"}, + {file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450"}, + {file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805"}, + {file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382"}, + {file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee"}, + {file = "uvloop-0.16.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464"}, + {file = "uvloop-0.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab"}, + {file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f"}, + {file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897"}, + {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f"}, + {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"}, + {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, ] virtualenv = [ - {file = "virtualenv-20.4.7-py2.py3-none-any.whl", hash = "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76"}, - {file = "virtualenv-20.4.7.tar.gz", hash = "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467"}, + {file = "virtualenv-20.8.0-py2.py3-none-any.whl", hash = "sha256:a4b987ec31c3c9996cf1bc865332f967fe4a0512c41b39652d6224f696e69da5"}, + {file = "virtualenv-20.8.0.tar.gz", hash = "sha256:4da4ac43888e97de9cf4fdd870f48ed864bbfd133d2c46cbdec941fed4a25aef"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, @@ -2088,6 +2100,6 @@ yarl = [ {file = "yarl-1.6.3.tar.gz", hash = "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10"}, ] zipp = [ - {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, - {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, + {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, + {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, ] diff --git a/pyproject.toml b/pyproject.toml index 7195b488..d7546e16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,32 +32,39 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.8" -magic-filter = "^1.0.0" +magic-filter = "^1.0.2" aiohttp = "^3.7.4" -pydantic = "^1.8.1" -Babel = "^2.9.1" -aiofiles = "^0.6.0" +pydantic = "^1.8.2" +aiofiles = "^0.7.0" async_lru = "^1.0.2" +# Fast +uvloop = { version = "^0.16.0", markers = "sys_platform == 'darwin' or sys_platform == 'linux'", optional = true } +# i18n +Babel = { version = "^2.9.1", optional = true } +# Proxy aiohttp-socks = { version = "^0.5.5", optional = true } -aioredis = { version = "^2.0.0", allow-prereleases = true, optional = true } -sphinx = { version = "^3.1.0", optional = true } +# Redis +aioredis = { version = "^2.0.0", optional = true } +# Docs +Sphinx = { version = "^4.2.0", optional = true } sphinx-intl = { version = "^2.0.1", optional = true } -sphinx-autobuild = { version = "^2020.9.1", optional = true } -sphinx-copybutton = { version = "^0.3.1", optional = true } -furo = { version = "^2021.6.18-beta.36", optional = true } -sphinx-prompt = { version = "^1.3.0", optional = true } +sphinx-autobuild = { version = "^2021.3.14", optional = true } +sphinx-copybutton = { version = "^0.4.0", optional = true } +furo = { version = "^2021.9.8", optional = true } +sphinx-prompt = { version = "^1.5.0", optional = true } Sphinx-Substitution-Extensions = { version = "^2020.9.30", optional = true } +towncrier = { version = "^21.3.0", optional = true } +pygments = { version = "^2.4", optional = true } +pymdown-extensions = { version = "^8.0", optional = true } +markdown-include = { version = "^0.6", optional = true } [tool.poetry.dev-dependencies] -aiohttp-socks = "^0.5" -aioredis = { version = "^2.0.0a1", allow-prereleases = true } ipython = "^7.22.0" -uvloop = { version = "^0.15.2", markers = "sys_platform == 'darwin' or sys_platform == 'linux'" } black = "^21.4b2" isort = "^5.8.0" flake8 = "^3.9.1" flake8-html = "^0.4.1" -mypy = "^0.812" +mypy = "^0.910" pytest = "^6.2.3" pytest-html = "^3.1.1" pytest-asyncio = "^0.15.1" @@ -68,27 +75,17 @@ pytest-cov = "^2.11.1" aresponses = "^2.1.4" asynctest = "^0.13.0" toml = "^0.10.2" -pygments = "^2.4" -pymdown-extensions = "^8.0" -markdown-include = "^0.6" + pre-commit = "^2.15.0" packaging = "^20.3" typing-extensions = "^3.7.4" -sphinx = "^3.1.0" -sphinx-intl = "^2.0.1" -sphinx-autobuild = "^2020.9.1" -sphinx-copybutton = "^0.3.1" -furo = "^2021.6.18-beta.36" -sphinx-prompt = "^1.3.0" -Sphinx-Substitution-Extensions = "^2020.9.30" -towncrier = "^21.3.0" -diagrams = "^0.20.0" [tool.poetry.extras] fast = ["uvloop"] redis = ["aioredis"] proxy = ["aiohttp-socks"] +i18n = ["Babel"] docs = [ "sphinx", "sphinx-intl", @@ -98,6 +95,10 @@ docs = [ "black", "sphinx-prompt", "Sphinx-Substitution-Extensions", + "towncrier", + "pygments", + "pymdown-extensions", + "markdown-include", ] [tool.black] diff --git a/tests/conftest.py b/tests/conftest.py index f2d0cebc..51621a43 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest from _pytest.config import UsageError from aioredis.connection import parse_url as parse_redis_url @@ -7,6 +9,8 @@ from aiogram.dispatcher.fsm.storage.memory import MemoryStorage from aiogram.dispatcher.fsm.storage.redis import RedisStorage from tests.mocked_bot import MockedBot +DATA_DIR = Path(__file__).parent / "data" + def pytest_addoption(parser): parser.addoption("--redis", default=None, help="run tests which require redis connection") diff --git a/tests/data/locales/en/LC_MESSAGES/messages.mo b/tests/data/locales/en/LC_MESSAGES/messages.mo new file mode 100644 index 00000000..02bb93bf Binary files /dev/null and b/tests/data/locales/en/LC_MESSAGES/messages.mo differ diff --git a/tests/data/locales/en/LC_MESSAGES/messages.po b/tests/data/locales/en/LC_MESSAGES/messages.po new file mode 100644 index 00000000..b2a8e35f --- /dev/null +++ b/tests/data/locales/en/LC_MESSAGES/messages.po @@ -0,0 +1,2 @@ +msgid "test" +msgstr "" diff --git a/tests/data/locales/messages.pot b/tests/data/locales/messages.pot new file mode 100644 index 00000000..b2a8e35f --- /dev/null +++ b/tests/data/locales/messages.pot @@ -0,0 +1,2 @@ +msgid "test" +msgstr "" diff --git a/tests/data/locales/uk/LC_MESSAGES/messages.mo b/tests/data/locales/uk/LC_MESSAGES/messages.mo new file mode 100644 index 00000000..d745c75c Binary files /dev/null and b/tests/data/locales/uk/LC_MESSAGES/messages.mo differ diff --git a/tests/data/locales/uk/LC_MESSAGES/messages.po b/tests/data/locales/uk/LC_MESSAGES/messages.po new file mode 100644 index 00000000..6097258d --- /dev/null +++ b/tests/data/locales/uk/LC_MESSAGES/messages.po @@ -0,0 +1,2 @@ +msgid "test" +msgstr "тест" diff --git a/tests/test_api/test_client/test_bot.py b/tests/test_api/test_client/test_bot.py index b36006cc..d2c9d56a 100644 --- a/tests/test_api/test_client/test_bot.py +++ b/tests/test_api/test_client/test_bot.py @@ -1,4 +1,6 @@ import io +import os +from tempfile import mkstemp import aiofiles import pytest @@ -6,6 +8,7 @@ from aresponses import ResponsesMockServer from aiogram import Bot from aiogram.client.session.aiohttp import AiohttpSession +from aiogram.client.telegram import TelegramAPIServer from aiogram.methods import GetFile, GetMe from aiogram.types import File, PhotoSize from tests.mocked_bot import MockedBot @@ -128,3 +131,15 @@ class TestBot: await bot.download( [PhotoSize(file_id="file id", file_unique_id="file id", width=123, height=123)] ) + + async def test_download_local_file(self, bot: MockedBot): + bot.session.api = TelegramAPIServer.from_base("http://localhost:8081", is_local=True) + fd, tmp = mkstemp(prefix="test-", suffix=".txt") + value = b"KABOOM" + try: + with open(fd, "wb") as f: + f.write(value) + content = await bot.download_file(tmp) + assert content.getvalue() == value + finally: + os.unlink(tmp) diff --git a/tests/test_api/test_client/test_session/test_aiohttp_session.py b/tests/test_api/test_client/test_session/test_aiohttp_session.py index 36c9e17a..291991be 100644 --- a/tests/test_api/test_client/test_session/test_aiohttp_session.py +++ b/tests/test_api/test_client/test_session/test_aiohttp_session.py @@ -9,9 +9,9 @@ from aresponses import ResponsesMockServer from aiogram import Bot from aiogram.client.session import aiohttp from aiogram.client.session.aiohttp import AiohttpSession +from aiogram.exceptions import TelegramNetworkError from aiogram.methods import Request, TelegramMethod from aiogram.types import UNSET, InputFile -from aiogram.utils.exceptions.network import NetworkError from tests.mocked_bot import MockedBot try: @@ -187,7 +187,7 @@ class TestAiohttpSession: new_callable=CoroutineMock, side_effect=side_effect, ): - with pytest.raises(NetworkError): + with pytest.raises(TelegramNetworkError): await bot.get_me() async def test_stream_content(self, aresponses: ResponsesMockServer): diff --git a/tests/test_api/test_client/test_session/test_base_session.py b/tests/test_api/test_client/test_session/test_base_session.py index d4e28293..217b593f 100644 --- a/tests/test_api/test_client/test_session/test_base_session.py +++ b/tests/test_api/test_client/test_session/test_base_session.py @@ -7,16 +7,21 @@ import pytest from aiogram import Bot from aiogram.client.session.base import BaseSession, TelegramType from aiogram.client.telegram import PRODUCTION, TelegramAPIServer +from aiogram.exceptions import ( + RestartingTelegram, + TelegramAPIError, + TelegramBadRequest, + TelegramConflictError, + TelegramEntityTooLarge, + TelegramForbiddenError, + TelegramMigrateToChat, + TelegramNotFound, + TelegramRetryAfter, + TelegramServerError, + TelegramUnauthorizedError, +) from aiogram.methods import DeleteMessage, GetMe, TelegramMethod from aiogram.types import UNSET, User -from aiogram.utils.exceptions.bad_request import BadRequest -from aiogram.utils.exceptions.base import TelegramAPIError -from aiogram.utils.exceptions.conflict import ConflictError -from aiogram.utils.exceptions.network import EntityTooLarge -from aiogram.utils.exceptions.not_found import NotFound -from aiogram.utils.exceptions.server import RestartingTelegram, ServerError -from aiogram.utils.exceptions.special import MigrateToChat, RetryAfter -from aiogram.utils.exceptions.unauthorized import UnauthorizedError from tests.mocked_bot import MockedBot try: @@ -153,25 +158,25 @@ class TestBaseSession: "status_code,content,error", [ [200, '{"ok":true,"result":true}', None], - [400, '{"ok":false,"description":"test"}', BadRequest], + [400, '{"ok":false,"description":"test"}', TelegramBadRequest], [ 400, '{"ok":false,"description":"test", "parameters": {"retry_after": 1}}', - RetryAfter, + TelegramRetryAfter, ], [ 400, '{"ok":false,"description":"test", "parameters": {"migrate_to_chat_id": -42}}', - MigrateToChat, + TelegramMigrateToChat, ], - [404, '{"ok":false,"description":"test"}', NotFound], - [401, '{"ok":false,"description":"test"}', UnauthorizedError], - [403, '{"ok":false,"description":"test"}', UnauthorizedError], - [409, '{"ok":false,"description":"test"}', ConflictError], - [413, '{"ok":false,"description":"test"}', EntityTooLarge], + [404, '{"ok":false,"description":"test"}', TelegramNotFound], + [401, '{"ok":false,"description":"test"}', TelegramUnauthorizedError], + [403, '{"ok":false,"description":"test"}', TelegramForbiddenError], + [409, '{"ok":false,"description":"test"}', TelegramConflictError], + [413, '{"ok":false,"description":"test"}', TelegramEntityTooLarge], [500, '{"ok":false,"description":"restarting"}', RestartingTelegram], - [500, '{"ok":false,"description":"test"}', ServerError], - [502, '{"ok":false,"description":"test"}', ServerError], + [500, '{"ok":false,"description":"test"}', TelegramServerError], + [502, '{"ok":false,"description":"test"}', TelegramServerError], [499, '{"ok":false,"description":"test"}', TelegramAPIError], [499, '{"ok":false,"description":"test"}', TelegramAPIError], ], diff --git a/tests/test_dispatcher/test_filters/test_state.py b/tests/test_dispatcher/test_filters/test_state.py new file mode 100644 index 00000000..d551f748 --- /dev/null +++ b/tests/test_dispatcher/test_filters/test_state.py @@ -0,0 +1,49 @@ +from inspect import isclass + +import pytest + +from aiogram.dispatcher.filters import StateFilter +from aiogram.dispatcher.fsm.state import State, StatesGroup +from aiogram.types import Update + +pytestmark = pytest.mark.asyncio + + +class MyGroup(StatesGroup): + state = State() + + +class TestStateFilter: + @pytest.mark.parametrize( + "state", [None, State("test"), MyGroup, MyGroup(), "state", ["state"]] + ) + def test_validator(self, state): + f = StateFilter(state=state) + assert isinstance(f.state, list) + value = f.state[0] + assert ( + isinstance(value, (State, str, MyGroup)) + or (isclass(value) and issubclass(value, StatesGroup)) + or value is None + ) + + @pytest.mark.parametrize( + "state,current_state,result", + [ + [State("state"), "@:state", True], + [[State("state")], "@:state", True], + [MyGroup, "MyGroup:state", True], + [[MyGroup], "MyGroup:state", True], + [MyGroup(), "MyGroup:state", True], + [[MyGroup()], "MyGroup:state", True], + ["*", "state", True], + [None, None, True], + [[None], None, True], + [None, "state", False], + [[], "state", False], + ], + ) + @pytestmark + async def test_filter(self, state, current_state, result): + f = StateFilter(state=state) + assert bool(await f(obj=Update(update_id=42), raw_state=current_state)) is result diff --git a/aiogram/utils/help/__init__.py b/tests/test_dispatcher/test_middlewares/__init__.py similarity index 100% rename from aiogram/utils/help/__init__.py rename to tests/test_dispatcher/test_middlewares/__init__.py diff --git a/tests/test_dispatcher/test_middlewares/test_user_context.py b/tests/test_dispatcher/test_middlewares/test_user_context.py new file mode 100644 index 00000000..8d289c2b --- /dev/null +++ b/tests/test_dispatcher/test_middlewares/test_user_context.py @@ -0,0 +1,14 @@ +import pytest + +from aiogram.dispatcher.middlewares.user_context import UserContextMiddleware + + +async def next_handler(*args, **kwargs): + pass + + +class TestUserContextMiddleware: + @pytest.mark.asyncio + async def test_unexpected_event_type(self): + with pytest.raises(RuntimeError): + await UserContextMiddleware()(next_handler, object(), {}) diff --git a/tests/test_utils/test_i18n.py b/tests/test_utils/test_i18n.py new file mode 100644 index 00000000..72da0cbc --- /dev/null +++ b/tests/test_utils/test_i18n.py @@ -0,0 +1,139 @@ +from typing import Any, Dict + +import pytest + +from aiogram import Dispatcher +from aiogram.dispatcher.fsm.context import FSMContext +from aiogram.dispatcher.fsm.storage.memory import MemoryStorage +from aiogram.types import Update, User +from aiogram.utils.i18n import ConstI18nMiddleware, FSMI18nMiddleware, I18n, SimpleI18nMiddleware +from aiogram.utils.i18n.context import ctx_i18n, get_i18n, gettext, lazy_gettext +from tests.conftest import DATA_DIR +from tests.mocked_bot import MockedBot + + +@pytest.fixture(name="i18n") +def i18n_fixture() -> I18n: + return I18n(path=DATA_DIR / "locales") + + +class TestI18nCore: + def test_init(self, i18n: I18n): + assert set(i18n.available_locales) == {"en", "uk"} + + def test_reload(self, i18n: I18n): + i18n.reload() + assert set(i18n.available_locales) == {"en", "uk"} + + def test_current_locale(self, i18n: I18n): + assert i18n.current_locale == "en" + i18n.current_locale = "uk" + assert i18n.current_locale == "uk" + assert i18n.ctx_locale.get() == "uk" + + def test_get_i18n(self, i18n: I18n): + with pytest.raises(LookupError): + get_i18n() + + token = ctx_i18n.set(i18n) + assert get_i18n() == i18n + ctx_i18n.reset(token) + + @pytest.mark.parametrize( + "locale,case,result", + [ + [None, dict(singular="test"), "test"], + [None, dict(singular="test", locale="uk"), "тест"], + ["en", dict(singular="test", locale="uk"), "тест"], + ["uk", dict(singular="test", locale="uk"), "тест"], + ["uk", dict(singular="test"), "тест"], + ["it", dict(singular="test"), "test"], + [None, dict(singular="test", n=2), "test"], + [None, dict(singular="test", n=2, locale="uk"), "тест"], + ["en", dict(singular="test", n=2, locale="uk"), "тест"], + ["uk", dict(singular="test", n=2, locale="uk"), "тест"], + ["uk", dict(singular="test", n=2), "тест"], + ["it", dict(singular="test", n=2), "test"], + [None, dict(singular="test", plural="test2", n=2), "test2"], + [None, dict(singular="test", plural="test2", n=2, locale="uk"), "test2"], + ["en", dict(singular="test", plural="test2", n=2, locale="uk"), "test2"], + ["uk", dict(singular="test", plural="test2", n=2, locale="uk"), "test2"], + ["uk", dict(singular="test", plural="test2", n=2), "test2"], + ["it", dict(singular="test", plural="test2", n=2), "test2"], + ], + ) + def test_gettext(self, i18n: I18n, locale: str, case: Dict[str, Any], result: str): + if locale is not None: + i18n.current_locale = locale + token = ctx_i18n.set(i18n) + try: + assert i18n.gettext(**case) == result + assert str(i18n.lazy_gettext(**case)) == result + assert gettext(**case) == result + assert str(lazy_gettext(**case)) == result + finally: + ctx_i18n.reset(token) + + +async def next_call(event, data): + assert "i18n" in data + assert "i18n_middleware" in data + return gettext("test") + + +@pytest.mark.asyncio +class TestSimpleI18nMiddleware: + @pytest.mark.parametrize( + "event_from_user,result", + [ + [None, "test"], + [User(id=42, is_bot=False, language_code="uk", first_name="Test"), "тест"], + [User(id=42, is_bot=False, language_code="it", first_name="Test"), "test"], + ], + ) + async def test_middleware(self, i18n: I18n, event_from_user, result): + middleware = SimpleI18nMiddleware(i18n=i18n) + result = await middleware( + next_call, + Update(update_id=42), + {"event_from_user": event_from_user}, + ) + assert result == result + + async def test_setup(self, i18n: I18n): + dp = Dispatcher() + middleware = SimpleI18nMiddleware(i18n=i18n) + middleware.setup(router=dp) + + assert middleware not in dp.update.outer_middlewares + assert middleware in dp.message.outer_middlewares + + +@pytest.mark.asyncio +class TestConstI18nMiddleware: + async def test_middleware(self, i18n: I18n): + middleware = ConstI18nMiddleware(i18n=i18n, locale="uk") + result = await middleware( + next_call, + Update(update_id=42), + {"event_from_user": User(id=42, is_bot=False, language_code="it", first_name="Test")}, + ) + assert result == "тест" + + +@pytest.mark.asyncio +class TestFSMI18nMiddleware: + async def test_middleware(self, i18n: I18n, bot: MockedBot): + middleware = FSMI18nMiddleware(i18n=i18n) + storage = MemoryStorage() + state = FSMContext(bot=bot, storage=storage, user_id=42, chat_id=42) + data = { + "event_from_user": User(id=42, is_bot=False, language_code="it", first_name="Test"), + "state": state, + } + result = await middleware(next_call, Update(update_id=42), data) + assert result == "test" + await middleware.set_locale(state, "uk") + assert i18n.current_locale == "uk" + result = await middleware(next_call, Update(update_id=42), data) + assert result == "тест" diff --git a/tests/test_utils/test_keyboard.py b/tests/test_utils/test_keyboard.py new file mode 100644 index 00000000..9923064f --- /dev/null +++ b/tests/test_utils/test_keyboard.py @@ -0,0 +1,226 @@ +import pytest + +from aiogram.dispatcher.filters.callback_data import CallbackData +from aiogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + KeyboardButton, + ReplyKeyboardMarkup, +) +from aiogram.utils.keyboard import InlineKeyboardBuilder, KeyboardBuilder, ReplyKeyboardBuilder + + +class MyCallback(CallbackData, prefix="test"): + value: str + + +class TestKeyboardBuilder: + def test_init(self): + with pytest.raises(ValueError): + KeyboardBuilder(button_type=object) + + def test_init_success(self): + builder = KeyboardBuilder(button_type=KeyboardButton) + assert builder._button_type is KeyboardButton + builder = InlineKeyboardBuilder() + assert builder._button_type is InlineKeyboardButton + builder = ReplyKeyboardBuilder() + assert builder._button_type is KeyboardButton + + def test_validate_button(self): + builder = InlineKeyboardBuilder() + with pytest.raises(ValueError): + builder._validate_button(button=object()) + with pytest.raises(ValueError): + builder._validate_button(button=KeyboardButton(text="test")) + assert builder._validate_button( + button=InlineKeyboardButton(text="test", callback_data="callback") + ) + + def test_validate_buttons(self): + builder = InlineKeyboardBuilder() + with pytest.raises(ValueError): + builder._validate_buttons(object(), object()) + with pytest.raises(ValueError): + builder._validate_buttons(KeyboardButton(text="test")) + with pytest.raises(ValueError): + builder._validate_buttons( + InlineKeyboardButton(text="test", callback_data="callback"), + KeyboardButton(text="test"), + ) + assert builder._validate_button( + InlineKeyboardButton(text="test", callback_data="callback") + ) + + def test_validate_row(self): + builder = ReplyKeyboardBuilder() + + with pytest.raises(ValueError): + assert builder._validate_row( + row=(KeyboardButton(text=f"test {index}") for index in range(10)) + ) + + with pytest.raises(ValueError): + assert builder._validate_row( + row=[KeyboardButton(text=f"test {index}") for index in range(10)] + ) + + for count in range(9): + assert builder._validate_row( + row=[KeyboardButton(text=f"test {index}") for index in range(count)] + ) + + def test_validate_markup(self): + builder = ReplyKeyboardBuilder() + + with pytest.raises(ValueError): + builder._validate_markup(markup=()) + + with pytest.raises(ValueError): + builder._validate_markup( + markup=[ + [KeyboardButton(text=f"{row}.{col}") for col in range(8)] for row in range(15) + ] + ) + + assert builder._validate_markup( + markup=[[KeyboardButton(text=f"{row}.{col}") for col in range(8)] for row in range(8)] + ) + + def test_validate_size(self): + builder = ReplyKeyboardBuilder() + with pytest.raises(ValueError): + builder._validate_size(None) + with pytest.raises(ValueError): + builder._validate_size(2.0) + + with pytest.raises(ValueError): + builder._validate_size(0) + + with pytest.raises(ValueError): + builder._validate_size(10) + for size in range(1, 9): + builder._validate_size(size) + + def test_export(self): + builder = ReplyKeyboardBuilder(markup=[[KeyboardButton(text="test")]]) + markup = builder.export() + assert id(builder._markup) != id(markup) + + markup.clear() + assert len(builder._markup) == 1 + assert len(markup) == 0 + + @pytest.mark.parametrize( + "builder,button", + [ + [ + ReplyKeyboardBuilder(markup=[[KeyboardButton(text="test")]]), + KeyboardButton(text="test2"), + ], + [ + InlineKeyboardBuilder(markup=[[InlineKeyboardButton(text="test")]]), + InlineKeyboardButton(text="test2"), + ], + [ + KeyboardBuilder( + button_type=InlineKeyboardButton, markup=[[InlineKeyboardButton(text="test")]] + ), + InlineKeyboardButton(text="test2"), + ], + ], + ) + def test_copy(self, builder, button): + builder1 = builder + builder2 = builder1.copy() + assert builder1 != builder2 + + builder1.add(button) + builder2.row(button) + + markup1 = builder1.export() + markup2 = builder2.export() + assert markup1 != markup2 + + assert len(markup1) == 1 + assert len(markup2) == 2 + assert len(markup1[0]) == 2 + assert len(markup2[0]) == 1 + + @pytest.mark.parametrize( + "count,rows,last_columns", + [[0, 0, 0], [3, 1, 3], [8, 1, 8], [9, 2, 1], [16, 2, 8], [19, 3, 3]], + ) + def test_add(self, count: int, rows: int, last_columns: int): + builder = ReplyKeyboardBuilder() + + for index in range(count): + builder.add(KeyboardButton(text=f"btn-{index}")) + markup = builder.export() + + assert len(list(builder.buttons)) == count + assert len(markup) == rows + if last_columns: + assert len(markup[-1]) == last_columns + + def test_row( + self, + ): + builder = ReplyKeyboardBuilder(markup=[[KeyboardButton(text="test")]]) + builder.row(*(KeyboardButton(text=f"test-{index}") for index in range(10)), width=3) + markup = builder.export() + assert len(markup) == 5 + + @pytest.mark.parametrize( + "count,repeat,sizes,shape", + [ + [0, False, [], []], + [0, False, [2], []], + [1, False, [2], [1]], + [3, False, [2], [2, 1]], + [10, False, [], [8, 2]], + [10, False, [3, 2, 1], [3, 2, 1, 1, 1, 1, 1]], + [12, True, [3, 2, 1], [3, 2, 1, 3, 2, 1]], + ], + ) + def test_adjust(self, count, repeat, sizes, shape): + builder = ReplyKeyboardBuilder() + builder.row(*(KeyboardButton(text=f"test-{index}") for index in range(count))) + builder.adjust(*sizes, repeat=repeat) + markup = builder.export() + + assert len(markup) == len(shape) + for row, expected_size in zip(markup, shape): + assert len(row) == expected_size + + @pytest.mark.parametrize( + "builder_type,kwargs,expected", + [ + [ReplyKeyboardBuilder, dict(text="test"), KeyboardButton(text="test")], + [ + InlineKeyboardBuilder, + dict(text="test", callback_data="callback"), + InlineKeyboardButton(text="test", callback_data="callback"), + ], + [ + InlineKeyboardBuilder, + dict(text="test", callback_data=MyCallback(value="test")), + InlineKeyboardButton(text="test", callback_data="test:test"), + ], + ], + ) + def test_button(self, builder_type, kwargs, expected): + builder = builder_type() + builder.button(**kwargs) + markup = builder.export() + assert markup[0][0] == expected + + @pytest.mark.parametrize( + "builder,expected", + [ + [ReplyKeyboardBuilder(), ReplyKeyboardMarkup], + [InlineKeyboardBuilder(), InlineKeyboardMarkup], + ], + ) + def test_as_markup(self, builder, expected): + assert isinstance(builder.as_markup(), expected)