From 6567fefc4b3f93589250e3785cc3884480f1b566 Mon Sep 17 00:00:00 2001 From: ksivaa <98091246+ksivaa@users.noreply.github.com> Date: Wed, 26 Jan 2022 20:30:27 +0200 Subject: [PATCH 01/35] Correct grammar errors (#811) * Correct grammar errors * Return punctuation and line break to the original * Update docs/dispatcher/router.rst Co-authored-by: Evgen Fil Co-authored-by: Evgen Fil --- docs/dispatcher/router.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/dispatcher/router.rst b/docs/dispatcher/router.rst index fe98537e..a7abeb69 100644 --- a/docs/dispatcher/router.rst +++ b/docs/dispatcher/router.rst @@ -12,12 +12,12 @@ Event observers .. warning:: - All handlers is always should be an asynchronous. - Name of handler function is not important. Event argument name is also is not important but is recommended to don't overlap the name with contextual data in due to function can not accept two arguments with the same name. + All handlers always should be asynchronous. + The name of the handler function is not important. The event argument name is also not important but it is recommended to not overlap the name with contextual data in due to function can not accept two arguments with the same name. -Here is list of available observers and examples how to register handlers (In examples used only @decorator-style): +Here is the list of available observers and examples of how to register handlers -For examples used decorator-style registering handlers but if you dont like @decorators just use :obj:`.register(...)` method instead. +In these examples only decorator-style registering handlers are used, but if you don't like @decorators just use :obj:`.register(...)` method instead. Update ------ @@ -29,7 +29,7 @@ Update .. note:: - By default Router is already have an update handler which route all event types to another observers. + By default Router already has an update handler which route all event types to another observers. Message @@ -40,9 +40,9 @@ Message Be attentive with filtering this event - You should expect than this event can be with different set's of attributes in different cases + You should expect that this event can be with different sets of attributes in different cases - (For example text, sticker and document is always is different content types of message) + (For example text, sticker and document are always of different content types of message) Recommended way to check field availability before usage or use :class:`aiogram.dispatcher.filters.content_types.ContentTypesFilter` @@ -183,7 +183,7 @@ Example: How it works? ------------- -For example dispatcher has 2 routers, last one router is also have one nested router: +For example, dispatcher has 2 routers, the last router also has one nested router: .. image:: ../_static/nested_routers_example.png :alt: Nested routers example From c7058584219a2138bab44b7760604bf62783aaf5 Mon Sep 17 00:00:00 2001 From: Gabben Date: Thu, 27 Jan 2022 01:26:07 +0400 Subject: [PATCH 02/35] Update index.rst (#808) --- docs/dispatcher/filters/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dispatcher/filters/index.rst b/docs/dispatcher/filters/index.rst index 0ce013ab..a94636e6 100644 --- a/docs/dispatcher/filters/index.rst +++ b/docs/dispatcher/filters/index.rst @@ -55,7 +55,7 @@ For example if you need to make simple text filter: .. code-block:: python - from aiogram.filters import BaseFilter + from aiogram.dispatcher.filters import BaseFilter class MyText(BaseFilter): From 1b8e22d90fab54a0360efabb750efa84ec3b2e66 Mon Sep 17 00:00:00 2001 From: darksidecat <58224121+darksidecat@users.noreply.github.com> Date: Sat, 12 Feb 2022 01:38:01 +0200 Subject: [PATCH 03/35] Fix: close #822 Fix exception filter (#827) * fix exceptions filters * Update CHANGES/827.bugfix Co-authored-by: Alex Root Junior Co-authored-by: Alex Root Junior --- CHANGES/827.bugfix | 1 + aiogram/dispatcher/filters/exception.py | 9 ++++++-- .../test_filters/test_exception.py | 23 ++++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 CHANGES/827.bugfix diff --git a/CHANGES/827.bugfix b/CHANGES/827.bugfix new file mode 100644 index 00000000..f0aab059 --- /dev/null +++ b/CHANGES/827.bugfix @@ -0,0 +1 @@ +Fixed exceptions filters diff --git a/aiogram/dispatcher/filters/exception.py b/aiogram/dispatcher/filters/exception.py index f4a077f8..58f03e8c 100644 --- a/aiogram/dispatcher/filters/exception.py +++ b/aiogram/dispatcher/filters/exception.py @@ -4,6 +4,7 @@ from typing import Any, Dict, Pattern, Tuple, Type, Union, cast from pydantic import validator from aiogram.dispatcher.filters import BaseFilter +from aiogram.types import TelegramObject class ExceptionTypeFilter(BaseFilter): @@ -17,7 +18,9 @@ class ExceptionTypeFilter(BaseFilter): class Config: arbitrary_types_allowed = True - async def __call__(self, exception: Exception) -> Union[bool, Dict[str, Any]]: + async def __call__( + self, obj: TelegramObject, exception: Exception + ) -> Union[bool, Dict[str, Any]]: return isinstance(exception, self.exception) @@ -38,7 +41,9 @@ class ExceptionMessageFilter(BaseFilter): return re.compile(value) return value - async def __call__(self, exception: Exception) -> Union[bool, Dict[str, Any]]: + async def __call__( + self, obj: TelegramObject, exception: Exception + ) -> Union[bool, Dict[str, Any]]: pattern = cast(Pattern[str], self.pattern) result = pattern.match(str(exception)) if not result: diff --git a/tests/test_dispatcher/test_filters/test_exception.py b/tests/test_dispatcher/test_filters/test_exception.py index ca37b9e9..c1ffb6d8 100644 --- a/tests/test_dispatcher/test_filters/test_exception.py +++ b/tests/test_dispatcher/test_filters/test_exception.py @@ -2,7 +2,9 @@ import re import pytest +from aiogram import Dispatcher, F from aiogram.dispatcher.filters import ExceptionMessageFilter, ExceptionTypeFilter +from aiogram.types import Update pytestmark = pytest.mark.asyncio @@ -16,10 +18,10 @@ class TestExceptionMessageFilter: async def test_match(self): obj = ExceptionMessageFilter(pattern="KABOOM") - result = await obj(Exception()) + result = await obj(Update(update_id=0), exception=Exception()) assert not result - result = await obj(Exception("KABOOM")) + result = await obj(Update(update_id=0), exception=Exception("KABOOM")) assert isinstance(result, dict) assert "match_exception" in result @@ -46,6 +48,21 @@ class TestExceptionTypeFilter: async def test_check(self, exception: Exception, value: bool): obj = ExceptionTypeFilter(exception=MyException) - result = await obj(exception) + result = await obj(Update(update_id=0), exception=exception) assert result == value + + +class TestDispatchException: + async def test_handle_exception(self, bot): + dp = Dispatcher() + + @dp.update() + async def update_handler(update): + raise ValueError("KABOOM") + + @dp.errors(ExceptionMessageFilter(pattern="KABOOM")) + async def handler0(update, exception): + return "Handled" + + assert await dp.feed_update(bot, Update(update_id=0)) == "Handled" From ac7f2dc4083a2ff982b12716d39e789a07a0083a Mon Sep 17 00:00:00 2001 From: Jasur Yusupov Date: Sat, 19 Feb 2022 03:50:17 +0500 Subject: [PATCH 04/35] Update README.rst (#840) * Update README.rst - Correct spelling - Correct grammar mistakes - Improve sentences * Update README.rst Bring back the line "Breaking News" --- README.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.rst b/README.rst index 9341b65a..7e0dd527 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ aiogram |beta badge| #################### .. danger:: - This version still in development! + This version is still in development! .. image:: https://img.shields.io/pypi/l/aiogram.svg :target: https://opensource.org/licenses/MIT @@ -41,12 +41,12 @@ aiogram |beta badge| :target: https://app.codecov.io/gh/aiogram/aiogram :alt: Codecov -**aiogram** modern and fully asynchronous framework for -`Telegram Bot API `_ written in Python 3.8 with +**aiogram** is a modern and fully asynchronous framework for +`Telegram Bot API `_ written in Python 3.8 using `asyncio `_ and `aiohttp `_. -It helps you to make your bots faster and simpler. +Make your bots faster and more powerful! .. danger:: @@ -54,7 +54,7 @@ It helps you to make your bots faster and simpler. *aiogram* 3.0 has breaking changes. - It breaks backwards compatibility by introducing new breaking changes! + It breaks backward compatibility by introducing new breaking changes! Features ======== @@ -62,19 +62,20 @@ Features - Asynchronous (`asyncio docs `_, :pep:`492`) - Has type hints (:pep:`484`) and can be used with `mypy `_ - Supports `Telegram Bot API 5.3 `_ -- Telegram Bot API integration code was `autogenerated `_ and can be easy re-generated when API was updated +- Telegram Bot API integration code was `autogenerated `_ and can be easily re-generated when API gets updated - Updates router (Blueprints) -- Finite State Machine +- Has Finite State Machine - Middlewares (incoming updates and API calls) - Provides `Replies into Webhook `_ - Integrated I18n/L10n support with GNU Gettext (or Fluent) .. warning:: - Before start using **aiogram** is highly recommend to know how to work - with `asyncio `_. + It is strongly advised that you have prior experience working + with `asyncio `_ + before beginning to use **aiogram**. - Also if you has questions you can go to our community chats in Telegram: + If you have any questions, you can visit our community chats on Telegram: - `English language `_ - `Russian language `_ From 7776cf9cf6e26cbd13168600883d95a3e5ce049d Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 19 Feb 2022 01:45:59 +0200 Subject: [PATCH 05/35] Bot API 5.7 and some new features (#834) * Update API, added some new features * Fixed unknown chat_action value * Separate events from dispatcher messages * Disabled cache for I18n LazyProxy * Rework events isolation * Added chat member status changed filter, update Bot API 5.7, other small changes * Improve exceptions in chat member status filter * Fixed tests, covered flags and events isolation modules * Try to fix flake8 unused type ignore * Fixed linter error * Cover chat member updated filter * Cover chat action sender * Added docs for chat action util * Try to fix tests for python <= 3.9 * Fixed headers * Added docs for flags functionality * Added docs for chat_member_updated filter * Added change notes * Update dependencies and fix mypy checks * Bump version --- .apiversion | 2 +- .coveragerc | 1 + CHANGES/830.misc | 1 + CHANGES/835.misc | 1 + CHANGES/836.feature | 1 + CHANGES/837.feature | 3 + CHANGES/838.misc | 2 + CHANGES/839.bugix | 1 + Makefile | 4 +- README.rst | 2 +- aiogram/__init__.py | 7 +- aiogram/client/bot.py | 83 +- aiogram/dispatcher/dispatcher.py | 23 +- aiogram/dispatcher/event/handler.py | 2 + aiogram/dispatcher/filters/__init__.py | 30 + .../dispatcher/filters/chat_member_updated.py | 179 ++++ aiogram/dispatcher/flags/__init__.py | 0 aiogram/dispatcher/flags/flag.py | 60 ++ aiogram/dispatcher/flags/getter.py | 56 ++ aiogram/dispatcher/fsm/middleware.py | 20 +- aiogram/dispatcher/fsm/storage/base.py | 32 +- aiogram/dispatcher/fsm/storage/memory.py | 39 +- aiogram/dispatcher/fsm/storage/redis.py | 67 +- aiogram/dispatcher/webhook/aiohttp_server.py | 6 +- aiogram/loggers.py | 1 + aiogram/methods/add_sticker_to_set.py | 9 +- aiogram/methods/copy_message.py | 2 + aiogram/methods/create_new_sticker_set.py | 9 +- aiogram/methods/forward_message.py | 2 + aiogram/methods/send_animation.py | 2 + aiogram/methods/send_audio.py | 2 + aiogram/methods/send_contact.py | 2 + aiogram/methods/send_dice.py | 2 + aiogram/methods/send_document.py | 2 + aiogram/methods/send_game.py | 2 + aiogram/methods/send_invoice.py | 2 + aiogram/methods/send_location.py | 2 + aiogram/methods/send_media_group.py | 2 + aiogram/methods/send_message.py | 2 + aiogram/methods/send_photo.py | 2 + aiogram/methods/send_poll.py | 2 + aiogram/methods/send_sticker.py | 4 +- aiogram/methods/send_venue.py | 2 + aiogram/methods/send_video.py | 2 + aiogram/methods/send_video_note.py | 2 + aiogram/methods/send_voice.py | 2 + aiogram/methods/set_sticker_set_thumb.py | 4 +- aiogram/methods/unban_chat_member.py | 2 +- aiogram/types/bot_command.py | 4 +- aiogram/types/chat_member.py | 51 ++ aiogram/types/message.py | 2 +- aiogram/types/message_entity.py | 2 +- aiogram/utils/chat_action.py | 347 ++++++++ aiogram/utils/i18n/context.py | 2 +- aiogram/utils/i18n/core.py | 4 +- aiogram/utils/i18n/middleware.py | 12 +- aiogram/utils/mixins.py | 4 +- .../filters/chat_member_updated.rst | 103 +++ docs/dispatcher/filters/index.rst | 3 +- docs/dispatcher/flags.rst | 89 ++ docs/dispatcher/index.rst | 1 + docs/utils/chat_action.rst | 56 ++ docs/utils/index.rst | 1 + poetry.lock | 793 ++++++++++-------- pyproject.toml | 54 +- tests/conftest.py | 56 +- tests/test_dispatcher/test_dispatcher.py | 21 +- .../test_filters/test_chat_member_updated.py | 345 ++++++++ tests/test_dispatcher/test_flags/__init__.py | 0 .../test_flags/test_decorator.py | 66 ++ .../test_dispatcher/test_flags/test_getter.py | 64 ++ .../test_fsm/storage/test_isolation.py | 30 + .../test_fsm/storage/test_redis.py | 16 +- .../test_fsm/storage/test_storages.py | 5 - .../test_webhook/test_aiohtt_server.py | 22 +- tests/test_utils/test_chat_action.py | 129 +++ tests/test_utils/test_i18n.py | 18 + 77 files changed, 2485 insertions(+), 502 deletions(-) create mode 100644 CHANGES/830.misc create mode 100644 CHANGES/835.misc create mode 100644 CHANGES/836.feature create mode 100644 CHANGES/837.feature create mode 100644 CHANGES/838.misc create mode 100644 CHANGES/839.bugix create mode 100644 aiogram/dispatcher/filters/chat_member_updated.py create mode 100644 aiogram/dispatcher/flags/__init__.py create mode 100644 aiogram/dispatcher/flags/flag.py create mode 100644 aiogram/dispatcher/flags/getter.py create mode 100644 aiogram/utils/chat_action.py create mode 100644 docs/dispatcher/filters/chat_member_updated.rst create mode 100644 docs/dispatcher/flags.rst create mode 100644 docs/utils/chat_action.rst create mode 100644 tests/test_dispatcher/test_filters/test_chat_member_updated.py create mode 100644 tests/test_dispatcher/test_flags/__init__.py create mode 100644 tests/test_dispatcher/test_flags/test_decorator.py create mode 100644 tests/test_dispatcher/test_flags/test_getter.py create mode 100644 tests/test_dispatcher/test_fsm/storage/test_isolation.py create mode 100644 tests/test_utils/test_chat_action.py diff --git a/.apiversion b/.apiversion index 9ad974f6..760606e1 100644 --- a/.apiversion +++ b/.apiversion @@ -1 +1 @@ -5.5 +5.7 diff --git a/.coveragerc b/.coveragerc index 9feee202..e1862099 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,3 +3,4 @@ exclude_lines = pragma: no cover if TYPE_CHECKING: @abstractmethod + @overload diff --git a/CHANGES/830.misc b/CHANGES/830.misc new file mode 100644 index 00000000..f01d8b98 --- /dev/null +++ b/CHANGES/830.misc @@ -0,0 +1 @@ +Logger name for processing events is changed to :code:`aiogram.events`. diff --git a/CHANGES/835.misc b/CHANGES/835.misc new file mode 100644 index 00000000..83be3cb6 --- /dev/null +++ b/CHANGES/835.misc @@ -0,0 +1 @@ +Added full support of Telegram Bot API 5.6 and 5.7 diff --git a/CHANGES/836.feature b/CHANGES/836.feature new file mode 100644 index 00000000..17c9cd49 --- /dev/null +++ b/CHANGES/836.feature @@ -0,0 +1 @@ +Added possibility to add handler flags via decorator (like `pytest.mark` decorator but `aiogram.flags`) diff --git a/CHANGES/837.feature b/CHANGES/837.feature new file mode 100644 index 00000000..57ce165b --- /dev/null +++ b/CHANGES/837.feature @@ -0,0 +1,3 @@ +Added :code:`ChatActionSender` utility to automatically sends chat action while long process is running. + +It also can be used as message middleware and can be customized via :code:`chat_action` flag. diff --git a/CHANGES/838.misc b/CHANGES/838.misc new file mode 100644 index 00000000..d4af8385 --- /dev/null +++ b/CHANGES/838.misc @@ -0,0 +1,2 @@ +**BREAKING** +Events isolation mechanism is moved from FSM storages to standalone managers diff --git a/CHANGES/839.bugix b/CHANGES/839.bugix new file mode 100644 index 00000000..89bbe247 --- /dev/null +++ b/CHANGES/839.bugix @@ -0,0 +1 @@ +Fixed I18n lazy-proxy. Disabled caching. diff --git a/Makefile b/Makefile index 63b117c0..c0508fea 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ clean: rm -rf `find . -name .pytest_cache` rm -rf *.egg-info rm -f report.html - rm -f .coverage* + rm -f .coverage rm -rf {build,dist,site,.cache,.mypy_cache,reports} # ================================================================================================= @@ -84,7 +84,7 @@ reformat: # ================================================================================================= .PHONY: test-run-services test-run-services: - docker-compose -f tests/docker-compose.yml -p aiogram3-dev up -d + @#docker-compose -f tests/docker-compose.yml -p aiogram3-dev up -d .PHONY: test test: test-run-services diff --git a/README.rst b/README.rst index 7e0dd527..99b88c07 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ aiogram |beta badge| :target: https://pypi.python.org/pypi/aiogram :alt: Supported python versions -.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.5-blue.svg?logo=telegram +.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.7-blue.svg?logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API diff --git a/aiogram/__init__.py b/aiogram/__init__.py index a820ee6c..d734cb83 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -2,6 +2,7 @@ from .client import session from .client.bot import Bot from .dispatcher import filters, handler from .dispatcher.dispatcher import Dispatcher +from .dispatcher.flags.flag import FlagGenerator from .dispatcher.middlewares.base import BaseMiddleware from .dispatcher.router import Router from .utils.magic_filter import MagicFilter @@ -18,6 +19,7 @@ except ImportError: # pragma: no cover F = MagicFilter() html = _html_decoration md = _markdown_decoration +flags = FlagGenerator() __all__ = ( "__api_version__", @@ -34,7 +36,8 @@ __all__ = ( "F", "html", "md", + "flags", ) -__version__ = "3.0.0b1" -__api_version__ = "5.5" +__version__ = "3.0.0b2" +__api_version__ = "5.7" diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index d6378215..94feb7b8 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -311,7 +311,9 @@ class Bot(ContextInstanceMixin["Bot"]): if isinstance(file, str): file_id = file else: - file_id = getattr(file, "file_id", None) + # type is ignored in due to: + # Incompatible types in assignment (expression has type "Optional[Any]", variable has type "str") + file_id = getattr(file, "file_id", None) # type: ignore if file_id is None: raise TypeError("file can only be of the string or Downloadable type") @@ -533,6 +535,7 @@ class Bot(ContextInstanceMixin["Bot"]): entities: Optional[List[MessageEntity]] = None, disable_web_page_preview: Optional[bool] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -551,6 +554,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param entities: A JSON-serialized list of special entities that appear in message text, which can be specified instead of *parse_mode* :param disable_web_page_preview: Disables link previews for links in this message :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -564,6 +568,7 @@ class Bot(ContextInstanceMixin["Bot"]): entities=entities, disable_web_page_preview=disable_web_page_preview, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -576,6 +581,7 @@ class Bot(ContextInstanceMixin["Bot"]): from_chat_id: Union[int, str], message_id: int, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, request_timeout: Optional[int] = None, ) -> Message: """ @@ -587,6 +593,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param from_chat_id: Unique identifier for the chat where the original message was sent (or channel username in the format :code:`@channelusername`) :param message_id: Message identifier in the chat specified in *from_chat_id* :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the forwarded message from forwarding and saving :param request_timeout: Request timeout :return: On success, the sent Message is returned. """ @@ -595,6 +602,7 @@ class Bot(ContextInstanceMixin["Bot"]): from_chat_id=from_chat_id, message_id=message_id, disable_notification=disable_notification, + protect_content=protect_content, ) return await self(call, request_timeout=request_timeout) @@ -607,6 +615,7 @@ class Bot(ContextInstanceMixin["Bot"]): parse_mode: Optional[str] = UNSET, caption_entities: Optional[List[MessageEntity]] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -626,6 +635,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param parse_mode: Mode for parsing entities in the new caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the new caption, which can be specified instead of *parse_mode* :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -640,6 +650,7 @@ class Bot(ContextInstanceMixin["Bot"]): parse_mode=parse_mode, caption_entities=caption_entities, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -654,6 +665,7 @@ class Bot(ContextInstanceMixin["Bot"]): parse_mode: Optional[str] = UNSET, caption_entities: Optional[List[MessageEntity]] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -672,6 +684,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param parse_mode: Mode for parsing entities in the photo caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -685,6 +698,7 @@ class Bot(ContextInstanceMixin["Bot"]): parse_mode=parse_mode, caption_entities=caption_entities, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -703,6 +717,7 @@ class Bot(ContextInstanceMixin["Bot"]): title: Optional[str] = None, thumb: Optional[Union[InputFile, str]] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -726,6 +741,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param title: Track name :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป ` :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -743,6 +759,7 @@ class Bot(ContextInstanceMixin["Bot"]): title=title, thumb=thumb, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -759,6 +776,7 @@ class Bot(ContextInstanceMixin["Bot"]): caption_entities: Optional[List[MessageEntity]] = None, disable_content_type_detection: Optional[bool] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -779,6 +797,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* :param disable_content_type_detection: Disables automatic server-side content type detection for files uploaded using multipart/form-data :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -794,6 +813,7 @@ class Bot(ContextInstanceMixin["Bot"]): caption_entities=caption_entities, disable_content_type_detection=disable_content_type_detection, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -813,6 +833,7 @@ class Bot(ContextInstanceMixin["Bot"]): caption_entities: Optional[List[MessageEntity]] = None, supports_streaming: Optional[bool] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -836,6 +857,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* :param supports_streaming: Pass :code:`True`, if the uploaded video is suitable for streaming :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -854,6 +876,7 @@ class Bot(ContextInstanceMixin["Bot"]): caption_entities=caption_entities, supports_streaming=supports_streaming, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -872,6 +895,7 @@ class Bot(ContextInstanceMixin["Bot"]): parse_mode: Optional[str] = UNSET, caption_entities: Optional[List[MessageEntity]] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -894,6 +918,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param parse_mode: Mode for parsing entities in the animation caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -911,6 +936,7 @@ class Bot(ContextInstanceMixin["Bot"]): parse_mode=parse_mode, caption_entities=caption_entities, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -926,6 +952,7 @@ class Bot(ContextInstanceMixin["Bot"]): caption_entities: Optional[List[MessageEntity]] = None, duration: Optional[int] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -945,6 +972,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* :param duration: Duration of the voice message in seconds :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -959,6 +987,7 @@ class Bot(ContextInstanceMixin["Bot"]): caption_entities=caption_entities, duration=duration, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -973,6 +1002,7 @@ class Bot(ContextInstanceMixin["Bot"]): length: Optional[int] = None, thumb: Optional[Union[InputFile, str]] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -991,6 +1021,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param length: Video width and height, i.e. diameter of the video message :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป ` :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -1004,6 +1035,7 @@ class Bot(ContextInstanceMixin["Bot"]): length=length, thumb=thumb, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -1015,6 +1047,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id: Union[int, str], media: List[Union[InputMediaAudio, InputMediaDocument, InputMediaPhoto, InputMediaVideo]], disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, request_timeout: Optional[int] = None, @@ -1027,6 +1060,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) :param media: A JSON-serialized array describing messages to be sent, must include 2-10 items :param disable_notification: Sends messages `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent messages from forwarding and saving :param reply_to_message_id: If the messages are a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param request_timeout: Request timeout @@ -1036,6 +1070,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id=chat_id, media=media, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, ) @@ -1051,6 +1086,7 @@ class Bot(ContextInstanceMixin["Bot"]): heading: Optional[int] = None, proximity_alert_radius: Optional[int] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -1071,6 +1107,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param heading: For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified. :param proximity_alert_radius: For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified. :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -1086,6 +1123,7 @@ class Bot(ContextInstanceMixin["Bot"]): heading=heading, proximity_alert_radius=proximity_alert_radius, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -1177,6 +1215,7 @@ class Bot(ContextInstanceMixin["Bot"]): google_place_id: Optional[str] = None, google_place_type: Optional[str] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -1199,6 +1238,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param google_place_id: Google Places identifier of the venue :param google_place_type: Google Places type of the venue. (See `supported types `_.) :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -1216,6 +1256,7 @@ class Bot(ContextInstanceMixin["Bot"]): google_place_id=google_place_id, google_place_type=google_place_type, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -1230,6 +1271,7 @@ class Bot(ContextInstanceMixin["Bot"]): last_name: Optional[str] = None, vcard: Optional[str] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -1248,6 +1290,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param last_name: Contact's last name :param vcard: Additional data about the contact in the form of a `vCard `_, 0-2048 bytes :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove keyboard or to force a reply from the user. @@ -1261,6 +1304,7 @@ class Bot(ContextInstanceMixin["Bot"]): last_name=last_name, vcard=vcard, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -1283,6 +1327,7 @@ class Bot(ContextInstanceMixin["Bot"]): close_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None, is_closed: Optional[bool] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -1309,6 +1354,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param close_date: Point in time (Unix timestamp) when the poll will be automatically closed. Must be at least 5 and no more than 600 seconds in the future. Can't be used together with *open_period*. :param is_closed: Pass :code:`True`, if the poll needs to be immediately closed. This can be useful for poll preview. :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -1330,6 +1376,7 @@ class Bot(ContextInstanceMixin["Bot"]): close_date=close_date, is_closed=is_closed, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -1341,6 +1388,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id: Union[int, str], emoji: Optional[str] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -1356,6 +1404,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) :param emoji: Emoji on which the dice throw animation is based. Currently, must be one of '๐ŸŽฒ', '๐ŸŽฏ', '๐Ÿ€', 'โšฝ', '๐ŸŽณ', or '๐ŸŽฐ'. Dice can have values 1-6 for '๐ŸŽฒ', '๐ŸŽฏ' and '๐ŸŽณ', values 1-5 for '๐Ÿ€' and 'โšฝ', and values 1-64 for '๐ŸŽฐ'. Defaults to '๐ŸŽฒ' :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -1366,6 +1415,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id=chat_id, emoji=emoji, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -1517,7 +1567,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#unbanchatmember - :param chat_id: Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@username`) + :param chat_id: Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@channelusername`) :param user_id: Unique identifier of the target user :param only_if_banned: Do nothing if the user is not banned :param request_timeout: Request timeout @@ -2512,6 +2562,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id: Union[int, str], sticker: Union[InputFile, str], disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[ @@ -2520,13 +2571,14 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> Message: """ - Use this method to send static .WEBP or `animated `_ .TGS stickers. On success, the sent :class:`aiogram.types.message.Message` is returned. + Use this method to send static .WEBP, `animated `_ .TGS, or `video `_ .WEBM stickers. On success, the sent :class:`aiogram.types.message.Message` is returned. Source: https://core.telegram.org/bots/api#sendsticker :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) :param sticker: Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: Additional interface options. A JSON-serialized object for an `inline keyboard `_, `custom reply keyboard `_, instructions to remove reply keyboard or to force a reply from the user. @@ -2537,6 +2589,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id=chat_id, sticker=sticker, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -2592,12 +2645,13 @@ class Bot(ContextInstanceMixin["Bot"]): emojis: str, png_sticker: Optional[Union[InputFile, str]] = None, tgs_sticker: Optional[InputFile] = None, + webm_sticker: Optional[InputFile] = None, contains_masks: Optional[bool] = None, mask_position: Optional[MaskPosition] = None, request_timeout: Optional[int] = None, ) -> bool: """ - Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Returns :code:`True` on success. + Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#createnewstickerset @@ -2606,7 +2660,8 @@ class Bot(ContextInstanceMixin["Bot"]): :param title: Sticker set title, 1-64 characters :param emojis: One or more emoji corresponding to the sticker :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` - :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for technical requirements + :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements + :param webm_sticker: **WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements :param contains_masks: Pass :code:`True`, if a set of mask stickers should be created :param mask_position: A JSON-serialized object for position where the mask should be placed on faces :param request_timeout: Request timeout @@ -2619,6 +2674,7 @@ class Bot(ContextInstanceMixin["Bot"]): emojis=emojis, png_sticker=png_sticker, tgs_sticker=tgs_sticker, + webm_sticker=webm_sticker, contains_masks=contains_masks, mask_position=mask_position, ) @@ -2631,11 +2687,12 @@ class Bot(ContextInstanceMixin["Bot"]): emojis: str, png_sticker: Optional[Union[InputFile, str]] = None, tgs_sticker: Optional[InputFile] = None, + webm_sticker: Optional[InputFile] = None, mask_position: Optional[MaskPosition] = None, request_timeout: Optional[int] = None, ) -> bool: """ - Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success. + Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#addstickertoset @@ -2643,7 +2700,8 @@ class Bot(ContextInstanceMixin["Bot"]): :param name: Sticker set name :param emojis: One or more emoji corresponding to the sticker :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` - :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for technical requirements + :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements + :param webm_sticker: **WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements :param mask_position: A JSON-serialized object for position where the mask should be placed on faces :param request_timeout: Request timeout :return: Returns True on success. @@ -2654,6 +2712,7 @@ class Bot(ContextInstanceMixin["Bot"]): emojis=emojis, png_sticker=png_sticker, tgs_sticker=tgs_sticker, + webm_sticker=webm_sticker, mask_position=mask_position, ) return await self(call, request_timeout=request_timeout) @@ -2707,13 +2766,13 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> bool: """ - Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Returns :code:`True` on success. + Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Video thumbnails can be set only for video sticker sets only. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#setstickersetthumb :param name: Sticker set name :param user_id: User identifier of the sticker set owner - :param thumb: A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for animated sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `. Animated sticker set thumbnail can't be uploaded via HTTP URL. + :param thumb: A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `. Animated sticker set thumbnails can't be uploaded via HTTP URL. :param request_timeout: Request timeout :return: Returns True on success. """ @@ -2798,6 +2857,7 @@ class Bot(ContextInstanceMixin["Bot"]): send_email_to_provider: Optional[bool] = None, is_flexible: Optional[bool] = None, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, @@ -2831,6 +2891,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param send_email_to_provider: Pass :code:`True`, if user's email address should be sent to provider :param is_flexible: Pass :code:`True`, if the final price depends on the shipping method :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: A JSON-serialized object for an `inline keyboard `_. If empty, one 'Pay :code:`total price`' button will be shown. If not empty, the first button must be a Pay button. @@ -2861,6 +2922,7 @@ class Bot(ContextInstanceMixin["Bot"]): send_email_to_provider=send_email_to_provider, is_flexible=is_flexible, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, @@ -2960,6 +3022,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id: int, game_short_name: str, disable_notification: Optional[bool] = None, + protect_content: Optional[bool] = None, reply_to_message_id: Optional[int] = None, allow_sending_without_reply: Optional[bool] = None, reply_markup: Optional[InlineKeyboardMarkup] = None, @@ -2973,6 +3036,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat :param game_short_name: Short name of the game, serves as the unique identifier for the game. Set up your games via `Botfather `_. :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. + :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message :param allow_sending_without_reply: Pass :code:`True`, if the message should be sent even if the specified replied-to message is not found :param reply_markup: A JSON-serialized object for an `inline keyboard `_. If empty, one 'Play game_title' button will be shown. If not empty, the first button must launch the game. @@ -2983,6 +3047,7 @@ class Bot(ContextInstanceMixin["Bot"]): chat_id=chat_id, game_short_name=game_short_name, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, allow_sending_without_reply=allow_sending_without_reply, reply_markup=reply_markup, diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 04941bf4..610053db 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -16,8 +16,8 @@ from ..utils.backoff import Backoff, BackoffConfig from .event.bases import UNHANDLED, SkipHandler from .event.telegram import TelegramEventObserver from .fsm.middleware import FSMContextMiddleware -from .fsm.storage.base import BaseStorage -from .fsm.storage.memory import MemoryStorage +from .fsm.storage.base import BaseEventIsolation, BaseStorage +from .fsm.storage.memory import DisabledEventIsolation, MemoryStorage from .fsm.strategy import FSMStrategy from .middlewares.error import ErrorsMiddleware from .middlewares.user_context import UserContextMiddleware @@ -35,7 +35,7 @@ class Dispatcher(Router): self, storage: Optional[BaseStorage] = None, fsm_strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT, - isolate_events: bool = False, + events_isolation: Optional[BaseEventIsolation] = None, **kwargs: Any, ) -> None: super(Dispatcher, self).__init__(**kwargs) @@ -48,19 +48,22 @@ class Dispatcher(Router): ) self.update.register(self._listen_update) - # Error handlers should works is out of all other functions and be registered before all other middlewares + # Error handlers should work is out of all other functions and be registered before all others middlewares self.update.outer_middleware(ErrorsMiddleware(self)) + # User context middleware makes small optimization for all other builtin # middlewares via caching the user and chat instances in the event context self.update.outer_middleware(UserContextMiddleware()) + # FSM middleware should always be registered after User context middleware # because here is used context from previous step self.fsm = FSMContextMiddleware( storage=storage if storage else MemoryStorage(), strategy=fsm_strategy, - isolate_events=isolate_events, + events_isolation=events_isolation if events_isolation else DisabledEventIsolation(), ) self.update.outer_middleware(self.fsm) + self.shutdown.register(self.fsm.close) self._running_lock = Lock() @@ -104,7 +107,7 @@ class Dispatcher(Router): finally: finish_time = loop.time() duration = (finish_time - start_time) * 1000 - loggers.dispatcher.info( + loggers.event.info( "Update id=%s is %s. Duration %d ms by bot id=%d", update.update_id, "handled" if handled else "not handled", @@ -213,11 +216,11 @@ class Dispatcher(Router): try: await bot(result) except TelegramAPIError as e: - # In due to WebHook mechanism doesn't allows to get response for + # In due to WebHook mechanism doesn't allow getting response for # requests called in answer to WebHook request. # Need to skip unsuccessful responses. # For debugging here is added logging. - loggers.dispatcher.error("Failed to make answer: %s: %s", e.__class__.__name__, e) + loggers.event.error("Failed to make answer: %s: %s", e.__class__.__name__, e) async def _process_update( self, bot: Bot, update: Update, call_answer: bool = True, **kwargs: Any @@ -238,7 +241,7 @@ class Dispatcher(Router): return response is not UNHANDLED except Exception as e: - loggers.dispatcher.exception( + loggers.event.exception( "Cause exception while process update id=%d by bot id=%d\n%s: %s", update.update_id, bot.id, @@ -282,7 +285,7 @@ class Dispatcher(Router): try: return await self.feed_update(bot, update, **kwargs) except Exception as e: - loggers.dispatcher.exception( + loggers.event.exception( "Cause exception while process update id=%d by bot id=%d\n%s: %s", update.update_id, bot.id, diff --git a/aiogram/dispatcher/event/handler.py b/aiogram/dispatcher/event/handler.py index 7937d209..813ddf51 100644 --- a/aiogram/dispatcher/event/handler.py +++ b/aiogram/dispatcher/event/handler.py @@ -8,6 +8,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Type, from magic_filter import MagicFilter from aiogram.dispatcher.filters.base import BaseFilter +from aiogram.dispatcher.flags.getter import extract_flags_from_object from aiogram.dispatcher.handler.base import BaseHandler CallbackType = Callable[..., Awaitable[Any]] @@ -71,6 +72,7 @@ class HandlerObject(CallableMixin): callback = inspect.unwrap(self.callback) if inspect.isclass(callback) and issubclass(callback, BaseHandler): self.awaitable = True + self.flags.update(extract_flags_from_object(callback)) async def check(self, *args: Any, **kwargs: Any) -> Tuple[bool, Dict[str, Any]]: if not self.filters: diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py index 74f175ac..a38b57af 100644 --- a/aiogram/dispatcher/filters/__init__.py +++ b/aiogram/dispatcher/filters/__init__.py @@ -1,6 +1,21 @@ from typing import Dict, Tuple, Type from .base import BaseFilter +from .chat_member_updated import ( + ADMINISTRATOR, + CREATOR, + IS_ADMIN, + IS_MEMBER, + IS_NOT_MEMBER, + JOIN_TRANSITION, + KICKED, + LEAVE_TRANSITION, + LEFT, + MEMBER, + PROMOTED_TRANSITION, + RESTRICTED, + ChatMemberUpdatedFilter, +) from .command import Command, CommandObject from .content_types import ContentTypesFilter from .exception import ExceptionMessageFilter, ExceptionTypeFilter @@ -19,6 +34,19 @@ __all__ = ( "ExceptionTypeFilter", "StateFilter", "MagicData", + "ChatMemberUpdatedFilter", + "CREATOR", + "ADMINISTRATOR", + "MEMBER", + "RESTRICTED", + "LEFT", + "KICKED", + "IS_MEMBER", + "IS_ADMIN", + "PROMOTED_TRANSITION", + "IS_NOT_MEMBER", + "JOIN_TRANSITION", + "LEAVE_TRANSITION", ) _ALL_EVENTS_FILTERS: Tuple[Type[BaseFilter], ...] = (MagicData,) @@ -84,10 +112,12 @@ BUILTIN_FILTERS: Dict[str, Tuple[Type[BaseFilter], ...]] = { "my_chat_member": ( *_ALL_EVENTS_FILTERS, *_TELEGRAM_EVENTS_FILTERS, + ChatMemberUpdatedFilter, ), "chat_member": ( *_ALL_EVENTS_FILTERS, *_TELEGRAM_EVENTS_FILTERS, + ChatMemberUpdatedFilter, ), "chat_join_request": ( *_ALL_EVENTS_FILTERS, diff --git a/aiogram/dispatcher/filters/chat_member_updated.py b/aiogram/dispatcher/filters/chat_member_updated.py new file mode 100644 index 00000000..3f520cb4 --- /dev/null +++ b/aiogram/dispatcher/filters/chat_member_updated.py @@ -0,0 +1,179 @@ +from typing import Any, Dict, Optional, TypeVar, Union + +from aiogram.dispatcher.filters import BaseFilter +from aiogram.types import ChatMember, ChatMemberUpdated + +MarkerT = TypeVar("MarkerT", bound="_MemberStatusMarker") +MarkerGroupT = TypeVar("MarkerGroupT", bound="_MemberStatusGroupMarker") +TransitionT = TypeVar("TransitionT", bound="_MemberStatusTransition") + + +class _MemberStatusMarker: + def __init__(self, name: str, *, is_member: Optional[bool] = None) -> None: + self.name = name + self.is_member = is_member + + def __str__(self) -> str: + result = self.name.upper() + if self.is_member is not None: + result = ("+" if self.is_member else "-") + result + return result + + def __pos__(self: MarkerT) -> MarkerT: + return type(self)(name=self.name, is_member=True) + + def __neg__(self: MarkerT) -> MarkerT: + return type(self)(name=self.name, is_member=False) + + def __or__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusGroupMarker": + if isinstance(other, _MemberStatusMarker): + return _MemberStatusGroupMarker(self, other) + if isinstance(other, _MemberStatusGroupMarker): + return other | self + raise TypeError( + f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}" + ) + + __ror__ = __or__ + + def __rshift__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusTransition": + old = _MemberStatusGroupMarker(self) + if isinstance(other, _MemberStatusMarker): + return _MemberStatusTransition(old=old, new=_MemberStatusGroupMarker(other)) + if isinstance(other, _MemberStatusGroupMarker): + return _MemberStatusTransition(old=old, new=other) + raise TypeError( + f"unsupported operand type(s) for >>: {type(self).__name__!r} and {type(other).__name__!r}" + ) + + def __lshift__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusTransition": + new = _MemberStatusGroupMarker(self) + if isinstance(other, _MemberStatusMarker): + return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=new) + if isinstance(other, _MemberStatusGroupMarker): + return _MemberStatusTransition(old=other, new=new) + raise TypeError( + f"unsupported operand type(s) for <<: {type(self).__name__!r} and {type(other).__name__!r}" + ) + + def __hash__(self) -> int: + return hash((self.name, self.is_member)) + + def check(self, *, member: ChatMember) -> bool: + if self.is_member is not None and member.is_member != self.is_member: + return False + return self.name == member.status + + +class _MemberStatusGroupMarker: + def __init__(self, *statuses: _MemberStatusMarker) -> None: + if not statuses: + raise ValueError("Member status group should have at least one status included") + self.statuses = frozenset(statuses) + + def __or__( + self: MarkerGroupT, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> MarkerGroupT: + if isinstance(other, _MemberStatusMarker): + return type(self)(*self.statuses, other) + elif isinstance(other, _MemberStatusGroupMarker): + return type(self)(*self.statuses, *other.statuses) + raise TypeError( + f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}" + ) + + def __rshift__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusTransition": + if isinstance(other, _MemberStatusMarker): + return _MemberStatusTransition(old=self, new=_MemberStatusGroupMarker(other)) + if isinstance(other, _MemberStatusGroupMarker): + return _MemberStatusTransition(old=self, new=other) + raise TypeError( + f"unsupported operand type(s) for >>: {type(self).__name__!r} and {type(other).__name__!r}" + ) + + def __lshift__( + self, other: Union["_MemberStatusMarker", "_MemberStatusGroupMarker"] + ) -> "_MemberStatusTransition": + if isinstance(other, _MemberStatusMarker): + return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=self) + if isinstance(other, _MemberStatusGroupMarker): + return _MemberStatusTransition(old=other, new=self) + raise TypeError( + f"unsupported operand type(s) for <<: {type(self).__name__!r} and {type(other).__name__!r}" + ) + + def __str__(self) -> str: + result = " | ".join(map(str, sorted(self.statuses, key=str))) + if len(self.statuses) != 1: + return f"({result})" + return result + + def check(self, *, member: ChatMember) -> bool: + for status in self.statuses: + if status.check(member=member): + return True + return False + + +class _MemberStatusTransition: + def __init__(self, *, old: _MemberStatusGroupMarker, new: _MemberStatusGroupMarker) -> None: + self.old = old + self.new = new + + def __str__(self) -> str: + return f"{self.old} >> {self.new}" + + def __invert__(self: TransitionT) -> TransitionT: + return type(self)(old=self.new, new=self.old) + + def check(self, *, old: ChatMember, new: ChatMember) -> bool: + return self.old.check(member=old) and self.new.check(member=new) + + +CREATOR = _MemberStatusMarker("creator") +ADMINISTRATOR = _MemberStatusMarker("administrator") +MEMBER = _MemberStatusMarker("member") +RESTRICTED = _MemberStatusMarker("restricted") +LEFT = _MemberStatusMarker("left") +KICKED = _MemberStatusMarker("kicked") + +IS_MEMBER = CREATOR | ADMINISTRATOR | MEMBER | +RESTRICTED +IS_ADMIN = CREATOR | ADMINISTRATOR +IS_NOT_MEMBER = LEFT | KICKED | -RESTRICTED + +JOIN_TRANSITION = IS_NOT_MEMBER >> IS_MEMBER +LEAVE_TRANSITION = ~JOIN_TRANSITION +PROMOTED_TRANSITION = (MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR + + +class ChatMemberUpdatedFilter(BaseFilter): + member_status_changed: Union[ + _MemberStatusMarker, + _MemberStatusGroupMarker, + _MemberStatusTransition, + ] + """Accepts the status transition or new status of the member (see usage in docs)""" + + class Config: + arbitrary_types_allowed = True + + async def __call__(self, member_updated: ChatMemberUpdated) -> Union[bool, Dict[str, Any]]: + old = member_updated.old_chat_member + new = member_updated.new_chat_member + rule = self.member_status_changed + + if isinstance(rule, (_MemberStatusMarker, _MemberStatusGroupMarker)): + return rule.check(member=new) + if isinstance(rule, _MemberStatusTransition): + return rule.check(old=old, new=new) + + # Impossible variant in due to pydantic validation + return False # pragma: no cover diff --git a/aiogram/dispatcher/flags/__init__.py b/aiogram/dispatcher/flags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/aiogram/dispatcher/flags/flag.py b/aiogram/dispatcher/flags/flag.py new file mode 100644 index 00000000..845b898b --- /dev/null +++ b/aiogram/dispatcher/flags/flag.py @@ -0,0 +1,60 @@ +from dataclasses import dataclass +from typing import Any, Callable, Optional, Union, cast, overload + +from magic_filter import AttrDict + +from aiogram.dispatcher.flags.getter import extract_flags_from_object + + +@dataclass(frozen=True) +class Flag: + name: str + value: Any + + +@dataclass(frozen=True) +class FlagDecorator: + flag: Flag + + @classmethod + def _with_flag(cls, flag: Flag) -> "FlagDecorator": + return cls(flag) + + def _with_value(self, value: Any) -> "FlagDecorator": + new_flag = Flag(self.flag.name, value) + return self._with_flag(new_flag) + + @overload + def __call__(self, value: Callable[..., Any]) -> Callable[..., Any]: # type: ignore + pass + + @overload + def __call__(self, value: Any) -> "FlagDecorator": + pass + + @overload + def __call__(self, **kwargs: Any) -> "FlagDecorator": + pass + + def __call__( + self, + value: Optional[Any] = None, + **kwargs: Any, + ) -> Union[Callable[..., Any], "FlagDecorator"]: + if value and kwargs: + raise ValueError("The arguments `value` and **kwargs can not be used together") + + if value is not None and callable(value): + value.aiogram_flag = { + **extract_flags_from_object(value), + self.flag.name: self.flag.value, + } + return cast(Callable[..., Any], value) + return self._with_value(AttrDict(kwargs) if value is None else value) + + +class FlagGenerator: + def __getattr__(self, name: str) -> FlagDecorator: + if name[0] == "_": + raise AttributeError("Flag name must NOT start with underscore") + return FlagDecorator(Flag(name, True)) diff --git a/aiogram/dispatcher/flags/getter.py b/aiogram/dispatcher/flags/getter.py new file mode 100644 index 00000000..0c2c90dd --- /dev/null +++ b/aiogram/dispatcher/flags/getter.py @@ -0,0 +1,56 @@ +from typing import TYPE_CHECKING, Any, Dict, Optional, Union, cast + +from magic_filter import AttrDict, MagicFilter + +if TYPE_CHECKING: + from aiogram.dispatcher.event.handler import HandlerObject + + +def extract_flags_from_object(obj: Any) -> Dict[str, Any]: + if not hasattr(obj, "aiogram_flag"): + return {} + return cast(Dict[str, Any], obj.aiogram_flag) + + +def extract_flags(handler: Union["HandlerObject", Dict[str, Any]]) -> Dict[str, Any]: + """ + Extract flags from handler or middleware context data + + :param handler: handler object or data + :return: dictionary with all handler flags + """ + if isinstance(handler, dict) and "handler" in handler: + handler = handler["handler"] + if not hasattr(handler, "flags"): + return {} + return handler.flags # type: ignore + + +def get_flag( + handler: Union["HandlerObject", Dict[str, Any]], + name: str, + *, + default: Optional[Any] = None, +) -> Any: + """ + Get flag by name + + :param handler: handler object or data + :param name: name of the flag + :param default: default value (None) + :return: value of the flag or default + """ + flags = extract_flags(handler) + return flags.get(name, default) + + +def check_flags(handler: Union["HandlerObject", Dict[str, Any]], magic: MagicFilter) -> Any: + """ + Check flags via magic filter + + :param handler: handler object or data + :param magic: instance of the magic + :return: the result of magic filter check + """ + flags = extract_flags(handler) + return magic.resolve(AttrDict(flags)) diff --git a/aiogram/dispatcher/fsm/middleware.py b/aiogram/dispatcher/fsm/middleware.py index 8d59ff67..29db32ee 100644 --- a/aiogram/dispatcher/fsm/middleware.py +++ b/aiogram/dispatcher/fsm/middleware.py @@ -2,7 +2,12 @@ from typing import Any, Awaitable, Callable, Dict, Optional, cast from aiogram import Bot from aiogram.dispatcher.fsm.context import FSMContext -from aiogram.dispatcher.fsm.storage.base import DEFAULT_DESTINY, BaseStorage, StorageKey +from aiogram.dispatcher.fsm.storage.base import ( + DEFAULT_DESTINY, + BaseEventIsolation, + BaseStorage, + StorageKey, +) from aiogram.dispatcher.fsm.strategy import FSMStrategy, apply_strategy from aiogram.dispatcher.middlewares.base import BaseMiddleware from aiogram.types import TelegramObject @@ -12,12 +17,12 @@ class FSMContextMiddleware(BaseMiddleware): def __init__( self, storage: BaseStorage, + events_isolation: BaseEventIsolation, strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT, - isolate_events: bool = True, ) -> None: self.storage = storage self.strategy = strategy - self.isolate_events = isolate_events + self.events_isolation = events_isolation async def __call__( self, @@ -30,9 +35,8 @@ class FSMContextMiddleware(BaseMiddleware): data["fsm_storage"] = self.storage if context: data.update({"state": context, "raw_state": await context.get_state()}) - if self.isolate_events: - async with self.storage.lock(bot=bot, key=context.key): - return await handler(event, data) + async with self.events_isolation.lock(bot=bot, key=context.key): + return await handler(event, data) return await handler(event, data) def resolve_event_context( @@ -81,3 +85,7 @@ class FSMContextMiddleware(BaseMiddleware): destiny=destiny, ), ) + + async def close(self) -> None: + await self.storage.close() + await self.events_isolation.close() diff --git a/aiogram/dispatcher/fsm/storage/base.py b/aiogram/dispatcher/fsm/storage/base.py index f4830e0f..71d6ff16 100644 --- a/aiogram/dispatcher/fsm/storage/base.py +++ b/aiogram/dispatcher/fsm/storage/base.py @@ -24,19 +24,6 @@ class BaseStorage(ABC): Base class for all FSM storages """ - @abstractmethod - @asynccontextmanager - async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]: - """ - Isolate events with lock. - Will be used as context manager - - :param bot: instance of the current bot - :param key: storage key - :return: An async generator - """ - yield None - @abstractmethod async def set_state(self, bot: Bot, key: StorageKey, state: StateType = None) -> None: """ @@ -101,3 +88,22 @@ class BaseStorage(ABC): Close storage (database connection, file or etc.) """ pass + + +class BaseEventIsolation(ABC): + @abstractmethod + @asynccontextmanager + async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]: + """ + Isolate events with lock. + Will be used as context manager + + :param bot: instance of the current bot + :param key: storage key + :return: An async generator + """ + yield None + + @abstractmethod + async def close(self) -> None: + pass diff --git a/aiogram/dispatcher/fsm/storage/memory.py b/aiogram/dispatcher/fsm/storage/memory.py index 19b43fa9..b65b5d11 100644 --- a/aiogram/dispatcher/fsm/storage/memory.py +++ b/aiogram/dispatcher/fsm/storage/memory.py @@ -2,18 +2,22 @@ from asyncio import Lock from collections import defaultdict from contextlib import asynccontextmanager from dataclasses import dataclass, field -from typing import Any, AsyncGenerator, DefaultDict, Dict, Optional +from typing import Any, AsyncGenerator, DefaultDict, Dict, Hashable, Optional from aiogram import Bot from aiogram.dispatcher.fsm.state import State -from aiogram.dispatcher.fsm.storage.base import BaseStorage, StateType, StorageKey +from aiogram.dispatcher.fsm.storage.base import ( + BaseEventIsolation, + BaseStorage, + StateType, + StorageKey, +) @dataclass class MemoryStorageRecord: data: Dict[str, Any] = field(default_factory=dict) state: Optional[str] = None - lock: Lock = field(default_factory=Lock) class MemoryStorage(BaseStorage): @@ -34,11 +38,6 @@ class MemoryStorage(BaseStorage): async def close(self) -> None: pass - @asynccontextmanager - async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]: - async with self.storage[key].lock: - yield None - async def set_state(self, bot: Bot, key: StorageKey, state: StateType = None) -> None: self.storage[key].state = state.state if isinstance(state, State) else state @@ -50,3 +49,27 @@ class MemoryStorage(BaseStorage): async def get_data(self, bot: Bot, key: StorageKey) -> Dict[str, Any]: return self.storage[key].data.copy() + + +class DisabledEventIsolation(BaseEventIsolation): + @asynccontextmanager + async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]: + yield + + async def close(self) -> None: + pass + + +class SimpleEventIsolation(BaseEventIsolation): + def __init__(self) -> None: + # TODO: Unused locks cleaner is needed + self._locks: DefaultDict[Hashable, Lock] = defaultdict(Lock) + + @asynccontextmanager + async def lock(self, bot: Bot, key: StorageKey) -> AsyncGenerator[None, None]: + lock = self._locks[key] + async with lock: + yield + + async def close(self) -> None: + self._locks.clear() diff --git a/aiogram/dispatcher/fsm/storage/redis.py b/aiogram/dispatcher/fsm/storage/redis.py index 8828691f..5ab880de 100644 --- a/aiogram/dispatcher/fsm/storage/redis.py +++ b/aiogram/dispatcher/fsm/storage/redis.py @@ -6,7 +6,13 @@ from aioredis import ConnectionPool, Redis from aiogram import Bot from aiogram.dispatcher.fsm.state import State -from aiogram.dispatcher.fsm.storage.base import DEFAULT_DESTINY, BaseStorage, StateType, StorageKey +from aiogram.dispatcher.fsm.storage.base import ( + DEFAULT_DESTINY, + BaseEventIsolation, + BaseStorage, + StateType, + StorageKey, +) DEFAULT_REDIS_LOCK_KWARGS = {"timeout": 60} @@ -121,19 +127,12 @@ class RedisStorage(BaseStorage): redis = Redis(connection_pool=pool) return cls(redis=redis, **kwargs) + def create_isolation(self, **kwargs: Any) -> "RedisEventIsolation": + return RedisEventIsolation(redis=self.redis, key_builder=self.key_builder, **kwargs) + async def close(self) -> None: await self.redis.close() # type: ignore - @asynccontextmanager - async def lock( - self, - bot: Bot, - key: StorageKey, - ) -> AsyncGenerator[None, None]: - redis_key = self.key_builder.build(key, "lock") - async with self.redis.lock(name=redis_key, **self.lock_kwargs): - yield None - async def set_state( self, bot: Bot, @@ -146,8 +145,8 @@ class RedisStorage(BaseStorage): else: await self.redis.set( redis_key, - state.state if isinstance(state, State) else state, # type: ignore[arg-type] - ex=self.state_ttl, # type: ignore[arg-type] + cast(str, state.state if isinstance(state, State) else state), + ex=self.state_ttl, ) async def get_state( @@ -174,7 +173,7 @@ class RedisStorage(BaseStorage): await self.redis.set( redis_key, bot.session.json_dumps(data), - ex=self.data_ttl, # type: ignore[arg-type] + ex=self.data_ttl, ) async def get_data( @@ -189,3 +188,43 @@ class RedisStorage(BaseStorage): if isinstance(value, bytes): value = value.decode("utf-8") return cast(Dict[str, Any], bot.session.json_loads(value)) + + +class RedisEventIsolation(BaseEventIsolation): + def __init__( + self, + redis: Redis, + key_builder: Optional[KeyBuilder] = None, + lock_kwargs: Optional[Dict[str, Any]] = None, + ) -> None: + if key_builder is None: + key_builder = DefaultKeyBuilder() + self.redis = redis + self.key_builder = key_builder + self.lock_kwargs = lock_kwargs or {} + + @classmethod + def from_url( + cls, + url: str, + connection_kwargs: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> "RedisEventIsolation": + if connection_kwargs is None: + connection_kwargs = {} + pool = ConnectionPool.from_url(url, **connection_kwargs) + redis = Redis(connection_pool=pool) + return cls(redis=redis, **kwargs) + + @asynccontextmanager + async def lock( + self, + bot: Bot, + key: StorageKey, + ) -> AsyncGenerator[None, None]: + redis_key = self.key_builder.build(key, "lock") + async with self.redis.lock(name=redis_key, **self.lock_kwargs): + yield None + + async def close(self) -> None: + pass diff --git a/aiogram/dispatcher/webhook/aiohttp_server.py b/aiogram/dispatcher/webhook/aiohttp_server.py index 105e0b38..a8d084f8 100644 --- a/aiogram/dispatcher/webhook/aiohttp_server.py +++ b/aiogram/dispatcher/webhook/aiohttp_server.py @@ -134,7 +134,7 @@ class BaseRequestHandler(ABC): bot=bot, update=await request.json(loads=bot.session.json_loads) ) ) - return web.json_response({}) + return web.json_response({}, dumps=bot.session.json_dumps) async def _handle_request(self, bot: Bot, request: web.Request) -> web.Response: result = await self.dispatcher.feed_webhook_update( @@ -143,8 +143,8 @@ class BaseRequestHandler(ABC): **self.data, ) if result: - return web.json_response(result) - return web.json_response({}) + return web.json_response(result, dumps=bot.session.json_dumps) + return web.json_response({}, dumps=bot.session.json_dumps) async def handle(self, request: web.Request) -> web.Response: bot = await self.resolve_bot(request) diff --git a/aiogram/loggers.py b/aiogram/loggers.py index eec44216..ae871eaf 100644 --- a/aiogram/loggers.py +++ b/aiogram/loggers.py @@ -1,5 +1,6 @@ import logging dispatcher = logging.getLogger("aiogram.dispatcher") +event = logging.getLogger("aiogram.event") middlewares = logging.getLogger("aiogram.middlewares") webhook = logging.getLogger("aiogram.webhook") diff --git a/aiogram/methods/add_sticker_to_set.py b/aiogram/methods/add_sticker_to_set.py index 43499324..7b676674 100644 --- a/aiogram/methods/add_sticker_to_set.py +++ b/aiogram/methods/add_sticker_to_set.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class AddStickerToSet(TelegramMethod[bool]): """ - Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success. + Use this method to add a new sticker to a set created by the bot. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Animated stickers can be added to animated sticker sets and only to them. Animated sticker sets can have up to 50 stickers. Static sticker sets can have up to 120 stickers. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#addstickertoset """ @@ -27,15 +27,18 @@ class AddStickerToSet(TelegramMethod[bool]): png_sticker: Optional[Union[InputFile, str]] = None """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" tgs_sticker: Optional[InputFile] = None - """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for technical requirements""" + """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements""" + webm_sticker: Optional[InputFile] = None + """**WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements""" mask_position: Optional[MaskPosition] = None """A JSON-serialized object for position where the mask should be placed on faces""" def build_request(self, bot: Bot) -> Request: - data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker"}) + data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker", "webm_sticker"}) files: Dict[str, InputFile] = {} prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker) prepare_file(data=data, files=files, name="tgs_sticker", value=self.tgs_sticker) + prepare_file(data=data, files=files, name="webm_sticker", value=self.webm_sticker) return Request(method="addStickerToSet", data=data, files=files) diff --git a/aiogram/methods/copy_message.py b/aiogram/methods/copy_message.py index 3c90baee..9a5cddae 100644 --- a/aiogram/methods/copy_message.py +++ b/aiogram/methods/copy_message.py @@ -40,6 +40,8 @@ class CopyMessage(TelegramMethod[MessageId]): """A JSON-serialized list of special entities that appear in the new caption, which can be specified instead of *parse_mode*""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/create_new_sticker_set.py b/aiogram/methods/create_new_sticker_set.py index baa29ad8..5c807963 100644 --- a/aiogram/methods/create_new_sticker_set.py +++ b/aiogram/methods/create_new_sticker_set.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class CreateNewStickerSet(TelegramMethod[bool]): """ - Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You **must** use exactly one of the fields *png_sticker* or *tgs_sticker*. Returns :code:`True` on success. + Use this method to create a new sticker set owned by a user. The bot will be able to edit the sticker set thus created. You **must** use exactly one of the fields *png_sticker*, *tgs_sticker*, or *webm_sticker*. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#createnewstickerset """ @@ -29,17 +29,20 @@ class CreateNewStickerSet(TelegramMethod[bool]): png_sticker: Optional[Union[InputFile, str]] = None """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" tgs_sticker: Optional[InputFile] = None - """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for technical requirements""" + """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements""" + webm_sticker: Optional[InputFile] = None + """**WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements""" contains_masks: Optional[bool] = None """Pass :code:`True`, if a set of mask stickers should be created""" mask_position: Optional[MaskPosition] = None """A JSON-serialized object for position where the mask should be placed on faces""" def build_request(self, bot: Bot) -> Request: - data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker"}) + data: Dict[str, Any] = self.dict(exclude={"png_sticker", "tgs_sticker", "webm_sticker"}) files: Dict[str, InputFile] = {} prepare_file(data=data, files=files, name="png_sticker", value=self.png_sticker) prepare_file(data=data, files=files, name="tgs_sticker", value=self.tgs_sticker) + prepare_file(data=data, files=files, name="webm_sticker", value=self.webm_sticker) return Request(method="createNewStickerSet", data=data, files=files) diff --git a/aiogram/methods/forward_message.py b/aiogram/methods/forward_message.py index 6ea5b233..ec4e7623 100644 --- a/aiogram/methods/forward_message.py +++ b/aiogram/methods/forward_message.py @@ -26,6 +26,8 @@ class ForwardMessage(TelegramMethod[Message]): """Message identifier in the chat specified in *from_chat_id*""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the forwarded message from forwarding and saving""" def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() diff --git a/aiogram/methods/send_animation.py b/aiogram/methods/send_animation.py index 1f0971f6..97b483a4 100644 --- a/aiogram/methods/send_animation.py +++ b/aiogram/methods/send_animation.py @@ -47,6 +47,8 @@ class SendAnimation(TelegramMethod[Message]): """A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_audio.py b/aiogram/methods/send_audio.py index 8b61e75b..16b24723 100644 --- a/aiogram/methods/send_audio.py +++ b/aiogram/methods/send_audio.py @@ -48,6 +48,8 @@ class SendAudio(TelegramMethod[Message]): """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_contact.py b/aiogram/methods/send_contact.py index 182654d2..d0cb4ccb 100644 --- a/aiogram/methods/send_contact.py +++ b/aiogram/methods/send_contact.py @@ -36,6 +36,8 @@ class SendContact(TelegramMethod[Message]): """Additional data about the contact in the form of a `vCard `_, 0-2048 bytes""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_dice.py b/aiogram/methods/send_dice.py index be3d567c..dd844534 100644 --- a/aiogram/methods/send_dice.py +++ b/aiogram/methods/send_dice.py @@ -30,6 +30,8 @@ class SendDice(TelegramMethod[Message]): """Emoji on which the dice throw animation is based. Currently, must be one of '๐ŸŽฒ', '๐ŸŽฏ', '๐Ÿ€', 'โšฝ', '๐ŸŽณ', or '๐ŸŽฐ'. Dice can have values 1-6 for '๐ŸŽฒ', '๐ŸŽฏ' and '๐ŸŽณ', values 1-5 for '๐Ÿ€' and 'โšฝ', and values 1-64 for '๐ŸŽฐ'. Defaults to '๐ŸŽฒ'""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_document.py b/aiogram/methods/send_document.py index 9fff7d5e..17f38dfb 100644 --- a/aiogram/methods/send_document.py +++ b/aiogram/methods/send_document.py @@ -43,6 +43,8 @@ class SendDocument(TelegramMethod[Message]): """Disables automatic server-side content type detection for files uploaded using multipart/form-data""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_game.py b/aiogram/methods/send_game.py index d4957707..ba430845 100644 --- a/aiogram/methods/send_game.py +++ b/aiogram/methods/send_game.py @@ -24,6 +24,8 @@ class SendGame(TelegramMethod[Message]): """Short name of the game, serves as the unique identifier for the game. Set up your games via `Botfather `_.""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_invoice.py b/aiogram/methods/send_invoice.py index 88a1bc5a..95c61ef6 100644 --- a/aiogram/methods/send_invoice.py +++ b/aiogram/methods/send_invoice.py @@ -64,6 +64,8 @@ class SendInvoice(TelegramMethod[Message]): """Pass :code:`True`, if the final price depends on the shipping method""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_location.py b/aiogram/methods/send_location.py index 31385884..225f23eb 100644 --- a/aiogram/methods/send_location.py +++ b/aiogram/methods/send_location.py @@ -40,6 +40,8 @@ class SendLocation(TelegramMethod[Message]): """For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified.""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_media_group.py b/aiogram/methods/send_media_group.py index 6a35e934..7d5b4502 100644 --- a/aiogram/methods/send_media_group.py +++ b/aiogram/methods/send_media_group.py @@ -31,6 +31,8 @@ class SendMediaGroup(TelegramMethod[List[Message]]): """A JSON-serialized array describing messages to be sent, must include 2-10 items""" disable_notification: Optional[bool] = None """Sends messages `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent messages from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the messages are a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_message.py b/aiogram/methods/send_message.py index bd7bfee0..d0269317 100644 --- a/aiogram/methods/send_message.py +++ b/aiogram/methods/send_message.py @@ -38,6 +38,8 @@ class SendMessage(TelegramMethod[Message]): """Disables link previews for links in this message""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_photo.py b/aiogram/methods/send_photo.py index 82eb06ac..faf9353f 100644 --- a/aiogram/methods/send_photo.py +++ b/aiogram/methods/send_photo.py @@ -39,6 +39,8 @@ class SendPhoto(TelegramMethod[Message]): """A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode*""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_poll.py b/aiogram/methods/send_poll.py index 11e0eec2..85455b0d 100644 --- a/aiogram/methods/send_poll.py +++ b/aiogram/methods/send_poll.py @@ -55,6 +55,8 @@ class SendPoll(TelegramMethod[Message]): """Pass :code:`True`, if the poll needs to be immediately closed. This can be useful for poll preview.""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_sticker.py b/aiogram/methods/send_sticker.py index c4435e77..573040e1 100644 --- a/aiogram/methods/send_sticker.py +++ b/aiogram/methods/send_sticker.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: class SendSticker(TelegramMethod[Message]): """ - Use this method to send static .WEBP or `animated `_ .TGS stickers. On success, the sent :class:`aiogram.types.message.Message` is returned. + Use this method to send static .WEBP, `animated `_ .TGS, or `video `_ .WEBM stickers. On success, the sent :class:`aiogram.types.message.Message` is returned. Source: https://core.telegram.org/bots/api#sendsticker """ @@ -31,6 +31,8 @@ class SendSticker(TelegramMethod[Message]): """Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_venue.py b/aiogram/methods/send_venue.py index dc62b2d0..df708ff8 100644 --- a/aiogram/methods/send_venue.py +++ b/aiogram/methods/send_venue.py @@ -44,6 +44,8 @@ class SendVenue(TelegramMethod[Message]): """Google Places type of the venue. (See `supported types `_.)""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_video.py b/aiogram/methods/send_video.py index 6867f37b..4ed09cf1 100644 --- a/aiogram/methods/send_video.py +++ b/aiogram/methods/send_video.py @@ -49,6 +49,8 @@ class SendVideo(TelegramMethod[Message]): """Pass :code:`True`, if the uploaded video is suitable for streaming""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_video_note.py b/aiogram/methods/send_video_note.py index 99e3651a..7431d582 100644 --- a/aiogram/methods/send_video_note.py +++ b/aiogram/methods/send_video_note.py @@ -37,6 +37,8 @@ class SendVideoNote(TelegramMethod[Message]): """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/send_voice.py b/aiogram/methods/send_voice.py index c991c12f..e753e9e1 100644 --- a/aiogram/methods/send_voice.py +++ b/aiogram/methods/send_voice.py @@ -41,6 +41,8 @@ class SendVoice(TelegramMethod[Message]): """Duration of the voice message in seconds""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" + protect_content: Optional[bool] = None + """Protects the contents of the sent message from forwarding and saving""" reply_to_message_id: Optional[int] = None """If the message is a reply, ID of the original message""" allow_sending_without_reply: Optional[bool] = None diff --git a/aiogram/methods/set_sticker_set_thumb.py b/aiogram/methods/set_sticker_set_thumb.py index 5ab66cd5..49ef7971 100644 --- a/aiogram/methods/set_sticker_set_thumb.py +++ b/aiogram/methods/set_sticker_set_thumb.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class SetStickerSetThumb(TelegramMethod[bool]): """ - Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Returns :code:`True` on success. + Use this method to set the thumbnail of a sticker set. Animated thumbnails can be set for animated sticker sets only. Video thumbnails can be set only for video sticker sets only. Returns :code:`True` on success. Source: https://core.telegram.org/bots/api#setstickersetthumb """ @@ -23,7 +23,7 @@ class SetStickerSetThumb(TelegramMethod[bool]): user_id: int """User identifier of the sticker set owner""" thumb: Optional[Union[InputFile, str]] = None - """A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/animated_stickers#technical-requirements `_`https://core.telegram.org/animated_stickers#technical-requirements `_ for animated sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `. Animated sticker set thumbnail can't be uploaded via HTTP URL.""" + """A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `. Animated sticker set thumbnails can't be uploaded via HTTP URL.""" def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"thumb"}) diff --git a/aiogram/methods/unban_chat_member.py b/aiogram/methods/unban_chat_member.py index e9938a84..3fce083b 100644 --- a/aiogram/methods/unban_chat_member.py +++ b/aiogram/methods/unban_chat_member.py @@ -18,7 +18,7 @@ class UnbanChatMember(TelegramMethod[bool]): __returning__ = bool chat_id: Union[int, str] - """Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@username`)""" + """Unique identifier for the target group or username of the target supergroup or channel (in the format :code:`@channelusername`)""" user_id: int """Unique identifier of the target user""" only_if_banned: Optional[bool] = None diff --git a/aiogram/types/bot_command.py b/aiogram/types/bot_command.py index 844abffb..1bf65eba 100644 --- a/aiogram/types/bot_command.py +++ b/aiogram/types/bot_command.py @@ -11,6 +11,6 @@ class BotCommand(MutableTelegramObject): """ command: str - """Text of the command, 1-32 characters. Can contain only lowercase English letters, digits and underscores.""" + """Text of the command; 1-32 characters. Can contain only lowercase English letters, digits and underscores.""" description: str - """Description of the command, 3-256 characters.""" + """Description of the command; 1-256 characters.""" diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py index 018bebda..b3d1419c 100644 --- a/aiogram/types/chat_member.py +++ b/aiogram/types/chat_member.py @@ -1,7 +1,13 @@ from __future__ import annotations +import datetime +from typing import TYPE_CHECKING, Optional, Union + from .base import TelegramObject +if TYPE_CHECKING: + from .user import User + class ChatMember(TelegramObject): """ @@ -16,3 +22,48 @@ class ChatMember(TelegramObject): Source: https://core.telegram.org/bots/api#chatmember """ + + status: str + """...""" + user: Optional[User] = None + """*Optional*. Information about the user""" + is_anonymous: Optional[bool] = None + """*Optional*. :code:`True`, if the user's presence in the chat is hidden""" + custom_title: Optional[str] = None + """*Optional*. Custom title for this user""" + can_be_edited: Optional[bool] = None + """*Optional*. :code:`True`, if the bot is allowed to edit administrator privileges of that user""" + can_manage_chat: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege""" + can_delete_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can delete messages of other users""" + can_manage_voice_chats: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can manage voice chats""" + can_restrict_members: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can restrict, ban or unban chat members""" + can_promote_members: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by the user)""" + can_change_info: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to change the chat title, photo and other settings""" + can_invite_users: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to invite new users to the chat""" + can_post_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can post in the channel; channels only""" + can_edit_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can edit messages of other users and can pin messages; channels only""" + can_pin_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to pin messages; groups and supergroups only""" + is_member: Optional[bool] = None + """*Optional*. :code:`True`, if the user is a member of the chat at the moment of the request""" + can_send_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to send text messages, contacts, locations and venues""" + can_send_media_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes""" + can_send_polls: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to send polls""" + can_send_other_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to send animations, games, stickers and use inline bots""" + can_add_web_page_previews: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to add web page previews to their messages""" + until_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None + """*Optional*. Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever""" diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 81ea168a..24435025 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -95,7 +95,7 @@ class Message(TelegramObject): forward_from_message_id: Optional[int] = None """*Optional*. For messages forwarded from channels, identifier of the original message in the channel""" forward_signature: Optional[str] = None - """*Optional*. For messages forwarded from channels, signature of the post author if present""" + """*Optional*. For forwarded messages that were originally sent in channels or by an anonymous chat administrator, signature of the message sender if present""" forward_sender_name: Optional[str] = None """*Optional*. Sender's name for messages forwarded from users who disallow adding a link to their account in forwarded messages""" forward_date: Optional[int] = None diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index ddaac506..28226286 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -18,7 +18,7 @@ class MessageEntity(MutableTelegramObject): """ type: str - """Type of the entity. Can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag`), 'cashtag' (:code:`$USD`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames `_)""" + """Type of the entity. Currently, can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag`), 'cashtag' (:code:`$USD`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames `_)""" offset: int """Offset in UTF-16 code units to the start of the entity""" length: int diff --git a/aiogram/utils/chat_action.py b/aiogram/utils/chat_action.py new file mode 100644 index 00000000..2a9dddcc --- /dev/null +++ b/aiogram/utils/chat_action.py @@ -0,0 +1,347 @@ +import asyncio +import logging +import time +from asyncio import Event, Lock +from contextlib import suppress +from types import TracebackType +from typing import Any, Awaitable, Callable, Dict, Optional, Type, Union + +from aiogram import BaseMiddleware, Bot +from aiogram.dispatcher.flags.getter import get_flag +from aiogram.types import Message, TelegramObject + +logger = logging.getLogger(__name__) +DEFAULT_INTERVAL = 5.0 +DEFAULT_INITIAL_SLEEP = 0.1 + + +class ChatActionSender: + """ + This utility helps to automatically send chat action until long actions is done + to take acknowledge bot users the bot is doing something and not crashed. + + Provides simply to use context manager. + + Technically sender start background task with infinity loop which works + until action will be finished and sends the `chat action `_ + every 5 seconds. + """ + + def __init__( + self, + *, + chat_id: Union[str, int], + action: str = "typing", + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + bot: Optional[Bot] = None, + ) -> None: + """ + :param chat_id: target chat id + :param action: chat action type + :param interval: interval between iterations + :param initial_sleep: sleep before first iteration + :param bot: instance of the bot, can be omitted from the context + """ + if bot is None: + bot = Bot.get_current(False) + + self.chat_id = chat_id + self.action = action + self.interval = interval + self.initial_sleep = initial_sleep + self.bot = bot + + self._lock = Lock() + self._close_event = Event() + self._closed_event = Event() + self._task: Optional[asyncio.Task[Any]] = None + + @property + def running(self) -> bool: + return bool(self._task) + + async def _wait(self, interval: float) -> None: + with suppress(asyncio.TimeoutError): + await asyncio.wait_for(self._close_event.wait(), interval) + + async def _worker(self) -> None: + logger.debug( + "Started chat action %r sender in chat_id=%s via bot id=%d", + self.action, + self.chat_id, + self.bot.id, + ) + try: + counter = 0 + await self._wait(self.initial_sleep) + while not self._close_event.is_set(): + start = time.monotonic() + logger.debug( + "Sent chat action %r to chat_id=%s via bot %d (already sent actions %d)", + self.action, + self.chat_id, + self.bot.id, + counter, + ) + await self.bot.send_chat_action(chat_id=self.chat_id, action=self.action) + counter += 1 + + interval = self.interval - (time.monotonic() - start) + await self._wait(interval) + finally: + logger.debug( + "Finished chat action %r sender in chat_id=%s via bot id=%d", + self.action, + self.chat_id, + self.bot.id, + ) + self._closed_event.set() + + async def _run(self) -> None: + async with self._lock: + self._close_event.clear() + self._closed_event.clear() + if self.running: + raise RuntimeError("Already running") + self._task = asyncio.create_task(self._worker()) + + async def _stop(self) -> None: + async with self._lock: + if not self.running: + return + if not self._close_event.is_set(): + self._close_event.set() + await self._closed_event.wait() + self._task = None + + async def __aenter__(self) -> "ChatActionSender": + await self._run() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> Any: + await self._stop() + + @classmethod + def typing( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `typing` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="typing", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def upload_photo( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `upload_photo` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="upload_photo", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def record_video( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `record_video` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="record_video", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def upload_video( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `upload_video` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="upload_video", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def record_voice( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `record_voice` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="record_voice", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def upload_voice( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `upload_voice` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="upload_voice", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def upload_document( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `upload_document` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="upload_document", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def choose_sticker( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `choose_sticker` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="choose_sticker", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def find_location( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `find_location` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="find_location", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def record_video_note( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `record_video_note` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="record_video_note", + interval=interval, + initial_sleep=initial_sleep, + ) + + @classmethod + def upload_video_note( + cls, + chat_id: Union[int, str], + bot: Optional[Bot] = None, + interval: float = DEFAULT_INTERVAL, + initial_sleep: float = DEFAULT_INITIAL_SLEEP, + ) -> "ChatActionSender": + """Create instance of the sender with `upload_video_note` action""" + return cls( + bot=bot, + chat_id=chat_id, + action="upload_video_note", + interval=interval, + initial_sleep=initial_sleep, + ) + + +class ChatActionMiddleware(BaseMiddleware): + """ + Helps to automatically use chat action sender for all message handlers + """ + + async def __call__( + self, + handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: Dict[str, Any], + ) -> Any: + if not isinstance(event, Message): + return await handler(event, data) + bot = data["bot"] + + chat_action = get_flag(data, "chat_action") or "typing" + kwargs = {} + if isinstance(chat_action, dict): + if initial_sleep := chat_action.get("initial_sleep"): + kwargs["initial_sleep"] = initial_sleep + if interval := chat_action.get("interval"): + kwargs["interval"] = interval + if action := chat_action.get("action"): + kwargs["action"] = action + elif isinstance(chat_action, bool): + kwargs["action"] = "typing" + else: + kwargs["action"] = chat_action + async with ChatActionSender(bot=bot, chat_id=event.chat.id, **kwargs): + return await handler(event, data) diff --git a/aiogram/utils/i18n/context.py b/aiogram/utils/i18n/context.py index 13de4d9c..245fee34 100644 --- a/aiogram/utils/i18n/context.py +++ b/aiogram/utils/i18n/context.py @@ -16,7 +16,7 @@ def gettext(*args: Any, **kwargs: Any) -> str: def lazy_gettext(*args: Any, **kwargs: Any) -> LazyProxy: - return LazyProxy(gettext, *args, **kwargs) + return LazyProxy(gettext, *args, **kwargs, enable_cache=False) ngettext = gettext diff --git a/aiogram/utils/i18n/core.py b/aiogram/utils/i18n/core.py index d564fdb3..830ead54 100644 --- a/aiogram/utils/i18n/core.py +++ b/aiogram/utils/i18n/core.py @@ -118,4 +118,6 @@ class I18n(ContextInstanceMixin["I18n"]): 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) + return LazyProxy( + self.gettext, singular=singular, plural=plural, n=n, locale=locale, enable_cache=False + ) diff --git a/aiogram/utils/i18n/middleware.py b/aiogram/utils/i18n/middleware.py index 2f6a73c2..dabbfae8 100644 --- a/aiogram/utils/i18n/middleware.py +++ b/aiogram/utils/i18n/middleware.py @@ -2,10 +2,14 @@ from abc import ABC, abstractmethod from typing import Any, Awaitable, Callable, Dict, Optional, Set, cast try: - from babel import Locale + from babel import Locale, UnknownLocaleError except ImportError: # pragma: no cover Locale = None + class UnknownLocaleError(Exception): # type: ignore + pass + + from aiogram import BaseMiddleware, Router from aiogram.dispatcher.fsm.context import FSMContext from aiogram.types import TelegramObject, User @@ -116,7 +120,11 @@ class SimpleI18nMiddleware(I18nMiddleware): event_from_user: Optional[User] = data.get("event_from_user", None) if event_from_user is None: return self.i18n.default_locale - locale = Locale.parse(event_from_user.language_code, sep="-") + try: + locale = Locale.parse(event_from_user.language_code, sep="-") + except UnknownLocaleError: + return self.i18n.default_locale + if locale.language not in self.i18n.available_locales: return self.i18n.default_locale return cast(str, locale.language) diff --git a/aiogram/utils/mixins.py b/aiogram/utils/mixins.py index 80f5afe9..86b3ed84 100644 --- a/aiogram/utils/mixins.py +++ b/aiogram/utils/mixins.py @@ -1,7 +1,7 @@ from __future__ import annotations import contextvars -from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generic, Optional, TypeVar, cast, overload +from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, TypeVar, cast, overload if TYPE_CHECKING: from typing_extensions import Literal @@ -38,7 +38,7 @@ ContextInstance = TypeVar("ContextInstance") class ContextInstanceMixin(Generic[ContextInstance]): - __context_instance: ClassVar[contextvars.ContextVar[ContextInstance]] + __context_instance: contextvars.ContextVar[ContextInstance] def __init_subclass__(cls, **kwargs: Any) -> None: super().__init_subclass__() diff --git a/docs/dispatcher/filters/chat_member_updated.rst b/docs/dispatcher/filters/chat_member_updated.rst new file mode 100644 index 00000000..d02313f4 --- /dev/null +++ b/docs/dispatcher/filters/chat_member_updated.rst @@ -0,0 +1,103 @@ +================= +ChatMemberUpdated +================= + +.. autoclass:: aiogram.dispatcher.filters.chat_member_updated.ChatMemberUpdatedFilter + :members: + :member-order: bysource + :undoc-members: False + +You can import from :code:`aiogram.dispatcher.filters` all available +variants of `statuses`_, `status groups`_ or `transitions`_: + +Statuses +======== + ++-------------------------+--------------------------------------+ +| name | Description | ++=========================+======================================+ +| :code:`CREATOR` | Chat owner | ++-------------------------+--------------------------------------+ +| :code:`ADMINISTRATOR` | Chat administrator | ++-------------------------+--------------------------------------+ +| :code:`MEMBER` | Member of the chat | ++-------------------------+--------------------------------------+ +| :code:`RESTRICTED` | Restricted user (can be not member) | ++-------------------------+--------------------------------------+ +| :code:`LEFT` | Isn't member of the chat | ++-------------------------+--------------------------------------+ +| :code:`KICKED` | Kicked member by administrators | ++-------------------------+--------------------------------------+ + +Statuses can be extended with `is_member` flag by prefixing with +:code:`+` (for :code:`is_member == True)` or :code:`-` (for :code:`is_member == False`) symbol, +like :code:`+RESTRICTED` or :code:`-RESTRICTED` + +Status groups +============= + +The particular statuses can be combined via bitwise :code:`or` operator, like :code:`CREATOR | ADMINISTRATOR` + ++-------------------------+-----------------------------------------------------------------------------------+ +| name | Description | ++=========================+===================================================================================+ +| :code:`IS_MEMBER` | Combination of :code:`(CREATOR | ADMINISTRATOR | MEMBER | +RESTRICTED)` statuses. | ++-------------------------+-----------------------------------------------------------------------------------+ +| :code:`IS_ADMIN` | Combination of :code:`(CREATOR | ADMINISTRATOR)` statuses. | ++-------------------------+-----------------------------------------------------------------------------------+ +| :code:`IS_NOT_MEMBER` | Combination of :code:`(LEFT | KICKED | -RESTRICTED)` statuses. | ++-------------------------+-----------------------------------------------------------------------------------+ + +Transitions +=========== + +Transitions can be defined via bitwise shift operators :code:`>>` and :code:`<<`. +Old chat member status should be defined in the left side for :code:`>>` operator (right side for :code:`<<`) +and new status should be specified on the right side for :code:`>>` operator (left side for :code:`<<`) + +The direction of transition can be changed via bitwise inversion operator: :code:`~JOIN_TRANSITION` +will produce swap of old and new statuses. + ++-----------------------------+-----------------------------------------------------------------------+ +| name | Description | ++=============================+=======================================================================+ +| :code:`JOIN_TRANSITION` | Means status changed from :code:`IS_NOT_MEMBER` to :code:`IS_MEMBER` | +| | (:code:`IS_NOT_MEMBER >> IS_MEMBER`) | ++-----------------------------+-----------------------------------------------------------------------+ +| :code:`LEAVE_TRANSITION` | Means status changed from :code:`IS_MEMBER` to :code:`IS_NOT_MEMBER` | +| | (:code:`~JOIN_TRANSITION`) | ++-----------------------------+-----------------------------------------------------------------------+ +| :code:`PROMOTED_TRANSITION` | Means status changed from | +| | :code:`(MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR` | +| | (:code:`(MEMBER | RESTRICTED | LEFT | KICKED) >> ADMINISTRATOR`) | ++-----------------------------+-----------------------------------------------------------------------+ + +.. note:: + + Note that if you define the status unions (via :code:`|`) you will need to add brackets for the statement + before use shift operator in due to operator priorities. + +Usage +===== + +Handle user leave or join events + +.. code-block:: python + + from aiogram.dispatcher.filters import IS_MEMBER, IS_NOT_MEMBER + + @router.chat_member(chat_member_updated=IS_MEMBER >> IS_NOT_MEMBER) + async def on_user_leave(event: ChatMemberUpdated): ... + + @router.chat_member(chat_member_updated=IS_NOT_MEMBER >> IS_MEMBER) + async def on_user_join(event: ChatMemberUpdated): ... + +Or construct your own terms via using pre-defined set of statuses and transitions. + +Allowed handlers +================ + +Allowed update types for this filter: + +- `my_chat_member` +- `chat_member` diff --git a/docs/dispatcher/filters/index.rst b/docs/dispatcher/filters/index.rst index a94636e6..106e0b0c 100644 --- a/docs/dispatcher/filters/index.rst +++ b/docs/dispatcher/filters/index.rst @@ -18,6 +18,7 @@ Here is list of builtin filters: command content_types text + chat_member_updated exception magic_filters magic_data @@ -83,7 +84,7 @@ Bound Filters with only default arguments will be automatically applied with def to each handler in the router and nested routers to which this filter is bound. For example, although we do not specify :code:`chat_type` in the handler filters, -but since the filter has a default value, the filter will be applied to the handler +but since the filter has a default value, the filter will be applied to the handler with a default value :code:`private`: .. code-block:: python diff --git a/docs/dispatcher/flags.rst b/docs/dispatcher/flags.rst new file mode 100644 index 00000000..f4e5e118 --- /dev/null +++ b/docs/dispatcher/flags.rst @@ -0,0 +1,89 @@ +===== +Flags +===== + +Flags is a markers for handlers that can be used in `middlewares <#use-in-middlewares>`_ +or special `utilities <#use-in-utilities>`_ to make classification of the handlers. + +Flags can be added to the handler via `decorators <#via-decorators>`_, +`handlers registration <#via-handler-registration-method>`_ or +`filters `_. + +Via decorators +============== + +For example mark handler with `chat_action` flag + +.. code-block:: python + + from aiogram import flags + + @flags.chat_action + async def my_handler(message: Message) + +Or just for rate-limit or something else + +.. code-block:: python + + from aiogram import flags + + @flags.rate_limit(rate=2, key="something") + async def my_handler(message: Message) + +Via handler registration method +=============================== + +.. code-block:: python + + @router.message(..., flags={'chat_action': 'typing', 'rate_limit': {'rate': 5}}) + +Via filters +=========== + +.. code-block:: python + + class Command(BaseFilter): + ... + + def update_handler_flags(self, flags: Dict[str, Any]) -> None: + commands = flags.setdefault("commands", []) + commands.append(self) + + + +Use in middlewares +================== + +.. automodule:: aiogram.dispatcher.flags.getter + :members: + +Example in middlewares +---------------------- + +.. code-block:: python + + async def my_middleware(handler, event, data): + typing = get_flag(data, "typing") # Check that handler marked with `typing` flag + if not typing: + return await handler(event, data) + + async with ChatActionSender.typing(chat_id=event.chat.id): + return await handler(event, data) + +Use in utilities +================ + +For example you can collect all registered commands with handler description and then it can be used for generating commands help + +.. code-block:: python + + def collect_commands(router: Router) -> Generator[Tuple[Command, str], None, None]: + for handler in router.message.handlers: + if "commands" not in handler.flags: # ignore all handler without commands + continue + # the Command filter adds the flag with list of commands attached to the handler + for command in handler.flags["commands"]: + yield command, handler.callback.__doc__ or "" + # Recursively extract commands from nested routers + for sub_router in router.sub_routers: + yield from collect_commands(sub_router) diff --git a/docs/dispatcher/index.rst b/docs/dispatcher/index.rst index 935522b2..c684ba36 100644 --- a/docs/dispatcher/index.rst +++ b/docs/dispatcher/index.rst @@ -24,3 +24,4 @@ Dispatcher is subclass of router and should be always is root router. filters/index middlewares finite_state_machine/index + flags diff --git a/docs/utils/chat_action.rst b/docs/utils/chat_action.rst new file mode 100644 index 00000000..4a911bfd --- /dev/null +++ b/docs/utils/chat_action.rst @@ -0,0 +1,56 @@ +================== +Chat action sender +================== + +Sender +====== + +.. autoclass:: aiogram.utils.chat_action.ChatActionSender + :members: __init__,running,typing,upload_photo,record_video,upload_video,record_voice,upload_voice,upload_document,choose_sticker,find_location,record_video_note,upload_video_note + +Usage +----- + +.. code-block:: python + + async with ChatActionSender.typing(bot=bot, chat_id=message.chat.id): + # Do something... + # Perform some long calculations + await message.answer(result) + + +Middleware +========== + +.. autoclass:: aiogram.utils.chat_action.ChatActionMiddleware + + +Usage +----- + +Before usa should be registered for the `message` event + +.. code-block:: python + + .message.middleware(ChatActionMiddleware()) + +After this action all handlers which works longer than `initial_sleep` will produce the '`typing`' chat action. + +Also sender can be customized via flags feature for particular handler. + +Change only action type: + +.. code-block:: python + + @router.message(...) + @flags.chat_action("sticker") + async def my_handler(message: Message): ... + + +Change sender configuration: + +.. code-block:: python + + @router.message(...) + @flags.chat_action(initial_sleep=2, action="upload_document", interval=3) + async def my_handler(message: Message): ... diff --git a/docs/utils/index.rst b/docs/utils/index.rst index 424ac0d9..9390e8ac 100644 --- a/docs/utils/index.rst +++ b/docs/utils/index.rst @@ -6,3 +6,4 @@ Utils i18n keyboard + chat_action diff --git a/poetry.lock b/poetry.lock index 3ed1df7c..22656370 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "aiofiles" -version = "0.7.0" +version = "0.8.0" description = "File support for asyncio." category = "main" optional = false @@ -8,7 +8,7 @@ python-versions = ">=3.6,<4.0" [[package]] name = "aiohttp" -version = "3.8.0" +version = "3.8.1" description = "Async http client/server framework (asyncio)" category = "main" optional = false @@ -28,7 +28,7 @@ speedups = ["aiodns", "brotli", "cchardet"] [[package]] name = "aiohttp-socks" -version = "0.5.5" +version = "0.7.1" description = "Proxy connector for aiohttp" category = "main" optional = true @@ -37,11 +37,11 @@ python-versions = "*" [package.dependencies] aiohttp = ">=2.3.2" attrs = ">=19.2.0" -python-socks = {version = ">=1.0.1", extras = ["asyncio"]} +python-socks = {version = ">=2.0.0,<3.0.0", extras = ["asyncio"]} [[package]] name = "aioredis" -version = "2.0.0" +version = "2.0.1" description = "asyncio (PEP 3156) Redis support" category = "main" optional = true @@ -83,7 +83,7 @@ python-versions = "*" [[package]] name = "aresponses" -version = "2.1.4" +version = "2.1.5" description = "Asyncio response mocking. Similar to the responses library used for 'requests'" category = "dev" optional = false @@ -93,6 +93,20 @@ python-versions = ">=3.6" aiohttp = ">=3.1.0,<4.0.0" pytest-asyncio = "*" +[[package]] +name = "asttokens" +version = "2.0.5" +description = "Annotate AST trees with source code positions" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid", "pytest"] + [[package]] name = "async-timeout" version = "4.0.0" @@ -182,29 +196,24 @@ lxml = ["lxml"] [[package]] name = "black" -version = "21.10b0" +version = "22.1.0" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.6.2" [package.dependencies] -click = ">=7.1.2" +click = ">=8.0.0" mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0,<1" +pathspec = ">=0.9.0" platformdirs = ">=2" -regex = ">=2020.1.8" -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\""}, -] +tomli = ">=1.1.0" +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -python2 = ["typed-ast (>=1.4.3)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] @@ -212,7 +221,7 @@ name = "certifi" version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" -optional = true +optional = false python-versions = "*" [[package]] @@ -266,11 +275,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.1.1" +version = "6.3.1" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} [package.extras] toml = ["tomli"] @@ -299,6 +311,14 @@ category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[[package]] +name = "executing" +version = "0.8.2" +description = "Get the currently executing AST node of a frame, and other information" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "filelock" version = "3.3.2" @@ -313,16 +333,16 @@ testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-co [[package]] name = "flake8" -version = "3.9.2" +version = "4.0.1" description = "the modular source code checker: pep8 pyflakes and co" category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.6" [package.dependencies] mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" [[package]] name = "flake8-html" @@ -348,7 +368,7 @@ python-versions = ">=3.6" [[package]] name = "furo" -version = "2021.10.9" +version = "2022.2.14.1" description = "A clean customisable Sphinx documentation theme." category = "main" optional = true @@ -356,11 +376,12 @@ python-versions = ">=3.6" [package.dependencies] beautifulsoup4 = "*" +pygments = ">=2.7,<3.0" sphinx = ">=4.0,<5.0" [package.extras] -doc = ["myst-parser", "sphinx-copybutton", "sphinx-design", "sphinx-inline-tabs"] test = ["pytest", "pytest-cov", "pytest-xdist"] +doc = ["myst-parser", "sphinx-copybutton", "sphinx-design", "sphinx-inline-tabs"] [[package]] name = "identify" @@ -426,15 +447,16 @@ python-versions = "*" [[package]] name = "ipython" -version = "7.29.0" +version = "8.0.1" description = "IPython: Productive Interactive Computing" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" [package.dependencies] appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" +black = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" jedi = ">=0.16" @@ -443,10 +465,11 @@ pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" pygments = "*" -traitlets = ">=4.2" +stack-data = "*" +traitlets = ">=5" [package.extras] -all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] +all = ["Sphinx (>=1.3)", "curio", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.19)", "pandas", "pygments", "pytest", "pytest-asyncio", "qtconsole", "testpath", "trio"] doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] @@ -454,11 +477,12 @@ nbformat = ["nbformat"] notebook = ["notebook", "ipywidgets"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.17)"] +test = ["pytest", "pytest-asyncio", "testpath", "pygments"] +test_extra = ["pytest", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pygments", "trio"] [[package]] name = "isort" -version = "5.10.0" +version = "5.10.1" description = "A Python utility / library to sort Python imports." category = "dev" optional = false @@ -513,7 +537,7 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "magic-filter" -version = "1.0.4" +version = "1.0.5" description = "This package provides magic filter based on dynamic attribute getter" category = "main" optional = false @@ -578,20 +602,20 @@ python-versions = ">=3.6" [[package]] name = "mypy" -version = "0.910" +version = "0.931" description = "Optional static typing for Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" [package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -toml = "*" -typing-extensions = ">=3.7.4" +mypy-extensions = ">=0.4.3" +tomli = ">=1.1.0" +typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] -python2 = ["typed-ast (>=1.4.0,<1.5.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] [[package]] name = "mypy-extensions" @@ -611,14 +635,14 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.9" +version = "21.3" description = "Core utilities for Python packages" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "parso" @@ -685,7 +709,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.15.0" +version = "2.17.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -718,6 +742,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +tests = ["pytest"] + [[package]] name = "py" version = "1.11.0" @@ -728,15 +763,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.8.0" description = "Python style guide checker" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pydantic" -version = "1.8.2" +version = "1.9.0" description = "Data validation and settings management using python 3.6 type hinting" category = "main" optional = false @@ -751,7 +786,7 @@ email = ["email-validator (>=1.0.3)"] [[package]] name = "pyflakes" -version = "2.3.1" +version = "2.4.0" description = "passive checker of Python programs" category = "dev" optional = false @@ -759,7 +794,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.10.0" +version = "2.11.2" description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false @@ -767,18 +802,18 @@ python-versions = ">=3.5" [[package]] name = "pymdown-extensions" -version = "8.2" +version = "9.2" description = "Extension pack for Python Markdown." category = "main" optional = true -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] Markdown = ">=3.2" [[package]] name = "pyparsing" -version = "3.0.5" +version = "3.0.7" description = "Python parsing module" category = "main" optional = false @@ -789,7 +824,7 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "6.2.5" +version = "7.0.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -803,49 +838,52 @@ iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" py = ">=1.8.2" -toml = "*" +tomli = ">=1.0.0" [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-aiohttp" -version = "0.3.0" -description = "pytest plugin for aiohttp support" +version = "1.0.4" +description = "Pytest plugin for aiohttp support" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [package.dependencies] -aiohttp = ">=2.3.5" -pytest = "*" +aiohttp = ">=3.8.1" +pytest = ">=6.1.0" +pytest-asyncio = ">=0.17.2" + +[package.extras] +testing = ["coverage (==6.2)", "mypy (==0.931)"] [[package]] name = "pytest-asyncio" -version = "0.15.1" -description = "Pytest support for asyncio." +version = "0.18.1" +description = "Pytest support for asyncio" category = "dev" optional = false -python-versions = ">= 3.6" +python-versions = ">=3.7" [package.dependencies] -pytest = ">=5.4.0" +pytest = ">=6.1.0" [package.extras] -testing = ["coverage", "hypothesis (>=5.7.1)"] +testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)"] [[package]] name = "pytest-cov" -version = "2.12.1" +version = "3.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -coverage = ">=5.2.1" +coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" -toml = "*" [package.extras] testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] @@ -886,11 +924,11 @@ pytest = ">=2.9.0" [[package]] name = "pytest-mock" -version = "3.6.1" +version = "3.7.0" description = "Thin-wrapper around the mock package for easier use with pytest" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] pytest = ">=5.0" @@ -900,7 +938,7 @@ dev = ["pre-commit", "tox", "pytest-asyncio"] [[package]] name = "pytest-mypy" -version = "0.8.1" +version = "0.9.1" description = "Mypy static type checker plugin for Pytest" category = "dev" optional = false @@ -913,11 +951,14 @@ mypy = [ {version = ">=0.700", markers = "python_version >= \"3.8\" and python_version < \"3.9\""}, {version = ">=0.780", markers = "python_version >= \"3.9\""}, ] -pytest = ">=3.5" +pytest = [ + {version = ">=6.2", markers = "python_version >= \"3.10\""}, + {version = ">=4.6", markers = "python_version >= \"3.6\" and python_version < \"3.10\""}, +] [[package]] name = "python-socks" -version = "2.0.0" +version = "2.0.3" description = "Core proxy (SOCKS4, SOCKS5, HTTP tunneling) functionality for Python" category = "main" optional = true @@ -948,14 +989,6 @@ category = "dev" optional = false python-versions = ">=3.6" -[[package]] -name = "regex" -version = "2021.11.2" -description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "requests" version = "2.26.0" @@ -974,6 +1007,36 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] +[[package]] +name = "sentry-sdk" +version = "1.5.5" +description = "Python client for Sentry (https://sentry.io)" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.10.0" + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +flask = ["flask (>=0.11)", "blinker (>=1.1)"] +httpx = ["httpx (>=0.16.0)"] +pure_eval = ["pure-eval", "executing", "asttokens"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["quart (>=0.16.1)", "blinker (>=1.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +tornado = ["tornado (>=5)"] + [[package]] name = "six" version = "1.16.0" @@ -1047,7 +1110,7 @@ test = ["pytest", "pytest-cov"] [[package]] name = "sphinx-copybutton" -version = "0.4.0" +version = "0.5.0" description = "Add a copy button to each of your code cells." category = "main" optional = true @@ -1058,7 +1121,7 @@ sphinx = ">=1.8" [package.extras] code_style = ["pre-commit (==2.12.1)"] -rtd = ["sphinx", "ipython", "sphinx-book-theme"] +rtd = ["sphinx", "ipython", "myst-nb", "sphinx-book-theme"] [[package]] name = "sphinx-intl" @@ -1176,11 +1239,27 @@ python-versions = ">=3.5" lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] +[[package]] +name = "stack-data" +version = "0.2.0" +description = "Extract data from python stack frames and tracebacks for informative displays" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +asttokens = "*" +executing = "*" +pure-eval = "*" + +[package.extras] +tests = ["pytest", "typeguard", "pygments", "littleutils", "cython"] + [[package]] name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -1188,7 +1267,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" name = "tomli" version = "1.2.2" description = "A lil' TOML parser" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -1202,7 +1281,7 @@ python-versions = ">= 3.5" [[package]] name = "towncrier" -version = "21.3.0" +version = "21.9.0" description = "Building newsfiles for your project." category = "main" optional = true @@ -1213,7 +1292,7 @@ click = "*" click-default-group = "*" incremental = "*" jinja2 = "*" -toml = "*" +tomli = {version = "*", markers = "python_version >= \"3.6\""} [package.extras] dev = ["packaging"] @@ -1231,18 +1310,18 @@ test = ["pytest"] [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "urllib3" version = "1.26.7" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] @@ -1324,94 +1403,94 @@ redis = ["aioredis"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "27d602728b6dab256d184fbd44953030676edf320652ad828c6e1b4beaa80f8b" +content-hash = "1f20a68c7d58bc8113a73a2cef1cf9e0027fc0a69507e3ac0a81d158e33b3acc" [metadata.files] aiofiles = [ - {file = "aiofiles-0.7.0-py3-none-any.whl", hash = "sha256:c67a6823b5f23fcab0a2595a289cec7d8c863ffcb4322fb8cd6b90400aedfdbc"}, - {file = "aiofiles-0.7.0.tar.gz", hash = "sha256:a1c4fc9b2ff81568c83e21392a82f344ea9d23da906e4f6a52662764545e19d4"}, + {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"}, + {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"}, ] aiohttp = [ - {file = "aiohttp-3.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:48f218a5257b6bc16bcf26a91d97ecea0c7d29c811a90d965f3dd97c20f016d6"}, - {file = "aiohttp-3.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2fee4d656a7cc9ab47771b2a9e8fad8a9a33331c1b59c3057ecf0ac858f5bfe"}, - {file = "aiohttp-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:688a1eb8c1a5f7e795c7cb67e0fe600194e6723ba35f138dfae0db20c0cb8f94"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ba09bb3dcb0b7ec936a485db2b64be44fe14cdce0a5eac56f50e55da3627385"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7715daf84f10bcebc083ad137e3eced3e1c8e7fa1f096ade9a8d02b08f0d91c"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e3f81fbbc170418e22918a9585fd7281bbc11d027064d62aa4b507552c92671"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1fa9f50aa1f114249b7963c98e20dc35c51be64096a85bc92433185f331de9cc"}, - {file = "aiohttp-3.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8a50150419b741ee048b53146c39c47053f060cb9d98e78be08fdbe942eaa3c4"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a84c335337b676d832c1e2bc47c3a97531b46b82de9f959dafb315cbcbe0dfcd"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88d4917c30fcd7f6404fb1dc713fa21de59d3063dcc048f4a8a1a90e6bbbd739"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b76669b7c058b8020b11008283c3b8e9c61bfd978807c45862956119b77ece45"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:84fe1732648c1bc303a70faa67cbc2f7f2e810c8a5bca94f6db7818e722e4c0a"}, - {file = "aiohttp-3.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:730b7c2b7382194d9985ffdc32ab317e893bca21e0665cb1186bdfbb4089d990"}, - {file = "aiohttp-3.8.0-cp310-cp310-win32.whl", hash = "sha256:0a96473a1f61d7920a9099bc8e729dc8282539d25f79c12573ee0fdb9c8b66a8"}, - {file = "aiohttp-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:764c7c6aa1f78bd77bd9674fc07d1ec44654da1818d0eef9fb48aa8371a3c847"}, - {file = "aiohttp-3.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9951c2696c4357703001e1fe6edc6ae8e97553ac630492ea1bf64b429cb712a3"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af379221975054162959e00daf21159ff69a712fc42ed0052caddbd70d52ff4"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9689af0f0a89e5032426c143fa3683b0451f06c83bf3b1e27902bd33acfae769"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe4a327da0c6b6e59f2e474ae79d6ee7745ac3279fd15f200044602fa31e3d79"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ecb314e59bedb77188017f26e6b684b1f6d0465e724c3122a726359fa62ca1ba"}, - {file = "aiohttp-3.8.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5399a44a529083951b55521cf4ecbf6ad79fd54b9df57dbf01699ffa0549fc9"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:09754a0d5eaab66c37591f2f8fac8f9781a5f61d51aa852a3261c4805ca6b984"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:adf0cb251b1b842c9dee5cfcdf880ba0aae32e841b8d0e6b6feeaef002a267c5"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a4759e85a191de58e0ea468ab6fd9c03941986eee436e0518d7a9291fab122c8"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:28369fe331a59d80393ec82df3d43307c7461bfaf9217999e33e2acc7984ff7c"}, - {file = "aiohttp-3.8.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2f44d1b1c740a9e2275160d77c73a11f61e8a916191c572876baa7b282bcc934"}, - {file = "aiohttp-3.8.0-cp36-cp36m-win32.whl", hash = "sha256:e27cde1e8d17b09730801ce97b6e0c444ba2a1f06348b169fd931b51d3402f0d"}, - {file = "aiohttp-3.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:15a660d06092b7c92ed17c1dbe6c1eab0a02963992d60e3e8b9d5fa7fa81f01e"}, - {file = "aiohttp-3.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:257f4fad1714d26d562572095c8c5cd271d5a333252795cb7a002dca41fdbad7"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6074a3b2fa2d0c9bf0963f8dfc85e1e54a26114cc8594126bc52d3fa061c40e"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a315ceb813208ef32bdd6ec3a85cbe3cb3be9bbda5fd030c234592fa9116993"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a52b141ff3b923a9166595de6e3768a027546e75052ffba267d95b54267f4ab"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a038cb1e6e55b26bb5520ccffab7f539b3786f5553af2ee47eb2ec5cbd7084e"}, - {file = "aiohttp-3.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98b1ea2763b33559dd9ec621d67fc17b583484cb90735bfb0ec3614c17b210e4"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9e8723c3256641e141cd18f6ce478d54a004138b9f1a36e41083b36d9ecc5fc5"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:14a6f026eca80dfa3d52e86be89feb5cd878f6f4a6adb34457e2c689fd85229b"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:c62d4791a8212c885b97a63ef5f3974b2cd41930f0cd224ada9c6ee6654f8150"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:90a97c2ed2830e7974cbe45f0838de0aefc1c123313f7c402e21c29ec063fbb4"}, - {file = "aiohttp-3.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dcc4d5dd5fba3affaf4fd08f00ef156407573de8c63338787614ccc64f96b321"}, - {file = "aiohttp-3.8.0-cp37-cp37m-win32.whl", hash = "sha256:de42f513ed7a997bc821bddab356b72e55e8396b1b7ba1bf39926d538a76a90f"}, - {file = "aiohttp-3.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7d76e8a83396e06abe3df569b25bd3fc88bf78b7baa2c8e4cf4aaf5983af66a3"}, - {file = "aiohttp-3.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d79174d96446a02664e2bffc95e7b6fa93b9e6d8314536c5840dff130d0878b"}, - {file = "aiohttp-3.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a6551057a846bf72c7a04f73de3fcaca269c0bd85afe475ceb59d261c6a938c"}, - {file = "aiohttp-3.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:871d4fdc56288caa58b1094c20f2364215f7400411f76783ea19ad13be7c8e19"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba08a71caa42eef64357257878fb17f3fba3fba6e81a51d170e32321569e079"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:51f90dabd9933b1621260b32c2f0d05d36923c7a5a909eb823e429dba0fd2f3e"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f348ebd20554e8bc26e8ef3ed8a134110c0f4bf015b3b4da6a4ddf34e0515b19"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d5f8c04574efa814a24510122810e3a3c77c0552f9f6ff65c9862f1f046be2c3"}, - {file = "aiohttp-3.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ecffdc748d3b40dd3618ede0170e4f5e1d3c9647cfb410d235d19e62cb54ee0"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:577cc2c7b807b174814dac2d02e673728f2e46c7f90ceda3a70ea4bb6d90b769"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6b79f6c31e68b6dafc0317ec453c83c86dd8db1f8f0c6f28e97186563fca87a0"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2bdd655732e38b40f8a8344d330cfae3c727fb257585df923316aabbd489ccb8"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:63fa57a0708573d3c059f7b5527617bd0c291e4559298473df238d502e4ab98c"}, - {file = "aiohttp-3.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d3f90ee275b1d7c942e65b5c44c8fb52d55502a0b9a679837d71be2bd8927661"}, - {file = "aiohttp-3.8.0-cp38-cp38-win32.whl", hash = "sha256:fa818609357dde5c4a94a64c097c6404ad996b1d38ca977a72834b682830a722"}, - {file = "aiohttp-3.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:097ecf52f6b9859b025c1e36401f8aa4573552e887d1b91b4b999d68d0b5a3b3"}, - {file = "aiohttp-3.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:be03a7483ad9ea60388f930160bb3728467dd0af538aa5edc60962ee700a0bdc"}, - {file = "aiohttp-3.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78d51e35ed163783d721b6f2ce8ce3f82fccfe471e8e50a10fba13a766d31f5a"}, - {file = "aiohttp-3.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bda75d73e7400e81077b0910c9a60bf9771f715420d7e35fa7739ae95555f195"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:707adc30ea6918fba725c3cb3fe782d271ba352b22d7ae54a7f9f2e8a8488c41"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f58aa995b905ab82fe228acd38538e7dc1509e01508dcf307dad5046399130f"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c996eb91bfbdab1e01e2c02e7ff678c51e2b28e3a04e26e41691991cc55795"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d6a1a66bb8bac9bc2892c2674ea363486bfb748b86504966a390345a11b1680e"}, - {file = "aiohttp-3.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dafc01a32b4a1d7d3ef8bfd3699406bb44f7b2e0d3eb8906d574846e1019b12f"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:949a605ef3907254b122f845baa0920407080cdb1f73aa64f8d47df4a7f4c4f9"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0d7b056fd3972d353cb4bc305c03f9381583766b7f8c7f1c44478dba69099e33"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f1d39a744101bf4043fa0926b3ead616607578192d0a169974fb5265ab1e9d2"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:67ca7032dfac8d001023fadafc812d9f48bf8a8c3bb15412d9cdcf92267593f4"}, - {file = "aiohttp-3.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cb751ef712570d3bda9a73fd765ff3e1aba943ec5d52a54a0c2e89c7eef9da1e"}, - {file = "aiohttp-3.8.0-cp39-cp39-win32.whl", hash = "sha256:6d3e027fe291b77f6be9630114a0200b2c52004ef20b94dc50ca59849cd623b3"}, - {file = "aiohttp-3.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:3c5e9981e449d54308c6824f172ec8ab63eb9c5f922920970249efee83f7e919"}, - {file = "aiohttp-3.8.0.tar.gz", hash = "sha256:d3b19d8d183bcfd68b25beebab8dc3308282fe2ca3d6ea3cb4cd101b3c279f8d"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, + {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, + {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, + {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, + {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, + {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, + {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, + {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, + {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, + {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, ] aiohttp-socks = [ - {file = "aiohttp_socks-0.5.5-py3-none-any.whl", hash = "sha256:faaa25ed4dc34440ca888d23e089420f3b1918dc4ecf062c3fd9474827ad6a39"}, - {file = "aiohttp_socks-0.5.5.tar.gz", hash = "sha256:2eb2059756bde34c55bb429541cbf2eba3fd53e36ac80875b461221e2858b04a"}, + {file = "aiohttp_socks-0.7.1-py3-none-any.whl", hash = "sha256:94bcff5ef73611c6c6231c2ffc1be4af1599abec90dbd2fdbbd63233ec2fb0ff"}, + {file = "aiohttp_socks-0.7.1.tar.gz", hash = "sha256:2215cac4891ef3fa14b7d600ed343ed0f0a670c23b10e4142aa862b3db20341a"}, ] aioredis = [ - {file = "aioredis-2.0.0-py3-none-any.whl", hash = "sha256:9921d68a3df5c5cdb0d5b49ad4fc88a4cfdd60c108325df4f0066e8410c55ffb"}, - {file = "aioredis-2.0.0.tar.gz", hash = "sha256:3a2de4b614e6a5f8e104238924294dc4e811aefbe17ddf52c04a93cbf06e67db"}, + {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"}, + {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"}, ] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, @@ -1426,8 +1505,12 @@ appnope = [ {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, ] aresponses = [ - {file = "aresponses-2.1.4-py3-none-any.whl", hash = "sha256:2a5a100c9b39e559bf55c26cc837a8ce64ab160ee086afa01ee9c4ef07f245db"}, - {file = "aresponses-2.1.4.tar.gz", hash = "sha256:39674af90700f1bfe2c7c9049cd8116f5c10d34d2e2427fd744b88d9e8644c94"}, + {file = "aresponses-2.1.5-py3-none-any.whl", hash = "sha256:06161209a39880aaf8ec3c67ab76d677aaea41944672e6a3e23a4464544879b1"}, + {file = "aresponses-2.1.5.tar.gz", hash = "sha256:16535e5d24302eab194e15edd18b9e126e1fb70cd84049e63eb6b15c89e16936"}, +] +asttokens = [ + {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, + {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, ] async-timeout = [ {file = "async-timeout-4.0.0.tar.gz", hash = "sha256:7d87a4e8adba8ededb52e579ce6bc8276985888913620c935094c2276fd83382"}, @@ -1462,8 +1545,29 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, ] black = [ - {file = "black-21.10b0-py3-none-any.whl", hash = "sha256:6eb7448da9143ee65b856a5f3676b7dda98ad9abe0f87fce8c59291f15e82a5b"}, - {file = "black-21.10b0.tar.gz", hash = "sha256:a9952229092e325fe5f3dae56d81f639b23f7131eb840781947e4b2886030f33"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -1489,52 +1593,47 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42a1fb5dee3355df90b635906bb99126faa7936d87dfc97eacc5293397618cb7"}, - {file = "coverage-6.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a00284dbfb53b42e35c7dd99fc0e26ef89b4a34efff68078ed29d03ccb28402a"}, - {file = "coverage-6.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:51a441011a30d693e71dea198b2a6f53ba029afc39f8e2aeb5b77245c1b282ef"}, - {file = "coverage-6.1.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e76f017b6d4140a038c5ff12be1581183d7874e41f1c0af58ecf07748d36a336"}, - {file = "coverage-6.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7833c872718dc913f18e51ee97ea0dece61d9930893a58b20b3daf09bb1af6b6"}, - {file = "coverage-6.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8186b5a4730c896cbe1e4b645bdc524e62d874351ae50e1db7c3e9f5dc81dc26"}, - {file = "coverage-6.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbca34dca5a2d60f81326d908d77313816fad23d11b6069031a3d6b8c97a54f9"}, - {file = "coverage-6.1.1-cp310-cp310-win32.whl", hash = "sha256:72bf437d54186d104388cbae73c9f2b0f8a3e11b6e8d7deb593bd14625c96026"}, - {file = "coverage-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:994ce5a7b3d20981b81d83618aa4882f955bfa573efdbef033d5632b58597ba9"}, - {file = "coverage-6.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ab6a0fe4c96f8058d41948ddf134420d3ef8c42d5508b5a341a440cce7a37a1d"}, - {file = "coverage-6.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10ab138b153e4cc408b43792cb7f518f9ee02f4ff55cd1ab67ad6fd7e9905c7e"}, - {file = "coverage-6.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7e083d32965d2eb6638a77e65b622be32a094fdc0250f28ce6039b0732fbcaa8"}, - {file = "coverage-6.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:359a32515e94e398a5c0fa057e5887a42e647a9502d8e41165cf5cb8d3d1ca67"}, - {file = "coverage-6.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:bf656cd74ff7b4ed7006cdb2a6728150aaad69c7242b42a2a532f77b63ea233f"}, - {file = "coverage-6.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:dc5023be1c2a8b0a0ab5e31389e62c28b2453eb31dd069f4b8d1a0f9814d951a"}, - {file = "coverage-6.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:557594a50bfe3fb0b1b57460f6789affe8850ad19c1acf2d14a3e12b2757d489"}, - {file = "coverage-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:9eb0a1923354e0fdd1c8a6f53f5db2e6180d670e2b587914bf2e79fa8acfd003"}, - {file = "coverage-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:04a92a6cf9afd99f9979c61348ec79725a9f9342fb45e63c889e33c04610d97b"}, - {file = "coverage-6.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:479228e1b798d3c246ac89b09897ee706c51b3e5f8f8d778067f38db73ccc717"}, - {file = "coverage-6.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78287731e3601ea5ce9d6468c82d88a12ef8fe625d6b7bdec9b45d96c1ad6533"}, - {file = "coverage-6.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c95257aa2ccf75d3d91d772060538d5fea7f625e48157f8ca44594f94d41cb33"}, - {file = "coverage-6.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9ad5895938a894c368d49d8470fe9f519909e5ebc6b8f8ea5190bd0df6aa4271"}, - {file = "coverage-6.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:326d944aad0189603733d646e8d4a7d952f7145684da973c463ec2eefe1387c2"}, - {file = "coverage-6.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e7d5606b9240ed4def9cbdf35be4308047d11e858b9c88a6c26974758d6225ce"}, - {file = "coverage-6.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:572f917267f363101eec375c109c9c1118037c7cc98041440b5eabda3185ac7b"}, - {file = "coverage-6.1.1-cp37-cp37m-win32.whl", hash = "sha256:35cd2230e1ed76df7d0081a997f0fe705be1f7d8696264eb508076e0d0b5a685"}, - {file = "coverage-6.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:65ad3ff837c89a229d626b8004f0ee32110f9bfdb6a88b76a80df36ccc60d926"}, - {file = "coverage-6.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:977ce557d79577a3dd510844904d5d968bfef9489f512be65e2882e1c6eed7d8"}, - {file = "coverage-6.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62512c0ec5d307f56d86504c58eace11c1bc2afcdf44e3ff20de8ca427ca1d0e"}, - {file = "coverage-6.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2e5b9c17a56b8bf0c0a9477fcd30d357deb486e4e1b389ed154f608f18556c8a"}, - {file = "coverage-6.1.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:666c6b32b69e56221ad1551d377f718ed00e6167c7a1b9257f780b105a101271"}, - {file = "coverage-6.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fb2fa2f6506c03c48ca42e3fe5a692d7470d290c047ee6de7c0f3e5fa7639ac9"}, - {file = "coverage-6.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f0f80e323a17af63eac6a9db0c9188c10f1fd815c3ab299727150cc0eb92c7a4"}, - {file = "coverage-6.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:738e823a746841248b56f0f3bd6abf3b73af191d1fd65e4c723b9c456216f0ad"}, - {file = "coverage-6.1.1-cp38-cp38-win32.whl", hash = "sha256:8605add58e6a960729aa40c0fd9a20a55909dd9b586d3e8104cc7f45869e4c6b"}, - {file = "coverage-6.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:6e994003e719458420e14ffb43c08f4c14990e20d9e077cb5cad7a3e419bbb54"}, - {file = "coverage-6.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e3c4f5211394cd0bf6874ac5d29684a495f9c374919833dcfff0bd6d37f96201"}, - {file = "coverage-6.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e14bceb1f3ae8a14374be2b2d7bc12a59226872285f91d66d301e5f41705d4d6"}, - {file = "coverage-6.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0147f7833c41927d84f5af9219d9b32f875c0689e5e74ac8ca3cb61e73a698f9"}, - {file = "coverage-6.1.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b1d0a1bce919de0dd8da5cff4e616b2d9e6ebf3bd1410ff645318c3dd615010a"}, - {file = "coverage-6.1.1-cp39-cp39-win32.whl", hash = "sha256:a11a2c019324fc111485e79d55907e7289e53d0031275a6c8daed30690bc50c0"}, - {file = "coverage-6.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4d8b453764b9b26b0dd2afb83086a7c3f9379134e340288d2a52f8a91592394b"}, - {file = "coverage-6.1.1-pp36-none-any.whl", hash = "sha256:3b270c6b48d3ff5a35deb3648028ba2643ad8434b07836782b1139cf9c66313f"}, - {file = "coverage-6.1.1-pp37-none-any.whl", hash = "sha256:ffa8fee2b1b9e60b531c4c27cf528d6b5d5da46b1730db1f4d6eee56ff282e07"}, - {file = "coverage-6.1.1-pp38-none-any.whl", hash = "sha256:4cd919057636f63ab299ccb86ea0e78b87812400c76abab245ca385f17d19fb5"}, - {file = "coverage-6.1.1.tar.gz", hash = "sha256:b8e4f15b672c9156c1154249a9c5746e86ac9ae9edc3799ee3afebc323d9d9e0"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, + {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, + {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, + {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, + {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, + {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, + {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, + {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, + {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, + {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, + {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, + {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, ] decorator = [ {file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"}, @@ -1548,13 +1647,17 @@ docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] +executing = [ + {file = "executing-0.8.2-py2.py3-none-any.whl", hash = "sha256:32fc6077b103bd19e6494a72682d66d5763cf20a106d5aa7c5ccbea4e47b0df7"}, + {file = "executing-0.8.2.tar.gz", hash = "sha256:c23bf42e9a7b9b212f185b1b2c3c91feb895963378887bb10e64a2e612ec0023"}, +] filelock = [ {file = "filelock-3.3.2-py3-none-any.whl", hash = "sha256:bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b"}, {file = "filelock-3.3.2.tar.gz", hash = "sha256:7afc856f74fa7006a289fd10fa840e1eebd8bbff6bffb69c26c54a0512ea8cf8"}, ] flake8 = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] flake8-html = [ {file = "flake8-html-0.4.1.tar.gz", hash = "sha256:2fb436cbfe1e109275bc8fb7fdd0cb00e67b3b48cfeb397309b6b2c61eeb4cb4"}, @@ -1635,8 +1738,8 @@ frozenlist = [ {file = "frozenlist-1.2.0.tar.gz", hash = "sha256:68201be60ac56aff972dc18085800b6ee07973c49103a8aba669dee3d71079de"}, ] furo = [ - {file = "furo-2021.10.9-py3-none-any.whl", hash = "sha256:8da4448836e72db59033cd8d70dbdae96b0523c4424d806216febd38253870e8"}, - {file = "furo-2021.10.9.tar.gz", hash = "sha256:2baa42a22ede3e6e95c6182ab364bac2ec15b79bc7f9ca7fb09fd9aec4b3540c"}, + {file = "furo-2022.2.14.1-py3-none-any.whl", hash = "sha256:d7cb8126034637212332350ec8490cb95732d36506b024318a58cee2e7de0fda"}, + {file = "furo-2022.2.14.1.tar.gz", hash = "sha256:1af3a3053e594666e27eefd347b84beae5d74d6d20f6294cc47777d46f5761a7"}, ] identify = [ {file = "identify-2.3.4-py2.py3-none-any.whl", hash = "sha256:4de55a93e0ba72bf917c840b3794eb1055a67272a1732351c557c88ec42011b1"}, @@ -1663,12 +1766,12 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] ipython = [ - {file = "ipython-7.29.0-py3-none-any.whl", hash = "sha256:a658beaf856ce46bc453366d5dc6b2ddc6c481efd3540cb28aa3943819caac9f"}, - {file = "ipython-7.29.0.tar.gz", hash = "sha256:4f69d7423a5a1972f6347ff233e38bbf4df6a150ef20fbb00c635442ac3060aa"}, + {file = "ipython-8.0.1-py3-none-any.whl", hash = "sha256:c503a0dd6ccac9c8c260b211f2dd4479c042b49636b097cc9a0d55fe62dff64c"}, + {file = "ipython-8.0.1.tar.gz", hash = "sha256:ab564d4521ea8ceaac26c3a2c6e5ddbca15c8848fd5a5cc325f960da88d42974"}, ] isort = [ - {file = "isort-5.10.0-py3-none-any.whl", hash = "sha256:1a18ccace2ed8910bd9458b74a3ecbafd7b2f581301b0ab65cfdd4338272d76f"}, - {file = "isort-5.10.0.tar.gz", hash = "sha256:e52ff6d38012b131628cf0f26c51e7bd3a7c81592eefe3ac71411e692f1b9345"}, + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] jedi = [ {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, @@ -1682,8 +1785,8 @@ livereload = [ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] magic-filter = [ - {file = "magic-filter-1.0.4.tar.gz", hash = "sha256:2b77de98bfef16a990c22b2a68a904b4a99913b656d48cf877367da8ae5f5c84"}, - {file = "magic_filter-1.0.4-py3-none-any.whl", hash = "sha256:daf869025b9490c4438f1c2d3f4f9bcb8295fa0ffa7fec643bc20486f519f349"}, + {file = "magic-filter-1.0.5.tar.gz", hash = "sha256:974cf2793bb02a770f202d3179abfb600d1917f4e0c2af1727ef0edbb90cd0c2"}, + {file = "magic_filter-1.0.5-py3-none-any.whl", hash = "sha256:fa0c5f94da30d6cae1f0cec34fa526056db9f2636c099527513d529cb0299787"}, ] markdown = [ {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, @@ -1846,29 +1949,26 @@ multidict = [ {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"}, ] mypy = [ - {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"}, + {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, + {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, + {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, + {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, + {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, + {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, + {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, + {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, + {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, + {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, + {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, + {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, + {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, + {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, + {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, + {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, + {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, + {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, + {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, + {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1879,8 +1979,8 @@ nodeenv = [ {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, ] packaging = [ - {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, - {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] parso = [ {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, @@ -1907,8 +2007,8 @@ pluggy = [ {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"}, + {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, + {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, ] prompt-toolkit = [ {file = "prompt_toolkit-3.0.22-py3-none-any.whl", hash = "sha256:48d85cdca8b6c4f16480c7ce03fd193666b62b0a21667ca56b4bb5ad679d1170"}, @@ -1918,69 +2018,86 @@ ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] +pure-eval = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] pydantic = [ - {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"}, - {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"}, - {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"}, - {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"}, - {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"}, - {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"}, - {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"}, - {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"}, - {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"}, - {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"}, - {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"}, - {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"}, - {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, - {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, + {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"}, + {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"}, + {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"}, + {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"}, + {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"}, + {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"}, + {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"}, + {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"}, + {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"}, + {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"}, + {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"}, + {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"}, + {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"}, + {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"}, + {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"}, + {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"}, + {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"}, + {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"}, + {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"}, + {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"}, + {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"}, + {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"}, + {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"}, + {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"}, + {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"}, + {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"}, + {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"}, + {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"}, + {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"}, + {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"}, + {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"}, + {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"}, + {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"}, + {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"}, + {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"}, ] pyflakes = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pygments = [ - {file = "Pygments-2.10.0-py3-none-any.whl", hash = "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380"}, - {file = "Pygments-2.10.0.tar.gz", hash = "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"}, + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pymdown-extensions = [ - {file = "pymdown-extensions-8.2.tar.gz", hash = "sha256:b6daa94aad9e1310f9c64c8b1f01e4ce82937ab7eb53bfc92876a97aca02a6f4"}, - {file = "pymdown_extensions-8.2-py3-none-any.whl", hash = "sha256:141452d8ed61165518f2c923454bf054866b85cf466feedb0eb68f04acdc2560"}, + {file = "pymdown-extensions-9.2.tar.gz", hash = "sha256:ed8f69a18bc158f00cbf03abc536b88b6e541b7e699156501e767c48f81d8850"}, + {file = "pymdown_extensions-9.2-py3-none-any.whl", hash = "sha256:f2fa7d9317c672a419868c893c20a28fb7ed7fc60d4ec4774c35e01398ab330c"}, ] pyparsing = [ - {file = "pyparsing-3.0.5-py3-none-any.whl", hash = "sha256:4881e3d2979f27b41a3a2421b10be9cbfa7ce2baa6c7117952222f8bbea6650c"}, - {file = "pyparsing-3.0.5.tar.gz", hash = "sha256:9329d1c1b51f0f76371c4ded42c5ec4cc0be18456b22193e0570c2da98ed288b"}, + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, ] pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, + {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, + {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, ] pytest-aiohttp = [ - {file = "pytest-aiohttp-0.3.0.tar.gz", hash = "sha256:c929854339637977375838703b62fef63528598bc0a9d451639eba95f4aaa44f"}, - {file = "pytest_aiohttp-0.3.0-py3-none-any.whl", hash = "sha256:0b9b660b146a65e1313e2083d0d2e1f63047797354af9a28d6b7c9f0726fa33d"}, + {file = "pytest-aiohttp-1.0.4.tar.gz", hash = "sha256:39ff3a0d15484c01d1436cbedad575c6eafbf0f57cdf76fb94994c97b5b8c5a4"}, + {file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"}, ] pytest-asyncio = [ - {file = "pytest-asyncio-0.15.1.tar.gz", hash = "sha256:2564ceb9612bbd560d19ca4b41347b54e7835c2f792c504f698e05395ed63f6f"}, - {file = "pytest_asyncio-0.15.1-py3-none-any.whl", hash = "sha256:3042bcdf1c5d978f6b74d96a151c4cfb9dcece65006198389ccd7e6c60eb1eea"}, + {file = "pytest-asyncio-0.18.1.tar.gz", hash = "sha256:c43fcdfea2335dd82ffe0f2774e40285ddfea78a8e81e56118d47b6a90fbb09e"}, + {file = "pytest_asyncio-0.18.1-py3-none-any.whl", hash = "sha256:c9ec48e8bbf5cc62755e18c4d8bc6907843ec9c5f4ac8f61464093baeba24a7e"}, ] pytest-cov = [ - {file = "pytest-cov-2.12.1.tar.gz", hash = "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"}, - {file = "pytest_cov-2.12.1-py2.py3-none-any.whl", hash = "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a"}, + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, ] pytest-html = [ {file = "pytest-html-3.1.1.tar.gz", hash = "sha256:3ee1cf319c913d19fe53aeb0bc400e7b0bc2dbeb477553733db1dad12eb75ee3"}, @@ -1995,16 +2112,16 @@ pytest-metadata = [ {file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"}, ] pytest-mock = [ - {file = "pytest-mock-3.6.1.tar.gz", hash = "sha256:40217a058c52a63f1042f0784f62009e976ba824c418cced42e88d5f40ab0e62"}, - {file = "pytest_mock-3.6.1-py3-none-any.whl", hash = "sha256:30c2f2cc9759e76eee674b81ea28c9f0b94f8f0445a1b87762cadf774f0df7e3"}, + {file = "pytest-mock-3.7.0.tar.gz", hash = "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534"}, + {file = "pytest_mock-3.7.0-py3-none-any.whl", hash = "sha256:6cff27cec936bf81dc5ee87f07132b807bcda51106b5ec4b90a04331cba76231"}, ] pytest-mypy = [ - {file = "pytest-mypy-0.8.1.tar.gz", hash = "sha256:1fa55723a4bf1d054fcba1c3bd694215a2a65cc95ab10164f5808afd893f3b11"}, - {file = "pytest_mypy-0.8.1-py3-none-any.whl", hash = "sha256:6e68e8eb7ceeb7d1c83a1590912f784879f037b51adfb9c17b95c6b2fc57466b"}, + {file = "pytest-mypy-0.9.1.tar.gz", hash = "sha256:9ffa3bf405c12c5c6be9e92e22bebb6ab2c91b9c32f45b0f0c93af473269ab5c"}, + {file = "pytest_mypy-0.9.1-py3-none-any.whl", hash = "sha256:a2505fcf61f1c0c51f950d4623ea8ca2daf6fb2101a5603554bad2e130202083"}, ] python-socks = [ - {file = "python-socks-2.0.0.tar.gz", hash = "sha256:7944dad882846ac73e5f79e180c841e3895ee058e16855b7e8fff24f4cd0b90b"}, - {file = "python_socks-2.0.0-py3-none-any.whl", hash = "sha256:aac65671cbd3b0eb55b20f8558c8de3894a315536aaab3ec0a7b9d46ff89c1bf"}, + {file = "python-socks-2.0.3.tar.gz", hash = "sha256:e3a9ca8e554733862ce4d8ce1d10efb480fd3a3acdafd03393943ec00c98ba8a"}, + {file = "python_socks-2.0.3-py3-none-any.whl", hash = "sha256:950723f27d2cf401e193a9e0a0d45baab848341298f5b397d27fda0c4635e9a9"}, ] pytz = [ {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, @@ -2045,61 +2162,14 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] -regex = [ - {file = "regex-2021.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:897c539f0f3b2c3a715be651322bef2167de1cdc276b3f370ae81a3bda62df71"}, - {file = "regex-2021.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:886f459db10c0f9d17c87d6594e77be915f18d343ee138e68d259eb385f044a8"}, - {file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:075b0fdbaea81afcac5a39a0d1bb91de887dd0d93bf692a5dd69c430e7fc58cb"}, - {file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6238d30dcff141de076344cf7f52468de61729c2f70d776fce12f55fe8df790"}, - {file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fab29411d75c2eb48070020a40f80255936d7c31357b086e5931c107d48306e"}, - {file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0148988af0182a0a4e5020e7c168014f2c55a16d11179610f7883dd48ac0ebe"}, - {file = "regex-2021.11.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be30cd315db0168063a1755fa20a31119da91afa51da2907553493516e165640"}, - {file = "regex-2021.11.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e9cec3a62d146e8e122d159ab93ac32c988e2ec0dcb1e18e9e53ff2da4fbd30c"}, - {file = "regex-2021.11.2-cp310-cp310-win32.whl", hash = "sha256:41c66bd6750237a8ed23028a6c9173dc0c92dc24c473e771d3bfb9ee817700c3"}, - {file = "regex-2021.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:0075fe4e2c2720a685fef0f863edd67740ff78c342cf20b2a79bc19388edf5db"}, - {file = "regex-2021.11.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0ed3465acf8c7c10aa2e0f3d9671da410ead63b38a77283ef464cbb64275df58"}, - {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab1fea8832976ad0bebb11f652b692c328043057d35e9ebc78ab0a7a30cf9a70"}, - {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb1e44d860345ab5d4f533b6c37565a22f403277f44c4d2d5e06c325da959883"}, - {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9486ebda015913909bc28763c6b92fcc3b5e5a67dee4674bceed112109f5dfb8"}, - {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20605bfad484e1341b2cbfea0708e4b211d233716604846baa54b94821f487cb"}, - {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f20f9f430c33597887ba9bd76635476928e76cad2981643ca8be277b8e97aa96"}, - {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1d85ca137756d62c8138c971453cafe64741adad1f6a7e63a22a5a8abdbd19fa"}, - {file = "regex-2021.11.2-cp36-cp36m-win32.whl", hash = "sha256:af23b9ca9a874ef0ec20e44467b8edd556c37b0f46f93abfa93752ea7c0e8d1e"}, - {file = "regex-2021.11.2-cp36-cp36m-win_amd64.whl", hash = "sha256:070336382ca92c16c45b4066c4ba9fa83fb0bd13d5553a82e07d344df8d58a84"}, - {file = "regex-2021.11.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ef4e53e2fdc997d91f5b682f81f7dc9661db9a437acce28745d765d251902d85"}, - {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35ed5714467fc606551db26f80ee5d6aa1f01185586a7bccd96f179c4b974a11"}, - {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee36d5113b6506b97f45f2e8447cb9af146e60e3f527d93013d19f6d0405f3b"}, - {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fba661a4966adbd2c3c08d3caad6822ecb6878f5456588e2475ae23a6e47929"}, - {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77f9d16f7970791f17ecce7e7f101548314ed1ee2583d4268601f30af3170856"}, - {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6a28e87ba69f3a4f30d775b179aac55be1ce59f55799328a0d9b6df8f16b39d"}, - {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9267e4fba27e6dd1008c4f2983cc548c98b4be4444e3e342db11296c0f45512f"}, - {file = "regex-2021.11.2-cp37-cp37m-win32.whl", hash = "sha256:d4bfe3bc3976ccaeb4ae32f51e631964e2f0e85b2b752721b7a02de5ce3b7f27"}, - {file = "regex-2021.11.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2bb7cae741de1aa03e3dd3a7d98c304871eb155921ca1f0d7cc11f5aade913fd"}, - {file = "regex-2021.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:23f93e74409c210de4de270d4bf88fb8ab736a7400f74210df63a93728cf70d6"}, - {file = "regex-2021.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8ee91e1c295beb5c132ebd78616814de26fedba6aa8687ea460c7f5eb289b72"}, - {file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e3ff69ab203b54ce5c480c3ccbe959394ea5beef6bd5ad1785457df7acea92e"}, - {file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3c00cb5c71da655e1e5161481455479b613d500dd1bd252aa01df4f037c641f"}, - {file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf35e16f4b639daaf05a2602c1b1d47370e01babf9821306aa138924e3fe92"}, - {file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb11c982a849dc22782210b01d0c1b98eb3696ce655d58a54180774e4880ac66"}, - {file = "regex-2021.11.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e3755e0f070bc31567dfe447a02011bfa8444239b3e9e5cca6773a22133839"}, - {file = "regex-2021.11.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0621c90f28d17260b41838b22c81a79ff436141b322960eb49c7b3f91d1cbab6"}, - {file = "regex-2021.11.2-cp38-cp38-win32.whl", hash = "sha256:8fbe1768feafd3d0156556677b8ff234c7bf94a8110e906b2d73506f577a3269"}, - {file = "regex-2021.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:f9ee98d658a146cb6507be720a0ce1b44f2abef8fb43c2859791d91aace17cd5"}, - {file = "regex-2021.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3794cea825f101fe0df9af8a00f9fad8e119c91e39a28636b95ee2b45b6c2e5"}, - {file = "regex-2021.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3576e173e7b4f88f683b4de7db0c2af1b209bb48b2bf1c827a6f3564fad59a97"}, - {file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b4f4810117a9072a5aa70f7fea5f86fa9efbe9a798312e0a05044bd707cc33"}, - {file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5930d334c2f607711d54761956aedf8137f83f1b764b9640be21d25a976f3a4"}, - {file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:956187ff49db7014ceb31e88fcacf4cf63371e6e44d209cf8816cd4a2d61e11a"}, - {file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e095f7f96a4b9f24b93c2c915f31a5201a6316618d919b0593afb070a5270e"}, - {file = "regex-2021.11.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a56735c35a3704603d9d7b243ee06139f0837bcac2171d9ba1d638ce1df0742a"}, - {file = "regex-2021.11.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:adf35d88d9cffc202e6046e4c32e1e11a1d0238b2fcf095c94f109e510ececea"}, - {file = "regex-2021.11.2-cp39-cp39-win32.whl", hash = "sha256:30fe317332de0e50195665bc61a27d46e903d682f94042c36b3f88cb84bd7958"}, - {file = "regex-2021.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:85289c25f658e3260b00178757c87f033f3d4b3e40aa4abdd4dc875ff11a94fb"}, - {file = "regex-2021.11.2.tar.gz", hash = "sha256:5e85dcfc5d0f374955015ae12c08365b565c6f1eaf36dd182476a4d8e5a1cdb7"}, -] requests = [ {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, ] +sentry-sdk = [ + {file = "sentry-sdk-1.5.5.tar.gz", hash = "sha256:98fd155fa5d5fec1dbabed32a1a4ae2705f1edaa5dae4e7f7b62a384ba30e759"}, + {file = "sentry_sdk-1.5.5-py2.py3-none-any.whl", hash = "sha256:3817274fba2498c8ebf6b896ee98ac916c5598706340573268c07bf2bb30d831"}, +] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -2121,8 +2191,8 @@ sphinx-autobuild = [ {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, ] sphinx-copybutton = [ - {file = "sphinx-copybutton-0.4.0.tar.gz", hash = "sha256:8daed13a87afd5013c3a9af3575cc4d5bec052075ccd3db243f895c07a689386"}, - {file = "sphinx_copybutton-0.4.0-py3-none-any.whl", hash = "sha256:4340d33c169dac6dd82dce2c83333412aa786a42dd01a81a8decac3b130dc8b0"}, + {file = "sphinx-copybutton-0.5.0.tar.gz", hash = "sha256:a0c059daadd03c27ba750da534a92a63e7a36a7736dcf684f26ee346199787f6"}, + {file = "sphinx_copybutton-0.5.0-py3-none-any.whl", hash = "sha256:9684dec7434bd73f0eea58dda93f9bb879d24bff2d8b187b1f2ec08dfe7b5f48"}, ] sphinx-intl = [ {file = "sphinx-intl-2.0.1.tar.gz", hash = "sha256:b25a6ec169347909e8d983eefe2d8adecb3edc2f27760db79b965c69950638b4"}, @@ -2159,6 +2229,10 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] +stack-data = [ + {file = "stack_data-0.2.0-py3-none-any.whl", hash = "sha256:999762f9c3132308789affa03e9271bbbe947bf78311851f4d485d8402ed858e"}, + {file = "stack_data-0.2.0.tar.gz", hash = "sha256:45692d41bd633a9503a5195552df22b583caf16f0b27c4e58c98d88c8b648e12"}, +] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -2211,17 +2285,16 @@ tornado = [ {file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"}, ] towncrier = [ - {file = "towncrier-21.3.0-py2.py3-none-any.whl", hash = "sha256:e6ccec65418bbcb8de5c908003e130e37fe0e9d6396cb77c1338241071edc082"}, - {file = "towncrier-21.3.0.tar.gz", hash = "sha256:6eed0bc924d72c98c000cb8a64de3bd566e5cb0d11032b73fcccf8a8f956ddfe"}, + {file = "towncrier-21.9.0-py2.py3-none-any.whl", hash = "sha256:fc5a88a2a54988e3a8ed2b60d553599da8330f65722cc607c839614ed87e0f92"}, + {file = "towncrier-21.9.0.tar.gz", hash = "sha256:9cb6f45c16e1a1eec9d0e7651165e7be60cd0ab81d13a5c96ca97a498ae87f48"}, ] traitlets = [ {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"}, {file = "traitlets-5.1.1.tar.gz", hash = "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7"}, ] typing-extensions = [ - {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"}, + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] urllib3 = [ {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, diff --git a/pyproject.toml b/pyproject.toml index 297f02a4..160f5380 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiogram" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" description = "Modern and fully asynchronous framework for Telegram Bot API" authors = [ "Alex Root Junior ", @@ -37,53 +37,55 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.8" -magic-filter = "^1.0.4" -aiohttp = "^3.8.0" -pydantic = "^1.8.2" -aiofiles = "^0.7.0" +magic-filter = "^1.0.5" +aiohttp = "^3.8.1" +pydantic = "^1.9.0" +aiofiles = "^0.8.0" # 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 } +aiohttp-socks = {version = "^0.7.1", optional = true} # Redis -aioredis = { version = "^2.0.0", optional = true } +aioredis = {version = "^2.0.1", optional = true} # Docs Sphinx = { version = "^4.2.0", optional = true } sphinx-intl = { version = "^2.0.1", 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-copybutton = {version = "^0.5.0", optional = true} +furo = {version = "^2022.2.14", 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 } +towncrier = {version = "^21.9.0", optional = true} pygments = { version = "^2.4", optional = true } -pymdown-extensions = { version = "^8.0", optional = true } +pymdown-extensions = {version = "^9.2", optional = true} markdown-include = { version = "^0.6", optional = true } +Pygments = {version = "^2.11.2", optional = true} [tool.poetry.dev-dependencies] -ipython = "^7.22.0" -black = "^21.4b2" -isort = "^5.8.0" -flake8 = "^3.9.1" +ipython = "^8.0.1" +black = "^22.1.0" +isort = "^5.10.1" +flake8 = "^4.0.1" flake8-html = "^0.4.1" -mypy = "^0.910" -pytest = "^6.2.3" +mypy = "^0.931" +pytest = "^7.0.1" pytest-html = "^3.1.1" -pytest-asyncio = "^0.15.1" +pytest-asyncio = "^0.18.1" pytest-lazy-fixture = "^0.6.3" -pytest-mock = "^3.6.0" -pytest-mypy = "^0.8.1" -pytest-cov = "^2.11.1" -pytest-aiohttp = "^0.3.0" -aresponses = "^2.1.4" +pytest-mock = "^3.7.0" +pytest-mypy = "^0.9.1" +pytest-cov = "^3.0.0" +pytest-aiohttp = "^1.0.4" +aresponses = "^2.1.5" asynctest = "^0.13.0" toml = "^0.10.2" -pre-commit = "^2.15.0" -packaging = "^20.3" -typing-extensions = "^3.7.4" +pre-commit = "^2.17.0" +packaging = "^21.3" +typing-extensions = "^4.1.1" +sentry-sdk = "^1.5.5" [tool.poetry.extras] diff --git a/tests/conftest.py b/tests/conftest.py index e57ec632..698ee5cf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,9 +4,13 @@ import pytest from _pytest.config import UsageError from aioredis.connection import parse_url as parse_redis_url -from aiogram import Bot -from aiogram.dispatcher.fsm.storage.memory import MemoryStorage -from aiogram.dispatcher.fsm.storage.redis import RedisStorage +from aiogram import Bot, Dispatcher +from aiogram.dispatcher.fsm.storage.memory import ( + DisabledEventIsolation, + MemoryStorage, + SimpleEventIsolation, +) +from aiogram.dispatcher.fsm.storage.redis import RedisEventIsolation, RedisStorage from tests.mocked_bot import MockedBot DATA_DIR = Path(__file__).parent / "data" @@ -67,6 +71,42 @@ async def memory_storage(): await storage.close() +@pytest.fixture() +@pytest.mark.redis +async def redis_isolation(redis_server): + if not redis_server: + pytest.skip("Redis is not available here") + isolation = RedisEventIsolation.from_url(redis_server) + try: + await isolation.redis.info() + except ConnectionError as e: + pytest.skip(str(e)) + try: + yield isolation + finally: + conn = await isolation.redis + await conn.flushdb() + await isolation.close() + + +@pytest.fixture() +async def lock_isolation(): + isolation = SimpleEventIsolation() + try: + yield isolation + finally: + await isolation.close() + + +@pytest.fixture() +async def disabled_isolation(): + isolation = DisabledEventIsolation() + try: + yield isolation + finally: + await isolation.close() + + @pytest.fixture() def bot(): bot = MockedBot() @@ -75,3 +115,13 @@ def bot(): yield bot finally: Bot.reset_current(token) + + +@pytest.fixture() +async def dispatcher(): + dp = Dispatcher() + await dp.emit_startup() + try: + yield dp + finally: + await dp.emit_shutdown() diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py index 31e437ca..1150f073 100644 --- a/tests/test_dispatcher/test_dispatcher.py +++ b/tests/test_dispatcher/test_dispatcher.py @@ -76,20 +76,15 @@ class TestDispatcher: assert dp.update.handlers[0].callback == dp._listen_update assert dp.update.outer_middlewares - def test_parent_router(self): - dp = Dispatcher() + def test_parent_router(self, dispatcher: Dispatcher): with pytest.raises(RuntimeError): - dp.parent_router = Router() - assert dp.parent_router is None - dp._parent_router = Router() - assert dp.parent_router is None + dispatcher.parent_router = Router() + assert dispatcher.parent_router is None + dispatcher._parent_router = Router() + assert dispatcher.parent_router is None - @pytest.mark.parametrize("isolate_events", (True, False)) - async def test_feed_update(self, isolate_events): - dp = Dispatcher(isolate_events=isolate_events) - bot = Bot("42:TEST") - - @dp.message() + async def test_feed_update(self, dispatcher: Dispatcher, bot: MockedBot): + @dispatcher.message() async def my_handler(message: Message, **kwargs): assert "bot" in kwargs assert isinstance(kwargs["bot"], Bot) @@ -97,7 +92,7 @@ class TestDispatcher: return message.text results_count = 0 - result = await dp.feed_update( + result = await dispatcher.feed_update( bot=bot, update=Update( update_id=42, diff --git a/tests/test_dispatcher/test_filters/test_chat_member_updated.py b/tests/test_dispatcher/test_filters/test_chat_member_updated.py new file mode 100644 index 00000000..63ee1245 --- /dev/null +++ b/tests/test_dispatcher/test_filters/test_chat_member_updated.py @@ -0,0 +1,345 @@ +from datetime import datetime + +import pytest + +from aiogram.dispatcher.filters.chat_member_updated import ( + ADMINISTRATOR, + IS_MEMBER, + JOIN_TRANSITION, + LEAVE_TRANSITION, + ChatMemberUpdatedFilter, + _MemberStatusGroupMarker, + _MemberStatusMarker, + _MemberStatusTransition, +) +from aiogram.types import Chat, ChatMember, ChatMemberUpdated, User + + +class TestMemberStatusMarker: + def test_str(self): + marker = _MemberStatusMarker("test") + assert str(marker) == "TEST" + assert str(+marker) == "+TEST" + assert str(-marker) == "-TEST" + + def test_pos(self): + marker = _MemberStatusMarker("test") + assert marker.is_member is None + + positive_marker = +marker + assert positive_marker is not marker + assert marker.is_member is None + assert positive_marker.is_member is True + + def test_neg(self): + marker = _MemberStatusMarker("test") + assert marker.is_member is None + + negative_marker = -marker + assert negative_marker is not marker + assert marker.is_member is None + assert negative_marker.is_member is False + + def test_or(self): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + + combination = marker1 | marker2 + assert isinstance(combination, _MemberStatusGroupMarker) + assert marker1 in combination.statuses + assert marker2 in combination.statuses + + combination2 = marker1 | marker1 + assert isinstance(combination2, _MemberStatusGroupMarker) + assert len(combination2.statuses) == 1 + + marker3 = _MemberStatusMarker("test3") + combination3 = marker3 | combination + assert isinstance(combination3, _MemberStatusGroupMarker) + assert marker3 in combination3.statuses + assert len(combination3.statuses) == 3 + assert combination3 is not combination + + with pytest.raises(TypeError): + marker1 | 42 + + def test_rshift(self): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + marker3 = _MemberStatusMarker("test3") + transition = marker1 >> marker2 + assert isinstance(transition, _MemberStatusTransition) + assert marker1 in transition.old.statuses + assert marker2 in transition.new.statuses + + transition2 = marker1 >> (marker2 | marker3) + assert isinstance(transition2, _MemberStatusTransition) + + with pytest.raises(TypeError): + marker1 >> 42 + + def test_lshift(self): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + marker3 = _MemberStatusMarker("test3") + transition = marker1 << marker2 + assert isinstance(transition, _MemberStatusTransition) + assert marker2 in transition.old.statuses + assert marker1 in transition.new.statuses + + transition2 = marker1 << (marker2 | marker3) + assert isinstance(transition2, _MemberStatusTransition) + + with pytest.raises(TypeError): + marker1 << 42 + + def test_hash(self): + marker1 = _MemberStatusMarker("test1") + marker1_1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + assert hash(marker1) != hash(marker2) + assert hash(marker1) == hash(marker1_1) + assert hash(marker1) != hash(-marker1) + + @pytest.mark.parametrize( + "name,is_member,member,result", + [ + ["test", None, ChatMember(status="member"), False], + ["test", None, ChatMember(status="test"), True], + ["test", True, ChatMember(status="test"), False], + ["test", True, ChatMember(status="test", is_member=True), True], + ["test", True, ChatMember(status="test", is_member=False), False], + ], + ) + def test_check(self, name, is_member, member, result): + marker = _MemberStatusMarker(name, is_member=is_member) + assert marker.check(member=member) == result + + +class TestMemberStatusGroupMarker: + def test_init_unique(self): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + marker3 = _MemberStatusMarker("test3") + + group = _MemberStatusGroupMarker(marker1, marker1, marker2, marker3) + assert len(group.statuses) == 3 + + def test_init_empty(self): + with pytest.raises(ValueError): + _MemberStatusGroupMarker() + + def test_or(self): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + marker3 = _MemberStatusMarker("test3") + marker4 = _MemberStatusMarker("test4") + + group1 = _MemberStatusGroupMarker(marker1, marker2) + group2 = _MemberStatusGroupMarker(marker3, marker4) + + group3 = group1 | marker3 + assert isinstance(group3, _MemberStatusGroupMarker) + assert len(group3.statuses) == 3 + + group4 = group1 | group2 + assert isinstance(group4, _MemberStatusGroupMarker) + assert len(group4.statuses) == 4 + + with pytest.raises(TypeError): + group4 | 42 + + def test_rshift(self): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + marker3 = _MemberStatusMarker("test3") + + group1 = _MemberStatusGroupMarker(marker1, marker2) + group2 = _MemberStatusGroupMarker(marker1, marker3) + + transition1 = group1 >> marker1 + assert isinstance(transition1, _MemberStatusTransition) + assert transition1.old is group1 + assert marker1 in transition1.new.statuses + + transition2 = group1 >> group2 + assert isinstance(transition2, _MemberStatusTransition) + + with pytest.raises(TypeError): + group1 >> 42 + + def test_lshift(self): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + marker3 = _MemberStatusMarker("test3") + + group1 = _MemberStatusGroupMarker(marker1, marker2) + group2 = _MemberStatusGroupMarker(marker1, marker3) + + transition1 = group1 << marker1 + assert isinstance(transition1, _MemberStatusTransition) + assert transition1.new is group1 + assert marker1 in transition1.old.statuses + + transition2 = group1 << group2 + assert isinstance(transition2, _MemberStatusTransition) + + with pytest.raises(TypeError): + group1 << 42 + + def test_str(self): + marker1 = _MemberStatusMarker("test1") + marker1_1 = +marker1 + marker2 = _MemberStatusMarker("test2") + + group1 = marker1 | marker1 + assert str(group1) == "TEST1" + + group2 = marker1 | marker2 + assert str(group2) == "(TEST1 | TEST2)" + + group3 = marker1 | marker1_1 + assert str(group3) == "(+TEST1 | TEST1)" + + @pytest.mark.parametrize( + "status,result", + [ + ["test", False], + ["test1", True], + ["test2", True], + ], + ) + def test_check(self, status, result): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + group = marker1 | marker2 + + assert group.check(member=ChatMember(status=status)) is result + + +class TestMemberStatusTransition: + def test_invert(self): + marker1 = _MemberStatusMarker("test1") + marker2 = _MemberStatusMarker("test2") + + transition1 = marker1 >> marker2 + transition2 = ~transition1 + + assert transition1 is not transition2 + assert transition1.old == transition2.new + assert transition1.new == transition2.old + + assert str(transition1) == "TEST1 >> TEST2" + assert str(transition2) == "TEST2 >> TEST1" + + @pytest.mark.parametrize( + "transition,old,new,result", + [ + [JOIN_TRANSITION, ChatMember(status="left"), ChatMember(status="member"), True], + [ + JOIN_TRANSITION, + ChatMember(status="restricted", is_member=True), + ChatMember(status="member"), + False, + ], + [ + JOIN_TRANSITION, + ChatMember(status="restricted", is_member=False), + ChatMember(status="member"), + True, + ], + [ + JOIN_TRANSITION, + ChatMember(status="member"), + ChatMember(status="restricted", is_member=False), + False, + ], + [ + LEAVE_TRANSITION, + ChatMember(status="member"), + ChatMember(status="restricted", is_member=False), + True, + ], + ], + ) + def test_check(self, transition, old, new, result): + assert transition.check(old=old, new=new) == result + + +class TestChatMemberUpdatedStatusFilter: + @pytest.mark.asyncio + @pytest.mark.parametrize( + "transition,old,new,result", + [ + [JOIN_TRANSITION, ChatMember(status="left"), ChatMember(status="member"), True], + [ + JOIN_TRANSITION, + ChatMember(status="restricted", is_member=True), + ChatMember(status="member"), + False, + ], + [ + JOIN_TRANSITION, + ChatMember(status="restricted", is_member=False), + ChatMember(status="member"), + True, + ], + [ + JOIN_TRANSITION, + ChatMember(status="member"), + ChatMember(status="restricted", is_member=False), + False, + ], + [ + LEAVE_TRANSITION, + ChatMember(status="member"), + ChatMember(status="restricted", is_member=False), + True, + ], + [ + ADMINISTRATOR, + ChatMember(status="member"), + ChatMember(status="administrator"), + True, + ], + [ + IS_MEMBER, + ChatMember(status="restricted", is_member=False), + ChatMember(status="member"), + True, + ], + ], + ) + async def test_call(self, transition, old, new, result): + updated_filter = ChatMemberUpdatedFilter(member_status_changed=transition) + user = User(id=42, first_name="Test", is_bot=False) + update = { + "user": user, + "until_date": datetime.now(), + "is_anonymous": False, + "can_be_edited": True, + "can_manage_chat": True, + "can_delete_messages": True, + "can_manage_voice_chats": True, + "can_restrict_members": True, + "can_promote_members": True, + "can_change_info": True, + "can_invite_users": True, + "can_post_messages": True, + "can_edit_messages": True, + "can_pin_messages": True, + "can_send_messages": True, + "can_send_media_messages": True, + "can_send_polls": True, + "can_send_other_messages": True, + "can_add_web_page_previews": True, + } + event = ChatMemberUpdated( + chat=Chat(id=42, type="test"), + from_user=user, + old_chat_member=old.copy(update=update), + new_chat_member=new.copy(update=update), + date=datetime.now(), + ) + + assert await updated_filter(event) is result diff --git a/tests/test_dispatcher/test_flags/__init__.py b/tests/test_dispatcher/test_flags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_dispatcher/test_flags/test_decorator.py b/tests/test_dispatcher/test_flags/test_decorator.py new file mode 100644 index 00000000..6c4e40df --- /dev/null +++ b/tests/test_dispatcher/test_flags/test_decorator.py @@ -0,0 +1,66 @@ +import pytest + +from aiogram.dispatcher.flags.flag import Flag, FlagDecorator, FlagGenerator + + +@pytest.fixture(name="flag") +def flag_fixture() -> Flag: + return Flag("test", True) + + +@pytest.fixture(name="flag_decorator") +def flag_decorator_fixture(flag: Flag) -> FlagDecorator: + return FlagDecorator(flag) + + +@pytest.fixture(name="flag_generator") +def flag_flag_generator() -> FlagGenerator: + return FlagGenerator() + + +class TestFlagDecorator: + def test_with_value(self, flag_decorator: FlagDecorator): + new_decorator = flag_decorator._with_value(True) + + assert new_decorator is not flag_decorator + assert new_decorator.flag is not flag_decorator.flag + assert new_decorator.flag + + def test_call_invalid(self, flag_decorator: FlagDecorator): + with pytest.raises(ValueError): + flag_decorator(True, test=True) + + def test_call_with_function(self, flag_decorator: FlagDecorator): + def func(): + pass + + decorated = flag_decorator(func) + assert decorated is func + assert hasattr(decorated, "aiogram_flag") + + def test_call_with_arg(self, flag_decorator: FlagDecorator): + new_decorator = flag_decorator("hello") + assert new_decorator is not flag_decorator + assert new_decorator.flag.value == "hello" + + def test_call_with_kwargs(self, flag_decorator: FlagDecorator): + new_decorator = flag_decorator(test=True) + assert new_decorator is not flag_decorator + assert isinstance(new_decorator.flag.value, dict) + assert "test" in new_decorator.flag.value + + +class TestFlagGenerator: + def test_getattr(self): + generator = FlagGenerator() + assert isinstance(generator.foo, FlagDecorator) + assert isinstance(generator.bar, FlagDecorator) + + assert generator.foo is not generator.foo + assert generator.foo is not generator.bar + + def test_failed_getattr(self): + generator = FlagGenerator() + + with pytest.raises(AttributeError): + generator._something diff --git a/tests/test_dispatcher/test_flags/test_getter.py b/tests/test_dispatcher/test_flags/test_getter.py new file mode 100644 index 00000000..afe7891c --- /dev/null +++ b/tests/test_dispatcher/test_flags/test_getter.py @@ -0,0 +1,64 @@ +from unittest.mock import patch + +import pytest + +from aiogram import F +from aiogram.dispatcher.event.handler import HandlerObject +from aiogram.dispatcher.flags.getter import ( + check_flags, + extract_flags, + extract_flags_from_object, + get_flag, +) + + +class TestGetters: + def test_extract_flags_from_object(self): + def func(): + pass + + assert extract_flags_from_object(func) == {} + + func.aiogram_flag = {"test": True} + assert extract_flags_from_object(func) == func.aiogram_flag + + @pytest.mark.parametrize( + "obj,result", + [ + [None, {}], + [{}, {}], + [{"handler": None}, {}], + [{"handler": HandlerObject(lambda: True, flags={"test": True})}, {"test": True}], + ], + ) + def test_extract_flags(self, obj, result): + assert extract_flags(obj) == result + + @pytest.mark.parametrize( + "obj,name,default,result", + [ + [None, "test", None, None], + [None, "test", 42, 42], + [{}, "test", None, None], + [{}, "test", 42, 42], + [{"handler": None}, "test", None, None], + [{"handler": None}, "test", 42, 42], + [{"handler": HandlerObject(lambda: True, flags={"test": True})}, "test", None, True], + [{"handler": HandlerObject(lambda: True, flags={"test": True})}, "test2", None, None], + [{"handler": HandlerObject(lambda: True, flags={"test": True})}, "test2", 42, 42], + ], + ) + def test_get_flag(self, obj, name, default, result): + assert get_flag(obj, name, default=default) == result + + @pytest.mark.parametrize( + "flags,magic,result", + [ + [{}, F.test, None], + [{"test": True}, F.test, True], + [{"test": True}, F.spam, None], + ], + ) + def test_check_flag(self, flags, magic, result): + with patch("aiogram.dispatcher.flags.getter.extract_flags", return_value=flags): + assert check_flags(object(), magic) == result diff --git a/tests/test_dispatcher/test_fsm/storage/test_isolation.py b/tests/test_dispatcher/test_fsm/storage/test_isolation.py new file mode 100644 index 00000000..8b582f45 --- /dev/null +++ b/tests/test_dispatcher/test_fsm/storage/test_isolation.py @@ -0,0 +1,30 @@ +import pytest + +from aiogram.dispatcher.fsm.storage.base import BaseEventIsolation, StorageKey +from tests.mocked_bot import MockedBot + +pytestmark = pytest.mark.asyncio + + +@pytest.fixture(name="storage_key") +def create_storate_key(bot: MockedBot): + return StorageKey(chat_id=-42, user_id=42, bot_id=bot.id) + + +@pytest.mark.parametrize( + "isolation", + [ + pytest.lazy_fixture("redis_isolation"), + pytest.lazy_fixture("lock_isolation"), + pytest.lazy_fixture("disabled_isolation"), + ], +) +class TestIsolations: + async def test_lock( + self, + bot: MockedBot, + isolation: BaseEventIsolation, + storage_key: StorageKey, + ): + async with isolation.lock(bot=bot, key=storage_key): + assert True, "You are kidding me?" diff --git a/tests/test_dispatcher/test_fsm/storage/test_redis.py b/tests/test_dispatcher/test_fsm/storage/test_redis.py index dcb71c3d..5bf3170e 100644 --- a/tests/test_dispatcher/test_fsm/storage/test_redis.py +++ b/tests/test_dispatcher/test_fsm/storage/test_redis.py @@ -1,9 +1,11 @@ -from typing import Literal - import pytest from aiogram.dispatcher.fsm.storage.base import DEFAULT_DESTINY, StorageKey -from aiogram.dispatcher.fsm.storage.redis import DefaultKeyBuilder +from aiogram.dispatcher.fsm.storage.redis import ( + DefaultKeyBuilder, + RedisEventIsolation, + RedisStorage, +) pytestmark = pytest.mark.asyncio @@ -45,3 +47,11 @@ class TestRedisDefaultKeyBuilder: ) with pytest.raises(ValueError): key_builder.build(key, FIELD) + + def test_create_isolation(self): + fake_redis = object() + storage = RedisStorage(redis=fake_redis) + isolation = storage.create_isolation() + assert isinstance(isolation, RedisEventIsolation) + assert isolation.redis is fake_redis + assert isolation.key_builder is storage.key_builder diff --git a/tests/test_dispatcher/test_fsm/storage/test_storages.py b/tests/test_dispatcher/test_fsm/storage/test_storages.py index 428f6d02..803e3059 100644 --- a/tests/test_dispatcher/test_fsm/storage/test_storages.py +++ b/tests/test_dispatcher/test_fsm/storage/test_storages.py @@ -16,11 +16,6 @@ def create_storate_key(bot: MockedBot): [pytest.lazy_fixture("redis_storage"), pytest.lazy_fixture("memory_storage")], ) class TestStorages: - async def test_lock(self, bot: MockedBot, storage: BaseStorage, storage_key: StorageKey): - # TODO: ?!? - async with storage.lock(bot=bot, key=storage_key): - assert True, "You are kidding me?" - async def test_set_state(self, bot: MockedBot, storage: BaseStorage, storage_key: StorageKey): assert await storage.get_state(bot=bot, key=storage_key) is None diff --git a/tests/test_dispatcher/test_webhook/test_aiohtt_server.py b/tests/test_dispatcher/test_webhook/test_aiohtt_server.py index fa9dad9c..7a044715 100644 --- a/tests/test_dispatcher/test_webhook/test_aiohtt_server.py +++ b/tests/test_dispatcher/test_webhook/test_aiohtt_server.py @@ -1,4 +1,6 @@ +import asyncio import time +from asyncio import Event from dataclasses import dataclass from typing import Any, Dict @@ -19,6 +21,12 @@ from aiogram.methods import GetMe, Request from aiogram.types import Message, User from tests.mocked_bot import MockedBot +try: + from asynctest import CoroutineMock, patch +except ImportError: + from unittest.mock import AsyncMock as CoroutineMock # type: ignore + from unittest.mock import patch + class TestAiohttpServer: def test_setup_application(self): @@ -74,8 +82,11 @@ class TestSimpleRequestHandler: app = Application() dp = Dispatcher() + handler_event = Event() + @dp.message(F.text == "test") def handle_message(msg: Message): + handler_event.set() return msg.answer("PASS") handler = SimpleRequestHandler( @@ -97,8 +108,15 @@ class TestSimpleRequestHandler: assert not result handler.handle_in_background = True - resp = await self.make_reqest(client=client) - assert resp.status == 200 + with patch( + "aiogram.dispatcher.dispatcher.Dispatcher.silent_call_request", + new_callable=CoroutineMock, + ) as mocked_silent_call_request: + handler_event.clear() + resp = await self.make_reqest(client=client) + assert resp.status == 200 + await asyncio.wait_for(handler_event.wait(), timeout=1) + mocked_silent_call_request.assert_awaited() result = await resp.json() assert not result diff --git a/tests/test_utils/test_chat_action.py b/tests/test_utils/test_chat_action.py new file mode 100644 index 00000000..caf05e33 --- /dev/null +++ b/tests/test_utils/test_chat_action.py @@ -0,0 +1,129 @@ +import asyncio +import time +from datetime import datetime + +import pytest + +from aiogram import Bot, flags +from aiogram.dispatcher.event.handler import HandlerObject +from aiogram.types import Chat, Message, User +from aiogram.utils.chat_action import ChatActionMiddleware, ChatActionSender +from tests.mocked_bot import MockedBot + +try: + from asynctest import CoroutineMock, patch +except ImportError: + from unittest.mock import AsyncMock as CoroutineMock # type: ignore + from unittest.mock import patch + +pytestmarm = pytest.mark.asyncio + + +class TestChatActionSender: + async def test_wait(self, bot: Bot, loop: asyncio.BaseEventLoop): + sender = ChatActionSender.typing(bot=bot, chat_id=42) + loop.call_soon(sender._close_event.set) + start = time.monotonic() + await sender._wait(1) + assert time.monotonic() - start < 1 + + @pytest.mark.parametrize( + "action", + [ + "typing", + "upload_photo", + "record_video", + "upload_video", + "record_voice", + "upload_voice", + "upload_document", + "choose_sticker", + "find_location", + "record_video_note", + "upload_video_note", + ], + ) + @pytest.mark.parametrize("pass_bot", [True, False]) + async def test_factory(self, action: str, bot: MockedBot, pass_bot: bool): + sender_factory = getattr(ChatActionSender, action) + sender = sender_factory(chat_id=42, bot=bot if pass_bot else None) + assert isinstance(sender, ChatActionSender) + assert sender.action == action + assert sender.chat_id == 42 + assert sender.bot is bot + + async def test_worker(self, bot: Bot): + with patch( + "aiogram.client.bot.Bot.send_chat_action", + new_callable=CoroutineMock, + ) as mocked_send_chat_action: + async with ChatActionSender.typing( + bot=bot, chat_id=42, interval=0.01, initial_sleep=0 + ): + await asyncio.sleep(0.1) + assert mocked_send_chat_action.await_count > 1 + mocked_send_chat_action.assert_awaited_with(action="typing", chat_id=42) + + async def test_contextmanager(self, bot: MockedBot): + sender: ChatActionSender = ChatActionSender.typing(bot=bot, chat_id=42) + assert not sender.running + await sender._stop() # nothing + + async with sender: + assert sender.running + assert not sender._close_event.is_set() + + with pytest.raises(RuntimeError): + await sender._run() + + assert not sender.running + + +class TestChatActionMiddleware: + @pytest.mark.parametrize( + "value", + [ + None, + "sticker", + {"action": "upload_photo"}, + {"interval": 1, "initial_sleep": 0.5}, + ], + ) + async def test_call_default(self, value, bot: Bot): + async def handler(event, data): + return "OK" + + if value is None: + handler1 = flags.chat_action(handler) + else: + handler1 = flags.chat_action(value)(handler) + + middleware = ChatActionMiddleware() + with patch( + "aiogram.utils.chat_action.ChatActionSender._run", + new_callable=CoroutineMock, + ) as mocked_run, patch( + "aiogram.utils.chat_action.ChatActionSender._stop", + new_callable=CoroutineMock, + ) as mocked_stop: + data = {"handler": HandlerObject(callback=handler1), "bot": bot} + message = Message( + chat=Chat(id=42, type="private", title="Test"), + from_user=User(id=42, is_bot=False, first_name="Test"), + date=datetime.now(), + message_id=42, + ) + + result = await middleware(handler=handler1, event=None, data=data) + assert result == "OK" + mocked_run.assert_not_awaited() + mocked_stop.assert_not_awaited() + + result = await middleware( + handler=handler1, + event=message, + data=data, + ) + assert result == "OK" + mocked_run.assert_awaited() + mocked_stop.assert_awaited() diff --git a/tests/test_utils/test_i18n.py b/tests/test_utils/test_i18n.py index e8581414..31843080 100644 --- a/tests/test_utils/test_i18n.py +++ b/tests/test_utils/test_i18n.py @@ -114,6 +114,24 @@ class TestSimpleI18nMiddleware: assert middleware not in dp.update.outer_middlewares assert middleware in dp.message.outer_middlewares + async def test_get_unknown_locale(self, i18n: I18n): + dp = Dispatcher() + middleware = SimpleI18nMiddleware(i18n=i18n) + middleware.setup(router=dp) + + locale = await middleware.get_locale( + None, + { + "event_from_user": User( + id=42, + is_bot=False, + first_name="Test", + language_code="unknown", + ) + }, + ) + assert locale == i18n.default_locale + @pytest.mark.asyncio class TestConstI18nMiddleware: From 880dd153d33c3266e21e7b392d4d4c89fef7a388 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 19 Feb 2022 02:01:23 +0200 Subject: [PATCH 06/35] Build changelog --- CHANGES.rst | 38 ++++++++++++++++++++++++++++++++++++++ CHANGES/785.feature | 2 -- CHANGES/791.bugfix | 1 - CHANGES/827.bugfix | 1 - CHANGES/830.misc | 1 - CHANGES/835.misc | 1 - CHANGES/836.feature | 1 - CHANGES/837.feature | 3 --- CHANGES/838.misc | 2 -- 9 files changed, 38 insertions(+), 12 deletions(-) delete mode 100644 CHANGES/785.feature delete mode 100644 CHANGES/791.bugfix delete mode 100644 CHANGES/827.bugfix delete mode 100644 CHANGES/830.misc delete mode 100644 CHANGES/835.misc delete mode 100644 CHANGES/836.feature delete mode 100644 CHANGES/837.feature delete mode 100644 CHANGES/838.misc diff --git a/CHANGES.rst b/CHANGES.rst index 2ab30aa9..9216b305 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,44 @@ Changelog .. towncrier release notes start +3.0.0b2 (2022-02-19) +===================== + +Features +-------- + +- Added possibility to pass additional arguments into the aiohttp webhook handler to use this + arguments inside handlers as the same as it possible in polling mode. + `#785 `_ +- Added possibility to add handler flags via decorator (like `pytest.mark` decorator but `aiogram.flags`) + `#836 `_ +- Added :code:`ChatActionSender` utility to automatically sends chat action while long process is running. + + It also can be used as message middleware and can be customized via :code:`chat_action` flag. + `#837 `_ + + +Bugfixes +-------- + +- Fixed unexpected behavior of sequences in the StateFilter. + `#791 `_ +- Fixed exceptions filters + `#827 `_ + + +Misc +---- + +- Logger name for processing events is changed to :code:`aiogram.events`. + `#830 `_ +- Added full support of Telegram Bot API 5.6 and 5.7 + `#835 `_ +- **BREAKING** + Events isolation mechanism is moved from FSM storages to standalone managers + `#838 `_ + + 3.0.0b1 (2021-12-12) ===================== diff --git a/CHANGES/785.feature b/CHANGES/785.feature deleted file mode 100644 index cefaf47b..00000000 --- a/CHANGES/785.feature +++ /dev/null @@ -1,2 +0,0 @@ -Added possibility to pass additional arguments into the aiohttp webhook handler to use this -arguments inside handlers as the same as it possible in polling mode. diff --git a/CHANGES/791.bugfix b/CHANGES/791.bugfix deleted file mode 100644 index 5a219d4a..00000000 --- a/CHANGES/791.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed unexpected behavior of sequences in the StateFilter. diff --git a/CHANGES/827.bugfix b/CHANGES/827.bugfix deleted file mode 100644 index f0aab059..00000000 --- a/CHANGES/827.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed exceptions filters diff --git a/CHANGES/830.misc b/CHANGES/830.misc deleted file mode 100644 index f01d8b98..00000000 --- a/CHANGES/830.misc +++ /dev/null @@ -1 +0,0 @@ -Logger name for processing events is changed to :code:`aiogram.events`. diff --git a/CHANGES/835.misc b/CHANGES/835.misc deleted file mode 100644 index 83be3cb6..00000000 --- a/CHANGES/835.misc +++ /dev/null @@ -1 +0,0 @@ -Added full support of Telegram Bot API 5.6 and 5.7 diff --git a/CHANGES/836.feature b/CHANGES/836.feature deleted file mode 100644 index 17c9cd49..00000000 --- a/CHANGES/836.feature +++ /dev/null @@ -1 +0,0 @@ -Added possibility to add handler flags via decorator (like `pytest.mark` decorator but `aiogram.flags`) diff --git a/CHANGES/837.feature b/CHANGES/837.feature deleted file mode 100644 index 57ce165b..00000000 --- a/CHANGES/837.feature +++ /dev/null @@ -1,3 +0,0 @@ -Added :code:`ChatActionSender` utility to automatically sends chat action while long process is running. - -It also can be used as message middleware and can be customized via :code:`chat_action` flag. diff --git a/CHANGES/838.misc b/CHANGES/838.misc deleted file mode 100644 index d4af8385..00000000 --- a/CHANGES/838.misc +++ /dev/null @@ -1,2 +0,0 @@ -**BREAKING** -Events isolation mechanism is moved from FSM storages to standalone managers From 79588b7c5d3168e8baa5c91e6b0592f629a64269 Mon Sep 17 00:00:00 2001 From: Gabben Date: Sat, 5 Mar 2022 00:33:45 +0000 Subject: [PATCH 07/35] Fix I18nMiddleware class (#859) --- docs/utils/i18n.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utils/i18n.rst b/docs/utils/i18n.rst index fbf6333f..9ca61510 100644 --- a/docs/utils/i18n.rst +++ b/docs/utils/i18n.rst @@ -118,7 +118,7 @@ I18nMiddleware or define you own based on abstract I18nMiddleware middleware: -.. autoclass:: aiogram.utils.i18n.middleware.FSMI18nMiddleware +.. autoclass:: aiogram.utils.i18n.middleware.I18nMiddleware :members: __init__,setup,get_locale From b50500e28da217ab41d35363776c8cad5e1a7687 Mon Sep 17 00:00:00 2001 From: Oleg A Date: Sat, 26 Mar 2022 18:22:11 +0300 Subject: [PATCH 08/35] fix: i18n User.language_code is optional (#871) user.language_code is Optional and may raise an exception #870 --- aiogram/utils/i18n/middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiogram/utils/i18n/middleware.py b/aiogram/utils/i18n/middleware.py index dabbfae8..929c6520 100644 --- a/aiogram/utils/i18n/middleware.py +++ b/aiogram/utils/i18n/middleware.py @@ -118,7 +118,7 @@ class SimpleI18nMiddleware(I18nMiddleware): ) event_from_user: Optional[User] = data.get("event_from_user", None) - if event_from_user is None: + if event_from_user is None or event_from_user.language_code is None: return self.i18n.default_locale try: locale = Locale.parse(event_from_user.language_code, sep="-") From a37bbba38c3c87d24525dff657f4f2ff26de382a Mon Sep 17 00:00:00 2001 From: Gabben Date: Sat, 26 Mar 2022 15:30:46 +0000 Subject: [PATCH 09/35] Fix docs and examples (#864) * Update magic_data Allowed handlers * Fix ChatMemberUpdated example * Fix examples with `.in_(...)` and delete with `@` * Fix commands in examples * Change List to Set --- docs/dispatcher/filters/chat_member_updated.rst | 4 ++-- docs/dispatcher/filters/magic_data.rst | 9 +++++++++ docs/dispatcher/filters/magic_filters.rst | 11 +++++------ examples/echo_bot.py | 4 ++-- examples/finite_state_machine.py | 4 ++-- examples/multibot.py | 2 +- examples/specify_updates.py | 2 +- 7 files changed, 22 insertions(+), 14 deletions(-) diff --git a/docs/dispatcher/filters/chat_member_updated.rst b/docs/dispatcher/filters/chat_member_updated.rst index d02313f4..55c877bd 100644 --- a/docs/dispatcher/filters/chat_member_updated.rst +++ b/docs/dispatcher/filters/chat_member_updated.rst @@ -86,10 +86,10 @@ Handle user leave or join events from aiogram.dispatcher.filters import IS_MEMBER, IS_NOT_MEMBER - @router.chat_member(chat_member_updated=IS_MEMBER >> IS_NOT_MEMBER) + @router.chat_member(member_status_changed=IS_MEMBER >> IS_NOT_MEMBER) async def on_user_leave(event: ChatMemberUpdated): ... - @router.chat_member(chat_member_updated=IS_NOT_MEMBER >> IS_MEMBER) + @router.chat_member(member_status_changed=IS_NOT_MEMBER >> IS_MEMBER) async def on_user_join(event: ChatMemberUpdated): ... Or construct your own terms via using pre-defined set of statuses and transitions. diff --git a/docs/dispatcher/filters/magic_data.rst b/docs/dispatcher/filters/magic_data.rst index 72cf2433..6335d19b 100644 --- a/docs/dispatcher/filters/magic_data.rst +++ b/docs/dispatcher/filters/magic_data.rst @@ -31,4 +31,13 @@ Allowed update types for this filter: - :code:`channel_post` - :code:`edited_channel_post` - :code:`inline_query` +- :code:`chosen_inline_result` - :code:`callback_query` +- :code:`shipping_query` +- :code:`pre_checkout_query` +- :code:`poll` +- :code:`poll_answer` +- :code:`my_chat_member` +- :code:`chat_member` +- :code:`chat_join_request` +- :code:`error` diff --git a/docs/dispatcher/filters/magic_filters.rst b/docs/dispatcher/filters/magic_filters.rst index 3117aeb3..3544e12a 100644 --- a/docs/dispatcher/filters/magic_filters.rst +++ b/docs/dispatcher/filters/magic_filters.rst @@ -61,9 +61,8 @@ Can be used as method named :code:`in_` or as matmul operator :code:`@` with any .. code-block:: python - F.from_user.id.in_(42, 1000, 123123) # lambda query: query.from_user.id in {42, 1000, 123123} - F.data.in_('foo', 'bar', 'baz') # lambda query: query.data in {'foo', 'bar', 'baz'} - F.text @ {'foo', 'bar'} # lambda message: message.text in {'foo', 'bar'} + F.from_user.id.in_({42, 1000, 123123}) # lambda query: query.from_user.id in {42, 1000, 123123} + F.data.in_({'foo', 'bar', 'baz'}) # lambda query: query.data in {'foo', 'bar', 'baz'} Contains -------- @@ -117,7 +116,7 @@ All operations can be combined via bitwise and/or operators - :code:`&`/:code:`| (F.from_user.id == 42) & (F.text == 'admin') F.text.startswith('a') | F.text.endswith('b') - (F.from_user.id @ {42, 777, 911}) & (F.text.startswith('!') | F.text.startswith('/')) & F.text.contains('ban') + (F.from_user.id.in_({42, 777, 911})) & (F.text.startswith('!') | F.text.startswith('/')) & F.text.contains('ban') Attribute modifiers - string manipulations @@ -130,7 +129,7 @@ Can be used only with string attributes. .. code-block:: python F.text.lower() == 'test' # lambda message: message.text.lower() == 'test' - F.text.upper().in_('FOO', 'BAR') # lambda message: message.text.upper() in {'FOO', 'BAR'} + F.text.upper().in_({'FOO', 'BAR'}) # lambda message: message.text.upper() in {'FOO', 'BAR'} F.text.len() == 5 # lambda message: len(message.text) == 5 @@ -157,7 +156,7 @@ Usage in *aiogram* @router.message(F.text == 'hello') @router.inline_query(F.data == 'button:1') @router.message(F.text.startswith('foo')) - @router.message(F.content_type.in_('text', 'sticker')) + @router.message(F.content_type.in_({'text', 'sticker'})) @router.message(F.text.regexp(r'\d+')) ... diff --git a/examples/echo_bot.py b/examples/echo_bot.py index f5689c3d..3c9b1218 100644 --- a/examples/echo_bot.py +++ b/examples/echo_bot.py @@ -10,7 +10,7 @@ dp = Dispatcher() logger = logging.getLogger(__name__) -@dp.message(commands={"start"}) +@dp.message(commands=["start"]) async def command_start_handler(message: Message) -> None: """ This handler receive messages with `/start` command @@ -23,7 +23,7 @@ async def command_start_handler(message: Message) -> None: @dp.message() -async def echo_handler(message: types.Message) -> Any: +async def echo_handler(message: types.Message) -> None: """ Handler will forward received message back to the sender diff --git a/examples/finite_state_machine.py b/examples/finite_state_machine.py index 0a540a0e..19b538c9 100644 --- a/examples/finite_state_machine.py +++ b/examples/finite_state_machine.py @@ -18,7 +18,7 @@ class Form(StatesGroup): language = State() -@form_router.message(commands={"start"}) +@form_router.message(commands=["start"]) async def command_start(message: Message, state: FSMContext) -> None: await state.set_state(Form.name) await message.answer( @@ -27,7 +27,7 @@ async def command_start(message: Message, state: FSMContext) -> None: ) -@form_router.message(commands={"cancel"}) +@form_router.message(commands=["cancel"]) @form_router.message(F.text.casefold() == "cancel") async def cancel_handler(message: Message, state: FSMContext) -> None: """ diff --git a/examples/multibot.py b/examples/multibot.py index 82c31715..47344169 100644 --- a/examples/multibot.py +++ b/examples/multibot.py @@ -40,7 +40,7 @@ def is_bot_token(value: str) -> Union[bool, Dict[str, Any]]: @main_router.message(Command(commands=["add"], command_magic=F.args.func(is_bot_token))) -async def command_add_bot(message: Message, command: CommandObject, bot: Bot): +async def command_add_bot(message: Message, command: CommandObject, bot: Bot) -> Any: new_bot = Bot(token=command.args, session=bot.session) try: bot_user = await new_bot.get_me() diff --git a/examples/specify_updates.py b/examples/specify_updates.py index 1ad3cd0f..160fa46d 100644 --- a/examples/specify_updates.py +++ b/examples/specify_updates.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) -@dp.message(commands={"start"}) +@dp.message(commands=["start"]) async def command_start_handler(message: Message) -> None: """ This handler receive messages with `/start` command From baf31a531aa18e2145153b9d666f0ca7ebdc9e68 Mon Sep 17 00:00:00 2001 From: Alexey <54932673+ksemele@users.noreply.github.com> Date: Sat, 26 Mar 2022 19:55:08 +0400 Subject: [PATCH 10/35] Update aiohttp.rst (#872) fix import --- docs/api/session/aiohttp.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/session/aiohttp.rst b/docs/api/session/aiohttp.rst index 7dd9713b..523d76b6 100644 --- a/docs/api/session/aiohttp.rst +++ b/docs/api/session/aiohttp.rst @@ -14,7 +14,7 @@ Usage example .. code-block:: from aiogram import Bot - from aiogram.session.aiohttp import AiohttpSession + from aiogram.client.session.aiohttp import AiohttpSession session = AiohttpSession() bot = Bot('42:token', session=session) From b8aa03bc38f19d0ec0b44088728ea9f07e6f9b29 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 27 Mar 2022 01:36:05 +0200 Subject: [PATCH 11/35] Added changelog changed validator (#873) * Added changelog changed validator --- .github/workflows/pull_request_changelog.yml | 69 ++++++++++++++++ CHANGES/873.misc | 1 + poetry.lock | 84 ++++++++++---------- 3 files changed, 112 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/pull_request_changelog.yml create mode 100644 CHANGES/873.misc diff --git a/.github/workflows/pull_request_changelog.yml b/.github/workflows/pull_request_changelog.yml new file mode 100644 index 00000000..c40c3d06 --- /dev/null +++ b/.github/workflows/pull_request_changelog.yml @@ -0,0 +1,69 @@ +on: + pull_request: + types: + - "opened" + - "reopened" + - "synchronize" + - "labeled" + - "unlabeled" + +jobs: + check_changes: + runs-on: ubuntu-latest + name: "Check that changes is described" + steps: + - name: "Checkout code" + uses: actions/checkout@master + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: '0' + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + - name: Install towncrier + run: pip install towncrier + + - name: "Check changelog" + env: + BASE_BRANCH: ${{ github.base_ref }} + run: | + git fetch --no-tags origin +refs/heads/${BASE_BRANCH}:refs/remotes/origin/${BASE_BRANCH} + towncrier check --compare-with origin/${BASE_BRANCH} + + - name: Find Comment + if: "always()" + uses: peter-evans/find-comment@v2 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Changelog + + - name: Create fail comment + if: "failure()" + uses: peter-evans/create-or-update-comment@v2 + with: + edit-mode: replace + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + # :x: Changelog is required! + + You need to add a brief description of the changes to the `CHANGES` directory. + + For example, you can run `towncrier create .` to create a file in the change directory and then write a description on that file. + + Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/quickstart.html#creating-news-fragments) + + - name: Create success comment + if: "success()" + uses: peter-evans/create-or-update-comment@v2 + with: + edit-mode: replace + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body: | + # :heavy_check_mark: Changelog found. + + Thank you for adding a description of the changes diff --git a/CHANGES/873.misc b/CHANGES/873.misc new file mode 100644 index 00000000..170dca4d --- /dev/null +++ b/CHANGES/873.misc @@ -0,0 +1 @@ +Added automated check that pull-request adds a changes description to **CHANGES** directory diff --git a/poetry.lock b/poetry.lock index 22656370..bd66ff27 100644 --- a/poetry.lock +++ b/poetry.lock @@ -275,7 +275,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.3.1" +version = "6.3.2" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -1593,47 +1593,47 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, - {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, - {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, - {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, - {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, - {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, - {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, - {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, - {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, - {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, - {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, - {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, - {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, - {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, - {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, - {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, - {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, - {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, - {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, - {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, - {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, - {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, - {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, + {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, + {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, + {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, + {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, + {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, + {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, + {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, + {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, + {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, + {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, + {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] decorator = [ {file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"}, From ffe9506dc52afe3310a2d1138a142d71da304a21 Mon Sep 17 00:00:00 2001 From: Oleg A Date: Wed, 6 Apr 2022 04:19:31 +0300 Subject: [PATCH 12/35] `parse_mode` should be `UNSET` instead of `None` (#877) * fix: parse_mode should be UNSET instead of None #876 * docs: add changelog * docs: fixed changelog name * fix: changelog typo fix --- CHANGES/876.bugfix | 1 + aiogram/types/message.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 CHANGES/876.bugfix diff --git a/CHANGES/876.bugfix b/CHANGES/876.bugfix new file mode 100644 index 00000000..b4fded2e --- /dev/null +++ b/CHANGES/876.bugfix @@ -0,0 +1 @@ +Fixed default `parse_mode` for `Message.copy_to()` method. diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 24435025..5ee37d00 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -1753,7 +1753,7 @@ class Message(TelegramObject): self, chat_id: Union[int, str], caption: Optional[str] = None, - parse_mode: Optional[str] = None, + parse_mode: Optional[str] = UNSET, caption_entities: Optional[List[MessageEntity]] = None, disable_notification: Optional[bool] = None, reply_to_message_id: Optional[int] = None, From 1e5d231869fb20c52f8b666d5bd00e71216e69a0 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Mon, 11 Apr 2022 03:31:37 +0300 Subject: [PATCH 13/35] Update changelog action, try to fix PR from external repositories --- .github/workflows/pull_request_changelog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull_request_changelog.yml b/.github/workflows/pull_request_changelog.yml index c40c3d06..a8edbf37 100644 --- a/.github/workflows/pull_request_changelog.yml +++ b/.github/workflows/pull_request_changelog.yml @@ -1,5 +1,5 @@ on: - pull_request: + pull_request_target: types: - "opened" - "reopened" From 370cd27471a7d6974fac9b1bac1d8e39866a7844 Mon Sep 17 00:00:00 2001 From: layerqa <52602110+layerqa@users.noreply.github.com> Date: Mon, 11 Apr 2022 05:34:24 +0500 Subject: [PATCH 14/35] Dispathcer to Dispatcher on docs (#881) --- docs/dispatcher/dispatcher.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dispatcher/dispatcher.rst b/docs/dispatcher/dispatcher.rst index 507b2ed0..d7403a66 100644 --- a/docs/dispatcher/dispatcher.rst +++ b/docs/dispatcher/dispatcher.rst @@ -1,5 +1,5 @@ ########## -Dispathcer +Dispatcher ########## Dispatcher is root :obj:`Router` and in code Dispatcher can be used directly for routing updates or attach another routers into dispatcher. From 7ae588d4b8687fae39ffe1f9f085650dddd6aae4 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Mon, 11 Apr 2022 03:59:09 +0300 Subject: [PATCH 15/35] Delete comment when pull request is labeled --- .github/workflows/pull_request_changelog.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/pull_request_changelog.yml b/.github/workflows/pull_request_changelog.yml index a8edbf37..832f7826 100644 --- a/.github/workflows/pull_request_changelog.yml +++ b/.github/workflows/pull_request_changelog.yml @@ -25,6 +25,7 @@ jobs: run: pip install towncrier - name: "Check changelog" + if: "!contains(github.event.pull_request.labels.*.name, 'skip news')" env: BASE_BRANCH: ${{ github.base_ref }} run: | @@ -67,3 +68,10 @@ jobs: # :heavy_check_mark: Changelog found. Thank you for adding a description of the changes + + - name: Delete comment when not labeled + if: "contains(github.event.pull_request.labels.*.name, 'skip news')" + uses: peter-evans/delete-comment@v2 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} From e8432d38f57af95b7f1c7e84cfd601df5f161f27 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Mon, 11 Apr 2022 04:06:39 +0300 Subject: [PATCH 16/35] Add comment when PR is labeled --- .github/workflows/pull_request_changelog.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request_changelog.yml b/.github/workflows/pull_request_changelog.yml index 832f7826..538ea1ed 100644 --- a/.github/workflows/pull_request_changelog.yml +++ b/.github/workflows/pull_request_changelog.yml @@ -69,9 +69,14 @@ jobs: Thank you for adding a description of the changes - - name: Delete comment when not labeled + - name: Comment when docs is not needed if: "contains(github.event.pull_request.labels.*.name, 'skip news')" - uses: peter-evans/delete-comment@v2 + uses: peter-evans/create-or-update-comment@v2 with: + edit-mode: replace comment-id: ${{ steps.fc.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} + body: | + # :corn: Changelog is not needed. + + This PR does not require a changelog in due to the `skip news` label. From 930bca0876a15a5d3d12a1b773f35c9bc7a5cafe Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 11 Apr 2022 04:08:27 +0300 Subject: [PATCH 17/35] Fixed path of I18n Class (#879) The I18n Class gets imported in 3.0.0b2 as follows: from aiogram.utils.i18n import I18n Whereas the documentation implies that it should be: from aiogram.utils.i18n.code import I18n --- docs/utils/i18n.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utils/i18n.rst b/docs/utils/i18n.rst index 9ca61510..13d47ed6 100644 --- a/docs/utils/i18n.rst +++ b/docs/utils/i18n.rst @@ -81,7 +81,7 @@ 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 +On top of your application the instance of :class:`aiogram.utils.i18n.I18n` should be created .. code-block:: From 286cf39c8ae0d2d80c7185cf49af2668703b2e4f Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 16 Apr 2022 19:07:32 +0300 Subject: [PATCH 18/35] Beta 3 (#884) * Rework middlewares, separate management to `MiddlewareManager` class * Rework middlewares * Added changes description for redis * Added changes description for redis * Fixed tests with Redis // aioredis replacement * Changed msg._text attributes behaviour * Added changelog for spoilers * Added possibility to get command magic result as handler arguments --- .pre-commit-config.yaml | 6 +- CHANGES/{839.bugix => 839.bugix.rst} | 0 CHANGES/865.bugfix.rst | 1 + CHANGES/{873.misc => 873.misc.rst} | 0 CHANGES/874.misc.rst | 2 + CHANGES/882.misc.rst | 1 + CHANGES/883.misc.rst | 3 + CHANGES/885.bugfix.rst | 1 + CHANGES/889.feature.rst | 1 + Makefile | 5 +- aiogram/client/session/base.py | 44 +- aiogram/client/session/middlewares/base.py | 22 +- aiogram/client/session/middlewares/manager.py | 79 ++ .../session/middlewares/request_logging.py | 9 +- aiogram/dispatcher/dispatcher.py | 47 +- aiogram/dispatcher/event/telegram.py | 145 +-- aiogram/dispatcher/filters/callback_data.py | 79 +- aiogram/dispatcher/filters/command.py | 18 +- aiogram/dispatcher/filters/magic_data.py | 8 +- aiogram/dispatcher/flags/flag.py | 25 +- aiogram/dispatcher/fsm/storage/redis.py | 8 +- aiogram/dispatcher/middlewares/manager.py | 61 ++ aiogram/dispatcher/router.py | 178 +++- aiogram/methods/base.py | 2 + aiogram/types/keyboard_button.py | 5 + aiogram/types/message.py | 11 +- aiogram/utils/chat_action.py | 2 +- aiogram/utils/link.py | 34 +- aiogram/utils/text_decorations.py | 42 +- docs/dispatcher/filters/callback_data.rst | 118 +++ docs/dispatcher/filters/index.rst | 1 + docs/utils/index.rst | 2 +- examples/echo_bot.py | 1 - mypy.ini | 2 +- poetry.lock | 970 +++++++++--------- pyproject.toml | 25 +- tests/conftest.py | 2 +- .../test_session/test_base_session.py | 6 +- .../test_middlewares/test_manager.py | 45 + tests/test_api/test_types/test_message.py | 19 +- tests/test_dispatcher/test_deprecated.py | 37 +- tests/test_dispatcher/test_dispatcher.py | 17 +- .../test_event/test_middleware.py | 42 + .../test_event/test_telegram.py | 11 +- .../test_filters/test_callback_data.py | 6 +- .../test_filters/test_command.py | 12 + .../test_filters/test_exception.py | 2 +- .../test_filters/test_magic_data.py | 6 +- tests/test_utils/test_i18n.py | 4 +- tests/test_utils/test_link.py | 11 +- tests/test_utils/test_text_decorations.py | 6 + 51 files changed, 1380 insertions(+), 804 deletions(-) rename CHANGES/{839.bugix => 839.bugix.rst} (100%) create mode 100644 CHANGES/865.bugfix.rst rename CHANGES/{873.misc => 873.misc.rst} (100%) create mode 100644 CHANGES/874.misc.rst create mode 100644 CHANGES/882.misc.rst create mode 100644 CHANGES/883.misc.rst create mode 100644 CHANGES/885.bugfix.rst create mode 100644 CHANGES/889.feature.rst create mode 100644 aiogram/client/session/middlewares/manager.py create mode 100644 aiogram/dispatcher/middlewares/manager.py create mode 100644 docs/dispatcher/filters/callback_data.rst create mode 100644 tests/test_api/test_client/test_session/test_middlewares/test_manager.py create mode 100644 tests/test_dispatcher/test_event/test_middleware.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 07fbe618..98dda96a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,20 +2,20 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v4.2.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace - id: check-merge-conflict - repo: https://github.com/psf/black - rev: 21.8b0 + rev: 22.3.0 hooks: - id: black files: &files '^(aiogram|tests|examples)' - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.9.3 + rev: v5.10.1 hooks: - id: isort additional_dependencies: [ toml ] diff --git a/CHANGES/839.bugix b/CHANGES/839.bugix.rst similarity index 100% rename from CHANGES/839.bugix rename to CHANGES/839.bugix.rst diff --git a/CHANGES/865.bugfix.rst b/CHANGES/865.bugfix.rst new file mode 100644 index 00000000..bab93962 --- /dev/null +++ b/CHANGES/865.bugfix.rst @@ -0,0 +1 @@ +Added parsing of spoiler message entity diff --git a/CHANGES/873.misc b/CHANGES/873.misc.rst similarity index 100% rename from CHANGES/873.misc rename to CHANGES/873.misc.rst diff --git a/CHANGES/874.misc.rst b/CHANGES/874.misc.rst new file mode 100644 index 00000000..d167c1d8 --- /dev/null +++ b/CHANGES/874.misc.rst @@ -0,0 +1,2 @@ +Changed :code:`Message.html_text` and :code:`Message.md_text` attributes behaviour when message has no text. +The empty string will be used instead of raising error. diff --git a/CHANGES/882.misc.rst b/CHANGES/882.misc.rst new file mode 100644 index 00000000..ca8b0ba4 --- /dev/null +++ b/CHANGES/882.misc.rst @@ -0,0 +1 @@ +Used `redis-py` instead of `aioredis` package in due to this packages was merged into single one diff --git a/CHANGES/883.misc.rst b/CHANGES/883.misc.rst new file mode 100644 index 00000000..8496c902 --- /dev/null +++ b/CHANGES/883.misc.rst @@ -0,0 +1,3 @@ +Solved common naming problem with middlewares that confusing too much developers +- now you can't see the `middleware` and `middlewares` attributes at the same point +because this functionality encapsulated to special interface. diff --git a/CHANGES/885.bugfix.rst b/CHANGES/885.bugfix.rst new file mode 100644 index 00000000..6d1bb095 --- /dev/null +++ b/CHANGES/885.bugfix.rst @@ -0,0 +1 @@ +Fixed CallbackData factory parsing IntEnum's diff --git a/CHANGES/889.feature.rst b/CHANGES/889.feature.rst new file mode 100644 index 00000000..8e58304c --- /dev/null +++ b/CHANGES/889.feature.rst @@ -0,0 +1 @@ +Added possibility to get command magic result as handler argument diff --git a/Makefile b/Makefile index c0508fea..8d9abab8 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ help: .PHONY: install install: - poetry install -E fast -E redis -E proxy -E i18n -E docs + poetry install -E fast -E redis -E proxy -E i18n -E docs --remove-untracked $(py) pre-commit install .PHONY: clean @@ -94,9 +94,6 @@ test: test-run-services test-coverage: test-run-services mkdir -p $(reports_dir)/tests/ $(py) pytest --cov=aiogram --cov-config .coveragerc --html=$(reports_dir)/tests/index.html tests/ --redis $(redis_connection) - -.PHONY: test-coverage-report -test-coverage-report: $(py) coverage html -d $(reports_dir)/coverage .PHONY: test-coverage-view diff --git a/aiogram/client/session/base.py b/aiogram/client/session/base.py index d5a95622..ba337c94 100644 --- a/aiogram/client/session/base.py +++ b/aiogram/client/session/base.py @@ -3,22 +3,9 @@ from __future__ import annotations import abc import datetime import json -from functools import partial from http import HTTPStatus from types import TracebackType -from typing import ( - TYPE_CHECKING, - Any, - AsyncGenerator, - Awaitable, - Callable, - Final, - List, - Optional, - Type, - Union, - cast, -) +from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Final, Optional, Type, Union, cast from aiogram.exceptions import ( RestartingTelegram, @@ -36,26 +23,15 @@ from aiogram.exceptions import ( from ...methods import Response, TelegramMethod from ...methods.base import TelegramType -from ...types import UNSET, TelegramObject +from ...types import UNSET from ..telegram import PRODUCTION, TelegramAPIServer -from .middlewares.base import BaseRequestMiddleware +from .middlewares.manager import RequestMiddlewareManager if TYPE_CHECKING: from ..bot import Bot _JsonLoads = Callable[..., Any] _JsonDumps = Callable[..., str] -NextRequestMiddlewareType = Callable[ - ["Bot", TelegramMethod[TelegramObject]], Awaitable[Response[TelegramObject]] -] - -RequestMiddlewareType = Union[ - BaseRequestMiddleware, - Callable[ - [NextRequestMiddlewareType, "Bot", TelegramMethod[TelegramType]], - Awaitable[Response[TelegramType]], - ], -] DEFAULT_TIMEOUT: Final[float] = 60.0 @@ -80,7 +56,7 @@ class BaseSession(abc.ABC): self.json_dumps = json_dumps self.timeout = timeout - self.middlewares: List[RequestMiddlewareType[TelegramObject]] = [] + self.middleware = RequestMiddlewareManager() def check_response( self, method: TelegramMethod[TelegramType], status_code: int, content: str @@ -185,19 +161,11 @@ class BaseSession(abc.ABC): return {k: self.clean_json(v) for k, v in value.items() if v is not None} return value - def middleware( - self, middleware: RequestMiddlewareType[TelegramObject] - ) -> RequestMiddlewareType[TelegramObject]: - self.middlewares.append(middleware) - return middleware - async def __call__( self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET ) -> TelegramType: - middleware = partial(self.make_request, timeout=timeout) - for m in reversed(self.middlewares): - middleware = partial(m, middleware) # type: ignore - return await middleware(bot, method) + middleware = self.middleware.wrap_middlewares(self.make_request, timeout=timeout) + return cast(TelegramType, await middleware(bot, method)) async def __aenter__(self) -> BaseSession: return self diff --git a/aiogram/client/session/middlewares/base.py b/aiogram/client/session/middlewares/base.py index 5b8d0f2b..90dcd09f 100644 --- a/aiogram/client/session/middlewares/base.py +++ b/aiogram/client/session/middlewares/base.py @@ -1,15 +1,23 @@ +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Awaitable, Callable +from typing import TYPE_CHECKING, Awaitable, Callable, Union from aiogram.methods import Response, TelegramMethod -from aiogram.types import TelegramObject +from aiogram.methods.base import TelegramType if TYPE_CHECKING: from ...bot import Bot - NextRequestMiddlewareType = Callable[ - ["Bot", TelegramMethod[TelegramObject]], Awaitable[Response[TelegramObject]] + ["Bot", TelegramMethod[TelegramType]], Awaitable[Response[TelegramType]] +] +RequestMiddlewareType = Union[ + "BaseRequestMiddleware", + Callable[ + [NextRequestMiddlewareType[TelegramType], "Bot", TelegramMethod[TelegramType]], + Awaitable[Response[TelegramType]], + ], ] @@ -21,10 +29,10 @@ class BaseRequestMiddleware(ABC): @abstractmethod async def __call__( self, - make_request: NextRequestMiddlewareType, + make_request: NextRequestMiddlewareType[TelegramType], bot: "Bot", - method: TelegramMethod[TelegramObject], - ) -> Response[TelegramObject]: + method: TelegramMethod[TelegramType], + ) -> Response[TelegramType]: """ Execute middleware diff --git a/aiogram/client/session/middlewares/manager.py b/aiogram/client/session/middlewares/manager.py new file mode 100644 index 00000000..0e76801e --- /dev/null +++ b/aiogram/client/session/middlewares/manager.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from functools import partial +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + List, + Optional, + Sequence, + Union, + overload, +) + +from aiogram.client.session.middlewares.base import ( + NextRequestMiddlewareType, + RequestMiddlewareType, +) +from aiogram.methods import Response +from aiogram.methods.base import TelegramMethod, TelegramType +from aiogram.types import TelegramObject + +if TYPE_CHECKING: + from aiogram import Bot + + +class RequestMiddlewareManager(Sequence[RequestMiddlewareType[TelegramObject]]): + def __init__(self) -> None: + self._middlewares: List[RequestMiddlewareType[TelegramObject]] = [] + + def register( + self, + middleware: RequestMiddlewareType[TelegramObject], + ) -> RequestMiddlewareType[TelegramObject]: + self._middlewares.append(middleware) + return middleware + + def unregister(self, middleware: RequestMiddlewareType[TelegramObject]) -> None: + self._middlewares.remove(middleware) + + def __call__( + self, + middleware: Optional[RequestMiddlewareType[TelegramObject]] = None, + ) -> Union[ + Callable[[RequestMiddlewareType[TelegramObject]], RequestMiddlewareType[TelegramObject]], + RequestMiddlewareType[TelegramObject], + ]: + if middleware is None: + return self.register + return self.register(middleware) + + @overload + def __getitem__(self, item: int) -> RequestMiddlewareType[TelegramObject]: + pass + + @overload + def __getitem__(self, item: slice) -> Sequence[RequestMiddlewareType[TelegramObject]]: + pass + + def __getitem__( + self, item: Union[int, slice] + ) -> Union[ + RequestMiddlewareType[TelegramObject], Sequence[RequestMiddlewareType[TelegramObject]] + ]: + return self._middlewares[item] + + def __len__(self) -> int: + return len(self._middlewares) + + def wrap_middlewares( + self, + callback: Callable[[Bot, TelegramMethod[TelegramType]], Awaitable[Response[TelegramType]]], + **kwargs: Any, + ) -> NextRequestMiddlewareType[TelegramType]: + middleware = partial(callback, **kwargs) + for m in reversed(self._middlewares): + middleware = partial(m, middleware) # type: ignore + return middleware diff --git a/aiogram/client/session/middlewares/request_logging.py b/aiogram/client/session/middlewares/request_logging.py index 67df277d..af7b9d6e 100644 --- a/aiogram/client/session/middlewares/request_logging.py +++ b/aiogram/client/session/middlewares/request_logging.py @@ -3,8 +3,7 @@ from typing import TYPE_CHECKING, Any, List, Optional, Type from aiogram import loggers from aiogram.methods import TelegramMethod -from aiogram.methods.base import Response -from aiogram.types import TelegramObject +from aiogram.methods.base import Response, TelegramType from .base import BaseRequestMiddleware, NextRequestMiddlewareType @@ -25,10 +24,10 @@ class RequestLogging(BaseRequestMiddleware): async def __call__( self, - make_request: NextRequestMiddlewareType, + make_request: NextRequestMiddlewareType[TelegramType], bot: "Bot", - method: TelegramMethod[TelegramObject], - ) -> Response[TelegramObject]: + method: TelegramMethod[TelegramType], + ) -> Response[TelegramType]: if type(method) not in self.ignore_methods: loggers.middlewares.info( "Make request with method=%r by bot id=%d", diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 610053db..9b84262c 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -36,8 +36,19 @@ class Dispatcher(Router): storage: Optional[BaseStorage] = None, fsm_strategy: FSMStrategy = FSMStrategy.USER_IN_CHAT, events_isolation: Optional[BaseEventIsolation] = None, + disable_fsm: bool = False, **kwargs: Any, ) -> None: + """ + Root router + + :param storage: Storage for FSM + :param fsm_strategy: FSM strategy + :param events_isolation: Events isolation + :param disable_fsm: Disable FSM, note that if you disable FSM + then you should not use storage and events isolation + :param kwargs: Other arguments, will be passed as keyword arguments to handlers + """ super(Dispatcher, self).__init__(**kwargs) # Telegram API provides originally only one event type - Update @@ -48,7 +59,8 @@ class Dispatcher(Router): ) self.update.register(self._listen_update) - # Error handlers should work is out of all other functions and be registered before all others middlewares + # Error handlers should work is out of all other functions + # and should be registered before all others middlewares self.update.outer_middleware(ErrorsMiddleware(self)) # User context middleware makes small optimization for all other builtin @@ -62,11 +74,31 @@ class Dispatcher(Router): strategy=fsm_strategy, events_isolation=events_isolation if events_isolation else DisabledEventIsolation(), ) - self.update.outer_middleware(self.fsm) + if not disable_fsm: + # Note that when FSM middleware is disabled, the event isolation is also disabled + # Because the isolation mechanism is a part of the FSM + self.update.outer_middleware(self.fsm) self.shutdown.register(self.fsm.close) + self._data: Dict[str, Any] = {} self._running_lock = Lock() + def __getitem__(self, item: str) -> Any: + return self._data[item] + + def __setitem__(self, key: str, value: Any) -> None: + self._data[key] = value + + def __delitem__(self, key: str) -> None: + del self._data[key] + + def get(self, key: str, /, default: Optional[Any] = None) -> Optional[Any]: + return self._data.get(key, default) + + @property + def storage(self) -> BaseStorage: + return self.fsm.storage + @property def parent_router(self) -> None: """ @@ -100,8 +132,15 @@ class Dispatcher(Router): token = Bot.set_current(bot) try: - kwargs.update(bot=bot) - response = await self.update.wrap_outer_middleware(self.update.trigger, update, kwargs) + response = await self.update.wrap_outer_middleware( + self.update.trigger, + update, + { + **self._data, + **kwargs, + "bot": bot, + }, + ) handled = response is not UNHANDLED return response finally: diff --git a/aiogram/dispatcher/event/telegram.py b/aiogram/dispatcher/event/telegram.py index a08d7cb1..fcf3d7d2 100644 --- a/aiogram/dispatcher/event/telegram.py +++ b/aiogram/dispatcher/event/telegram.py @@ -1,33 +1,17 @@ from __future__ import annotations -import functools +from inspect import isclass from itertools import chain -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Dict, - Generator, - List, - Optional, - Tuple, - Type, - Union, -) +from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Tuple, Type from pydantic import ValidationError +from aiogram.dispatcher.middlewares.manager import MiddlewareManager + from ...exceptions import FiltersResolveError from ...types import TelegramObject from ..filters.base import BaseFilter -from .bases import ( - REJECTED, - UNHANDLED, - MiddlewareEventType, - MiddlewareType, - NextMiddlewareType, - SkipHandler, -) +from .bases import REJECTED, UNHANDLED, MiddlewareType, SkipHandler from .handler import CallbackType, FilterObject, FilterType, HandlerObject, HandlerType if TYPE_CHECKING: @@ -48,8 +32,9 @@ class TelegramEventObserver: self.handlers: List[HandlerObject] = [] self.filters: List[Type[BaseFilter]] = [] - self.outer_middlewares: List[MiddlewareType[TelegramObject]] = [] - self.middlewares: List[MiddlewareType[TelegramObject]] = [] + + self.middleware = MiddlewareManager() + self.outer_middleware = MiddlewareManager() # Re-used filters check method from already implemented handler object # with dummy callback which never will be used @@ -75,7 +60,11 @@ class TelegramEventObserver: :param bound_filter: """ - if not issubclass(bound_filter, BaseFilter): + # TODO: This functionality should be deprecated in the future + # in due to bound filter has uncontrollable ordering and + # makes debugging process is harder that explicit using filters + + if not isclass(bound_filter) or not issubclass(bound_filter, BaseFilter): raise TypeError( "bound_filter() argument 'bound_filter' must be subclass of BaseFilter" ) @@ -97,18 +86,11 @@ class TelegramEventObserver: yield filter_ registry.append(filter_) - def _resolve_middlewares(self, *, outer: bool = False) -> List[MiddlewareType[TelegramObject]]: - """ - Get all middlewares in a tree - :param *: - """ - middlewares = [] - if outer: - middlewares.extend(self.outer_middlewares) - else: - for router in reversed(tuple(self.router.chain_head)): - observer = router.observers[self.event_name] - middlewares.extend(observer.middlewares) + def _resolve_middlewares(self) -> List[MiddlewareType[TelegramObject]]: + middlewares: List[MiddlewareType[TelegramObject]] = [] + for router in reversed(tuple(self.router.chain_head)): + observer = router.observers[self.event_name] + middlewares.extend(observer.middleware) return middlewares @@ -198,23 +180,13 @@ class TelegramEventObserver: ) return callback - @classmethod - def _wrap_middleware( - 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) - - middleware = mapper - for m in reversed(middlewares): - middleware = functools.partial(m, middleware) - return middleware - def wrap_outer_middleware( self, callback: Any, event: TelegramObject, data: Dict[str, Any] ) -> Any: - wrapped_outer = self._wrap_middleware(self._resolve_middlewares(outer=True), callback) + wrapped_outer = self.middleware.wrap_middlewares( + self.outer_middleware, + callback, + ) return wrapped_outer(event, data) async def trigger(self, event: TelegramObject, **kwargs: Any) -> Any: @@ -233,8 +205,9 @@ class TelegramEventObserver: if result: kwargs.update(data, handler=handler) try: - wrapped_inner = self._wrap_middleware( - self._resolve_middlewares(), handler.call + wrapped_inner = self.outer_middleware.wrap_middlewares( + self._resolve_middlewares(), + handler.call, ) return await wrapped_inner(event, kwargs) except SkipHandler: @@ -254,71 +227,3 @@ class TelegramEventObserver: return callback return wrapper - - def middleware( - self, - middleware: Optional[MiddlewareType[TelegramObject]] = None, - ) -> Union[ - Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]], - MiddlewareType[TelegramObject], - ]: - """ - Decorator for registering inner middlewares - - Usage: - - .. code-block:: python - - @.middleware() # via decorator (variant 1) - - .. code-block:: python - - @.middleware # via decorator (variant 2) - - .. code-block:: python - - async def my_middleware(handler, event, data): ... - .middleware(my_middleware) # via method - """ - - def wrapper(m: MiddlewareType[TelegramObject]) -> MiddlewareType[TelegramObject]: - self.middlewares.append(m) - return m - - if middleware is None: - return wrapper - return wrapper(middleware) - - def outer_middleware( - self, - middleware: Optional[MiddlewareType[TelegramObject]] = None, - ) -> Union[ - Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]], - MiddlewareType[TelegramObject], - ]: - """ - Decorator for registering outer middlewares - - Usage: - - .. code-block:: python - - @.outer_middleware() # via decorator (variant 1) - - .. code-block:: python - - @.outer_middleware # via decorator (variant 2) - - .. code-block:: python - - async def my_middleware(handler, event, data): ... - .outer_middleware(my_middleware) # via method - """ - - def wrapper(m: MiddlewareType[TelegramObject]) -> MiddlewareType[TelegramObject]: - self.outer_middlewares.append(m) - return m - - if middleware is None: - return wrapper - return wrapper(middleware) diff --git a/aiogram/dispatcher/filters/callback_data.py b/aiogram/dispatcher/filters/callback_data.py index d220da70..21aa6700 100644 --- a/aiogram/dispatcher/filters/callback_data.py +++ b/aiogram/dispatcher/filters/callback_data.py @@ -3,7 +3,7 @@ from __future__ import annotations from decimal import Decimal from enum import Enum from fractions import Fraction -from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Literal, Optional, Type, TypeVar, Union from uuid import UUID from magic_filter import MagicFilter @@ -22,9 +22,20 @@ class CallbackDataException(Exception): class CallbackData(BaseModel): + """ + Base class for callback data wrapper + + This class should be used as super-class of user-defined callbacks. + + The class-keyword :code:`prefix` is required to define prefix + and also the argument :code:`sep` can be passed to define separator (default is :code:`:`). + """ + if TYPE_CHECKING: - sep: str - prefix: str + __separator__: ClassVar[str] + """Data separator (default is :code:`:`)""" + __prefix__: ClassVar[str] + """Callback prefix""" def __init_subclass__(cls, **kwargs: Any) -> None: if "prefix" not in kwargs: @@ -32,12 +43,14 @@ class CallbackData(BaseModel): f"prefix required, usage example: " f"`class {cls.__name__}(CallbackData, prefix='my_callback'): ...`" ) - cls.sep = kwargs.pop("sep", ":") - cls.prefix = kwargs.pop("prefix") - if cls.sep in cls.prefix: + cls.__separator__ = kwargs.pop("sep", ":") + cls.__prefix__ = kwargs.pop("prefix") + if cls.__separator__ in cls.__prefix__: raise ValueError( - f"Separator symbol {cls.sep!r} can not be used inside prefix {cls.prefix!r}" + f"Separator symbol {cls.__separator__!r} can not be used " + f"inside prefix {cls.__prefix__!r}" ) + super().__init_subclass__(**kwargs) def _encode_value(self, key: str, value: Any) -> str: if value is None: @@ -52,31 +65,45 @@ class CallbackData(BaseModel): ) def pack(self) -> str: - result = [self.prefix] + """ + Generate callback data string + + :return: valid callback data for Telegram Bot API + """ + result = [self.__prefix__] for key, value in self.dict().items(): encoded = self._encode_value(key, value) - if self.sep in encoded: + if self.__separator__ in encoded: raise ValueError( - f"Separator symbol {self.sep!r} can not be used in value {key}={encoded!r}" + f"Separator symbol {self.__separator__!r} can not be used " + f"in value {key}={encoded!r}" ) result.append(encoded) - callback_data = self.sep.join(result) + callback_data = self.__separator__.join(result) if len(callback_data.encode()) > MAX_CALLBACK_LENGTH: raise ValueError( - f"Resulted callback data is too long! len({callback_data!r}.encode()) > {MAX_CALLBACK_LENGTH}" + f"Resulted callback data is too long! " + f"len({callback_data!r}.encode()) > {MAX_CALLBACK_LENGTH}" ) return callback_data @classmethod def unpack(cls: Type[T], value: str) -> T: - prefix, *parts = value.split(cls.sep) + """ + Parse callback data string + + :param value: value from Telegram + :return: instance of CallbackData + """ + prefix, *parts = value.split(cls.__separator__) names = cls.__fields__.keys() if len(parts) != len(names): raise TypeError( - f"Callback data {cls.__name__!r} takes {len(names)} arguments but {len(parts)} were given" + f"Callback data {cls.__name__!r} takes {len(names)} arguments " + f"but {len(parts)} were given" ) - if prefix != cls.prefix: - raise ValueError(f"Bad prefix ({prefix!r} != {cls.prefix!r})") + if prefix != cls.__prefix__: + raise ValueError(f"Bad prefix ({prefix!r} != {cls.__prefix__!r})") payload = {} for k, v in zip(names, parts): # type: str, Optional[str] if field := cls.__fields__.get(k): @@ -87,15 +114,30 @@ class CallbackData(BaseModel): @classmethod def filter(cls, rule: Optional[MagicFilter] = None) -> CallbackQueryFilter: + """ + Generates a filter for callback query with rule + + :param rule: magic rule + :return: instance of filter + """ return CallbackQueryFilter(callback_data=cls, rule=rule) - class Config: - use_enum_values = True + # class Config: + # use_enum_values = True class CallbackQueryFilter(BaseFilter): + """ + This filter helps to handle callback query. + + Should not be used directly, you should create the instance of this filter + via callback data instance + """ + callback_data: Type[CallbackData] + """Expected type of callback data""" rule: Optional[MagicFilter] = None + """Magic rule""" async def __call__(self, query: CallbackQuery) -> Union[Literal[False], Dict[str, Any]]: if not isinstance(query, CallbackQuery) or not query.data: @@ -111,3 +153,4 @@ class CallbackQueryFilter(BaseFilter): class Config: arbitrary_types_allowed = True + use_enum_values = True diff --git a/aiogram/dispatcher/filters/command.py b/aiogram/dispatcher/filters/command.py index 03cb41c7..3690f884 100644 --- a/aiogram/dispatcher/filters/command.py +++ b/aiogram/dispatcher/filters/command.py @@ -59,7 +59,10 @@ class Command(BaseFilter): command = await self.parse_command(text=text, bot=bot) except CommandException: return False - return {"command": command} + result = {"command": command} + if command.magic_result and isinstance(command.magic_result, dict): + result.update(command.magic_result) + return result def extract_command(self, text: str) -> CommandObject: # First step: separate command with arguments @@ -110,20 +113,22 @@ class Command(BaseFilter): self.validate_prefix(command=command) await self.validate_mention(bot=bot, command=command) command = self.validate_command(command) - self.do_magic(command=command) + command = self.do_magic(command=command) return command - def do_magic(self, command: CommandObject) -> None: + def do_magic(self, command: CommandObject) -> Any: if not self.command_magic: - return - if not self.command_magic.resolve(command): + return command + result = self.command_magic.resolve(command) + if not result: raise CommandException("Rejected via magic filter") + return replace(command, magic_result=result) class Config: arbitrary_types_allowed = True -@dataclass +@dataclass(frozen=True) class CommandObject: """ Instance of this object is always has command and it prefix. @@ -140,6 +145,7 @@ class CommandObject: """Command argument""" regexp_match: Optional[Match[str]] = field(repr=False, default=None) """Will be presented match result if the command is presented as regexp in filter""" + magic_result: Optional[Any] = field(repr=False, default=None) @property def mentioned(self) -> bool: diff --git a/aiogram/dispatcher/filters/magic_data.py b/aiogram/dispatcher/filters/magic_data.py index 2fa5f46a..3c98806a 100644 --- a/aiogram/dispatcher/filters/magic_data.py +++ b/aiogram/dispatcher/filters/magic_data.py @@ -12,9 +12,7 @@ class MagicData(BaseFilter): class Config: arbitrary_types_allowed = True - async def __call__(self, event: TelegramObject, *args: Any, **kwargs: Any) -> bool: - return bool( - self.magic_data.resolve( - AttrDict({"event": event, **{k: v for k, v in enumerate(args)}, **kwargs}) - ) + async def __call__(self, event: TelegramObject, *args: Any, **kwargs: Any) -> Any: + return self.magic_data.resolve( + AttrDict({"event": event, **{k: v for k, v in enumerate(args)}, **kwargs}) ) diff --git a/aiogram/dispatcher/flags/flag.py b/aiogram/dispatcher/flags/flag.py index 845b898b..1028393a 100644 --- a/aiogram/dispatcher/flags/flag.py +++ b/aiogram/dispatcher/flags/flag.py @@ -1,10 +1,13 @@ from dataclasses import dataclass -from typing import Any, Callable, Optional, Union, cast, overload +from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast, overload from magic_filter import AttrDict from aiogram.dispatcher.flags.getter import extract_flags_from_object +if TYPE_CHECKING: + pass + @dataclass(frozen=True) class Flag: @@ -25,11 +28,11 @@ class FlagDecorator: return self._with_flag(new_flag) @overload - def __call__(self, value: Callable[..., Any]) -> Callable[..., Any]: # type: ignore + def __call__(self, value: Callable[..., Any], /) -> Callable[..., Any]: # type: ignore pass @overload - def __call__(self, value: Any) -> "FlagDecorator": + def __call__(self, value: Any, /) -> "FlagDecorator": pass @overload @@ -53,8 +56,24 @@ class FlagDecorator: return self._with_value(AttrDict(kwargs) if value is None else value) +if TYPE_CHECKING: + + class _ChatActionFlagProtocol(FlagDecorator): + def __call__( # type: ignore[override] + self, + action: str = ..., + interval: float = ..., + initial_sleep: float = ..., + **kwargs: Any, + ) -> FlagDecorator: + pass + + class FlagGenerator: def __getattr__(self, name: str) -> FlagDecorator: if name[0] == "_": raise AttributeError("Flag name must NOT start with underscore") return FlagDecorator(Flag(name, True)) + + if TYPE_CHECKING: + chat_action: _ChatActionFlagProtocol diff --git a/aiogram/dispatcher/fsm/storage/redis.py b/aiogram/dispatcher/fsm/storage/redis.py index 5ab880de..36ac57c3 100644 --- a/aiogram/dispatcher/fsm/storage/redis.py +++ b/aiogram/dispatcher/fsm/storage/redis.py @@ -2,7 +2,9 @@ from abc import ABC, abstractmethod from contextlib import asynccontextmanager from typing import Any, AsyncGenerator, Dict, Literal, Optional, cast -from aioredis import ConnectionPool, Redis +from redis.asyncio.client import Redis +from redis.asyncio.connection import ConnectionPool +from redis.asyncio.lock import Lock from aiogram import Bot from aiogram.dispatcher.fsm.state import State @@ -131,7 +133,7 @@ class RedisStorage(BaseStorage): return RedisEventIsolation(redis=self.redis, key_builder=self.key_builder, **kwargs) async def close(self) -> None: - await self.redis.close() # type: ignore + await self.redis.close() async def set_state( self, @@ -223,7 +225,7 @@ class RedisEventIsolation(BaseEventIsolation): key: StorageKey, ) -> AsyncGenerator[None, None]: redis_key = self.key_builder.build(key, "lock") - async with self.redis.lock(name=redis_key, **self.lock_kwargs): + async with self.redis.lock(name=redis_key, **self.lock_kwargs, lock_class=Lock): yield None async def close(self) -> None: diff --git a/aiogram/dispatcher/middlewares/manager.py b/aiogram/dispatcher/middlewares/manager.py new file mode 100644 index 00000000..89892e77 --- /dev/null +++ b/aiogram/dispatcher/middlewares/manager.py @@ -0,0 +1,61 @@ +import functools +from typing import Any, Callable, Dict, List, Optional, Sequence, Union, overload + +from aiogram.dispatcher.event.bases import MiddlewareEventType, MiddlewareType, NextMiddlewareType +from aiogram.dispatcher.event.handler import HandlerType +from aiogram.types import TelegramObject + + +class MiddlewareManager(Sequence[MiddlewareType[TelegramObject]]): + def __init__(self) -> None: + self._middlewares: List[MiddlewareType[TelegramObject]] = [] + + def register( + self, + middleware: MiddlewareType[TelegramObject], + ) -> MiddlewareType[TelegramObject]: + self._middlewares.append(middleware) + return middleware + + def unregister(self, middleware: MiddlewareType[TelegramObject]) -> None: + self._middlewares.remove(middleware) + + def __call__( + self, + middleware: Optional[MiddlewareType[TelegramObject]] = None, + ) -> Union[ + Callable[[MiddlewareType[TelegramObject]], MiddlewareType[TelegramObject]], + MiddlewareType[TelegramObject], + ]: + if middleware is None: + return self.register + return self.register(middleware) + + @overload + def __getitem__(self, item: int) -> MiddlewareType[TelegramObject]: + pass + + @overload + def __getitem__(self, item: slice) -> Sequence[MiddlewareType[TelegramObject]]: + pass + + def __getitem__( + self, item: Union[int, slice] + ) -> Union[MiddlewareType[TelegramObject], Sequence[MiddlewareType[TelegramObject]]]: + return self._middlewares[item] + + def __len__(self) -> int: + return len(self._middlewares) + + @staticmethod + def wrap_middlewares( + middlewares: Sequence[MiddlewareType[MiddlewareEventType]], handler: HandlerType + ) -> NextMiddlewareType[MiddlewareEventType]: + @functools.wraps(handler) + def handler_wrapper(event: TelegramObject, kwargs: Dict[str, Any]) -> Any: + return handler(event, **kwargs) + + middleware = handler_wrapper + for m in reversed(middlewares): + middleware = functools.partial(m, middleware) + return middleware diff --git a/aiogram/dispatcher/router.py b/aiogram/dispatcher/router.py index c9e7dea9..32e82195 100644 --- a/aiogram/dispatcher/router.py +++ b/aiogram/dispatcher/router.py @@ -8,6 +8,7 @@ from ..utils.imports import import_module from ..utils.warnings import CodeHasNoEffect from .event.bases import REJECTED, UNHANDLED from .event.event import EventObserver +from .event.handler import HandlerType from .event.telegram import TelegramEventObserver from .filters import BUILTIN_FILTERS @@ -253,7 +254,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.message @property @@ -264,7 +264,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.edited_message @property @@ -275,7 +274,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.channel_post @property @@ -286,7 +284,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.edited_channel_post @property @@ -297,7 +294,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.inline_query @property @@ -308,7 +304,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.chosen_inline_result @property @@ -319,7 +314,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.callback_query @property @@ -330,7 +324,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.shipping_query @property @@ -341,7 +334,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.pre_checkout_query @property @@ -352,7 +344,6 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.poll @property @@ -363,9 +354,38 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.poll_answer + @property + def my_chat_member_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.my_chat_member_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.my_chat_member(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.my_chat_member + + @property + def chat_member_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.chat_member_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.chat_member(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.chat_member + + @property + def chat_join_request_handler(self) -> TelegramEventObserver: + warnings.warn( + "`Router.chat_join_request_handler(...)` is deprecated and will be removed in version 3.2 " + "use `Router.chat_join_request(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.chat_join_request + @property def errors_handler(self) -> TelegramEventObserver: warnings.warn( @@ -374,5 +394,139 @@ class Router: DeprecationWarning, stacklevel=2, ) - return self.errors + + def register_message(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_message(...)` is deprecated and will be removed in version 3.2 " + "use `Router.message.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.message.register(*args, **kwargs) + + def register_edited_message(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_edited_message(...)` is deprecated and will be removed in version 3.2 " + "use `Router.edited_message.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.edited_message.register(*args, **kwargs) + + def register_channel_post(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_channel_post(...)` is deprecated and will be removed in version 3.2 " + "use `Router.channel_post.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.channel_post.register(*args, **kwargs) + + def register_edited_channel_post(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_edited_channel_post(...)` is deprecated and will be removed in version 3.2 " + "use `Router.edited_channel_post.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.edited_channel_post.register(*args, **kwargs) + + def register_inline_query(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_inline_query(...)` is deprecated and will be removed in version 3.2 " + "use `Router.inline_query.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.inline_query.register(*args, **kwargs) + + def register_chosen_inline_result(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_chosen_inline_result(...)` is deprecated and will be removed in version 3.2 " + "use `Router.chosen_inline_result.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.chosen_inline_result.register(*args, **kwargs) + + def register_callback_query(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_callback_query(...)` is deprecated and will be removed in version 3.2 " + "use `Router.callback_query.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.callback_query.register(*args, **kwargs) + + def register_shipping_query(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_shipping_query(...)` is deprecated and will be removed in version 3.2 " + "use `Router.shipping_query.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.shipping_query.register(*args, **kwargs) + + def register_pre_checkout_query(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_pre_checkout_query(...)` is deprecated and will be removed in version 3.2 " + "use `Router.pre_checkout_query.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.pre_checkout_query.register(*args, **kwargs) + + def register_poll(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_poll(...)` is deprecated and will be removed in version 3.2 " + "use `Router.poll.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.poll.register(*args, **kwargs) + + def register_poll_answer(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_poll_answer(...)` is deprecated and will be removed in version 3.2 " + "use `Router.poll_answer.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.poll_answer.register(*args, **kwargs) + + def register_my_chat_member(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_my_chat_member(...)` is deprecated and will be removed in version 3.2 " + "use `Router.my_chat_member.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.my_chat_member.register(*args, **kwargs) + + def register_chat_member(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_chat_member(...)` is deprecated and will be removed in version 3.2 " + "use `Router.chat_member.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.chat_member.register(*args, **kwargs) + + def register_chat_join_request(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_chat_join_request(...)` is deprecated and will be removed in version 3.2 " + "use `Router.chat_join_request.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.chat_join_request.register(*args, **kwargs) + + def register_errors(self, *args: Any, **kwargs: Any) -> HandlerType: + warnings.warn( + "`Router.register_errors(...)` is deprecated and will be removed in version 3.2 " + "use `Router.errors.register(...)`", + DeprecationWarning, + stacklevel=2, + ) + return self.errors.register(*args, **kwargs) diff --git a/aiogram/methods/base.py b/aiogram/methods/base.py index 6b1d9619..4d787a7b 100644 --- a/aiogram/methods/base.py +++ b/aiogram/methods/base.py @@ -84,6 +84,8 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[TelegramType]): async def emit(self, bot: Bot) -> TelegramType: return await bot(self) + as_ = emit + def __await__(self) -> Generator[Any, None, TelegramType]: from aiogram.client.bot import Bot diff --git a/aiogram/types/keyboard_button.py b/aiogram/types/keyboard_button.py index c1e3e5f5..c4eed8d1 100644 --- a/aiogram/types/keyboard_button.py +++ b/aiogram/types/keyboard_button.py @@ -8,6 +8,10 @@ if TYPE_CHECKING: from .keyboard_button_poll_type import KeyboardButtonPollType +class WebApp(MutableTelegramObject): + url: str + + class KeyboardButton(MutableTelegramObject): """ This object represents one button of the reply keyboard. For simple text buttons *String* can be used instead of this object to specify text of the button. Optional fields *request_contact*, *request_location*, and *request_poll* are mutually exclusive. @@ -26,3 +30,4 @@ class KeyboardButton(MutableTelegramObject): """*Optional*. If :code:`True`, the user's current location will be sent when the button is pressed. Available in private chats only""" request_poll: Optional[KeyboardButtonPollType] = None """*Optional*. If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only""" + web_app: Optional[WebApp] = None diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 5ee37d00..d1ab7cbd 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -71,7 +71,7 @@ if TYPE_CHECKING: from .voice_chat_started import VoiceChatStarted -class Message(TelegramObject): +class _BaseMessage(TelegramObject): """ This object represents a message. @@ -195,6 +195,8 @@ class Message(TelegramObject): reply_markup: Optional[InlineKeyboardMarkup] = None """*Optional*. Inline keyboard attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons.""" + +class Message(_BaseMessage): @property def content_type(self) -> str: if self.text: @@ -265,11 +267,8 @@ class Message(TelegramObject): return ContentType.UNKNOWN def _unparse_entities(self, text_decoration: TextDecoration) -> str: - text = self.text or self.caption - if text is None: - raise TypeError("This message doesn't have any text.") - - entities = self.entities or self.caption_entities + text = self.text or self.caption or "" + entities = self.entities or self.caption_entities or [] return text_decoration.unparse(text=text, entities=entities) @property diff --git a/aiogram/utils/chat_action.py b/aiogram/utils/chat_action.py index 2a9dddcc..358c9336 100644 --- a/aiogram/utils/chat_action.py +++ b/aiogram/utils/chat_action.py @@ -12,7 +12,7 @@ from aiogram.types import Message, TelegramObject logger = logging.getLogger(__name__) DEFAULT_INTERVAL = 5.0 -DEFAULT_INITIAL_SLEEP = 0.1 +DEFAULT_INITIAL_SLEEP = 0.0 class ChatActionSender: diff --git a/aiogram/utils/link.py b/aiogram/utils/link.py index 87d402e2..e4bc28f2 100644 --- a/aiogram/utils/link.py +++ b/aiogram/utils/link.py @@ -1,18 +1,28 @@ -from typing import Any +from typing import Any, Optional from urllib.parse import urlencode, urljoin +BASE_DOCS_URL = "https://docs.aiogram.dev/" +BRANCH = "dev-3.x" + +BASE_PAGE_URL = f"{BASE_DOCS_URL}/en/{BRANCH}/" + + +def _format_url(url: str, *path: str, fragment_: Optional[str] = None, **query: Any) -> str: + url = urljoin(url, "/".join(path), allow_fragments=True) + if query: + url += "?" + urlencode(query) + if fragment_: + url += "#" + fragment_ + return url + + +def docs_url(*path: str, fragment_: Optional[str] = None, **query: Any) -> str: + return _format_url(BASE_PAGE_URL, *path, fragment_=fragment_, **query) + def create_tg_link(link: str, **kwargs: Any) -> str: - url = f"tg://{link}" - if kwargs: - query = urlencode(kwargs) - url += f"?{query}" - return url + return _format_url(f"tg://{link}", **kwargs) -def create_telegram_link(uri: str, **kwargs: Any) -> str: - url = urljoin("https://t.me", uri) - if kwargs: - query = urlencode(query=kwargs) - url += f"?{query}" - return url +def create_telegram_link(*path: str, **kwargs: Any) -> str: + return _format_url("https://t.me", *path, **kwargs) diff --git a/aiogram/utils/text_decorations.py b/aiogram/utils/text_decorations.py index bd45fae8..70b5bc63 100644 --- a/aiogram/utils/text_decorations.py +++ b/aiogram/utils/text_decorations.py @@ -37,7 +37,7 @@ class TextDecoration(ABC): if entity.type in {"bot_command", "url", "mention", "phone_number"}: # This entities should not be changed return text - if entity.type in {"bold", "italic", "code", "underline", "strikethrough"}: + if entity.type in {"bold", "italic", "code", "underline", "strikethrough", "spoiler"}: return cast(str, getattr(self, entity.type)(value=text)) if entity.type == "pre": return ( @@ -102,35 +102,39 @@ class TextDecoration(ABC): yield self.quote(remove_surrogates(text[offset:length])) @abstractmethod - def link(self, value: str, link: str) -> str: # pragma: no cover + def link(self, value: str, link: str) -> str: pass @abstractmethod - def bold(self, value: str) -> str: # pragma: no cover + def bold(self, value: str) -> str: pass @abstractmethod - def italic(self, value: str) -> str: # pragma: no cover + def italic(self, value: str) -> str: pass @abstractmethod - def code(self, value: str) -> str: # pragma: no cover + def code(self, value: str) -> str: pass @abstractmethod - def pre(self, value: str) -> str: # pragma: no cover + def pre(self, value: str) -> str: pass @abstractmethod - def pre_language(self, value: str, language: str) -> str: # pragma: no cover + def pre_language(self, value: str, language: str) -> str: pass @abstractmethod - def underline(self, value: str) -> str: # pragma: no cover + def underline(self, value: str) -> str: pass @abstractmethod - def strikethrough(self, value: str) -> str: # pragma: no cover + def strikethrough(self, value: str) -> str: + pass + + @abstractmethod + def spoiler(self, value: str) -> str: pass @abstractmethod @@ -139,14 +143,20 @@ class TextDecoration(ABC): class HtmlDecoration(TextDecoration): + BOLD_TAG = "b" + ITALIC_TAG = "i" + UNDERLINE_TAG = "u" + STRIKETHROUGH_TAG = "s" + SPOILER_TAG = ('span class="tg-spoiler"', "span") + def link(self, value: str, link: str) -> str: return f'{value}' def bold(self, value: str) -> str: - return f"{value}" + return f"<{self.BOLD_TAG}>{value}" def italic(self, value: str) -> str: - return f"{value}" + return f"<{self.ITALIC_TAG}>{value}" def code(self, value: str) -> str: return f"{value}" @@ -158,10 +168,13 @@ class HtmlDecoration(TextDecoration): return f'
{value}
' def underline(self, value: str) -> str: - return f"{value}" + return f"<{self.UNDERLINE_TAG}>{value}" def strikethrough(self, value: str) -> str: - return f"{value}" + return f"<{self.STRIKETHROUGH_TAG}>{value}" + + def spoiler(self, value: str) -> str: + return f"<{self.SPOILER_TAG[0]}>{value}" def quote(self, value: str) -> str: return html.escape(value, quote=False) @@ -194,6 +207,9 @@ class MarkdownDecoration(TextDecoration): def strikethrough(self, value: str) -> str: return f"~{value}~" + def spoiler(self, value: str) -> str: + return f"|{value}|" + def quote(self, value: str) -> str: return re.sub(pattern=self.MARKDOWN_QUOTE_PATTERN, repl=r"\\\1", string=value) diff --git a/docs/dispatcher/filters/callback_data.rst b/docs/dispatcher/filters/callback_data.rst new file mode 100644 index 00000000..6ff777c4 --- /dev/null +++ b/docs/dispatcher/filters/callback_data.rst @@ -0,0 +1,118 @@ +============================== +Callback Data Factory & Filter +============================== + +.. autoclass:: aiogram.dispatcher.filters.callback_data.CallbackData + :members: + :member-order: bysource + :undoc-members: False + +Usage +===== + +Create subclass of :code:`CallbackData`: + +.. code-block:: python + + class MyCallback(CallbackData, prefix="my"): + foo: str + bar: int + +After that you can generate any callback based on this class, for example: + +.. code-block:: python + + cb1 = MyCallback(foo="demo", bar=42) + cb1.pack() # returns 'my:demo:42' + cb1.unpack('my:demo:42') # returns + +So... Now you can use this class to generate any callbacks with defined structure + +.. code-block:: python + + ... + # Pass it into the markup + InlineKeyboardButton( + text="demo", + callback_data=MyCallback(foo="demo", bar="42").pack() # value should be packed to string + ) + ... + +... and handle by specific rules + +.. code-block:: python + + # Filter callback by type and value of field :code:`foo` + @router.callback_query(MyCallback.filter(F.foo == "demo")) + async def my_callback_foo(query: CallbackQuery, callback_data: MyCallback): + await query.answer(...) + ... + print("bar =", callback_data.bar) + +Also can be used in :doc:`Keyboard builder `: + +.. code-block:: python + + builder = InlineKeyboardBuilder() + builder.button( + text="demo", + callback_data=MyCallback(foo="demo", bar="42") # Value can be not packed to string inplace, because builder knows what to do with callback instance + ) + + +Another abstract example: + +.. code-block:: python + + class Action(str, Enum): + ban = "ban" + kick = "kick" + warn = "warn" + + class AdminAction(CallbackData, prefix="adm"): + action: Action + chat_id: int + user_id: int + + ... + # Inside handler + builder = InlineKeyboardBuilder() + for action in Action: + builder.button( + text=action.value.title(), + callback_data=AdminAction(action=action, chat_id=chat_id, user_id=user_id), + ) + await bot.send_message( + chat_id=admins_chat, + text=f"What do you want to do with {html.quote(name)}", + reply_markup=builder.as_markup(), + ) + ... + + @router.callback_query(AdminAction.filter(F.action == Action.ban)) + async def ban_user(query: CallbackQuery, callback_data: AdminAction, bot: Bot): + await bot.ban_chat_member( + chat_id=callback_data.chat_id, + user_id=callback_data.user_id, + ... + ) + +Known limitations +================= + +Allowed types and their subclasses: + +- :code:`str` +- :code:`int` +- :code:`bool` +- :code:`float` +- :code:`Decimal` (:code:`from decimal import Decimal`) +- :code:`Fraction` (:code:`from fractions import Fraction`) +- :code:`UUID` (:code:`from uuid import UUID`) +- :code:`Enum` (:code:`from enum import Enum`, only for string enums) +- :code:`IntEnum` (:code:`from enum import IntEnum`, only for int enums) + + +.. note:: + + Note that the integer Enum's should be always is subclasses of :code:`IntEnum` in due to parsing issues. diff --git a/docs/dispatcher/filters/index.rst b/docs/dispatcher/filters/index.rst index 106e0b0c..5c54f1bd 100644 --- a/docs/dispatcher/filters/index.rst +++ b/docs/dispatcher/filters/index.rst @@ -22,6 +22,7 @@ Here is list of builtin filters: exception magic_filters magic_data + callback_data Own filters specification ========================= diff --git a/docs/utils/index.rst b/docs/utils/index.rst index 9390e8ac..4f6eccf5 100644 --- a/docs/utils/index.rst +++ b/docs/utils/index.rst @@ -4,6 +4,6 @@ Utils .. toctree:: - i18n keyboard + i18n chat_action diff --git a/examples/echo_bot.py b/examples/echo_bot.py index 3c9b1218..f590ee57 100644 --- a/examples/echo_bot.py +++ b/examples/echo_bot.py @@ -1,5 +1,4 @@ import logging -from typing import Any from aiogram import Bot, Dispatcher, types from aiogram.types import Message diff --git a/mypy.ini b/mypy.ini index 2c1b8b31..c6ca9065 100644 --- a/mypy.ini +++ b/mypy.ini @@ -30,7 +30,7 @@ ignore_missing_imports = True [mypy-uvloop] ignore_missing_imports = True -[mypy-aioredis] +[mypy-redis.*] ignore_missing_imports = True [mypy-babel.*] diff --git a/poetry.lock b/poetry.lock index bd66ff27..0ac8ef45 100644 --- a/poetry.lock +++ b/poetry.lock @@ -39,21 +39,6 @@ aiohttp = ">=2.3.2" attrs = ">=19.2.0" python-socks = {version = ">=2.0.0,<3.0.0", extras = ["asyncio"]} -[[package]] -name = "aioredis" -version = "2.0.1" -description = "asyncio (PEP 3156) Redis support" -category = "main" -optional = true -python-versions = ">=3.6" - -[package.dependencies] -async-timeout = "*" -typing-extensions = "*" - -[package.extras] -hiredis = ["hiredis (>=1.0)"] - [[package]] name = "aiosignal" version = "1.2.0" @@ -75,7 +60,7 @@ python-versions = "*" [[package]] name = "appnope" -version = "0.1.2" +version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" category = "dev" optional = false @@ -109,15 +94,12 @@ test = ["astroid", "pytest"] [[package]] name = "async-timeout" -version = "4.0.0" +version = "4.0.2" description = "Timeout context manager for asyncio programs" category = "main" optional = false python-versions = ">=3.6" -[package.dependencies] -typing-extensions = ">=3.6.5" - [[package]] name = "asynctest" version = "0.13.0" @@ -136,17 +118,17 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "21.4.0" description = "Classes Without Boilerplate" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] [[package]] name = "babel" @@ -167,25 +149,13 @@ 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.10.0" +version = "4.11.1" description = "Screen-scraping library" category = "main" optional = true -python-versions = ">3.0.0" +python-versions = ">=3.6.0" [package.dependencies] soupsieve = ">1.2" @@ -196,7 +166,7 @@ lxml = ["lxml"] [[package]] name = "black" -version = "22.1.0" +version = "22.3.0" description = "The uncompromising code formatter." category = "dev" optional = false @@ -207,7 +177,7 @@ click = ">=8.0.0" mypy-extensions = ">=0.4.3" pathspec = ">=0.9.0" platformdirs = ">=2" -tomli = ">=1.1.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} [package.extras] @@ -234,7 +204,7 @@ python-versions = ">=3.6.1" [[package]] name = "charset-normalizer" -version = "2.0.7" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false @@ -245,11 +215,11 @@ unicode_backport = ["unicodedata2"] [[package]] name = "click" -version = "8.0.3" +version = "8.1.2" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -289,15 +259,29 @@ toml = ["tomli"] [[package]] name = "decorator" -version = "5.1.0" +version = "5.1.1" description = "Decorators for Humans" category = "dev" optional = false python-versions = ">=3.5" +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"] + [[package]] name = "distlib" -version = "0.3.3" +version = "0.3.4" description = "Distribution utilities" category = "dev" optional = false @@ -313,7 +297,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "executing" -version = "0.8.2" +version = "0.8.3" description = "Get the currently executing AST node of a frame, and other information" category = "dev" optional = false @@ -321,11 +305,11 @@ python-versions = "*" [[package]] name = "filelock" -version = "3.3.2" +version = "3.6.0" description = "A platform independent file lock." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] @@ -344,31 +328,17 @@ mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.8.0,<2.9.0" pyflakes = ">=2.4.0,<2.5.0" -[[package]] -name = "flake8-html" -version = "0.4.1" -description = "Generate HTML reports of flake8 violations" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -flake8 = ">=3.3.0" -importlib-metadata = "*" -jinja2 = ">=2.9.0" -pygments = ">=2.2.0" - [[package]] name = "frozenlist" -version = "1.2.0" +version = "1.3.0" description = "A list-like structure which implements collections.abc.MutableSequence" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "furo" -version = "2022.2.14.1" +version = "2022.4.7" description = "A clean customisable Sphinx documentation theme." category = "main" optional = true @@ -379,20 +349,16 @@ beautifulsoup4 = "*" pygments = ">=2.7,<3.0" sphinx = ">=4.0,<5.0" -[package.extras] -test = ["pytest", "pytest-cov", "pytest-xdist"] -doc = ["myst-parser", "sphinx-copybutton", "sphinx-design", "sphinx-inline-tabs"] - [[package]] name = "identify" -version = "2.3.4" +version = "2.4.12" description = "File identification library for Python" category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.7" [package.extras] -license = ["editdistance-s"] +license = ["ukkonen"] [[package]] name = "idna" @@ -404,7 +370,7 @@ python-versions = ">=3.5" [[package]] name = "imagesize" -version = "1.2.0" +version = "1.3.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" category = "main" optional = true @@ -412,19 +378,19 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.8.1" +version = "4.11.3" description = "Read metadata from Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" +category = "main" +optional = true +python-versions = ">=3.7" [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 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)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] [[package]] name = "incremental" @@ -447,7 +413,7 @@ python-versions = "*" [[package]] name = "ipython" -version = "8.0.1" +version = "8.2.0" description = "IPython: Productive Interactive Computing" category = "dev" optional = false @@ -456,7 +422,6 @@ python-versions = ">=3.8" [package.dependencies] appnope = {version = "*", markers = "sys_platform == \"darwin\""} backcall = "*" -black = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" jedi = ">=0.16" @@ -464,21 +429,22 @@ matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} pickleshare = "*" prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" -pygments = "*" +pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5" [package.extras] -all = ["Sphinx (>=1.3)", "curio", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.19)", "pandas", "pygments", "pytest", "pytest-asyncio", "qtconsole", "testpath", "trio"] +all = ["black", "Sphinx (>=1.3)", "ipykernel", "nbconvert", "nbformat", "ipywidgets", "notebook", "ipyparallel", "qtconsole", "pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "numpy (>=1.19)", "pandas", "trio"] +black = ["black"] doc = ["Sphinx (>=1.3)"] kernel = ["ipykernel"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] -notebook = ["notebook", "ipywidgets"] +notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["pytest", "pytest-asyncio", "testpath", "pygments"] -test_extra = ["pytest", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pygments", "trio"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test_extra = ["pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "trio"] [[package]] name = "isort" @@ -496,7 +462,7 @@ plugins = ["setuptools"] [[package]] name = "jedi" -version = "0.18.0" +version = "0.18.1" description = "An autocompletion tool for Python that can be used for text editors." category = "dev" optional = false @@ -507,15 +473,15 @@ parso = ">=0.8.0,<0.9.0" [package.extras] qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<6.0.0)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "jinja2" -version = "3.0.2" +version = "3.1.1" description = "A very fast and expressive template engine." category = "main" -optional = false -python-versions = ">=3.6" +optional = true +python-versions = ">=3.7" [package.dependencies] MarkupSafe = ">=2.0" @@ -537,7 +503,7 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "magic-filter" -version = "1.0.5" +version = "1.0.6" description = "This package provides magic filter based on dynamic attribute getter" category = "main" optional = false @@ -545,12 +511,15 @@ python-versions = ">=3.6.2,<4.0.0" [[package]] name = "markdown" -version = "3.3.4" +version = "3.3.6" description = "Python implementation of Markdown." category = "main" optional = true python-versions = ">=3.6" +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + [package.extras] testing = ["coverage", "pyyaml"] @@ -567,11 +536,11 @@ markdown = "*" [[package]] name = "markupsafe" -version = "2.0.1" +version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." category = "main" -optional = false -python-versions = ">=3.6" +optional = true +python-versions = ">=3.7" [[package]] name = "matplotlib-inline" @@ -594,15 +563,15 @@ python-versions = "*" [[package]] name = "multidict" -version = "5.2.0" +version = "6.0.2" description = "multidict implementation" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "mypy" -version = "0.931" +version = "0.942" description = "Optional static typing for Python" category = "dev" optional = false @@ -616,6 +585,7 @@ typing-extensions = ">=3.10" [package.extras] dmypy = ["psutil (>=4.0)"] python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] [[package]] name = "mypy-extensions" @@ -646,7 +616,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "parso" -version = "0.8.2" +version = "0.8.3" description = "A Python Parser" category = "dev" optional = false @@ -685,11 +655,11 @@ python-versions = "*" [[package]] name = "platformdirs" -version = "2.4.0" +version = "2.5.1" 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" +python-versions = ">=3.7" [package.extras] docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] @@ -709,11 +679,11 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.17.0" +version = "2.18.1" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.7" [package.dependencies] cfgv = ">=2.0.0" @@ -725,7 +695,7 @@ virtualenv = ">=20.0.8" [[package]] name = "prompt-toolkit" -version = "3.0.22" +version = "3.0.29" description = "Library for building powerful interactive command lines in Python" category = "dev" optional = false @@ -802,7 +772,7 @@ python-versions = ">=3.5" [[package]] name = "pymdown-extensions" -version = "9.2" +version = "9.3" description = "Extension pack for Python Markdown." category = "main" optional = true @@ -813,22 +783,22 @@ Markdown = ">=3.2" [[package]] name = "pyparsing" -version = "3.0.7" -description = "Python parsing module" +version = "3.0.8" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.8" [package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pytest" -version = "7.0.1" +version = "7.1.1" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} @@ -861,7 +831,7 @@ testing = ["coverage (==6.2)", "mypy (==0.931)"] [[package]] name = "pytest-asyncio" -version = "0.18.1" +version = "0.18.3" description = "Pytest support for asyncio" category = "dev" optional = false @@ -871,7 +841,7 @@ python-versions = ">=3.7" pytest = ">=6.1.0" [package.extras] -testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)"] +testing = ["coverage (==6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (==0.931)", "pytest-trio (>=0.7.0)"] [[package]] name = "pytest-cov" @@ -913,14 +883,14 @@ pytest = ">=3.2.5" [[package]] name = "pytest-metadata" -version = "1.11.0" +version = "2.0.0" description = "pytest plugin for test session metadata" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7,<4.0" [package.dependencies] -pytest = ">=2.9.0" +pytest = ">=7.1.1,<8.0.0" [[package]] name = "pytest-mock" @@ -975,7 +945,7 @@ trio = ["trio (>=0.16.0)"] [[package]] name = "pytz" -version = "2021.3" +version = "2022.1" description = "World timezone definitions, modern and historical" category = "main" optional = true @@ -989,9 +959,26 @@ category = "dev" optional = false python-versions = ">=3.6" +[[package]] +name = "redis" +version = "4.2.2" +description = "Python client for Redis database and key-value store" +category = "main" +optional = true +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=4.0.2" +deprecated = ">=1.2.3" +packaging = ">=20.4" + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + [[package]] name = "requests" -version = "2.26.0" +version = "2.27.1" description = "Python HTTP for Humans." category = "main" optional = true @@ -1009,7 +996,7 @@ use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "sentry-sdk" -version = "1.5.5" +version = "1.5.8" description = "Python client for Sentry (https://sentry.io)" category = "dev" optional = false @@ -1047,7 +1034,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "snowballstemmer" -version = "2.1.0" +version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "main" optional = true @@ -1055,7 +1042,7 @@ python-versions = "*" [[package]] name = "soupsieve" -version = "2.3" +version = "2.3.2" description = "A modern CSS selector implementation for Beautiful Soup." category = "main" optional = true @@ -1063,7 +1050,7 @@ python-versions = ">=3.6" [[package]] name = "sphinx" -version = "4.2.0" +version = "4.5.0" description = "Python documentation generator" category = "main" optional = true @@ -1075,6 +1062,7 @@ babel = ">=1.3" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} docutils = ">=0.14,<0.18" imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} Jinja2 = ">=2.3" packaging = "*" Pygments = ">=2.0" @@ -1089,7 +1077,7 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.900)", "docutils-stubs", "types-typed-ast", "types-pkg-resources", "types-requests"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] [[package]] @@ -1265,11 +1253,11 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "1.2.2" +version = "2.0.1" description = "A lil' TOML parser" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "tornado" @@ -1318,14 +1306,14 @@ python-versions = ">=3.6" [[package]] name = "urllib3" -version = "1.26.7" +version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "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)"] @@ -1344,14 +1332,13 @@ test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,< [[package]] name = "virtualenv" -version = "20.10.0" +version = "20.14.0" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [package.dependencies] -"backports.entry-points-selectable" = ">=1.0.4" distlib = ">=0.3.1,<1" filelock = ">=3.2,<4" platformdirs = ">=2,<3" @@ -1369,6 +1356,14 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "wrapt" +version = "1.14.0" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + [[package]] name = "yarl" version = "1.7.2" @@ -1383,27 +1378,27 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.6.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.6" +category = "main" +optional = true +python-versions = ">=3.7" [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)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=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 (>=0.9.1)"] [extras] 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"] +redis = ["redis"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "1f20a68c7d58bc8113a73a2cef1cf9e0027fc0a69507e3ac0a81d158e33b3acc" +content-hash = "610623143deda7ddf0a604a0447178549a89c8914509d2c1bff4367917e8aaa1" [metadata.files] aiofiles = [ @@ -1488,10 +1483,6 @@ aiohttp-socks = [ {file = "aiohttp_socks-0.7.1-py3-none-any.whl", hash = "sha256:94bcff5ef73611c6c6231c2ffc1be4af1599abec90dbd2fdbbd63233ec2fb0ff"}, {file = "aiohttp_socks-0.7.1.tar.gz", hash = "sha256:2215cac4891ef3fa14b7d600ed343ed0f0a670c23b10e4142aa862b3db20341a"}, ] -aioredis = [ - {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"}, - {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"}, -] aiosignal = [ {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, @@ -1501,8 +1492,8 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] appnope = [ - {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, - {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, ] aresponses = [ {file = "aresponses-2.1.5-py3-none-any.whl", hash = "sha256:06161209a39880aaf8ec3c67ab76d677aaea41944672e6a3e23a4464544879b1"}, @@ -1513,8 +1504,8 @@ asttokens = [ {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, ] async-timeout = [ - {file = "async-timeout-4.0.0.tar.gz", hash = "sha256:7d87a4e8adba8ededb52e579ce6bc8276985888913620c935094c2276fd83382"}, - {file = "async_timeout-4.0.0-py3-none-any.whl", hash = "sha256:f3303dddf6cafa748a92747ab6c2ecf60e0aeca769aee4c151adfce243a05d9b"}, + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, ] asynctest = [ {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, @@ -1525,8 +1516,8 @@ atomicwrites = [ {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] babel = [ {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, @@ -1536,38 +1527,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.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"}, - {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"}, + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, ] black = [ - {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, - {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, - {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, - {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, - {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, - {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, - {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, - {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, - {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, - {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, - {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, - {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, - {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, - {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, - {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, - {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, - {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, - {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, - {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, - {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, - {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, - {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, - {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, + {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, + {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, + {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, + {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, + {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, + {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, + {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, + {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, + {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, + {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, + {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, + {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, + {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, + {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, + {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, + {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, + {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -1578,12 +1565,12 @@ cfgv = [ {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, ] charset-normalizer = [ - {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, - {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, + {file = "click-8.1.2-py3-none-any.whl", hash = "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e"}, + {file = "click-8.1.2.tar.gz", hash = "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72"}, ] click-default-group = [ {file = "click-default-group-1.2.2.tar.gz", hash = "sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904"}, @@ -1636,126 +1623,113 @@ coverage = [ {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, ] decorator = [ - {file = "decorator-5.1.0-py3-none-any.whl", hash = "sha256:7b12e7c3c6ab203a29e157335e9122cb03de9ab7264b137594103fd4a683b374"}, - {file = "decorator-5.1.0.tar.gz", hash = "sha256:e59913af105b9860aa2c8d3272d9de5a56a4e608db9a2f167a8480b323d529a7"}, + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, ] distlib = [ - {file = "distlib-0.3.3-py2.py3-none-any.whl", hash = "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31"}, - {file = "distlib-0.3.3.zip", hash = "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05"}, + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, ] docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] executing = [ - {file = "executing-0.8.2-py2.py3-none-any.whl", hash = "sha256:32fc6077b103bd19e6494a72682d66d5763cf20a106d5aa7c5ccbea4e47b0df7"}, - {file = "executing-0.8.2.tar.gz", hash = "sha256:c23bf42e9a7b9b212f185b1b2c3c91feb895963378887bb10e64a2e612ec0023"}, + {file = "executing-0.8.3-py2.py3-none-any.whl", hash = "sha256:d1eef132db1b83649a3905ca6dd8897f71ac6f8cac79a7e58a1a09cf137546c9"}, + {file = "executing-0.8.3.tar.gz", hash = "sha256:c6554e21c6b060590a6d3be4b82fb78f8f0194d809de5ea7df1c093763311501"}, ] filelock = [ - {file = "filelock-3.3.2-py3-none-any.whl", hash = "sha256:bb2a1c717df74c48a2d00ed625e5a66f8572a3a30baacb7657add1d7bac4097b"}, - {file = "filelock-3.3.2.tar.gz", hash = "sha256:7afc856f74fa7006a289fd10fa840e1eebd8bbff6bffb69c26c54a0512ea8cf8"}, + {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, + {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, ] flake8 = [ {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, ] -flake8-html = [ - {file = "flake8-html-0.4.1.tar.gz", hash = "sha256:2fb436cbfe1e109275bc8fb7fdd0cb00e67b3b48cfeb397309b6b2c61eeb4cb4"}, - {file = "flake8_html-0.4.1-py2.py3-none-any.whl", hash = "sha256:17324eb947e7006807e4184ee26953e67baf421b3cf9e646a38bfec34eec5a94"}, -] frozenlist = [ - {file = "frozenlist-1.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:977a1438d0e0d96573fd679d291a1542097ea9f4918a8b6494b06610dfeefbf9"}, - {file = "frozenlist-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8d86547a5e98d9edd47c432f7a14b0c5592624b496ae9880fb6332f34af1edc"}, - {file = "frozenlist-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:181754275d5d32487431a0a29add4f897968b7157204bc1eaaf0a0ce80c5ba7d"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5df31bb2b974f379d230a25943d9bf0d3bc666b4b0807394b131a28fca2b0e5f"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4766632cd8a68e4f10f156a12c9acd7b1609941525569dd3636d859d79279ed3"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16eef427c51cb1203a7c0ab59d1b8abccaba9a4f58c4bfca6ed278fc896dc193"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:01d79515ed5aa3d699b05f6bdcf1fe9087d61d6b53882aa599a10853f0479c6c"}, - {file = "frozenlist-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:28e164722ea0df0cf6d48c4d5bdf3d19e87aaa6dfb39b0ba91153f224b912020"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e63ad0beef6ece06475d29f47d1f2f29727805376e09850ebf64f90777962792"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41de4db9b9501679cf7cddc16d07ac0f10ef7eb58c525a1c8cbff43022bddca4"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c6a9d84ee6427b65a81fc24e6ef589cb794009f5ca4150151251c062773e7ed2"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f5f3b2942c3b8b9bfe76b408bbaba3d3bb305ee3693e8b1d631fe0a0d4f93673"}, - {file = "frozenlist-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c98d3c04701773ad60d9545cd96df94d955329efc7743fdb96422c4b669c633b"}, - {file = "frozenlist-1.2.0-cp310-cp310-win32.whl", hash = "sha256:72cfbeab7a920ea9e74b19aa0afe3b4ad9c89471e3badc985d08756efa9b813b"}, - {file = "frozenlist-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:11ff401951b5ac8c0701a804f503d72c048173208490c54ebb8d7bb7c07a6d00"}, - {file = "frozenlist-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b46f997d5ed6d222a863b02cdc9c299101ee27974d9bbb2fd1b3c8441311c408"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351686ca020d1bcd238596b1fa5c8efcbc21bffda9d0efe237aaa60348421e2a"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfbaa08cf1452acad9cb1c1d7b89394a41e712f88df522cea1a0f296b57782a0"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ae2f5e9fa10805fb1c9adbfefaaecedd9e31849434be462c3960a0139ed729"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6790b8d96bbb74b7a6f4594b6f131bd23056c25f2aa5d816bd177d95245a30e3"}, - {file = "frozenlist-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:41f62468af1bd4e4b42b5508a3fe8cc46a693f0cdd0ca2f443f51f207893d837"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:ec6cf345771cdb00791d271af9a0a6fbfc2b6dd44cb753f1eeaa256e21622adb"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:14a5cef795ae3e28fb504b73e797c1800e9249f950e1c964bb6bdc8d77871161"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8b54cdd2fda15467b9b0bfa78cee2ddf6dbb4585ef23a16e14926f4b076dfae4"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f025f1d6825725b09c0038775acab9ae94264453a696cc797ce20c0769a7b367"}, - {file = "frozenlist-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:84e97f59211b5b9083a2e7a45abf91cfb441369e8bb6d1f5287382c1c526def3"}, - {file = "frozenlist-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:c5328ed53fdb0a73c8a50105306a3bc013e5ca36cca714ec4f7bd31d38d8a97f"}, - {file = "frozenlist-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:9ade70aea559ca98f4b1b1e5650c45678052e76a8ab2f76d90f2ac64180215a2"}, - {file = "frozenlist-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0d3ffa8772464441b52489b985d46001e2853a3b082c655ec5fad9fb6a3d618"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3457f8cf86deb6ce1ba67e120f1b0128fcba1332a180722756597253c465fc1d"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a72eecf37eface331636951249d878750db84034927c997d47f7f78a573b72b"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:acc4614e8d1feb9f46dd829a8e771b8f5c4b1051365d02efb27a3229048ade8a"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:87521e32e18a2223311afc2492ef2d99946337da0779ddcda77b82ee7319df59"}, - {file = "frozenlist-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b4c7665a17c3a5430edb663e4ad4e1ad457614d1b2f2b7f87052e2ef4fa45ca"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ed58803563a8c87cf4c0771366cf0ad1aa265b6b0ae54cbbb53013480c7ad74d"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:aa44c4740b4e23fcfa259e9dd52315d2b1770064cde9507457e4c4a65a04c397"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:2de5b931701257d50771a032bba4e448ff958076380b049fd36ed8738fdb375b"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6e105013fa84623c057a4381dc8ea0361f4d682c11f3816cc80f49a1f3bc17c6"}, - {file = "frozenlist-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:705c184b77565955a99dc360f359e8249580c6b7eaa4dc0227caa861ef46b27a"}, - {file = "frozenlist-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:a37594ad6356e50073fe4f60aa4187b97d15329f2138124d252a5a19c8553ea4"}, - {file = "frozenlist-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:25b358aaa7dba5891b05968dd539f5856d69f522b6de0bf34e61f133e077c1a4"}, - {file = "frozenlist-1.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af2a51c8a381d76eabb76f228f565ed4c3701441ecec101dd18be70ebd483cfd"}, - {file = "frozenlist-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:82d22f6e6f2916e837c91c860140ef9947e31194c82aaeda843d6551cec92f19"}, - {file = "frozenlist-1.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cfe6fef507f8bac40f009c85c7eddfed88c1c0d38c75e72fe10476cef94e10f"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f602e380a5132880fa245c92030abb0fc6ff34e0c5500600366cedc6adb06a"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ad065b2ebd09f32511ff2be35c5dfafee6192978b5a1e9d279a5c6e121e3b03"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc93f5f62df3bdc1f677066327fc81f92b83644852a31c6aa9b32c2dde86ea7d"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:89fdfc84c6bf0bff2ff3170bb34ecba8a6911b260d318d377171429c4be18c73"}, - {file = "frozenlist-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:47b2848e464883d0bbdcd9493c67443e5e695a84694efff0476f9059b4cb6257"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4f52d0732e56906f8ddea4bd856192984650282424049c956857fed43697ea43"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:16ef7dd5b7d17495404a2e7a49bac1bc13d6d20c16d11f4133c757dd94c4144c"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1cf63243bc5f5c19762943b0aa9e0d3fb3723d0c514d820a18a9b9a5ef864315"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:54a1e09ab7a69f843cd28fefd2bcaf23edb9e3a8d7680032c8968b8ac934587d"}, - {file = "frozenlist-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:954b154a4533ef28bd3e83ffdf4eadf39deeda9e38fb8feaf066d6069885e034"}, - {file = "frozenlist-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cb3957c39668d10e2b486acc85f94153520a23263b6401e8f59422ef65b9520d"}, - {file = "frozenlist-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0a7c7cce70e41bc13d7d50f0e5dd175f14a4f1837a8549b0936ed0cbe6170bf9"}, - {file = "frozenlist-1.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4c457220468d734e3077580a3642b7f682f5fd9507f17ddf1029452450912cdc"}, - {file = "frozenlist-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e74f8b4d8677ebb4015ac01fcaf05f34e8a1f22775db1f304f497f2f88fdc697"}, - {file = "frozenlist-1.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fbd4844ff111449f3bbe20ba24fbb906b5b1c2384d0f3287c9f7da2354ce6d23"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0081a623c886197ff8de9e635528fd7e6a387dccef432149e25c13946cb0cd0"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9b6e21e5770df2dea06cb7b6323fbc008b13c4a4e3b52cb54685276479ee7676"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:406aeb340613b4b559db78d86864485f68919b7141dec82aba24d1477fd2976f"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:878ebe074839d649a1cdb03a61077d05760624f36d196884a5cafb12290e187b"}, - {file = "frozenlist-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1fef737fd1388f9b93bba8808c5f63058113c10f4e3c0763ced68431773f72f9"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a495c3d513573b0b3f935bfa887a85d9ae09f0627cf47cad17d0cc9b9ba5c38"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e7d0dd3e727c70c2680f5f09a0775525229809f1a35d8552b92ff10b2b14f2c2"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:66a518731a21a55b7d3e087b430f1956a36793acc15912e2878431c7aec54210"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:94728f97ddf603d23c8c3dd5cae2644fa12d33116e69f49b1644a71bb77b89ae"}, - {file = "frozenlist-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c1e8e9033d34c2c9e186e58279879d78c94dd365068a3607af33f2bc99357a53"}, - {file = "frozenlist-1.2.0-cp39-cp39-win32.whl", hash = "sha256:83334e84a290a158c0c4cc4d22e8c7cfe0bba5b76d37f1c2509dabd22acafe15"}, - {file = "frozenlist-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:735f386ec522e384f511614c01d2ef9cf799f051353876b4c6fb93ef67a6d1ee"}, - {file = "frozenlist-1.2.0.tar.gz", hash = "sha256:68201be60ac56aff972dc18085800b6ee07973c49103a8aba669dee3d71079de"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win32.whl", hash = "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c"}, + {file = "frozenlist-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3"}, + {file = "frozenlist-1.3.0-cp38-cp38-win32.whl", hash = "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07"}, + {file = "frozenlist-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b"}, + {file = "frozenlist-1.3.0-cp39-cp39-win32.whl", hash = "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08"}, + {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, + {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, ] furo = [ - {file = "furo-2022.2.14.1-py3-none-any.whl", hash = "sha256:d7cb8126034637212332350ec8490cb95732d36506b024318a58cee2e7de0fda"}, - {file = "furo-2022.2.14.1.tar.gz", hash = "sha256:1af3a3053e594666e27eefd347b84beae5d74d6d20f6294cc47777d46f5761a7"}, + {file = "furo-2022.4.7-py3-none-any.whl", hash = "sha256:7f3e3d2fb977483590f8ecb2c2cd511bd82661b79c18efb24de9558bc9cdf2d7"}, + {file = "furo-2022.4.7.tar.gz", hash = "sha256:96204ab7cd047e4b6c523996e0279c4c629a8fc31f4f109b2efd470c17f49c80"}, ] identify = [ - {file = "identify-2.3.4-py2.py3-none-any.whl", hash = "sha256:4de55a93e0ba72bf917c840b3794eb1055a67272a1732351c557c88ec42011b1"}, - {file = "identify-2.3.4.tar.gz", hash = "sha256:595283a1c3a078ac5774ad4dc4d1bdd0c1602f60bcf11ae673b64cb2b1945762"}, + {file = "identify-2.4.12-py2.py3-none-any.whl", hash = "sha256:5f06b14366bd1facb88b00540a1de05b69b310cbc2654db3c7e07fa3a4339323"}, + {file = "identify-2.4.12.tar.gz", hash = "sha256:3f3244a559290e7d3deb9e9adc7b33594c1bc85a9dd82e0f1be519bf12a1ec17"}, ] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, ] imagesize = [ - {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, - {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, + {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, + {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, + {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, + {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, ] incremental = [ {file = "incremental-21.3.0-py2.py3-none-any.whl", hash = "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321"}, @@ -1766,105 +1740,76 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] ipython = [ - {file = "ipython-8.0.1-py3-none-any.whl", hash = "sha256:c503a0dd6ccac9c8c260b211f2dd4479c042b49636b097cc9a0d55fe62dff64c"}, - {file = "ipython-8.0.1.tar.gz", hash = "sha256:ab564d4521ea8ceaac26c3a2c6e5ddbca15c8848fd5a5cc325f960da88d42974"}, + {file = "ipython-8.2.0-py3-none-any.whl", hash = "sha256:1b672bfd7a48d87ab203d9af8727a3b0174a4566b4091e9447c22fb63ea32857"}, + {file = "ipython-8.2.0.tar.gz", hash = "sha256:70e5eb132cac594a34b5f799bd252589009905f05104728aea6a403ec2519dc1"}, ] isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] jedi = [ - {file = "jedi-0.18.0-py2.py3-none-any.whl", hash = "sha256:18456d83f65f400ab0c2d3319e48520420ef43b23a086fdc05dff34132f0fb93"}, - {file = "jedi-0.18.0.tar.gz", hash = "sha256:92550a404bad8afed881a137ec9a461fed49eca661414be45059329614ed0707"}, + {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, + {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, ] jinja2 = [ - {file = "Jinja2-3.0.2-py3-none-any.whl", hash = "sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c"}, - {file = "Jinja2-3.0.2.tar.gz", hash = "sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45"}, + {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, + {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, ] livereload = [ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] magic-filter = [ - {file = "magic-filter-1.0.5.tar.gz", hash = "sha256:974cf2793bb02a770f202d3179abfb600d1917f4e0c2af1727ef0edbb90cd0c2"}, - {file = "magic_filter-1.0.5-py3-none-any.whl", hash = "sha256:fa0c5f94da30d6cae1f0cec34fa526056db9f2636c099527513d529cb0299787"}, + {file = "magic-filter-1.0.6.tar.gz", hash = "sha256:dd022b088df644b94fd087b4ef8bb3eabb7d96dfc93f59ae9a859ca26881ef61"}, + {file = "magic_filter-1.0.6-py3-none-any.whl", hash = "sha256:499a76a4c023fd6e90e49c4fca6edaf338725c3a923987a6dcdb52fdf4d93ed9"}, ] markdown = [ - {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, - {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, + {file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"}, + {file = "Markdown-3.3.6.tar.gz", hash = "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006"}, ] markdown-include = [ {file = "markdown-include-0.6.0.tar.gz", hash = "sha256:6f5d680e36f7780c7f0f61dca53ca581bd50d1b56137ddcd6353efafa0c3e4a2"}, ] markupsafe = [ - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, - {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, - {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, - {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, - {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, - {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, - {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] matplotlib-inline = [ {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, @@ -1875,100 +1820,90 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] multidict = [ - {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3822c5894c72e3b35aae9909bef66ec83e44522faf767c0ad39e0e2de11d3b55"}, - {file = "multidict-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:28e6d883acd8674887d7edc896b91751dc2d8e87fbdca8359591a13872799e4e"}, - {file = "multidict-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b61f85101ef08cbbc37846ac0e43f027f7844f3fade9b7f6dd087178caedeee7"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9b668c065968c5979fe6b6fa6760bb6ab9aeb94b75b73c0a9c1acf6393ac3bf"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:517d75522b7b18a3385726b54a081afd425d4f41144a5399e5abd97ccafdf36b"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b4ac3ba7a97b35a5ccf34f41b5a8642a01d1e55454b699e5e8e7a99b5a3acf5"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:df23c83398715b26ab09574217ca21e14694917a0c857e356fd39e1c64f8283f"}, - {file = "multidict-5.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e58a9b5cc96e014ddf93c2227cbdeca94b56a7eb77300205d6e4001805391747"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f76440e480c3b2ca7f843ff8a48dc82446b86ed4930552d736c0bac507498a52"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cfde464ca4af42a629648c0b0d79b8f295cf5b695412451716531d6916461628"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0fed465af2e0eb6357ba95795d003ac0bdb546305cc2366b1fc8f0ad67cc3fda"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:b70913cbf2e14275013be98a06ef4b412329fe7b4f83d64eb70dce8269ed1e1a"}, - {file = "multidict-5.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5635bcf1b75f0f6ef3c8a1ad07b500104a971e38d3683167b9454cb6465ac86"}, - {file = "multidict-5.2.0-cp310-cp310-win32.whl", hash = "sha256:77f0fb7200cc7dedda7a60912f2059086e29ff67cefbc58d2506638c1a9132d7"}, - {file = "multidict-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:9416cf11bcd73c861267e88aea71e9fcc35302b3943e45e1dbb4317f91a4b34f"}, - {file = "multidict-5.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fd77c8f3cba815aa69cb97ee2b2ef385c7c12ada9c734b0f3b32e26bb88bbf1d"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ec9aea6223adf46999f22e2c0ab6cf33f5914be604a404f658386a8f1fba37"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5283c0a00f48e8cafcecadebfa0ed1dac8b39e295c7248c44c665c16dc1138b"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5f79c19c6420962eb17c7e48878a03053b7ccd7b69f389d5831c0a4a7f1ac0a1"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e4a67f1080123de76e4e97a18d10350df6a7182e243312426d508712e99988d4"}, - {file = "multidict-5.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:94b117e27efd8e08b4046c57461d5a114d26b40824995a2eb58372b94f9fca02"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2e77282fd1d677c313ffcaddfec236bf23f273c4fba7cdf198108f5940ae10f5"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:116347c63ba049c1ea56e157fa8aa6edaf5e92925c9b64f3da7769bdfa012858"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:dc3a866cf6c13d59a01878cd806f219340f3e82eed514485e094321f24900677"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac42181292099d91217a82e3fa3ce0e0ddf3a74fd891b7c2b347a7f5aa0edded"}, - {file = "multidict-5.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:f0bb0973f42ffcb5e3537548e0767079420aefd94ba990b61cf7bb8d47f4916d"}, - {file = "multidict-5.2.0-cp36-cp36m-win32.whl", hash = "sha256:ea21d4d5104b4f840b91d9dc8cbc832aba9612121eaba503e54eaab1ad140eb9"}, - {file = "multidict-5.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e6453f3cbeb78440747096f239d282cc57a2997a16b5197c9bc839099e1633d0"}, - {file = "multidict-5.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3def943bfd5f1c47d51fd324df1e806d8da1f8e105cc7f1c76a1daf0f7e17b0"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35591729668a303a02b06e8dba0eb8140c4a1bfd4c4b3209a436a02a5ac1de11"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8cacda0b679ebc25624d5de66c705bc53dcc7c6f02a7fb0f3ca5e227d80422"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:baf1856fab8212bf35230c019cde7c641887e3fc08cadd39d32a421a30151ea3"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a43616aec0f0d53c411582c451f5d3e1123a68cc7b3475d6f7d97a626f8ff90d"}, - {file = "multidict-5.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:25cbd39a9029b409167aa0a20d8a17f502d43f2efebfe9e3ac019fe6796c59ac"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a2cbcfbea6dc776782a444db819c8b78afe4db597211298dd8b2222f73e9cd0"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d2d7d1fff8e09d99354c04c3fd5b560fb04639fd45926b34e27cfdec678a704"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a37e9a68349f6abe24130846e2f1d2e38f7ddab30b81b754e5a1fde32f782b23"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:637c1896497ff19e1ee27c1c2c2ddaa9f2d134bbb5e0c52254361ea20486418d"}, - {file = "multidict-5.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9815765f9dcda04921ba467957be543423e5ec6a1136135d84f2ae092c50d87b"}, - {file = "multidict-5.2.0-cp37-cp37m-win32.whl", hash = "sha256:8b911d74acdc1fe2941e59b4f1a278a330e9c34c6c8ca1ee21264c51ec9b67ef"}, - {file = "multidict-5.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:380b868f55f63d048a25931a1632818f90e4be71d2081c2338fcf656d299949a"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7d81ce5744757d2f05fc41896e3b2ae0458464b14b5a2c1e87a6a9d69aefaa8"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d1d55cdf706ddc62822d394d1df53573d32a7a07d4f099470d3cb9323b721b6"}, - {file = "multidict-5.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4771d0d0ac9d9fe9e24e33bed482a13dfc1256d008d101485fe460359476065"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7d57ea65744d249427793c042094c4016789eb2562576fb831870f9c878d9e"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdd68778f96216596218b4e8882944d24a634d984ee1a5a049b300377878fa7c"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecc99bce8ee42dcad15848c7885197d26841cb24fa2ee6e89d23b8993c871c64"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:067150fad08e6f2dd91a650c7a49ba65085303fcc3decbd64a57dc13a2733031"}, - {file = "multidict-5.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78c106b2b506b4d895ddc801ff509f941119394b89c9115580014127414e6c2d"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e6c4fa1ec16e01e292315ba76eb1d012c025b99d22896bd14a66628b245e3e01"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b227345e4186809d31f22087d0265655114af7cda442ecaf72246275865bebe4"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:06560fbdcf22c9387100979e65b26fba0816c162b888cb65b845d3def7a54c9b"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7878b61c867fb2df7a95e44b316f88d5a3742390c99dfba6c557a21b30180cac"}, - {file = "multidict-5.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:246145bff76cc4b19310f0ad28bd0769b940c2a49fc601b86bfd150cbd72bb22"}, - {file = "multidict-5.2.0-cp38-cp38-win32.whl", hash = "sha256:c30ac9f562106cd9e8071c23949a067b10211917fdcb75b4718cf5775356a940"}, - {file = "multidict-5.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:f19001e790013ed580abfde2a4465388950728861b52f0da73e8e8a9418533c0"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c1ff762e2ee126e6f1258650ac641e2b8e1f3d927a925aafcfde943b77a36d24"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bd6c9c50bf2ad3f0448edaa1a3b55b2e6866ef8feca5d8dbec10ec7c94371d21"}, - {file = "multidict-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc66d4016f6e50ed36fb39cd287a3878ffcebfa90008535c62e0e90a7ab713ae"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9acb76d5f3dd9421874923da2ed1e76041cb51b9337fd7f507edde1d86535d6"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dfc924a7e946dd3c6360e50e8f750d51e3ef5395c95dc054bc9eab0f70df4f9c"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32fdba7333eb2351fee2596b756d730d62b5827d5e1ab2f84e6cbb287cc67fe0"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9aad49466b8d828b96b9e3630006234879c8d3e2b0a9d99219b3121bc5cdb17"}, - {file = "multidict-5.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:93de39267c4c676c9ebb2057e98a8138bade0d806aad4d864322eee0803140a0"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9bef5cff994ca3026fcc90680e326d1a19df9841c5e3d224076407cc21471a1"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:5f841c4f14331fd1e36cbf3336ed7be2cb2a8f110ce40ea253e5573387db7621"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:38ba256ee9b310da6a1a0f013ef4e422fca30a685bcbec86a969bd520504e341"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3bc3b1621b979621cee9f7b09f024ec76ec03cc365e638126a056317470bde1b"}, - {file = "multidict-5.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ee908c070020d682e9b42c8f621e8bb10c767d04416e2ebe44e37d0f44d9ad5"}, - {file = "multidict-5.2.0-cp39-cp39-win32.whl", hash = "sha256:1c7976cd1c157fa7ba5456ae5d31ccdf1479680dc9b8d8aa28afabc370df42b8"}, - {file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"}, - {file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] mypy = [ - {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"}, - {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"}, - {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"}, - {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"}, - {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"}, - {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"}, - {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"}, - {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"}, - {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"}, - {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"}, - {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"}, - {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"}, - {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"}, - {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"}, - {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"}, - {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"}, - {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"}, - {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"}, - {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"}, - {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"}, + {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, + {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, + {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, + {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, + {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, + {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, + {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, + {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, + {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, + {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, + {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, + {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, + {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, + {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, + {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, + {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, + {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, + {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, + {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, + {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, + {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, + {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, + {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1983,8 +1918,8 @@ packaging = [ {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] parso = [ - {file = "parso-0.8.2-py2.py3-none-any.whl", hash = "sha256:a8c4922db71e4fdb90e0d0bc6e50f9b273d3397925e5e60a717e719201778d22"}, - {file = "parso-0.8.2.tar.gz", hash = "sha256:12b83492c6239ce32ff5eed6d3639d6a536170723c6f3f1506869f1ace413398"}, + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, ] pathspec = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, @@ -1999,20 +1934,20 @@ pickleshare = [ {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, ] platformdirs = [ - {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, - {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, ] pluggy = [ {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.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, - {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, + {file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"}, + {file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"}, ] prompt-toolkit = [ - {file = "prompt_toolkit-3.0.22-py3-none-any.whl", hash = "sha256:48d85cdca8b6c4f16480c7ce03fd193666b62b0a21667ca56b4bb5ad679d1170"}, - {file = "prompt_toolkit-3.0.22.tar.gz", hash = "sha256:449f333dd120bd01f5d296a8ce1452114ba3a71fae7288d2f0ae2c918764fa72"}, + {file = "prompt_toolkit-3.0.29-py3-none-any.whl", hash = "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752"}, + {file = "prompt_toolkit-3.0.29.tar.gz", hash = "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7"}, ] ptyprocess = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, @@ -2076,24 +2011,25 @@ pygments = [ {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, ] pymdown-extensions = [ - {file = "pymdown-extensions-9.2.tar.gz", hash = "sha256:ed8f69a18bc158f00cbf03abc536b88b6e541b7e699156501e767c48f81d8850"}, - {file = "pymdown_extensions-9.2-py3-none-any.whl", hash = "sha256:f2fa7d9317c672a419868c893c20a28fb7ed7fc60d4ec4774c35e01398ab330c"}, + {file = "pymdown-extensions-9.3.tar.gz", hash = "sha256:a80553b243d3ed2d6c27723bcd64ca9887e560e6f4808baa96f36e93061eaf90"}, + {file = "pymdown_extensions-9.3-py3-none-any.whl", hash = "sha256:b37461a181c1c8103cfe1660081726a0361a8294cbfda88e5b02cefe976f0546"}, ] pyparsing = [ - {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, - {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, + {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, + {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] pytest = [ - {file = "pytest-7.0.1-py3-none-any.whl", hash = "sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db"}, - {file = "pytest-7.0.1.tar.gz", hash = "sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"}, + {file = "pytest-7.1.1-py3-none-any.whl", hash = "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea"}, + {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"}, ] pytest-aiohttp = [ {file = "pytest-aiohttp-1.0.4.tar.gz", hash = "sha256:39ff3a0d15484c01d1436cbedad575c6eafbf0f57cdf76fb94994c97b5b8c5a4"}, {file = "pytest_aiohttp-1.0.4-py3-none-any.whl", hash = "sha256:1d2dc3a304c2be1fd496c0c2fb6b31ab60cd9fc33984f761f951f8ea1eb4ca95"}, ] pytest-asyncio = [ - {file = "pytest-asyncio-0.18.1.tar.gz", hash = "sha256:c43fcdfea2335dd82ffe0f2774e40285ddfea78a8e81e56118d47b6a90fbb09e"}, - {file = "pytest_asyncio-0.18.1-py3-none-any.whl", hash = "sha256:c9ec48e8bbf5cc62755e18c4d8bc6907843ec9c5f4ac8f61464093baeba24a7e"}, + {file = "pytest-asyncio-0.18.3.tar.gz", hash = "sha256:7659bdb0a9eb9c6e3ef992eef11a2b3e69697800ad02fb06374a210d85b29f91"}, + {file = "pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213"}, + {file = "pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84"}, ] pytest-cov = [ {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, @@ -2108,8 +2044,8 @@ pytest-lazy-fixture = [ {file = "pytest_lazy_fixture-0.6.3-py3-none-any.whl", hash = "sha256:e0b379f38299ff27a653f03eaa69b08a6fd4484e46fd1c9907d984b9f9daeda6"}, ] pytest-metadata = [ - {file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"}, - {file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"}, + {file = "pytest-metadata-2.0.0.tar.gz", hash = "sha256:08dcc2779f4393309dd6d341ea1ddc15265239b6c4d51671737e784406ec07dc"}, + {file = "pytest_metadata-2.0.0-py3-none-any.whl", hash = "sha256:e25f1a77ed02baf1d83911604247a70d60d7dcb970aa12be38e1ed58d4d38e65"}, ] pytest-mock = [ {file = "pytest-mock-3.7.0.tar.gz", hash = "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534"}, @@ -2124,8 +2060,8 @@ python-socks = [ {file = "python_socks-2.0.3-py3-none-any.whl", hash = "sha256:950723f27d2cf401e193a9e0a0d45baab848341298f5b397d27fda0c4635e9a9"}, ] pytz = [ - {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, - {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, + {file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"}, + {file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"}, ] pyyaml = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, @@ -2162,29 +2098,33 @@ pyyaml = [ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] +redis = [ + {file = "redis-4.2.2-py3-none-any.whl", hash = "sha256:4e95f4ec5f49e636efcf20061a5a9110c20852f607cfca6865c07aaa8a739ee2"}, + {file = "redis-4.2.2.tar.gz", hash = "sha256:0107dc8e98a4f1d1d4aa00100e044287f77121a1e6d2085545c4b7fa94a7a27f"}, +] requests = [ - {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, - {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] sentry-sdk = [ - {file = "sentry-sdk-1.5.5.tar.gz", hash = "sha256:98fd155fa5d5fec1dbabed32a1a4ae2705f1edaa5dae4e7f7b62a384ba30e759"}, - {file = "sentry_sdk-1.5.5-py2.py3-none-any.whl", hash = "sha256:3817274fba2498c8ebf6b896ee98ac916c5598706340573268c07bf2bb30d831"}, + {file = "sentry-sdk-1.5.8.tar.gz", hash = "sha256:38fd16a92b5ef94203db3ece10e03bdaa291481dd7e00e77a148aa0302267d47"}, + {file = "sentry_sdk-1.5.8-py2.py3-none-any.whl", hash = "sha256:32af1a57954576709242beb8c373b3dbde346ac6bd616921def29d68846fb8c3"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] snowballstemmer = [ - {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, - {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] soupsieve = [ - {file = "soupsieve-2.3-py3-none-any.whl", hash = "sha256:617ffc4d0dfd39c66f4d1413a6e165663a34eca86be9b54f97b91756300ff6df"}, - {file = "soupsieve-2.3.tar.gz", hash = "sha256:e4860f889dfa88774c07da0b276b70c073b6470fa1a4a8350800bb7bce3dcc76"}, + {file = "soupsieve-2.3.2-py3-none-any.whl", hash = "sha256:a714129d3021ec17ce5be346b1007300558b378332c289a1a20e7d4de6ff18a5"}, + {file = "soupsieve-2.3.2.tar.gz", hash = "sha256:0bcc6d7432153063e3df09c3ac9442af3eba488715bfcad6a4c38ccb2a523124"}, ] sphinx = [ - {file = "Sphinx-4.2.0-py3-none-any.whl", hash = "sha256:98a535c62a4fcfcc362528592f69b26f7caec587d32cd55688db580be0287ae0"}, - {file = "Sphinx-4.2.0.tar.gz", hash = "sha256:94078db9184491e15bce0a56d9186e0aec95f16ac20b12d00e06d4e36f1058a6"}, + {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"}, + {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"}, ] sphinx-autobuild = [ {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, @@ -2238,8 +2178,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, - {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] tornado = [ {file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"}, @@ -2297,8 +2237,8 @@ typing-extensions = [ {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, ] urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, ] uvloop = [ {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, @@ -2319,13 +2259,79 @@ uvloop = [ {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, ] virtualenv = [ - {file = "virtualenv-20.10.0-py2.py3-none-any.whl", hash = "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814"}, - {file = "virtualenv-20.10.0.tar.gz", hash = "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218"}, + {file = "virtualenv-20.14.0-py2.py3-none-any.whl", hash = "sha256:1e8588f35e8b42c6ec6841a13c5e88239de1e6e4e4cedfd3916b306dc826ec66"}, + {file = "virtualenv-20.14.0.tar.gz", hash = "sha256:8e5b402037287126e81ccde9432b95a8be5b19d36584f64957060a3488c11ca8"}, ] wcwidth = [ {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, ] +wrapt = [ + {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd"}, + {file = "wrapt-1.14.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b"}, + {file = "wrapt-1.14.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5"}, + {file = "wrapt-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330"}, + {file = "wrapt-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa"}, + {file = "wrapt-1.14.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8"}, + {file = "wrapt-1.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763"}, + {file = "wrapt-1.14.0-cp310-cp310-win32.whl", hash = "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff"}, + {file = "wrapt-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23"}, + {file = "wrapt-1.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3"}, + {file = "wrapt-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0"}, + {file = "wrapt-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425"}, + {file = "wrapt-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e"}, + {file = "wrapt-1.14.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd"}, + {file = "wrapt-1.14.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036"}, + {file = "wrapt-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8"}, + {file = "wrapt-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06"}, + {file = "wrapt-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce"}, + {file = "wrapt-1.14.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0"}, + {file = "wrapt-1.14.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9"}, + {file = "wrapt-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68"}, + {file = "wrapt-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3"}, + {file = "wrapt-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d"}, + {file = "wrapt-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1"}, + {file = "wrapt-1.14.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe"}, + {file = "wrapt-1.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0"}, + {file = "wrapt-1.14.0-cp38-cp38-win32.whl", hash = "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f"}, + {file = "wrapt-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e"}, + {file = "wrapt-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1"}, + {file = "wrapt-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d"}, + {file = "wrapt-1.14.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0"}, + {file = "wrapt-1.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c"}, + {file = "wrapt-1.14.0-cp39-cp39-win32.whl", hash = "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350"}, + {file = "wrapt-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc"}, + {file = "wrapt-1.14.0.tar.gz", hash = "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311"}, +] yarl = [ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"}, @@ -2401,6 +2407,6 @@ yarl = [ {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, ] zipp = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] diff --git a/pyproject.toml b/pyproject.toml index 160f5380..ba67b272 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.8" -magic-filter = "^1.0.5" +magic-filter = "^1.0.6" aiohttp = "^3.8.1" pydantic = "^1.9.0" aiofiles = "^0.8.0" @@ -46,30 +46,29 @@ uvloop = { version = "^0.16.0", markers = "sys_platform == 'darwin' or sys_platf # i18n Babel = { version = "^2.9.1", optional = true } # Proxy -aiohttp-socks = {version = "^0.7.1", optional = true} +aiohttp-socks = { version = "^0.7.1", optional = true } # Redis -aioredis = {version = "^2.0.1", optional = true} +redis = { version = "^4.2.2", optional = true } # Docs Sphinx = { version = "^4.2.0", optional = true } sphinx-intl = { version = "^2.0.1", optional = true } sphinx-autobuild = { version = "^2021.3.14", optional = true } -sphinx-copybutton = {version = "^0.5.0", optional = true} -furo = {version = "^2022.2.14", optional = true} +sphinx-copybutton = { version = "^0.5.0", optional = true } +furo = { version = "^2022.4.7", optional = true } sphinx-prompt = { version = "^1.5.0", optional = true } Sphinx-Substitution-Extensions = { version = "^2020.9.30", optional = true } -towncrier = {version = "^21.9.0", optional = true} +towncrier = { version = "^21.9.0", optional = true } pygments = { version = "^2.4", optional = true } -pymdown-extensions = {version = "^9.2", optional = true} +pymdown-extensions = { version = "^9.3", optional = true } markdown-include = { version = "^0.6", optional = true } -Pygments = {version = "^2.11.2", optional = true} +Pygments = { version = "^2.11.2", optional = true } [tool.poetry.dev-dependencies] -ipython = "^8.0.1" +ipython = "^8.1.1" black = "^22.1.0" isort = "^5.10.1" flake8 = "^4.0.1" -flake8-html = "^0.4.1" -mypy = "^0.931" +mypy = "^0.942" pytest = "^7.0.1" pytest-html = "^3.1.1" pytest-asyncio = "^0.18.1" @@ -90,7 +89,7 @@ sentry-sdk = "^1.5.5" [tool.poetry.extras] fast = ["uvloop"] -redis = ["aioredis"] +redis = ["redis"] proxy = ["aiohttp-socks"] i18n = ["Babel"] docs = [ @@ -110,7 +109,7 @@ docs = [ [tool.black] line-length = 99 -target-version = ['py37', 'py38'] +target-version = ['py38', 'py39', 'py310'] exclude = ''' ( \.eggs diff --git a/tests/conftest.py b/tests/conftest.py index 698ee5cf..e2afa7a9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,7 @@ from pathlib import Path import pytest from _pytest.config import UsageError -from aioredis.connection import parse_url as parse_redis_url +from redis.asyncio.connection import parse_url as parse_redis_url from aiogram import Bot, Dispatcher from aiogram.dispatcher.fsm.storage.memory import ( 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 a6c5f700..88a5e626 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 @@ -215,11 +215,11 @@ class TestBaseSession: return await make_request(bot, method) session = CustomSession() - assert not session.middlewares + assert not session.middleware._middlewares session.middleware(my_middleware) - assert my_middleware in session.middlewares - assert len(session.middlewares) == 1 + assert my_middleware in session.middleware + assert len(session.middleware) == 1 async def test_use_middleware(self, bot: MockedBot): flag_before = False diff --git a/tests/test_api/test_client/test_session/test_middlewares/test_manager.py b/tests/test_api/test_client/test_session/test_middlewares/test_manager.py new file mode 100644 index 00000000..1bc2a6a3 --- /dev/null +++ b/tests/test_api/test_client/test_session/test_middlewares/test_manager.py @@ -0,0 +1,45 @@ +from aiogram import Bot +from aiogram.client.session.middlewares.base import ( + BaseRequestMiddleware, + NextRequestMiddlewareType, +) +from aiogram.client.session.middlewares.manager import RequestMiddlewareManager +from aiogram.methods import Response, TelegramMethod +from aiogram.types import TelegramObject + + +class TestMiddlewareManager: + async def test_register(self): + manager = RequestMiddlewareManager() + + @manager + async def middleware(handler, event, data): + await handler(event, data) + + assert middleware in manager._middlewares + manager.unregister(middleware) + assert middleware not in manager._middlewares + + async def test_wrap_middlewares(self): + manager = RequestMiddlewareManager() + + class MyMiddleware(BaseRequestMiddleware): + async def __call__( + self, + make_request: NextRequestMiddlewareType, + bot: Bot, + method: TelegramMethod[TelegramObject], + ) -> Response[TelegramObject]: + return await make_request(bot, method) + + manager.register(MyMiddleware()) + + @manager() + @manager + async def middleware(make_request, bot, method): + return await make_request(bot, method) + + async def target_call(bot, method, timeout: int = None): + return timeout + + assert await manager.wrap_middlewares(target_call, timeout=42)(None, None) == 42 diff --git a/tests/test_api/test_types/test_message.py b/tests/test_api/test_types/test_message.py index 93341eb2..b2b66b77 100644 --- a/tests/test_api/test_types/test_message.py +++ b/tests/test_api/test_types/test_message.py @@ -641,13 +641,15 @@ class TestMessage: assert method.message_id == message.message_id @pytest.mark.parametrize( - "text,entities,correct", + "text,entities,mode,expected_value", [ - ["test", [MessageEntity(type="bold", offset=0, length=4)], True], - ["", [], False], + ["test", [MessageEntity(type="bold", offset=0, length=4)], "html", "test"], + ["test", [MessageEntity(type="bold", offset=0, length=4)], "md", "*test*"], + ["", [], "html", ""], + ["", [], "md", ""], ], ) - def test_html_text(self, text, entities, correct): + def test_html_text(self, text, entities, mode, expected_value): message = Message( message_id=42, chat=Chat(id=42, type="private"), @@ -655,11 +657,4 @@ class TestMessage: text=text, entities=entities, ) - if correct: - assert message.html_text - assert message.md_text - else: - with pytest.raises(TypeError): - assert message.html_text - with pytest.raises(TypeError): - assert message.md_text + assert getattr(message, f"{mode}_text") == expected_value diff --git a/tests/test_dispatcher/test_deprecated.py b/tests/test_dispatcher/test_deprecated.py index d2609079..853e5902 100644 --- a/tests/test_dispatcher/test_deprecated.py +++ b/tests/test_dispatcher/test_deprecated.py @@ -5,27 +5,38 @@ from aiogram.dispatcher.router import Router from tests.deprecated import check_deprecated OBSERVERS = { - "callback_query", - "channel_post", - "chosen_inline_result", - "edited_channel_post", - "edited_message", - "errors", - "inline_query", "message", + "edited_message", + "channel_post", + "edited_channel_post", + "inline_query", + "chosen_inline_result", + "callback_query", + "shipping_query", + "pre_checkout_query", "poll", "poll_answer", - "pre_checkout_query", - "shipping_query", + "my_chat_member", + "chat_member", + "chat_join_request", + "errors", } -DEPRECATED_OBSERVERS = {observer + "_handler" for observer in OBSERVERS} - -@pytest.mark.parametrize("observer_name", DEPRECATED_OBSERVERS) +@pytest.mark.parametrize("observer_name", OBSERVERS) def test_deprecated_handlers_name(observer_name: str): router = Router() with check_deprecated("3.2", exception=AttributeError): - observer = getattr(router, observer_name) + observer = getattr(router, f"{observer_name}_handler") assert isinstance(observer, TelegramEventObserver) + + +@pytest.mark.parametrize("observer_name", OBSERVERS) +def test_deprecated_register_handlers(observer_name: str): + router = Router() + + with check_deprecated("3.2", exception=AttributeError): + register = getattr(router, f"register_{observer_name}") + register(lambda event: True) + assert callable(register) diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py index 1150f073..89d027b1 100644 --- a/tests/test_dispatcher/test_dispatcher.py +++ b/tests/test_dispatcher/test_dispatcher.py @@ -74,7 +74,22 @@ class TestDispatcher: assert dp.update.handlers assert dp.update.handlers[0].callback == dp._listen_update - assert dp.update.outer_middlewares + assert dp.update.outer_middleware + + def test_data_bind(self): + dp = Dispatcher() + assert dp.get("foo") is None + assert dp.get("foo", 42) == 42 + + dp["foo"] = 1 + assert dp._data["foo"] == 1 + assert dp["foo"] == 1 + + del dp["foo"] + assert "foo" not in dp._data + + def test_storage_property(self, dispatcher: Dispatcher): + assert dispatcher.storage is dispatcher.fsm.storage def test_parent_router(self, dispatcher: Dispatcher): with pytest.raises(RuntimeError): diff --git a/tests/test_dispatcher/test_event/test_middleware.py b/tests/test_dispatcher/test_event/test_middleware.py new file mode 100644 index 00000000..ffbe634d --- /dev/null +++ b/tests/test_dispatcher/test_event/test_middleware.py @@ -0,0 +1,42 @@ +from functools import partial + +from aiogram.dispatcher.middlewares.manager import MiddlewareManager + + +class TestMiddlewareManager: + async def test_register(self): + manager = MiddlewareManager() + + @manager + async def middleware(handler, event, data): + await handler(event, data) + + assert middleware in manager._middlewares + manager.unregister(middleware) + assert middleware not in manager._middlewares + + async def test_wrap_middlewares(self): + manager = MiddlewareManager() + + async def target(*args, **kwargs): + kwargs["target"] = True + kwargs["stack"].append(-1) + return kwargs + + async def middleware1(handler, event, data): + data["mw1"] = True + data["stack"].append(1) + return await handler(event, data) + + async def middleware2(handler, event, data): + data["mw2"] = True + data["stack"].append(2) + return await handler(event, data) + + wrapped = manager.wrap_middlewares([middleware1, middleware2], target) + + assert isinstance(wrapped, partial) + assert wrapped.func is middleware1 + + result = await wrapped(None, {"stack": []}) + assert result == {"mw1": True, "mw2": True, "target": True, "stack": [1, 2, -1]} diff --git a/tests/test_dispatcher/test_event/test_telegram.py b/tests/test_dispatcher/test_event/test_telegram.py index a30eb3a9..3a569010 100644 --- a/tests/test_dispatcher/test_event/test_telegram.py +++ b/tests/test_dispatcher/test_event/test_telegram.py @@ -297,10 +297,9 @@ class TestTelegramEventObserver: def test_register_middleware(self, middleware_type): event_observer = TelegramEventObserver(Router(), "test") - middlewares = getattr(event_observer, f"{middleware_type}s") - decorator = getattr(event_observer, middleware_type) + middlewares = getattr(event_observer, middleware_type) - @decorator + @middlewares async def my_middleware1(handler, event, data): pass @@ -308,7 +307,7 @@ class TestTelegramEventObserver: assert my_middleware1.__name__ == "my_middleware1" assert my_middleware1 in middlewares - @decorator() + @middlewares() async def my_middleware2(handler, event, data): pass @@ -319,13 +318,13 @@ class TestTelegramEventObserver: async def my_middleware3(handler, event, data): pass - decorator(my_middleware3) + middlewares(my_middleware3) assert my_middleware3 is not None assert my_middleware3.__name__ == "my_middleware3" assert my_middleware3 in middlewares - assert middlewares == [my_middleware1, my_middleware2, my_middleware3] + assert list(middlewares) == [my_middleware1, my_middleware2, my_middleware3] def test_register_global_filters(self): router = Router(use_builtin_filters=False) diff --git a/tests/test_dispatcher/test_filters/test_callback_data.py b/tests/test_dispatcher/test_filters/test_callback_data.py index cd7bc53e..9376cd33 100644 --- a/tests/test_dispatcher/test_filters/test_callback_data.py +++ b/tests/test_dispatcher/test_filters/test_callback_data.py @@ -30,7 +30,7 @@ class MyCallback(CallbackData, prefix="test"): class TestCallbackData: def test_init_subclass_prefix_required(self): - assert MyCallback.prefix == "test" + assert MyCallback.__prefix__ == "test" with pytest.raises(ValueError, match="prefix required.+"): @@ -38,12 +38,12 @@ class TestCallbackData: pass def test_init_subclass_sep_validation(self): - assert MyCallback.sep == ":" + assert MyCallback.__separator__ == ":" class MyCallback2(CallbackData, prefix="test2", sep="@"): pass - assert MyCallback2.sep == "@" + assert MyCallback2.__separator__ == "@" with pytest.raises(ValueError, match="Separator symbol '@' .+ 'sp@m'"): diff --git a/tests/test_dispatcher/test_filters/test_command.py b/tests/test_dispatcher/test_filters/test_command.py index d7a0ef55..38faaa7d 100644 --- a/tests/test_dispatcher/test_filters/test_command.py +++ b/tests/test_dispatcher/test_filters/test_command.py @@ -92,6 +92,18 @@ class TestCommandFilter: command = Command(commands=["test"]) assert bool(await command(message=message, bot=bot)) is result + async def test_command_magic_result(self, bot: MockedBot): + message = Message( + message_id=0, + text="/test 42", + chat=Chat(id=42, type="private"), + date=datetime.datetime.now(), + ) + command = Command(commands=["test"], command_magic=(F.args.as_("args"))) + result = await command(message=message, bot=bot) + assert "args" in result + assert result["args"] == "42" + class TestCommandObject: @pytest.mark.parametrize( diff --git a/tests/test_dispatcher/test_filters/test_exception.py b/tests/test_dispatcher/test_filters/test_exception.py index c1ffb6d8..1fcf7015 100644 --- a/tests/test_dispatcher/test_filters/test_exception.py +++ b/tests/test_dispatcher/test_filters/test_exception.py @@ -2,7 +2,7 @@ import re import pytest -from aiogram import Dispatcher, F +from aiogram import Dispatcher from aiogram.dispatcher.filters import ExceptionMessageFilter, ExceptionTypeFilter from aiogram.types import Update diff --git a/tests/test_dispatcher/test_filters/test_magic_data.py b/tests/test_dispatcher/test_filters/test_magic_data.py index 4a23f99a..5b74c182 100644 --- a/tests/test_dispatcher/test_filters/test_magic_data.py +++ b/tests/test_dispatcher/test_filters/test_magic_data.py @@ -22,9 +22,9 @@ class TestMagicDataFilter: assert value.spam is True return value - f = MagicData(magic_data=F.func(check)) + f = MagicData(magic_data=F.func(check).as_("test")) result = await f(Update(update_id=123), "foo", "bar", spam=True) assert called - assert isinstance(result, bool) - assert result + assert isinstance(result, dict) + assert result["test"] diff --git a/tests/test_utils/test_i18n.py b/tests/test_utils/test_i18n.py index 31843080..a4381c45 100644 --- a/tests/test_utils/test_i18n.py +++ b/tests/test_utils/test_i18n.py @@ -111,8 +111,8 @@ class TestSimpleI18nMiddleware: middleware = SimpleI18nMiddleware(i18n=i18n) middleware.setup(router=dp) - assert middleware not in dp.update.outer_middlewares - assert middleware in dp.message.outer_middlewares + assert middleware not in dp.update.outer_middleware + assert middleware in dp.message.outer_middleware async def test_get_unknown_locale(self, i18n: I18n): dp = Dispatcher() diff --git a/tests/test_utils/test_link.py b/tests/test_utils/test_link.py index 4dbfe8a2..8d74f0c6 100644 --- a/tests/test_utils/test_link.py +++ b/tests/test_utils/test_link.py @@ -2,7 +2,7 @@ from typing import Any, Dict import pytest -from aiogram.utils.link import create_telegram_link, create_tg_link +from aiogram.utils.link import BRANCH, create_telegram_link, create_tg_link, docs_url class TestLink: @@ -22,3 +22,12 @@ class TestLink: ) def test_create_telegram_link(self, base: str, params: Dict[str, Any], result: str): assert create_telegram_link(base, **params) == result + + def test_fragment(self): + assert ( + docs_url("test.html", fragment_="test") + == f"https://docs.aiogram.dev/en/{BRANCH}/test.html#test" + ) + + def test_docs(self): + assert docs_url("test.html") == f"https://docs.aiogram.dev/en/{BRANCH}/test.html" diff --git a/tests/test_utils/test_text_decorations.py b/tests/test_utils/test_text_decorations.py index da171575..f87bbfe0 100644 --- a/tests/test_utils/test_text_decorations.py +++ b/tests/test_utils/test_text_decorations.py @@ -47,6 +47,11 @@ class TestTextDecoration: 'test', ], [html_decoration, MessageEntity(type="url", offset=0, length=5), "test"], + [ + html_decoration, + MessageEntity(type="spoiler", offset=0, length=5), + 'test', + ], [ html_decoration, MessageEntity(type="text_link", offset=0, length=5, url="https://aiogram.dev"), @@ -76,6 +81,7 @@ class TestTextDecoration: [markdown_decoration, MessageEntity(type="bot_command", offset=0, length=5), "test"], [markdown_decoration, MessageEntity(type="email", offset=0, length=5), "test"], [markdown_decoration, MessageEntity(type="phone_number", offset=0, length=5), "test"], + [markdown_decoration, MessageEntity(type="spoiler", offset=0, length=5), "|test|"], [ markdown_decoration, MessageEntity( From 497436595d4c77d765c009d6a7c1210f13b8fea5 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 19 Apr 2022 22:03:24 +0300 Subject: [PATCH 19/35] [3.x] Bot API 6.0 (#890) * Base implementation * Bump license * Revert re-generated tests * Fix tests, improved docs * Remove TODO * Removed unreachable code * Changed type of `last_synchronization_error_date` * Fixed wrongly cleaned code --- .readthedocs.yml | 1 + CHANGES/890.feature.rst | 1 + LICENSE | 2 +- aiogram/client/bot.py | 120 +++++- aiogram/dispatcher/dispatcher.py | 12 +- aiogram/dispatcher/filters/text.py | 2 +- aiogram/dispatcher/webhook/aiohttp_server.py | 1 + aiogram/methods/__init__.py | 10 + aiogram/methods/answer_web_app_query.py | 31 ++ aiogram/methods/create_new_sticker_set.py | 2 +- aiogram/methods/get_chat_menu_button.py | 27 ++ .../get_my_default_administrator_rights.py | 27 ++ aiogram/methods/promote_chat_member.py | 4 +- aiogram/methods/set_chat_menu_button.py | 29 ++ .../set_my_default_administrator_rights.py | 29 ++ aiogram/types/__init__.py | 32 +- aiogram/types/chat_administrator_rights.py | 39 ++ aiogram/types/chat_member.py | 4 +- aiogram/types/chat_member_administrator.py | 4 +- aiogram/types/inline_keyboard_button.py | 7 +- aiogram/types/keyboard_button.py | 12 +- aiogram/types/menu_button.py | 22 + aiogram/types/menu_button_commands.py | 16 + aiogram/types/menu_button_default.py | 16 + aiogram/types/menu_button_web_app.py | 25 ++ aiogram/types/message.py | 55 +-- aiogram/types/sent_web_app_message.py | 16 + aiogram/types/sticker.py | 2 + aiogram/types/sticker_set.py | 4 +- aiogram/types/video_chat_ended.py | 19 + .../types/video_chat_participants_invited.py | 19 + aiogram/types/video_chat_scheduled.py | 16 + aiogram/types/video_chat_started.py | 11 + aiogram/types/voice_chat_ended.py | 14 - .../types/voice_chat_participants_invited.py | 19 - aiogram/types/voice_chat_scheduled.py | 14 - aiogram/types/voice_chat_started.py | 11 - aiogram/types/web_app_data.py | 16 + aiogram/types/web_app_info.py | 14 + aiogram/types/webhook_info.py | 2 + aiogram/utils/web_app.py | 129 ++++++ docs/api/methods/answer_web_app_query.rst | 51 +++ docs/api/methods/get_chat_menu_button.rst | 44 ++ .../get_my_default_administrator_rights.rst | 44 ++ docs/api/methods/index.rst | 5 + docs/api/methods/set_chat_menu_button.rst | 51 +++ .../set_my_default_administrator_rights.rst | 51 +++ docs/api/types/chat_administrator_rights.rst | 9 + docs/api/types/index.rst | 16 +- docs/api/types/menu_button.rst | 9 + ...scheduled.rst => menu_button_commands.rst} | 4 +- docs/api/types/menu_button_default.rst | 9 + docs/api/types/menu_button_web_app.rst | 9 + docs/api/types/sent_web_app_message.rst | 9 + ...ce_chat_ended.rst => video_chat_ended.rst} | 4 +- ...st => video_chat_participants_invited.rst} | 4 +- docs/api/types/video_chat_scheduled.rst | 9 + ...hat_started.rst => video_chat_started.rst} | 4 +- docs/api/types/web_app_data.rst | 9 + docs/api/types/web_app_info.rst | 9 + docs/utils/index.rst | 1 + docs/utils/web_app.rst | 55 +++ examples/web_app/demo.html | 376 ++++++++++++++++++ examples/web_app/handlers.py | 48 +++ examples/web_app/main.py | 49 +++ examples/web_app/routes.py | 64 +++ .../test_methods/test_answer_web_app_query.py | 33 ++ .../test_approve_chat_join_request.py | 0 .../test_methods/test_ban_chat_sender_chat.py | 0 .../test_decline_chat_join_request.py | 0 .../test_methods/test_get_chat_menu_button.py | 27 ++ ...est_get_my_default_administrator_rights.py | 53 +++ .../test_methods/test_get_sticker_set.py | 4 + .../test_methods/test_send_sticker.py | 2 + .../test_methods/test_set_chat_menu_button.py | 26 ++ ...est_set_my_default_administrator_rights.py | 26 ++ .../test_unban_chat_sender_chat.py | 0 tests/test_api/test_types/test_message.py | 53 ++- tests/test_dispatcher/test_dispatcher.py | 4 +- .../test_filters/test_chat_member_updated.py | 2 +- tests/test_utils/test_web_app.py | 80 ++++ 81 files changed, 1942 insertions(+), 147 deletions(-) create mode 100644 CHANGES/890.feature.rst create mode 100644 aiogram/methods/answer_web_app_query.py create mode 100644 aiogram/methods/get_chat_menu_button.py create mode 100644 aiogram/methods/get_my_default_administrator_rights.py create mode 100644 aiogram/methods/set_chat_menu_button.py create mode 100644 aiogram/methods/set_my_default_administrator_rights.py create mode 100644 aiogram/types/chat_administrator_rights.py create mode 100644 aiogram/types/menu_button.py create mode 100644 aiogram/types/menu_button_commands.py create mode 100644 aiogram/types/menu_button_default.py create mode 100644 aiogram/types/menu_button_web_app.py create mode 100644 aiogram/types/sent_web_app_message.py create mode 100644 aiogram/types/video_chat_ended.py create mode 100644 aiogram/types/video_chat_participants_invited.py create mode 100644 aiogram/types/video_chat_scheduled.py create mode 100644 aiogram/types/video_chat_started.py delete mode 100644 aiogram/types/voice_chat_ended.py delete mode 100644 aiogram/types/voice_chat_participants_invited.py delete mode 100644 aiogram/types/voice_chat_scheduled.py delete mode 100644 aiogram/types/voice_chat_started.py create mode 100644 aiogram/types/web_app_data.py create mode 100644 aiogram/types/web_app_info.py create mode 100644 aiogram/utils/web_app.py create mode 100644 docs/api/methods/answer_web_app_query.rst create mode 100644 docs/api/methods/get_chat_menu_button.rst create mode 100644 docs/api/methods/get_my_default_administrator_rights.rst create mode 100644 docs/api/methods/set_chat_menu_button.rst create mode 100644 docs/api/methods/set_my_default_administrator_rights.rst create mode 100644 docs/api/types/chat_administrator_rights.rst create mode 100644 docs/api/types/menu_button.rst rename docs/api/types/{voice_chat_scheduled.rst => menu_button_commands.rst} (60%) create mode 100644 docs/api/types/menu_button_default.rst create mode 100644 docs/api/types/menu_button_web_app.rst create mode 100644 docs/api/types/sent_web_app_message.rst rename docs/api/types/{voice_chat_ended.rst => video_chat_ended.rst} (61%) rename docs/api/types/{voice_chat_participants_invited.rst => video_chat_participants_invited.rst} (58%) create mode 100644 docs/api/types/video_chat_scheduled.rst rename docs/api/types/{voice_chat_started.rst => video_chat_started.rst} (60%) create mode 100644 docs/api/types/web_app_data.rst create mode 100644 docs/api/types/web_app_info.rst create mode 100644 docs/utils/web_app.rst create mode 100644 examples/web_app/demo.html create mode 100644 examples/web_app/handlers.py create mode 100644 examples/web_app/main.py create mode 100644 examples/web_app/routes.py create mode 100644 tests/test_api/test_methods/test_answer_web_app_query.py mode change 100644 => 100755 tests/test_api/test_methods/test_approve_chat_join_request.py mode change 100644 => 100755 tests/test_api/test_methods/test_ban_chat_sender_chat.py mode change 100644 => 100755 tests/test_api/test_methods/test_decline_chat_join_request.py create mode 100644 tests/test_api/test_methods/test_get_chat_menu_button.py create mode 100644 tests/test_api/test_methods/test_get_my_default_administrator_rights.py create mode 100644 tests/test_api/test_methods/test_set_chat_menu_button.py create mode 100644 tests/test_api/test_methods/test_set_my_default_administrator_rights.py mode change 100644 => 100755 tests/test_api/test_methods/test_unban_chat_sender_chat.py create mode 100644 tests/test_utils/test_web_app.py diff --git a/.readthedocs.yml b/.readthedocs.yml index 1efe11cb..e03323e6 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -12,3 +12,4 @@ python: path: . extra_requirements: - docs + - redis diff --git a/CHANGES/890.feature.rst b/CHANGES/890.feature.rst new file mode 100644 index 00000000..10c60c05 --- /dev/null +++ b/CHANGES/890.feature.rst @@ -0,0 +1 @@ +Added full support of `Telegram Bot API 6.0 `_ diff --git a/LICENSE b/LICENSE index 872283c6..f9721b14 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2017-2019 Alex Root Junior +Copyright (c) 2017-2022 Alex Root Junior Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index 94feb7b8..16453574 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -27,6 +27,7 @@ from ..methods import ( AnswerInlineQuery, AnswerPreCheckoutQuery, AnswerShippingQuery, + AnswerWebAppQuery, ApproveChatJoinRequest, BanChatMember, BanChatSenderChat, @@ -54,10 +55,12 @@ from ..methods import ( GetChatMember, GetChatMemberCount, GetChatMembersCount, + GetChatMenuButton, GetFile, GetGameHighScores, GetMe, GetMyCommands, + GetMyDefaultAdministratorRights, GetStickerSet, GetUpdates, GetUserProfilePhotos, @@ -89,12 +92,14 @@ from ..methods import ( SendVoice, SetChatAdministratorCustomTitle, SetChatDescription, + SetChatMenuButton, SetChatPermissions, SetChatPhoto, SetChatStickerSet, SetChatTitle, SetGameScore, SetMyCommands, + SetMyDefaultAdministratorRights, SetPassportDataErrors, SetStickerPositionInSet, SetStickerSetThumb, @@ -113,6 +118,7 @@ from ..types import ( BotCommand, BotCommandScope, Chat, + ChatAdministratorRights, ChatInviteLink, ChatMemberAdministrator, ChatMemberBanned, @@ -135,6 +141,7 @@ from ..types import ( InputMediaVideo, LabeledPrice, MaskPosition, + MenuButton, Message, MessageEntity, MessageId, @@ -142,6 +149,7 @@ from ..types import ( Poll, ReplyKeyboardMarkup, ReplyKeyboardRemove, + SentWebAppMessage, ShippingOption, StickerSet, Update, @@ -1618,7 +1626,7 @@ class Bot(ContextInstanceMixin["Bot"]): can_post_messages: Optional[bool] = None, can_edit_messages: Optional[bool] = None, can_delete_messages: Optional[bool] = None, - can_manage_voice_chats: Optional[bool] = None, + can_manage_video_chats: Optional[bool] = None, can_restrict_members: Optional[bool] = None, can_promote_members: Optional[bool] = None, can_change_info: Optional[bool] = None, @@ -1638,7 +1646,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param can_post_messages: Pass :code:`True`, if the administrator can create channel posts, channels only :param can_edit_messages: Pass :code:`True`, if the administrator can edit messages of other users and can pin messages, channels only :param can_delete_messages: Pass :code:`True`, if the administrator can delete messages of other users - :param can_manage_voice_chats: Pass :code:`True`, if the administrator can manage voice chats + :param can_manage_video_chats: Pass :code:`True`, if the administrator can manage video chats :param can_restrict_members: Pass :code:`True`, if the administrator can restrict, ban or unban chat members :param can_promote_members: Pass :code:`True`, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him) :param can_change_info: Pass :code:`True`, if the administrator can change chat title, photo and other settings @@ -1655,7 +1663,7 @@ class Bot(ContextInstanceMixin["Bot"]): can_post_messages=can_post_messages, can_edit_messages=can_edit_messages, can_delete_messages=can_delete_messages, - can_manage_voice_chats=can_manage_voice_chats, + can_manage_video_chats=can_manage_video_chats, can_restrict_members=can_restrict_members, can_promote_members=can_promote_members, can_change_info=can_change_info, @@ -2344,6 +2352,88 @@ class Bot(ContextInstanceMixin["Bot"]): ) return await self(call, request_timeout=request_timeout) + async def set_chat_menu_button( + self, + chat_id: Optional[int] = None, + menu_button: Optional[MenuButton] = None, + request_timeout: Optional[int] = None, + ) -> bool: + """ + Use this method to change the bot's menu button in a private chat, or the default menu button. Returns :code:`True` on success. + + Source: https://core.telegram.org/bots/api#setchatmenubutton + + :param chat_id: Unique identifier for the target private chat. If not specified, default bot's menu button will be changed + :param menu_button: A JSON-serialized object for the new bot's menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault` + :param request_timeout: Request timeout + :return: Returns True on success. + """ + call = SetChatMenuButton( + chat_id=chat_id, + menu_button=menu_button, + ) + return await self(call, request_timeout=request_timeout) + + async def get_chat_menu_button( + self, + chat_id: Optional[int] = None, + request_timeout: Optional[int] = None, + ) -> MenuButton: + """ + Use this method to get the current value of the bot's menu button in a private chat, or the default menu button. Returns :class:`aiogram.types.menu_button.MenuButton` on success. + + Source: https://core.telegram.org/bots/api#getchatmenubutton + + :param chat_id: Unique identifier for the target private chat. If not specified, default bot's menu button will be returned + :param request_timeout: Request timeout + :return: Returns MenuButton on success. + """ + call = GetChatMenuButton( + chat_id=chat_id, + ) + return await self(call, request_timeout=request_timeout) + + async def set_my_default_administrator_rights( + self, + rights: Optional[ChatAdministratorRights] = None, + for_channels: Optional[bool] = None, + request_timeout: Optional[int] = None, + ) -> bool: + """ + Use this method to change the default administrator rights requested by the bot when it's added as an administrator to groups or channels. These rights will be suggested to users, but they are are free to modify the list before adding the bot. Returns :code:`True` on success. + + Source: https://core.telegram.org/bots/api#setmydefaultadministratorrights + + :param rights: A JSON-serialized object describing new default administrator rights. If not specified, the default administrator rights will be cleared. + :param for_channels: Pass :code:`True` to change the default administrator rights of the bot in channels. Otherwise, the default administrator rights of the bot for groups and supergroups will be changed. + :param request_timeout: Request timeout + :return: Returns True on success. + """ + call = SetMyDefaultAdministratorRights( + rights=rights, + for_channels=for_channels, + ) + return await self(call, request_timeout=request_timeout) + + async def get_my_default_administrator_rights( + self, + for_channels: Optional[bool] = None, + request_timeout: Optional[int] = None, + ) -> ChatAdministratorRights: + """ + Use this method to get the current default administrator rights of the bot. Returns :class:`aiogram.types.chat_administrator_rights.ChatAdministratorRights` on success. + + Source: https://core.telegram.org/bots/api#getmydefaultadministratorrights + + :param for_channels: Pass :code:`True` to get default administrator rights of the bot in channels. Otherwise, default administrator rights of the bot for groups and supergroups will be returned. + :param request_timeout: Request timeout + :return: Returns ChatAdministratorRights on success. + """ + call = GetMyDefaultAdministratorRights( + for_channels=for_channels, + ) + return await self(call, request_timeout=request_timeout) + # ============================================================================================= # Group: Updating messages # Source: https://core.telegram.org/bots/api#updating-messages @@ -2656,7 +2746,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#createnewstickerset :param user_id: User identifier of created sticker set owner - :param name: Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in *'_by_'*. ** is case insensitive. 1-64 characters. + :param name: Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_"`. :code:`` is case insensitive. 1-64 characters. :param title: Sticker set title, 1-64 characters :param emojis: One or more emoji corresponding to the sticker :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` @@ -2827,6 +2917,28 @@ class Bot(ContextInstanceMixin["Bot"]): ) return await self(call, request_timeout=request_timeout) + async def answer_web_app_query( + self, + web_app_query_id: str, + result: InlineQueryResult, + request_timeout: Optional[int] = None, + ) -> SentWebAppMessage: + """ + Use this method to set the result of an interaction with a `Web App `_ and send a corresponding message on behalf of the user to the chat from which the query originated. On success, a :class:`aiogram.types.sent_web_app_message.SentWebAppMessage` object is returned. + + Source: https://core.telegram.org/bots/api#answerwebappquery + + :param web_app_query_id: Unique identifier for the query to be answered + :param result: A JSON-serialized object describing the message to be sent + :param request_timeout: Request timeout + :return: On success, a SentWebAppMessage object is returned. + """ + call = AnswerWebAppQuery( + web_app_query_id=web_app_query_id, + result=result, + ) + return await self(call, request_timeout=request_timeout) + # ============================================================================================= # Group: Payments # Source: https://core.telegram.org/bots/api#payments diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 9b84262c..f18deabf 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -80,20 +80,20 @@ class Dispatcher(Router): self.update.outer_middleware(self.fsm) self.shutdown.register(self.fsm.close) - self._data: Dict[str, Any] = {} + self.workflow_data: Dict[str, Any] = {} self._running_lock = Lock() def __getitem__(self, item: str) -> Any: - return self._data[item] + return self.workflow_data[item] def __setitem__(self, key: str, value: Any) -> None: - self._data[key] = value + self.workflow_data[key] = value def __delitem__(self, key: str) -> None: - del self._data[key] + del self.workflow_data[key] def get(self, key: str, /, default: Optional[Any] = None) -> Optional[Any]: - return self._data.get(key, default) + return self.workflow_data.get(key, default) @property def storage(self) -> BaseStorage: @@ -136,7 +136,7 @@ class Dispatcher(Router): self.update.trigger, update, { - **self._data, + **self.workflow_data, **kwargs, "bot": bot, }, diff --git a/aiogram/dispatcher/filters/text.py b/aiogram/dispatcher/filters/text.py index 3dd36fbd..cd7195dd 100644 --- a/aiogram/dispatcher/filters/text.py +++ b/aiogram/dispatcher/filters/text.py @@ -6,7 +6,7 @@ from aiogram.dispatcher.filters import BaseFilter from aiogram.types import CallbackQuery, InlineQuery, Message, Poll if TYPE_CHECKING: - from aiogram.utils.i18n.lazy_proxy import LazyProxy + from aiogram.utils.i18n.lazy_proxy import LazyProxy # NOQA TextType = Union[str, "LazyProxy"] diff --git a/aiogram/dispatcher/webhook/aiohttp_server.py b/aiogram/dispatcher/webhook/aiohttp_server.py index a8d084f8..eb3b2c5b 100644 --- a/aiogram/dispatcher/webhook/aiohttp_server.py +++ b/aiogram/dispatcher/webhook/aiohttp_server.py @@ -26,6 +26,7 @@ def setup_application(app: Application, dispatcher: Dispatcher, /, **kwargs: Any "app": app, "dispatcher": dispatcher, **kwargs, + **dispatcher.workflow_data, } async def on_startup(*a: Any, **kw: Any) -> None: # pragma: no cover diff --git a/aiogram/methods/__init__.py b/aiogram/methods/__init__.py index 085044ae..f7b75066 100644 --- a/aiogram/methods/__init__.py +++ b/aiogram/methods/__init__.py @@ -3,6 +3,7 @@ from .answer_callback_query import AnswerCallbackQuery from .answer_inline_query import AnswerInlineQuery from .answer_pre_checkout_query import AnswerPreCheckoutQuery from .answer_shipping_query import AnswerShippingQuery +from .answer_web_app_query import AnswerWebAppQuery from .approve_chat_join_request import ApproveChatJoinRequest from .ban_chat_member import BanChatMember from .ban_chat_sender_chat import BanChatSenderChat @@ -31,10 +32,12 @@ from .get_chat_administrators import GetChatAdministrators from .get_chat_member import GetChatMember from .get_chat_member_count import GetChatMemberCount from .get_chat_members_count import GetChatMembersCount +from .get_chat_menu_button import GetChatMenuButton from .get_file import GetFile from .get_game_high_scores import GetGameHighScores from .get_me import GetMe from .get_my_commands import GetMyCommands +from .get_my_default_administrator_rights import GetMyDefaultAdministratorRights from .get_sticker_set import GetStickerSet from .get_updates import GetUpdates from .get_user_profile_photos import GetUserProfilePhotos @@ -66,12 +69,14 @@ from .send_video_note import SendVideoNote from .send_voice import SendVoice from .set_chat_administrator_custom_title import SetChatAdministratorCustomTitle from .set_chat_description import SetChatDescription +from .set_chat_menu_button import SetChatMenuButton from .set_chat_permissions import SetChatPermissions from .set_chat_photo import SetChatPhoto from .set_chat_sticker_set import SetChatStickerSet from .set_chat_title import SetChatTitle from .set_game_score import SetGameScore from .set_my_commands import SetMyCommands +from .set_my_default_administrator_rights import SetMyDefaultAdministratorRights from .set_passport_data_errors import SetPassportDataErrors from .set_sticker_position_in_set import SetStickerPositionInSet from .set_sticker_set_thumb import SetStickerSetThumb @@ -150,6 +155,10 @@ __all__ = ( "SetMyCommands", "DeleteMyCommands", "GetMyCommands", + "SetChatMenuButton", + "GetChatMenuButton", + "SetMyDefaultAdministratorRights", + "GetMyDefaultAdministratorRights", "EditMessageText", "EditMessageCaption", "EditMessageMedia", @@ -165,6 +174,7 @@ __all__ = ( "DeleteStickerFromSet", "SetStickerSetThumb", "AnswerInlineQuery", + "AnswerWebAppQuery", "SendInvoice", "AnswerShippingQuery", "AnswerPreCheckoutQuery", diff --git a/aiogram/methods/answer_web_app_query.py b/aiogram/methods/answer_web_app_query.py new file mode 100644 index 00000000..3211ed38 --- /dev/null +++ b/aiogram/methods/answer_web_app_query.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict + +from ..types import InlineQueryResult, SentWebAppMessage +from .base import Request, TelegramMethod, prepare_parse_mode + +if TYPE_CHECKING: + from ..client.bot import Bot + + +class AnswerWebAppQuery(TelegramMethod[SentWebAppMessage]): + """ + Use this method to set the result of an interaction with a `Web App `_ and send a corresponding message on behalf of the user to the chat from which the query originated. On success, a :class:`aiogram.types.sent_web_app_message.SentWebAppMessage` object is returned. + + Source: https://core.telegram.org/bots/api#answerwebappquery + """ + + __returning__ = SentWebAppMessage + + web_app_query_id: str + """Unique identifier for the query to be answered""" + result: InlineQueryResult + """A JSON-serialized object describing the message to be sent""" + + def build_request(self, bot: Bot) -> Request: + data: Dict[str, Any] = self.dict() + prepare_parse_mode( + bot, data["result"], parse_mode_property="parse_mode", entities_property="entities" + ) + return Request(method="answerWebAppQuery", data=data) diff --git a/aiogram/methods/create_new_sticker_set.py b/aiogram/methods/create_new_sticker_set.py index 5c807963..5faab9ef 100644 --- a/aiogram/methods/create_new_sticker_set.py +++ b/aiogram/methods/create_new_sticker_set.py @@ -21,7 +21,7 @@ class CreateNewStickerSet(TelegramMethod[bool]): user_id: int """User identifier of created sticker set owner""" name: str - """Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in *'_by_'*. ** is case insensitive. 1-64 characters.""" + """Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_"`. :code:`` is case insensitive. 1-64 characters.""" title: str """Sticker set title, 1-64 characters""" emojis: str diff --git a/aiogram/methods/get_chat_menu_button.py b/aiogram/methods/get_chat_menu_button.py new file mode 100644 index 00000000..e2a97134 --- /dev/null +++ b/aiogram/methods/get_chat_menu_button.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union + +from ..types import MenuButton, MenuButtonCommands, MenuButtonDefault, MenuButtonWebApp +from .base import Request, TelegramMethod + +if TYPE_CHECKING: + from ..client.bot import Bot + + +class GetChatMenuButton(TelegramMethod[MenuButton]): + """ + Use this method to get the current value of the bot's menu button in a private chat, or the default menu button. Returns :class:`aiogram.types.menu_button.MenuButton` on success. + + Source: https://core.telegram.org/bots/api#getchatmenubutton + """ + + __returning__ = Union[MenuButtonDefault, MenuButtonWebApp, MenuButtonCommands] + + chat_id: Optional[int] = None + """Unique identifier for the target private chat. If not specified, default bot's menu button will be returned""" + + def build_request(self, bot: Bot) -> Request: + data: Dict[str, Any] = self.dict() + + return Request(method="getChatMenuButton", data=data) diff --git a/aiogram/methods/get_my_default_administrator_rights.py b/aiogram/methods/get_my_default_administrator_rights.py new file mode 100644 index 00000000..53a8e494 --- /dev/null +++ b/aiogram/methods/get_my_default_administrator_rights.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional + +from ..types import ChatAdministratorRights +from .base import Request, TelegramMethod + +if TYPE_CHECKING: + from ..client.bot import Bot + + +class GetMyDefaultAdministratorRights(TelegramMethod[ChatAdministratorRights]): + """ + Use this method to get the current default administrator rights of the bot. Returns :class:`aiogram.types.chat_administrator_rights.ChatAdministratorRights` on success. + + Source: https://core.telegram.org/bots/api#getmydefaultadministratorrights + """ + + __returning__ = ChatAdministratorRights + + for_channels: Optional[bool] = None + """Pass :code:`True` to get default administrator rights of the bot in channels. Otherwise, default administrator rights of the bot for groups and supergroups will be returned.""" + + def build_request(self, bot: Bot) -> Request: + data: Dict[str, Any] = self.dict() + + return Request(method="getMyDefaultAdministratorRights", data=data) diff --git a/aiogram/methods/promote_chat_member.py b/aiogram/methods/promote_chat_member.py index f2d8374f..4fd1a0f2 100644 --- a/aiogram/methods/promote_chat_member.py +++ b/aiogram/methods/promote_chat_member.py @@ -31,8 +31,8 @@ class PromoteChatMember(TelegramMethod[bool]): """Pass :code:`True`, if the administrator can edit messages of other users and can pin messages, channels only""" can_delete_messages: Optional[bool] = None """Pass :code:`True`, if the administrator can delete messages of other users""" - can_manage_voice_chats: Optional[bool] = None - """Pass :code:`True`, if the administrator can manage voice chats""" + can_manage_video_chats: Optional[bool] = None + """Pass :code:`True`, if the administrator can manage video chats""" can_restrict_members: Optional[bool] = None """Pass :code:`True`, if the administrator can restrict, ban or unban chat members""" can_promote_members: Optional[bool] = None diff --git a/aiogram/methods/set_chat_menu_button.py b/aiogram/methods/set_chat_menu_button.py new file mode 100644 index 00000000..6578ec4e --- /dev/null +++ b/aiogram/methods/set_chat_menu_button.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional + +from ..types import MenuButton +from .base import Request, TelegramMethod + +if TYPE_CHECKING: + from ..client.bot import Bot + + +class SetChatMenuButton(TelegramMethod[bool]): + """ + Use this method to change the bot's menu button in a private chat, or the default menu button. Returns :code:`True` on success. + + Source: https://core.telegram.org/bots/api#setchatmenubutton + """ + + __returning__ = bool + + chat_id: Optional[int] = None + """Unique identifier for the target private chat. If not specified, default bot's menu button will be changed""" + menu_button: Optional[MenuButton] = None + """A JSON-serialized object for the new bot's menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault`""" + + def build_request(self, bot: Bot) -> Request: + data: Dict[str, Any] = self.dict() + + return Request(method="setChatMenuButton", data=data) diff --git a/aiogram/methods/set_my_default_administrator_rights.py b/aiogram/methods/set_my_default_administrator_rights.py new file mode 100644 index 00000000..84341180 --- /dev/null +++ b/aiogram/methods/set_my_default_administrator_rights.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Optional + +from ..types import ChatAdministratorRights +from .base import Request, TelegramMethod + +if TYPE_CHECKING: + from ..client.bot import Bot + + +class SetMyDefaultAdministratorRights(TelegramMethod[bool]): + """ + Use this method to change the default administrator rights requested by the bot when it's added as an administrator to groups or channels. These rights will be suggested to users, but they are are free to modify the list before adding the bot. Returns :code:`True` on success. + + Source: https://core.telegram.org/bots/api#setmydefaultadministratorrights + """ + + __returning__ = bool + + rights: Optional[ChatAdministratorRights] = None + """A JSON-serialized object describing new default administrator rights. If not specified, the default administrator rights will be cleared.""" + for_channels: Optional[bool] = None + """Pass :code:`True` to change the default administrator rights of the bot in channels. Otherwise, the default administrator rights of the bot for groups and supergroups will be changed.""" + + def build_request(self, bot: Bot) -> Request: + data: Dict[str, Any] = self.dict() + + return Request(method="setMyDefaultAdministratorRights", data=data) diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index ebf4c839..ec04b855 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -13,6 +13,7 @@ from .bot_command_scope_default import BotCommandScopeDefault from .callback_game import CallbackGame from .callback_query import CallbackQuery from .chat import Chat +from .chat_administrator_rights import ChatAdministratorRights from .chat_invite_link import ChatInviteLink from .chat_join_request import ChatJoinRequest from .chat_location import ChatLocation @@ -81,6 +82,10 @@ from .labeled_price import LabeledPrice from .location import Location from .login_url import LoginUrl from .mask_position import MaskPosition +from .menu_button import MenuButton +from .menu_button_commands import MenuButtonCommands +from .menu_button_default import MenuButtonDefault +from .menu_button_web_app import MenuButtonWebApp from .message import ContentType, Message from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged from .message_entity import MessageEntity @@ -107,6 +112,7 @@ from .proximity_alert_triggered import ProximityAlertTriggered from .reply_keyboard_markup import ReplyKeyboardMarkup from .reply_keyboard_remove import ReplyKeyboardRemove from .response_parameters import ResponseParameters +from .sent_web_app_message import SentWebAppMessage from .shipping_address import ShippingAddress from .shipping_option import ShippingOption from .shipping_query import ShippingQuery @@ -118,12 +124,14 @@ from .user import User from .user_profile_photos import UserProfilePhotos from .venue import Venue from .video import Video +from .video_chat_ended import VideoChatEnded +from .video_chat_participants_invited import VideoChatParticipantsInvited +from .video_chat_scheduled import VideoChatScheduled +from .video_chat_started import VideoChatStarted from .video_note import VideoNote from .voice import Voice -from .voice_chat_ended import VoiceChatEnded -from .voice_chat_participants_invited import VoiceChatParticipantsInvited -from .voice_chat_scheduled import VoiceChatScheduled -from .voice_chat_started import VoiceChatStarted +from .web_app_data import WebAppData +from .web_app_info import WebAppInfo from .webhook_info import WebhookInfo __all__ = ( @@ -155,14 +163,16 @@ __all__ = ( "Poll", "Location", "Venue", + "WebAppData", "ProximityAlertTriggered", "MessageAutoDeleteTimerChanged", - "VoiceChatScheduled", - "VoiceChatStarted", - "VoiceChatEnded", - "VoiceChatParticipantsInvited", + "VideoChatScheduled", + "VideoChatStarted", + "VideoChatEnded", + "VideoChatParticipantsInvited", "UserProfilePhotos", "File", + "WebAppInfo", "ReplyKeyboardMarkup", "KeyboardButton", "KeyboardButtonPollType", @@ -174,6 +184,7 @@ __all__ = ( "ForceReply", "ChatPhoto", "ChatInviteLink", + "ChatAdministratorRights", "ChatMember", "ChatMemberOwner", "ChatMemberAdministrator", @@ -194,6 +205,10 @@ __all__ = ( "BotCommandScopeChat", "BotCommandScopeChatAdministrators", "BotCommandScopeChatMember", + "MenuButton", + "MenuButtonCommands", + "MenuButtonWebApp", + "MenuButtonDefault", "ResponseParameters", "InputMedia", "InputMediaPhoto", @@ -234,6 +249,7 @@ __all__ = ( "InputContactMessageContent", "InputInvoiceMessageContent", "ChosenInlineResult", + "SentWebAppMessage", "LabeledPrice", "Invoice", "ShippingAddress", diff --git a/aiogram/types/chat_administrator_rights.py b/aiogram/types/chat_administrator_rights.py new file mode 100644 index 00000000..20f4b65c --- /dev/null +++ b/aiogram/types/chat_administrator_rights.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from .base import TelegramObject + +if TYPE_CHECKING: + pass + + +class ChatAdministratorRights(TelegramObject): + """ + Represents the rights of an administrator in a chat. + + Source: https://core.telegram.org/bots/api#chatadministratorrights + """ + + is_anonymous: bool + """:code:`True`, if the user's presence in the chat is hidden""" + can_manage_chat: bool + """:code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege""" + can_delete_messages: bool + """:code:`True`, if the administrator can delete messages of other users""" + can_manage_video_chats: bool + """:code:`True`, if the administrator can manage video chats""" + can_restrict_members: bool + """:code:`True`, if the administrator can restrict, ban or unban chat members""" + can_promote_members: bool + """:code:`True`, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by the user)""" + can_change_info: bool + """:code:`True`, if the user is allowed to change the chat title, photo and other settings""" + can_invite_users: bool + """:code:`True`, if the user is allowed to invite new users to the chat""" + can_post_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can post in the channel; channels only""" + can_edit_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can edit messages of other users and can pin messages; channels only""" + can_pin_messages: Optional[bool] = None + """*Optional*. :code:`True`, if the user is allowed to pin messages; groups and supergroups only""" diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py index b3d1419c..d430e0ce 100644 --- a/aiogram/types/chat_member.py +++ b/aiogram/types/chat_member.py @@ -37,8 +37,8 @@ class ChatMember(TelegramObject): """*Optional*. :code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege""" can_delete_messages: Optional[bool] = None """*Optional*. :code:`True`, if the administrator can delete messages of other users""" - can_manage_voice_chats: Optional[bool] = None - """*Optional*. :code:`True`, if the administrator can manage voice chats""" + can_manage_video_chats: Optional[bool] = None + """*Optional*. :code:`True`, if the administrator can manage video chats""" can_restrict_members: Optional[bool] = None """*Optional*. :code:`True`, if the administrator can restrict, ban or unban chat members""" can_promote_members: Optional[bool] = None diff --git a/aiogram/types/chat_member_administrator.py b/aiogram/types/chat_member_administrator.py index a27156f9..896033d2 100644 --- a/aiogram/types/chat_member_administrator.py +++ b/aiogram/types/chat_member_administrator.py @@ -29,8 +29,8 @@ class ChatMemberAdministrator(ChatMember): """:code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege""" can_delete_messages: bool """:code:`True`, if the administrator can delete messages of other users""" - can_manage_voice_chats: bool - """:code:`True`, if the administrator can manage voice chats""" + can_manage_video_chats: bool + """:code:`True`, if the administrator can manage video chats""" can_restrict_members: bool """:code:`True`, if the administrator can restrict, ban or unban chat members""" can_promote_members: bool diff --git a/aiogram/types/inline_keyboard_button.py b/aiogram/types/inline_keyboard_button.py index b661339a..aeb546f1 100644 --- a/aiogram/types/inline_keyboard_button.py +++ b/aiogram/types/inline_keyboard_button.py @@ -7,6 +7,7 @@ from .base import MutableTelegramObject if TYPE_CHECKING: from .callback_game import CallbackGame from .login_url import LoginUrl + from .web_app_info import WebAppInfo class InlineKeyboardButton(MutableTelegramObject): @@ -20,10 +21,12 @@ class InlineKeyboardButton(MutableTelegramObject): """Label text on the button""" url: Optional[str] = None """*Optional*. HTTP or tg:// url to be opened when the button is pressed. Links :code:`tg://user?id=` can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings.""" - login_url: Optional[LoginUrl] = None - """*Optional*. An HTTP URL used to automatically authorize the user. Can be used as a replacement for the `Telegram Login Widget `_.""" callback_data: Optional[str] = None """*Optional*. Data to be sent in a `callback query `_ to the bot when button is pressed, 1-64 bytes""" + web_app: Optional[WebAppInfo] = None + """*Optional*. Description of the `Web App `_ that will be launched when the user presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the method :class:`aiogram.methods.answer_web_app_query.AnswerWebAppQuery`. Available only in private chats between a user and the bot.""" + login_url: Optional[LoginUrl] = None + """*Optional*. An HTTP URL used to automatically authorize the user. Can be used as a replacement for the `Telegram Login Widget `_.""" switch_inline_query: Optional[str] = None """*Optional*. If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. Can be empty, in which case just the bot's username will be inserted.""" switch_inline_query_current_chat: Optional[str] = None diff --git a/aiogram/types/keyboard_button.py b/aiogram/types/keyboard_button.py index c4eed8d1..bf8b0258 100644 --- a/aiogram/types/keyboard_button.py +++ b/aiogram/types/keyboard_button.py @@ -6,6 +6,7 @@ from .base import MutableTelegramObject if TYPE_CHECKING: from .keyboard_button_poll_type import KeyboardButtonPollType + from .web_app_info import WebAppInfo class WebApp(MutableTelegramObject): @@ -19,15 +20,18 @@ class KeyboardButton(MutableTelegramObject): **Note:** *request_poll* option will only work in Telegram versions released after 23 January, 2020. Older clients will display *unsupported message*. + **Note:** *web_app* option will only work in Telegram versions released after 16 April, 2022. Older clients will display *unsupported message*. + Source: https://core.telegram.org/bots/api#keyboardbutton """ text: str """Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed""" request_contact: Optional[bool] = None - """*Optional*. If :code:`True`, the user's phone number will be sent as a contact when the button is pressed. Available in private chats only""" + """*Optional*. If :code:`True`, the user's phone number will be sent as a contact when the button is pressed. Available in private chats only.""" request_location: Optional[bool] = None - """*Optional*. If :code:`True`, the user's current location will be sent when the button is pressed. Available in private chats only""" + """*Optional*. If :code:`True`, the user's current location will be sent when the button is pressed. Available in private chats only.""" request_poll: Optional[KeyboardButtonPollType] = None - """*Optional*. If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only""" - web_app: Optional[WebApp] = None + """*Optional*. If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only.""" + web_app: Optional[WebAppInfo] = None + """*Optional*. If specified, the described `Web App `_ will be launched when the button is pressed. The Web App will be able to send a 'web_app_data' service message. Available in private chats only.""" diff --git a/aiogram/types/menu_button.py b/aiogram/types/menu_button.py new file mode 100644 index 00000000..0a709dd6 --- /dev/null +++ b/aiogram/types/menu_button.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .base import TelegramObject + +if TYPE_CHECKING: + pass + + +class MenuButton(TelegramObject): + """ + This object describes the bot's menu button in a private chat. It should be one of + + - :class:`aiogram.types.menu_button_commands.MenuButtonCommands` + - :class:`aiogram.types.menu_button_web_app.MenuButtonWebApp` + - :class:`aiogram.types.menu_button_default.MenuButtonDefault` + + If a menu button other than :class:`aiogram.types.menu_button_default.MenuButtonDefault` is set for a private chat, then it is applied in the chat. Otherwise the default menu button is applied. By default, the menu button opens the list of bot commands. + + Source: https://core.telegram.org/bots/api#menubutton + """ diff --git a/aiogram/types/menu_button_commands.py b/aiogram/types/menu_button_commands.py new file mode 100644 index 00000000..5f4e252b --- /dev/null +++ b/aiogram/types/menu_button_commands.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from pydantic import Field + +from . import MenuButton + + +class MenuButtonCommands(MenuButton): + """ + Represents a menu button, which opens the bot's list of commands. + + Source: https://core.telegram.org/bots/api#menubuttoncommands + """ + + type: str = Field("commands", const=True) + """Type of the button, must be *commands*""" diff --git a/aiogram/types/menu_button_default.py b/aiogram/types/menu_button_default.py new file mode 100644 index 00000000..13cd3a37 --- /dev/null +++ b/aiogram/types/menu_button_default.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from pydantic import Field + +from . import MenuButton + + +class MenuButtonDefault(MenuButton): + """ + Describes that no specific value for the menu button was set. + + Source: https://core.telegram.org/bots/api#menubuttondefault + """ + + type: str = Field("default", const=True) + """Type of the button, must be *default*""" diff --git a/aiogram/types/menu_button_web_app.py b/aiogram/types/menu_button_web_app.py new file mode 100644 index 00000000..0de45b94 --- /dev/null +++ b/aiogram/types/menu_button_web_app.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pydantic import Field + +from . import MenuButton + +if TYPE_CHECKING: + from .web_app_info import WebAppInfo + + +class MenuButtonWebApp(MenuButton): + """ + Represents a menu button, which launches a `Web App `_. + + Source: https://core.telegram.org/bots/api#menubuttonwebapp + """ + + type: str = Field("web_app", const=True) + """Type of the button, must be *web_app*""" + text: str + """Text on the button""" + web_app: WebAppInfo + """Description of the Web App that will be launched when the user presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the method :class:`aiogram.methods.answer_web_app_query.AnswerWebAppQuery`.""" diff --git a/aiogram/types/message.py b/aiogram/types/message.py index d1ab7cbd..1fe8a4f5 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -63,15 +63,16 @@ if TYPE_CHECKING: from .user import User from .venue import Venue from .video import Video + from .video_chat_ended import VideoChatEnded + from .video_chat_participants_invited import VideoChatParticipantsInvited + from .video_chat_scheduled import VideoChatScheduled + from .video_chat_started import VideoChatStarted from .video_note import VideoNote from .voice import Voice - from .voice_chat_ended import VoiceChatEnded - from .voice_chat_participants_invited import VoiceChatParticipantsInvited - from .voice_chat_scheduled import VoiceChatScheduled - from .voice_chat_started import VoiceChatStarted + from .web_app_data import WebAppData -class _BaseMessage(TelegramObject): +class Message(TelegramObject): """ This object represents a message. @@ -184,19 +185,19 @@ class _BaseMessage(TelegramObject): """*Optional*. Telegram Passport data""" proximity_alert_triggered: Optional[ProximityAlertTriggered] = None """*Optional*. Service message. A user in the chat triggered another user's proximity alert while sharing Live Location.""" - voice_chat_scheduled: Optional[VoiceChatScheduled] = None - """*Optional*. Service message: voice chat scheduled""" - voice_chat_started: Optional[VoiceChatStarted] = None - """*Optional*. Service message: voice chat started""" - voice_chat_ended: Optional[VoiceChatEnded] = None - """*Optional*. Service message: voice chat ended""" - voice_chat_participants_invited: Optional[VoiceChatParticipantsInvited] = None - """*Optional*. Service message: new participants invited to a voice chat""" + video_chat_scheduled: Optional[VideoChatScheduled] = None + """*Optional*. Service message: video chat scheduled""" + video_chat_started: Optional[VideoChatStarted] = None + """*Optional*. Service message: video chat started""" + video_chat_ended: Optional[VideoChatEnded] = None + """*Optional*. Service message: video chat ended""" + video_chat_participants_invited: Optional[VideoChatParticipantsInvited] = None + """*Optional*. Service message: new participants invited to a video chat""" + web_app_data: Optional[WebAppData] = None + """*Optional*. Service message: data sent by a Web App""" reply_markup: Optional[InlineKeyboardMarkup] = None """*Optional*. Inline keyboard attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons.""" - -class Message(_BaseMessage): @property def content_type(self) -> str: if self.text: @@ -257,12 +258,16 @@ class Message(_BaseMessage): return ContentType.DICE if self.message_auto_delete_timer_changed: return ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED - if self.voice_chat_started: - return ContentType.VOICE_CHAT_STARTED - if self.voice_chat_ended: - return ContentType.VOICE_CHAT_ENDED - if self.voice_chat_participants_invited: - return ContentType.VOICE_CHAT_PARTICIPANTS_INVITED + if self.video_chat_scheduled: + return ContentType.VIDEO_CHAT_SCHEDULED + if self.video_chat_started: + return ContentType.VIDEO_CHAT_STARTED + if self.video_chat_ended: + return ContentType.VIDEO_CHAT_ENDED + if self.video_chat_participants_invited: + return ContentType.VIDEO_CHAT_PARTICIPANTS_INVITED + if self.web_app_data: + return ContentType.WEB_APP_DATA return ContentType.UNKNOWN @@ -1899,9 +1904,11 @@ class ContentType(helper.Helper): POLL = helper.Item() # poll DICE = helper.Item() # dice MESSAGE_AUTO_DELETE_TIMER_CHANGED = helper.Item() # message_auto_delete_timer_changed - VOICE_CHAT_STARTED = helper.Item() # voice_chat_started - VOICE_CHAT_ENDED = helper.Item() # voice_chat_ended - VOICE_CHAT_PARTICIPANTS_INVITED = helper.Item() # voice_chat_participants_invited + VIDEO_CHAT_SCHEDULED = helper.Item() # video_chat_scheduled + VIDEO_CHAT_STARTED = helper.Item() # video_chat_started + VIDEO_CHAT_ENDED = helper.Item() # video_chat_ended + VIDEO_CHAT_PARTICIPANTS_INVITED = helper.Item() # video_chat_participants_invited + WEB_APP_DATA = helper.Item() # web_app_data UNKNOWN = helper.Item() # unknown ANY = helper.Item() # any diff --git a/aiogram/types/sent_web_app_message.py b/aiogram/types/sent_web_app_message.py new file mode 100644 index 00000000..7295382c --- /dev/null +++ b/aiogram/types/sent_web_app_message.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Optional + +from .base import TelegramObject + + +class SentWebAppMessage(TelegramObject): + """ + Contains information about an inline message sent by a `Web App `_ on behalf of a user. + + Source: https://core.telegram.org/bots/api#sentwebappmessage + """ + + inline_message_id: Optional[str] = None + """*Optional*. Identifier of the sent inline message. Available only if there is an `inline keyboard `_ attached to the message.""" diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py index 2379d547..979d7e37 100644 --- a/aiogram/types/sticker.py +++ b/aiogram/types/sticker.py @@ -26,6 +26,8 @@ class Sticker(TelegramObject): """Sticker height""" is_animated: bool """:code:`True`, if the sticker is `animated `_""" + is_video: bool + """:code:`True`, if the sticker is a `video sticker `_""" thumb: Optional[PhotoSize] = None """*Optional*. Sticker thumbnail in the .WEBP or .JPG format""" emoji: Optional[str] = None diff --git a/aiogram/types/sticker_set.py b/aiogram/types/sticker_set.py index d26d206d..3ed5055c 100644 --- a/aiogram/types/sticker_set.py +++ b/aiogram/types/sticker_set.py @@ -22,9 +22,11 @@ class StickerSet(TelegramObject): """Sticker set title""" is_animated: bool """:code:`True`, if the sticker set contains `animated stickers `_""" + is_video: bool + """:code:`True`, if the sticker set contains `video stickers `_""" contains_masks: bool """:code:`True`, if the sticker set contains masks""" stickers: List[Sticker] """List of all set stickers""" thumb: Optional[PhotoSize] = None - """*Optional*. Sticker set thumbnail in the .WEBP or .TGS format""" + """*Optional*. Sticker set thumbnail in the .WEBP, .TGS, or .WEBM format""" diff --git a/aiogram/types/video_chat_ended.py b/aiogram/types/video_chat_ended.py new file mode 100644 index 00000000..cb85a931 --- /dev/null +++ b/aiogram/types/video_chat_ended.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .base import TelegramObject + +if TYPE_CHECKING: + pass + + +class VideoChatEnded(TelegramObject): + """ + This object represents a service message about a video chat ended in the chat. + + Source: https://core.telegram.org/bots/api#videochatended + """ + + duration: int + """Video chat duration in seconds""" diff --git a/aiogram/types/video_chat_participants_invited.py b/aiogram/types/video_chat_participants_invited.py new file mode 100644 index 00000000..3361f8ee --- /dev/null +++ b/aiogram/types/video_chat_participants_invited.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from .base import TelegramObject + +if TYPE_CHECKING: + from .user import User + + +class VideoChatParticipantsInvited(TelegramObject): + """ + This object represents a service message about new members invited to a video chat. + + Source: https://core.telegram.org/bots/api#videochatparticipantsinvited + """ + + users: List[User] + """New members that were invited to the video chat""" diff --git a/aiogram/types/video_chat_scheduled.py b/aiogram/types/video_chat_scheduled.py new file mode 100644 index 00000000..541e988a --- /dev/null +++ b/aiogram/types/video_chat_scheduled.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from datetime import datetime + +from .base import TelegramObject + + +class VideoChatScheduled(TelegramObject): + """ + This object represents a service message about a video chat scheduled in the chat. + + Source: https://core.telegram.org/bots/api#videochatscheduled + """ + + start_date: datetime + """Point in time (Unix timestamp) when the video chat is supposed to be started by a chat administrator""" diff --git a/aiogram/types/video_chat_started.py b/aiogram/types/video_chat_started.py new file mode 100644 index 00000000..a6f9aed0 --- /dev/null +++ b/aiogram/types/video_chat_started.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from .base import TelegramObject + + +class VideoChatStarted(TelegramObject): + """ + This object represents a service message about a video chat started in the chat. Currently holds no information. + + Source: https://core.telegram.org/bots/api#videochatstarted + """ diff --git a/aiogram/types/voice_chat_ended.py b/aiogram/types/voice_chat_ended.py deleted file mode 100644 index 12c705c9..00000000 --- a/aiogram/types/voice_chat_ended.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import annotations - -from .base import TelegramObject - - -class VoiceChatEnded(TelegramObject): - """ - This object represents a service message about a voice chat ended in the chat. - - Source: https://core.telegram.org/bots/api#voicechatended - """ - - duration: int - """Voice chat duration in seconds""" diff --git a/aiogram/types/voice_chat_participants_invited.py b/aiogram/types/voice_chat_participants_invited.py deleted file mode 100644 index b24ef91d..00000000 --- a/aiogram/types/voice_chat_participants_invited.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, List, Optional - -from .base import TelegramObject - -if TYPE_CHECKING: - from .user import User - - -class VoiceChatParticipantsInvited(TelegramObject): - """ - This object represents a service message about new members invited to a voice chat. - - Source: https://core.telegram.org/bots/api#voicechatparticipantsinvited - """ - - users: Optional[List[User]] = None - """*Optional*. New members that were invited to the voice chat""" diff --git a/aiogram/types/voice_chat_scheduled.py b/aiogram/types/voice_chat_scheduled.py deleted file mode 100644 index 37c6c7bd..00000000 --- a/aiogram/types/voice_chat_scheduled.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import annotations - -from .base import TelegramObject - - -class VoiceChatScheduled(TelegramObject): - """ - This object represents a service message about a voice chat scheduled in the chat. - - Source: https://core.telegram.org/bots/api#voicechatscheduled - """ - - start_date: int - """Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator""" diff --git a/aiogram/types/voice_chat_started.py b/aiogram/types/voice_chat_started.py deleted file mode 100644 index 6ad45263..00000000 --- a/aiogram/types/voice_chat_started.py +++ /dev/null @@ -1,11 +0,0 @@ -from __future__ import annotations - -from .base import TelegramObject - - -class VoiceChatStarted(TelegramObject): - """ - This object represents a service message about a voice chat started in the chat. Currently holds no information. - - Source: https://core.telegram.org/bots/api#voicechatstarted - """ diff --git a/aiogram/types/web_app_data.py b/aiogram/types/web_app_data.py new file mode 100644 index 00000000..6a108fef --- /dev/null +++ b/aiogram/types/web_app_data.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from .base import TelegramObject + + +class WebAppData(TelegramObject): + """ + Contains data sent from a `Web App `_ to the bot. + + Source: https://core.telegram.org/bots/api#webappdata + """ + + data: str + """The data. Be aware that a bad client can send arbitrary data in this field.""" + button_text: str + """Text of the *web_app* keyboard button, from which the Web App was opened. Be aware that a bad client can send arbitrary data in this field.""" diff --git a/aiogram/types/web_app_info.py b/aiogram/types/web_app_info.py new file mode 100644 index 00000000..9317fae9 --- /dev/null +++ b/aiogram/types/web_app_info.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from .base import TelegramObject + + +class WebAppInfo(TelegramObject): + """ + Contains information about a `Web App `_. + + Source: https://core.telegram.org/bots/api#webappinfo + """ + + url: str + """An HTTPS URL of a Web App to be opened with additional data as specified in `Initializing Web Apps `_""" diff --git a/aiogram/types/webhook_info.py b/aiogram/types/webhook_info.py index 3b1a64a0..a3ec68e5 100644 --- a/aiogram/types/webhook_info.py +++ b/aiogram/types/webhook_info.py @@ -25,6 +25,8 @@ class WebhookInfo(TelegramObject): """*Optional*. Unix time for the most recent error that happened when trying to deliver an update via webhook""" last_error_message: Optional[str] = None """*Optional*. Error message in human-readable format for the most recent error that happened when trying to deliver an update via webhook""" + last_synchronization_error_date: Optional[datetime.datetime] = None + """*Optional*. Unix time of the most recent error that happened when trying to synchronize available updates with Telegram datacenters""" max_connections: Optional[int] = None """*Optional*. Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery""" allowed_updates: Optional[List[str]] = None diff --git a/aiogram/utils/web_app.py b/aiogram/utils/web_app.py new file mode 100644 index 00000000..9cce8e06 --- /dev/null +++ b/aiogram/utils/web_app.py @@ -0,0 +1,129 @@ +import hashlib +import hmac +import json +from datetime import datetime +from operator import itemgetter +from typing import Any, Callable, Optional +from urllib.parse import parse_qsl + +from aiogram.types import TelegramObject + + +class WebAppUser(TelegramObject): + """ + This object contains the data of the Web App user. + + Source: https://core.telegram.org/bots/webapps#webappuser + """ + + id: int + """A unique identifier for the user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. It has at most 52 significant bits, so a 64-bit integer or a double-precision float type is safe for storing this identifier.""" + is_bot: Optional[bool] = None + """True, if this user is a bot. Returns in the receiver field only.""" + first_name: str + """First name of the user or bot.""" + last_name: Optional[str] = None + """Last name of the user or bot.""" + username: Optional[str] = None + """Username of the user or bot.""" + language_code: Optional[str] = None + """IETF language tag of the user's language. Returns in user field only.""" + photo_url: Optional[str] = None + """URL of the userโ€™s profile photo. The photo can be in .jpeg or .svg formats. Only returned for Web Apps launched from the attachment menu.""" + + +class WebAppInitData(TelegramObject): + """ + This object contains data that is transferred to the Web App when it is opened. It is empty if the Web App was launched from a keyboard button. + + Source: https://core.telegram.org/bots/webapps#webappinitdata + """ + + query_id: Optional[str] = None + """A unique identifier for the Web App session, required for sending messages via the answerWebAppQuery method.""" + user: Optional[WebAppUser] = None + """An object containing data about the current user.""" + receiver: Optional[WebAppUser] = None + """An object containing data about the chat partner of the current user in the chat where the bot was launched via the attachment menu. Returned only for Web Apps launched via the attachment menu.""" + start_param: Optional[str] = None + """The value of the startattach parameter, passed via link. Only returned for Web Apps when launched from the attachment menu via link. The value of the start_param parameter will also be passed in the GET-parameter tgWebAppStartParam, so the Web App can load the correct interface right away.""" + auth_date: datetime + """Unix time when the form was opened.""" + hash: str + """A hash of all passed parameters, which the bot server can use to check their validity.""" + + +def check_webapp_signature(token: str, init_data: str) -> bool: + """ + Check incoming WebApp init data signature + + Source: https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app + + :param token: bot Token + :param init_data: data from frontend to be validated + :return: + """ + try: + parsed_data = dict(parse_qsl(init_data, strict_parsing=True)) + except ValueError: # pragma: no cover + # Init data is not a valid query string + return False + if "hash" not in parsed_data: + # Hash is not present in init data + return False + hash_ = parsed_data.pop("hash") + + data_check_string = "\n".join( + f"{k}={v}" for k, v in sorted(parsed_data.items(), key=itemgetter(0)) + ) + secret_key = hmac.new(key=b"WebAppData", msg=token.encode(), digestmod=hashlib.sha256) + calculated_hash = hmac.new( + key=secret_key.digest(), msg=data_check_string.encode(), digestmod=hashlib.sha256 + ).hexdigest() + return calculated_hash == hash_ + + +def parse_webapp_init_data( + init_data: str, + *, + loads: Callable[..., Any] = json.loads, +) -> WebAppInitData: + """ + Parse WebApp init data and return it as WebAppInitData object + + This method doesn't make any security check, so you shall not trust to this data, + use :code:`safe_parse_webapp_init_data` instead. + + :param init_data: data from frontend to be parsed + :param loads: + :return: + """ + result = {} + for key, value in parse_qsl(init_data): + if (value.startswith("[") and value.endswith("]")) or ( + value.startswith("{") and value.endswith("}") + ): + value = loads(value) + result[key] = value + return WebAppInitData(**result) + + +def safe_parse_webapp_init_data( + token: str, + init_data: str, + *, + loads: Callable[..., Any] = json.loads, +) -> WebAppInitData: + """ + Validate raw WebApp init data and return it as WebAppInitData object + + Raise :type:`ValueError` when data is invalid + + :param token: bot token + :param init_data: data from frontend to be parsed and validated + :param loads: + :return: + """ + if check_webapp_signature(token, init_data): + return parse_webapp_init_data(init_data, loads=loads) + raise ValueError("Invalid init data signature") diff --git a/docs/api/methods/answer_web_app_query.rst b/docs/api/methods/answer_web_app_query.rst new file mode 100644 index 00000000..a608083f --- /dev/null +++ b/docs/api/methods/answer_web_app_query.rst @@ -0,0 +1,51 @@ +################# +answerWebAppQuery +################# + +Returns: :obj:`SentWebAppMessage` + +.. automodule:: aiogram.methods.answer_web_app_query + :members: + :member-order: bysource + :undoc-members: True + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: SentWebAppMessage = await bot.answer_web_app_query(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.answer_web_app_query import AnswerWebAppQuery` +- alias: :code:`from aiogram.methods import AnswerWebAppQuery` + +In handlers with current bot +---------------------------- + +.. code-block:: python + + result: SentWebAppMessage = await AnswerWebAppQuery(...) + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: SentWebAppMessage = await bot(AnswerWebAppQuery(...)) + +As reply into Webhook in handler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + return AnswerWebAppQuery(...) diff --git a/docs/api/methods/get_chat_menu_button.rst b/docs/api/methods/get_chat_menu_button.rst new file mode 100644 index 00000000..8e3df76a --- /dev/null +++ b/docs/api/methods/get_chat_menu_button.rst @@ -0,0 +1,44 @@ +################# +getChatMenuButton +################# + +Returns: :obj:`MenuButton` + +.. automodule:: aiogram.methods.get_chat_menu_button + :members: + :member-order: bysource + :undoc-members: True + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: MenuButton = await bot.get_chat_menu_button(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.get_chat_menu_button import GetChatMenuButton` +- alias: :code:`from aiogram.methods import GetChatMenuButton` + +In handlers with current bot +---------------------------- + +.. code-block:: python + + result: MenuButton = await GetChatMenuButton(...) + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: MenuButton = await bot(GetChatMenuButton(...)) diff --git a/docs/api/methods/get_my_default_administrator_rights.rst b/docs/api/methods/get_my_default_administrator_rights.rst new file mode 100644 index 00000000..d73c54f8 --- /dev/null +++ b/docs/api/methods/get_my_default_administrator_rights.rst @@ -0,0 +1,44 @@ +############################### +getMyDefaultAdministratorRights +############################### + +Returns: :obj:`ChatAdministratorRights` + +.. automodule:: aiogram.methods.get_my_default_administrator_rights + :members: + :member-order: bysource + :undoc-members: True + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: ChatAdministratorRights = await bot.get_my_default_administrator_rights(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.get_my_default_administrator_rights import GetMyDefaultAdministratorRights` +- alias: :code:`from aiogram.methods import GetMyDefaultAdministratorRights` + +In handlers with current bot +---------------------------- + +.. code-block:: python + + result: ChatAdministratorRights = await GetMyDefaultAdministratorRights(...) + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: ChatAdministratorRights = await bot(GetMyDefaultAdministratorRights(...)) diff --git a/docs/api/methods/index.rst b/docs/api/methods/index.rst index 1a4143d0..6c778282 100644 --- a/docs/api/methods/index.rst +++ b/docs/api/methods/index.rst @@ -82,6 +82,10 @@ Available methods set_my_commands delete_my_commands get_my_commands + set_chat_menu_button + get_chat_menu_button + set_my_default_administrator_rights + get_my_default_administrator_rights Updating messages ================= @@ -118,6 +122,7 @@ Inline mode :maxdepth: 1 answer_inline_query + answer_web_app_query Payments ======== diff --git a/docs/api/methods/set_chat_menu_button.rst b/docs/api/methods/set_chat_menu_button.rst new file mode 100644 index 00000000..6a60fae9 --- /dev/null +++ b/docs/api/methods/set_chat_menu_button.rst @@ -0,0 +1,51 @@ +################# +setChatMenuButton +################# + +Returns: :obj:`bool` + +.. automodule:: aiogram.methods.set_chat_menu_button + :members: + :member-order: bysource + :undoc-members: True + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: bool = await bot.set_chat_menu_button(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.set_chat_menu_button import SetChatMenuButton` +- alias: :code:`from aiogram.methods import SetChatMenuButton` + +In handlers with current bot +---------------------------- + +.. code-block:: python + + result: bool = await SetChatMenuButton(...) + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: bool = await bot(SetChatMenuButton(...)) + +As reply into Webhook in handler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + return SetChatMenuButton(...) diff --git a/docs/api/methods/set_my_default_administrator_rights.rst b/docs/api/methods/set_my_default_administrator_rights.rst new file mode 100644 index 00000000..c115568c --- /dev/null +++ b/docs/api/methods/set_my_default_administrator_rights.rst @@ -0,0 +1,51 @@ +############################### +setMyDefaultAdministratorRights +############################### + +Returns: :obj:`bool` + +.. automodule:: aiogram.methods.set_my_default_administrator_rights + :members: + :member-order: bysource + :undoc-members: True + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: bool = await bot.set_my_default_administrator_rights(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.set_my_default_administrator_rights import SetMyDefaultAdministratorRights` +- alias: :code:`from aiogram.methods import SetMyDefaultAdministratorRights` + +In handlers with current bot +---------------------------- + +.. code-block:: python + + result: bool = await SetMyDefaultAdministratorRights(...) + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: bool = await bot(SetMyDefaultAdministratorRights(...)) + +As reply into Webhook in handler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + return SetMyDefaultAdministratorRights(...) diff --git a/docs/api/types/chat_administrator_rights.rst b/docs/api/types/chat_administrator_rights.rst new file mode 100644 index 00000000..ef86eede --- /dev/null +++ b/docs/api/types/chat_administrator_rights.rst @@ -0,0 +1,9 @@ +####################### +ChatAdministratorRights +####################### + + +.. automodule:: aiogram.types.chat_administrator_rights + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/api/types/index.rst b/docs/api/types/index.rst index f9b1bfc2..abcfe205 100644 --- a/docs/api/types/index.rst +++ b/docs/api/types/index.rst @@ -40,14 +40,16 @@ Available types poll location venue + web_app_data proximity_alert_triggered message_auto_delete_timer_changed - voice_chat_scheduled - voice_chat_started - voice_chat_ended - voice_chat_participants_invited + video_chat_scheduled + video_chat_started + video_chat_ended + video_chat_participants_invited user_profile_photos file + web_app_info reply_keyboard_markup keyboard_button keyboard_button_poll_type @@ -59,6 +61,7 @@ Available types force_reply chat_photo chat_invite_link + chat_administrator_rights chat_member chat_member_owner chat_member_administrator @@ -79,6 +82,10 @@ Available types bot_command_scope_chat bot_command_scope_chat_administrators bot_command_scope_chat_member + menu_button + menu_button_commands + menu_button_web_app + menu_button_default response_parameters input_media input_media_photo @@ -135,6 +142,7 @@ Inline mode input_contact_message_content input_invoice_message_content chosen_inline_result + sent_web_app_message Payments ======== diff --git a/docs/api/types/menu_button.rst b/docs/api/types/menu_button.rst new file mode 100644 index 00000000..44eeb4c3 --- /dev/null +++ b/docs/api/types/menu_button.rst @@ -0,0 +1,9 @@ +########## +MenuButton +########## + + +.. automodule:: aiogram.types.menu_button + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/api/types/voice_chat_scheduled.rst b/docs/api/types/menu_button_commands.rst similarity index 60% rename from docs/api/types/voice_chat_scheduled.rst rename to docs/api/types/menu_button_commands.rst index e63936e2..66614b04 100644 --- a/docs/api/types/voice_chat_scheduled.rst +++ b/docs/api/types/menu_button_commands.rst @@ -1,9 +1,9 @@ ################## -VoiceChatScheduled +MenuButtonCommands ################## -.. automodule:: aiogram.types.voice_chat_scheduled +.. automodule:: aiogram.types.menu_button_commands :members: :member-order: bysource :undoc-members: True diff --git a/docs/api/types/menu_button_default.rst b/docs/api/types/menu_button_default.rst new file mode 100644 index 00000000..f114387c --- /dev/null +++ b/docs/api/types/menu_button_default.rst @@ -0,0 +1,9 @@ +################# +MenuButtonDefault +################# + + +.. automodule:: aiogram.types.menu_button_default + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/api/types/menu_button_web_app.rst b/docs/api/types/menu_button_web_app.rst new file mode 100644 index 00000000..bf5c0806 --- /dev/null +++ b/docs/api/types/menu_button_web_app.rst @@ -0,0 +1,9 @@ +################ +MenuButtonWebApp +################ + + +.. automodule:: aiogram.types.menu_button_web_app + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/api/types/sent_web_app_message.rst b/docs/api/types/sent_web_app_message.rst new file mode 100644 index 00000000..1a7d2084 --- /dev/null +++ b/docs/api/types/sent_web_app_message.rst @@ -0,0 +1,9 @@ +################# +SentWebAppMessage +################# + + +.. automodule:: aiogram.types.sent_web_app_message + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/api/types/voice_chat_ended.rst b/docs/api/types/video_chat_ended.rst similarity index 61% rename from docs/api/types/voice_chat_ended.rst rename to docs/api/types/video_chat_ended.rst index cce70b7c..aed8b7a9 100644 --- a/docs/api/types/voice_chat_ended.rst +++ b/docs/api/types/video_chat_ended.rst @@ -1,9 +1,9 @@ ############## -VoiceChatEnded +VideoChatEnded ############## -.. automodule:: aiogram.types.voice_chat_ended +.. automodule:: aiogram.types.video_chat_ended :members: :member-order: bysource :undoc-members: True diff --git a/docs/api/types/voice_chat_participants_invited.rst b/docs/api/types/video_chat_participants_invited.rst similarity index 58% rename from docs/api/types/voice_chat_participants_invited.rst rename to docs/api/types/video_chat_participants_invited.rst index 89a94fa9..9ca905bd 100644 --- a/docs/api/types/voice_chat_participants_invited.rst +++ b/docs/api/types/video_chat_participants_invited.rst @@ -1,9 +1,9 @@ ############################ -VoiceChatParticipantsInvited +VideoChatParticipantsInvited ############################ -.. automodule:: aiogram.types.voice_chat_participants_invited +.. automodule:: aiogram.types.video_chat_participants_invited :members: :member-order: bysource :undoc-members: True diff --git a/docs/api/types/video_chat_scheduled.rst b/docs/api/types/video_chat_scheduled.rst new file mode 100644 index 00000000..0d5f8c45 --- /dev/null +++ b/docs/api/types/video_chat_scheduled.rst @@ -0,0 +1,9 @@ +################## +VideoChatScheduled +################## + + +.. automodule:: aiogram.types.video_chat_scheduled + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/api/types/voice_chat_started.rst b/docs/api/types/video_chat_started.rst similarity index 60% rename from docs/api/types/voice_chat_started.rst rename to docs/api/types/video_chat_started.rst index c1a1964b..5d59a22e 100644 --- a/docs/api/types/voice_chat_started.rst +++ b/docs/api/types/video_chat_started.rst @@ -1,9 +1,9 @@ ################ -VoiceChatStarted +VideoChatStarted ################ -.. automodule:: aiogram.types.voice_chat_started +.. automodule:: aiogram.types.video_chat_started :members: :member-order: bysource :undoc-members: True diff --git a/docs/api/types/web_app_data.rst b/docs/api/types/web_app_data.rst new file mode 100644 index 00000000..1a94573f --- /dev/null +++ b/docs/api/types/web_app_data.rst @@ -0,0 +1,9 @@ +########## +WebAppData +########## + + +.. automodule:: aiogram.types.web_app_data + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/api/types/web_app_info.rst b/docs/api/types/web_app_info.rst new file mode 100644 index 00000000..b21f0aea --- /dev/null +++ b/docs/api/types/web_app_info.rst @@ -0,0 +1,9 @@ +########## +WebAppInfo +########## + + +.. automodule:: aiogram.types.web_app_info + :members: + :member-order: bysource + :undoc-members: True diff --git a/docs/utils/index.rst b/docs/utils/index.rst index 4f6eccf5..a7a8474d 100644 --- a/docs/utils/index.rst +++ b/docs/utils/index.rst @@ -7,3 +7,4 @@ Utils keyboard i18n chat_action + web_app diff --git a/docs/utils/web_app.rst b/docs/utils/web_app.rst new file mode 100644 index 00000000..82e9cc42 --- /dev/null +++ b/docs/utils/web_app.rst @@ -0,0 +1,55 @@ +====== +WebApะท +====== + +Telegram Bot API 6.0 announces a revolution in the development of chatbots using WebApp feature. + +You can read more details on it in the official `blog `_ +and `documentation `_. + +`aiogram` implements simple utils to remove headache with the data validation from Telegram WebApp on the backend side. + +Usage +===== + +For example from frontend you will pass :code:`application/x-www-form-urlencoded` POST request +with :code:`_auth` field in body and wants to return User info inside response as :code:`application/json` + +.. code-block:: python + + from aiogram.utils.web_app import safe_parse_webapp_init_data + from aiohttp.web_request import Request + from aiohttp.web_response import json_response + + async def check_data_handler(request: Request): + bot: Bot = request.app["bot"] + + data = await request.post() # application/x-www-form-urlencoded + try: + data = safe_parse_webapp_init_data(token=bot.token, init_data=data["_auth"]) + except ValueError: + return json_response({"ok": False, "err": "Unauthorized"}, status=401) + return json_response({"ok": True, "data": data.user.dict()}) + +Functions +========= + +.. autofunction:: aiogram.utils.web_app.check_webapp_signature + +.. autofunction:: aiogram.utils.web_app.parse_webapp_init_data + +.. autofunction:: aiogram.utils.web_app.safe_parse_webapp_init_data + + +Types +===== + +.. autoclass:: aiogram.utils.web_app.WebAppInitData + :members: + :member-order: bysource + :undoc-members: True + +.. autoclass:: aiogram.utils.web_app.WebAppUser + :members: + :member-order: bysource + :undoc-members: True diff --git a/examples/web_app/demo.html b/examples/web_app/demo.html new file mode 100644 index 00000000..40726974 --- /dev/null +++ b/examples/web_app/demo.html @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + + +
+ + + + + +

Test links:

+ +

Test permissions:

+ +
+
+ Data passed to webview. + +
+
+
+ Theme params +
+
+
+
+ + + + + diff --git a/examples/web_app/handlers.py b/examples/web_app/handlers.py new file mode 100644 index 00000000..745d6645 --- /dev/null +++ b/examples/web_app/handlers.py @@ -0,0 +1,48 @@ +from aiogram import Bot, F, Router +from aiogram.dispatcher.filters import Command +from aiogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + MenuButtonWebApp, + Message, + WebAppInfo, +) + +my_router = Router() + + +@my_router.message(Command(commands=["start"])) +async def command_start(message: Message, bot: Bot, base_url: str): + await bot.set_chat_menu_button( + chat_id=message.chat.id, + menu_button=MenuButtonWebApp(text="Open Menu", web_app=WebAppInfo(url=f"{base_url}/demo")), + ) + await message.answer("""Hi!\nSend me any type of message to start.\nOr just send /webview""") + + +@my_router.message(Command(commands=["webview"])) +async def command_webview(message: Message, base_url: str): + await message.answer( + "Good. Now you can try to send it via Webview", + reply_markup=InlineKeyboardMarkup( + inline_keyboard=[ + [ + InlineKeyboardButton( + text="Open Webview", web_app=WebAppInfo(url=f"{base_url}/demo") + ) + ] + ] + ), + ) + + +@my_router.message(~F.message.via_bot) # Echo to all messages except messages via bot +async def echo_all(message: Message, base_url: str): + await message.answer( + "Test webview", + reply_markup=InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text="Open", web_app=WebAppInfo(url=f"{base_url}/demo"))] + ] + ), + ) diff --git a/examples/web_app/main.py b/examples/web_app/main.py new file mode 100644 index 00000000..9f5260ad --- /dev/null +++ b/examples/web_app/main.py @@ -0,0 +1,49 @@ +import logging +from os import getenv + +from aiohttp.web import run_app +from aiohttp.web_app import Application +from handlers import my_router +from routes import check_data_handler, demo_handler, send_message_handler + +from aiogram import Bot, Dispatcher +from aiogram.dispatcher.webhook.aiohttp_server import SimpleRequestHandler, setup_application +from aiogram.types import MenuButtonWebApp, WebAppInfo + +TELEGRAM_TOKEN = getenv("TELEGRAM_TOKEN") +APP_BASE_URL = getenv("APP_BASE_URL") + + +async def on_startup(bot: Bot, base_url: str): + await bot.set_webhook(f"{base_url}/webhook") + await bot.set_chat_menu_button( + menu_button=MenuButtonWebApp(text="Open Menu", web_app=WebAppInfo(url=f"{base_url}/demo")) + ) + + +def main(): + bot = Bot(token=TELEGRAM_TOKEN, parse_mode="HTML") + dispatcher = Dispatcher() + dispatcher["base_url"] = APP_BASE_URL + dispatcher.startup.register(on_startup) + + dispatcher.include_router(my_router) + + app = Application() + app["bot"] = bot + + app.router.add_get("/demo", demo_handler) + app.router.add_post("/demo/checkData", check_data_handler) + app.router.add_post("/demo/sendMessage", send_message_handler) + SimpleRequestHandler( + dispatcher=dispatcher, + bot=bot, + ).register(app, path="/webhook") + setup_application(app, dispatcher, bot=bot) + + run_app(app, host="127.0.0.1", port=8081) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + main() diff --git a/examples/web_app/routes.py b/examples/web_app/routes.py new file mode 100644 index 00000000..d8c6b697 --- /dev/null +++ b/examples/web_app/routes.py @@ -0,0 +1,64 @@ +from pathlib import Path + +from aiohttp.web_fileresponse import FileResponse +from aiohttp.web_request import Request +from aiohttp.web_response import json_response + +from aiogram import Bot +from aiogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + InlineQueryResultArticle, + InputTextMessageContent, + WebAppInfo, +) +from aiogram.utils.web_app import check_webapp_signature, safe_parse_webapp_init_data + + +async def demo_handler(request: Request): + return FileResponse(Path(__file__).parent.resolve() / "demo.html") + + +async def check_data_handler(request: Request): + bot: Bot = request.app["bot"] + + data = await request.post() + if check_webapp_signature(bot.token, data["_auth"]): + return json_response({"ok": True}) + return json_response({"ok": False, "err": "Unauthorized"}, status=401) + + +async def send_message_handler(request: Request): + bot: Bot = request.app["bot"] + data = await request.post() + try: + web_app_init_data = safe_parse_webapp_init_data(token=bot.token, init_data=data["_auth"]) + except ValueError: + return json_response({"ok": False, "err": "Unauthorized"}, status=401) + + print(data) + reply_markup = None + if data["with_webview"] == "1": + reply_markup = InlineKeyboardMarkup( + inline_keyboard=[ + [ + InlineKeyboardButton( + text="Open", + web_app=WebAppInfo(url=str(request.url.with_scheme("https"))), + ) + ] + ] + ) + await bot.answer_web_app_query( + web_app_query_id=web_app_init_data.query_id, + result=InlineQueryResultArticle( + id=web_app_init_data.query_id, + title="Demo", + input_message_content=InputTextMessageContent( + message_text="Hello, World!", + parse_mode=None, + ), + reply_markup=reply_markup, + ), + ) + return json_response({"ok": True}) diff --git a/tests/test_api/test_methods/test_answer_web_app_query.py b/tests/test_api/test_methods/test_answer_web_app_query.py new file mode 100644 index 00000000..8d9848da --- /dev/null +++ b/tests/test_api/test_methods/test_answer_web_app_query.py @@ -0,0 +1,33 @@ +import pytest + +from aiogram.methods import AnswerWebAppQuery, Request +from aiogram.types import InlineQueryResult, SentWebAppMessage +from tests.mocked_bot import MockedBot + + +class TestAnswerWebAppQuery: + @pytest.mark.asyncio + async def test_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(AnswerWebAppQuery, ok=True, result=SentWebAppMessage()) + + response: SentWebAppMessage = await AnswerWebAppQuery( + web_app_query_id="test", + result=InlineQueryResult(), + ) + request: Request = bot.get_request() + assert request.method == "answerWebAppQuery" + # assert request.data == {} + assert response == prepare_result.result + + @pytest.mark.asyncio + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(AnswerWebAppQuery, ok=True, result=SentWebAppMessage()) + + response: SentWebAppMessage = await bot.answer_web_app_query( + web_app_query_id="test", + result=InlineQueryResult(), + ) + request: Request = bot.get_request() + assert request.method == "answerWebAppQuery" + # assert request.data == {} + assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_approve_chat_join_request.py b/tests/test_api/test_methods/test_approve_chat_join_request.py old mode 100644 new mode 100755 diff --git a/tests/test_api/test_methods/test_ban_chat_sender_chat.py b/tests/test_api/test_methods/test_ban_chat_sender_chat.py old mode 100644 new mode 100755 diff --git a/tests/test_api/test_methods/test_decline_chat_join_request.py b/tests/test_api/test_methods/test_decline_chat_join_request.py old mode 100644 new mode 100755 diff --git a/tests/test_api/test_methods/test_get_chat_menu_button.py b/tests/test_api/test_methods/test_get_chat_menu_button.py new file mode 100644 index 00000000..a7c2fd37 --- /dev/null +++ b/tests/test_api/test_methods/test_get_chat_menu_button.py @@ -0,0 +1,27 @@ +import pytest + +from aiogram.methods import GetChatMenuButton, Request +from aiogram.types import MenuButton, MenuButtonDefault +from tests.mocked_bot import MockedBot + + +class TestGetChatMenuButton: + @pytest.mark.asyncio + async def test_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=MenuButtonDefault()) + + response: MenuButton = await GetChatMenuButton() + request: Request = bot.get_request() + assert request.method == "getChatMenuButton" + # assert request.data == {} + assert response == prepare_result.result + + @pytest.mark.asyncio + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=MenuButtonDefault()) + + response: MenuButton = await bot.get_chat_menu_button() + request: Request = bot.get_request() + assert request.method == "getChatMenuButton" + # assert request.data == {} + assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_my_default_administrator_rights.py b/tests/test_api/test_methods/test_get_my_default_administrator_rights.py new file mode 100644 index 00000000..179b468d --- /dev/null +++ b/tests/test_api/test_methods/test_get_my_default_administrator_rights.py @@ -0,0 +1,53 @@ +import pytest + +from aiogram.methods import GetMyDefaultAdministratorRights, Request +from aiogram.types import ChatAdministratorRights +from tests.mocked_bot import MockedBot + + +class TestGetMyDefaultAdministratorRights: + @pytest.mark.asyncio + async def test_method(self, bot: MockedBot): + prepare_result = bot.add_result_for( + GetMyDefaultAdministratorRights, + ok=True, + result=ChatAdministratorRights( + is_anonymous=False, + can_manage_chat=False, + can_delete_messages=False, + can_manage_video_chats=False, + can_restrict_members=False, + can_promote_members=False, + can_change_info=False, + can_invite_users=False, + ), + ) + + response: ChatAdministratorRights = await GetMyDefaultAdministratorRights() + request: Request = bot.get_request() + assert request.method == "getMyDefaultAdministratorRights" + # assert request.data == {} + assert response == prepare_result.result + + @pytest.mark.asyncio + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for( + GetMyDefaultAdministratorRights, + ok=True, + result=ChatAdministratorRights( + is_anonymous=False, + can_manage_chat=False, + can_delete_messages=False, + can_manage_video_chats=False, + can_restrict_members=False, + can_promote_members=False, + can_change_info=False, + can_invite_users=False, + ), + ) + + response: ChatAdministratorRights = await bot.get_my_default_administrator_rights() + request: Request = bot.get_request() + assert request.method == "getMyDefaultAdministratorRights" + # assert request.data == {} + assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_get_sticker_set.py b/tests/test_api/test_methods/test_get_sticker_set.py index baed1d40..d778f1f7 100644 --- a/tests/test_api/test_methods/test_get_sticker_set.py +++ b/tests/test_api/test_methods/test_get_sticker_set.py @@ -16,6 +16,7 @@ class TestGetStickerSet: name="test", title="test", is_animated=False, + is_video=False, contains_masks=False, stickers=[ Sticker( @@ -23,6 +24,7 @@ class TestGetStickerSet: width=42, height=42, is_animated=False, + is_video=False, file_unique_id="file id", ) ], @@ -42,6 +44,7 @@ class TestGetStickerSet: name="test", title="test", is_animated=False, + is_video=False, contains_masks=False, stickers=[ Sticker( @@ -49,6 +52,7 @@ class TestGetStickerSet: width=42, height=42, is_animated=False, + is_video=False, file_unique_id="file id", ) ], diff --git a/tests/test_api/test_methods/test_send_sticker.py b/tests/test_api/test_methods/test_send_sticker.py index d356e8ae..239065eb 100644 --- a/tests/test_api/test_methods/test_send_sticker.py +++ b/tests/test_api/test_methods/test_send_sticker.py @@ -22,6 +22,7 @@ class TestSendSticker: width=42, height=42, is_animated=False, + is_video=False, file_unique_id="file id", ), chat=Chat(id=42, type="private"), @@ -45,6 +46,7 @@ class TestSendSticker: width=42, height=42, is_animated=False, + is_video=False, file_unique_id="file id", ), chat=Chat(id=42, type="private"), diff --git a/tests/test_api/test_methods/test_set_chat_menu_button.py b/tests/test_api/test_methods/test_set_chat_menu_button.py new file mode 100644 index 00000000..97e2fa90 --- /dev/null +++ b/tests/test_api/test_methods/test_set_chat_menu_button.py @@ -0,0 +1,26 @@ +import pytest + +from aiogram.methods import Request, SetChatMenuButton +from tests.mocked_bot import MockedBot + + +class TestSetChatMenuButton: + @pytest.mark.asyncio + async def test_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=True) + + response: bool = await SetChatMenuButton() + request: Request = bot.get_request() + assert request.method == "setChatMenuButton" + # assert request.data == {} + assert response == prepare_result.result + + @pytest.mark.asyncio + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=True) + + response: bool = await bot.set_chat_menu_button() + request: Request = bot.get_request() + assert request.method == "setChatMenuButton" + # assert request.data == {} + assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_set_my_default_administrator_rights.py b/tests/test_api/test_methods/test_set_my_default_administrator_rights.py new file mode 100644 index 00000000..4bd08822 --- /dev/null +++ b/tests/test_api/test_methods/test_set_my_default_administrator_rights.py @@ -0,0 +1,26 @@ +import pytest + +from aiogram.methods import Request, SetMyDefaultAdministratorRights +from tests.mocked_bot import MockedBot + + +class TestSetMyDefaultAdministratorRights: + @pytest.mark.asyncio + async def test_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=True) + + response: bool = await SetMyDefaultAdministratorRights() + request: Request = bot.get_request() + assert request.method == "setMyDefaultAdministratorRights" + # assert request.data == {} + assert response == prepare_result.result + + @pytest.mark.asyncio + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=True) + + response: bool = await bot.set_my_default_administrator_rights() + request: Request = bot.get_request() + assert request.method == "setMyDefaultAdministratorRights" + # assert request.data == {} + assert response == prepare_result.result diff --git a/tests/test_api/test_methods/test_unban_chat_sender_chat.py b/tests/test_api/test_methods/test_unban_chat_sender_chat.py old mode 100644 new mode 100755 diff --git a/tests/test_api/test_types/test_message.py b/tests/test_api/test_types/test_message.py index b2b66b77..fcf357c4 100644 --- a/tests/test_api/test_types/test_message.py +++ b/tests/test_api/test_types/test_message.py @@ -52,11 +52,13 @@ from aiogram.types import ( User, Venue, Video, + VideoChatEnded, + VideoChatParticipantsInvited, + VideoChatScheduled, + VideoChatStarted, VideoNote, Voice, - VoiceChatEnded, - VoiceChatParticipantsInvited, - VoiceChatStarted, + WebAppData, ) from aiogram.types.message import ContentType, Message @@ -122,6 +124,7 @@ TEST_MESSAGE_STICKER = Message( width=42, height=42, is_animated=False, + is_video=False, ), chat=Chat(id=42, type="private"), from_user=User(id=42, is_bot=False, first_name="Test"), @@ -318,29 +321,38 @@ TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED = Message( message_auto_delete_timer_changed=MessageAutoDeleteTimerChanged(message_auto_delete_time=42), from_user=User(id=42, is_bot=False, first_name="Test"), ) -TEST_MESSAGE_VOICE_CHAT_STARTED = Message( +TEST_MESSAGE_VIDEO_CHAT_STARTED = Message( message_id=42, date=datetime.datetime.now(), chat=Chat(id=42, type="private"), from_user=User(id=42, is_bot=False, first_name="Test"), - voice_chat_started=VoiceChatStarted(), + video_chat_started=VideoChatStarted(), ) -TEST_MESSAGE_VOICE_CHAT_ENDED = Message( +TEST_MESSAGE_VIDEO_CHAT_ENDED = Message( message_id=42, date=datetime.datetime.now(), chat=Chat(id=42, type="private"), from_user=User(id=42, is_bot=False, first_name="Test"), - voice_chat_ended=VoiceChatEnded(duration=42), + video_chat_ended=VideoChatEnded(duration=42), ) -TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED = Message( +TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED = Message( message_id=42, date=datetime.datetime.now(), chat=Chat(id=42, type="private"), from_user=User(id=42, is_bot=False, first_name="Test"), - voice_chat_participants_invited=VoiceChatParticipantsInvited( + video_chat_participants_invited=VideoChatParticipantsInvited( users=[User(id=69, is_bot=False, first_name="Test")] ), ) +TEST_MESSAGE_VIDEO_CHAT_SCHEDULED = Message( + message_id=42, + date=datetime.datetime.now(), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + video_chat_scheduled=VideoChatScheduled( + start_date=datetime.datetime.now(), + ), +) TEST_MESSAGE_DICE = Message( message_id=42, date=datetime.datetime.now(), @@ -348,6 +360,13 @@ TEST_MESSAGE_DICE = Message( dice=Dice(value=6, emoji="X"), from_user=User(id=42, is_bot=False, first_name="Test"), ) +TEST_MESSAGE_WEB_APP_DATA = Message( + message_id=42, + date=datetime.datetime.now(), + chat=Chat(id=42, type="private"), + web_app_data=WebAppData(data="test", button_text="Test"), + from_user=User(id=42, is_bot=False, first_name="Test"), +) TEST_MESSAGE_UNKNOWN = Message( message_id=42, date=datetime.datetime.now(), @@ -391,13 +410,15 @@ class TestMessage: TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED, ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED, ], - [TEST_MESSAGE_VOICE_CHAT_STARTED, ContentType.VOICE_CHAT_STARTED], - [TEST_MESSAGE_VOICE_CHAT_ENDED, ContentType.VOICE_CHAT_ENDED], + [TEST_MESSAGE_VIDEO_CHAT_SCHEDULED, ContentType.VIDEO_CHAT_SCHEDULED], + [TEST_MESSAGE_VIDEO_CHAT_STARTED, ContentType.VIDEO_CHAT_STARTED], + [TEST_MESSAGE_VIDEO_CHAT_ENDED, ContentType.VIDEO_CHAT_ENDED], [ - TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED, - ContentType.VOICE_CHAT_PARTICIPANTS_INVITED, + TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED, + ContentType.VIDEO_CHAT_PARTICIPANTS_INVITED, ], [TEST_MESSAGE_DICE, ContentType.DICE], + [TEST_MESSAGE_WEB_APP_DATA, ContentType.WEB_APP_DATA], [TEST_MESSAGE_UNKNOWN, ContentType.UNKNOWN], ], ) @@ -535,9 +556,9 @@ class TestMessage: [TEST_MESSAGE_PASSPORT_DATA, None], [TEST_MESSAGE_POLL, SendPoll], [TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED, None], - [TEST_MESSAGE_VOICE_CHAT_STARTED, None], - [TEST_MESSAGE_VOICE_CHAT_ENDED, None], - [TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED, None], + [TEST_MESSAGE_VIDEO_CHAT_STARTED, None], + [TEST_MESSAGE_VIDEO_CHAT_ENDED, None], + [TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED, None], [TEST_MESSAGE_DICE, SendDice], [TEST_MESSAGE_UNKNOWN, None], ], diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py index 89d027b1..f501ed75 100644 --- a/tests/test_dispatcher/test_dispatcher.py +++ b/tests/test_dispatcher/test_dispatcher.py @@ -82,11 +82,11 @@ class TestDispatcher: assert dp.get("foo", 42) == 42 dp["foo"] = 1 - assert dp._data["foo"] == 1 + assert dp.workflow_data["foo"] == 1 assert dp["foo"] == 1 del dp["foo"] - assert "foo" not in dp._data + assert "foo" not in dp.workflow_data def test_storage_property(self, dispatcher: Dispatcher): assert dispatcher.storage is dispatcher.fsm.storage diff --git a/tests/test_dispatcher/test_filters/test_chat_member_updated.py b/tests/test_dispatcher/test_filters/test_chat_member_updated.py index 63ee1245..dae0e985 100644 --- a/tests/test_dispatcher/test_filters/test_chat_member_updated.py +++ b/tests/test_dispatcher/test_filters/test_chat_member_updated.py @@ -320,7 +320,7 @@ class TestChatMemberUpdatedStatusFilter: "can_be_edited": True, "can_manage_chat": True, "can_delete_messages": True, - "can_manage_voice_chats": True, + "can_manage_video_chats": True, "can_restrict_members": True, "can_promote_members": True, "can_change_info": True, diff --git a/tests/test_utils/test_web_app.py b/tests/test_utils/test_web_app.py new file mode 100644 index 00000000..abebd909 --- /dev/null +++ b/tests/test_utils/test_web_app.py @@ -0,0 +1,80 @@ +import pytest + +from aiogram.utils.web_app import ( + WebAppInitData, + check_webapp_signature, + parse_webapp_init_data, + safe_parse_webapp_init_data, +) + + +class TestWebApp: + @pytest.mark.parametrize( + "token,case,result", + [ + [ + "42:TEST", + "auth_date=1650385342" + "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D" + "&query_id=test" + "&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803", + True, + ], + [ + "42:INVALID", + "auth_date=1650385342" + "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D" + "&query_id=test" + "&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803", + False, + ], + [ + "42:TEST", + "user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D&query_id=test&hash=test", + False, + ], + [ + "42:TEST", + "user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D&query_id=test", + False, + ], + ["42:TEST", "", False], + ["42:TEST", "test&foo=bar=baz", False], + ], + ) + def test_check_webapp_signature(self, token, case, result): + assert check_webapp_signature(token, case) is result + + def test_parse_web_app_init_data(self): + parsed = parse_webapp_init_data( + "auth_date=1650385342" + "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D" + "&query_id=test" + "&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803", + ) + assert isinstance(parsed, WebAppInitData) + assert parsed.user + assert parsed.user.first_name == "Test" + assert parsed.user.id == 42 + assert parsed.query_id == "test" + assert parsed.hash == "46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803" + assert parsed.auth_date.year == 2022 + + def test_valid_safe_parse_webapp_init_data(self): + assert safe_parse_webapp_init_data( + "42:TEST", + "auth_date=1650385342" + "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D" + "&query_id=test" + "&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803", + ) + + def test_invalid_safe_parse_webapp_init_data(self): + with pytest.raises(ValueError): + safe_parse_webapp_init_data( + "42:TOKEN", + "auth_date=1650385342" + "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D" + "&query_id=test" + "&hash=test", + ) From 0d650fc38c591a9b519fb295e8fe53cb2afc834e Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 19 Apr 2022 22:09:43 +0300 Subject: [PATCH 20/35] Release 3.0.0-beta.3 --- .apiversion | 2 +- CHANGES.rst | 39 +++++++++++++++++++++++++++++++++++++++ CHANGES/865.bugfix.rst | 1 - CHANGES/873.misc.rst | 1 - CHANGES/874.misc.rst | 2 -- CHANGES/876.bugfix | 1 - CHANGES/882.misc.rst | 1 - CHANGES/883.misc.rst | 3 --- CHANGES/885.bugfix.rst | 1 - CHANGES/889.feature.rst | 1 - CHANGES/890.feature.rst | 1 - Makefile | 10 ++++++---- README.rst | 6 +++--- aiogram/__init__.py | 4 ++-- pyproject.toml | 2 +- 15 files changed, 52 insertions(+), 23 deletions(-) delete mode 100644 CHANGES/865.bugfix.rst delete mode 100644 CHANGES/873.misc.rst delete mode 100644 CHANGES/874.misc.rst delete mode 100644 CHANGES/876.bugfix delete mode 100644 CHANGES/882.misc.rst delete mode 100644 CHANGES/883.misc.rst delete mode 100644 CHANGES/885.bugfix.rst delete mode 100644 CHANGES/889.feature.rst delete mode 100644 CHANGES/890.feature.rst diff --git a/.apiversion b/.apiversion index 760606e1..e0ea36fe 100644 --- a/.apiversion +++ b/.apiversion @@ -1 +1 @@ -5.7 +6.0 diff --git a/CHANGES.rst b/CHANGES.rst index 9216b305..66602c32 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,45 @@ Changelog .. towncrier release notes start +3.0.0b3 (2022-04-19) +===================== + +Features +-------- + +- Added possibility to get command magic result as handler argument + `#889 `_ +- Added full support of `Telegram Bot API 6.0 `_ + `#890 `_ + + +Bugfixes +-------- + +- Added parsing of spoiler message entity + `#865 `_ +- Fixed default `parse_mode` for `Message.copy_to()` method. + `#876 `_ +- Fixed CallbackData factory parsing IntEnum's + `#885 `_ + + +Misc +---- + +- Added automated check that pull-request adds a changes description to **CHANGES** directory + `#873 `_ +- Changed :code:`Message.html_text` and :code:`Message.md_text` attributes behaviour when message has no text. + The empty string will be used instead of raising error. + `#874 `_ +- Used `redis-py` instead of `aioredis` package in due to this packages was merged into single one + `#882 `_ +- Solved common naming problem with middlewares that confusing too much developers + - now you can't see the `middleware` and `middlewares` attributes at the same point + because this functionality encapsulated to special interface. + `#883 `_ + + 3.0.0b2 (2022-02-19) ===================== diff --git a/CHANGES/865.bugfix.rst b/CHANGES/865.bugfix.rst deleted file mode 100644 index bab93962..00000000 --- a/CHANGES/865.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Added parsing of spoiler message entity diff --git a/CHANGES/873.misc.rst b/CHANGES/873.misc.rst deleted file mode 100644 index 170dca4d..00000000 --- a/CHANGES/873.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Added automated check that pull-request adds a changes description to **CHANGES** directory diff --git a/CHANGES/874.misc.rst b/CHANGES/874.misc.rst deleted file mode 100644 index d167c1d8..00000000 --- a/CHANGES/874.misc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Changed :code:`Message.html_text` and :code:`Message.md_text` attributes behaviour when message has no text. -The empty string will be used instead of raising error. diff --git a/CHANGES/876.bugfix b/CHANGES/876.bugfix deleted file mode 100644 index b4fded2e..00000000 --- a/CHANGES/876.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed default `parse_mode` for `Message.copy_to()` method. diff --git a/CHANGES/882.misc.rst b/CHANGES/882.misc.rst deleted file mode 100644 index ca8b0ba4..00000000 --- a/CHANGES/882.misc.rst +++ /dev/null @@ -1 +0,0 @@ -Used `redis-py` instead of `aioredis` package in due to this packages was merged into single one diff --git a/CHANGES/883.misc.rst b/CHANGES/883.misc.rst deleted file mode 100644 index 8496c902..00000000 --- a/CHANGES/883.misc.rst +++ /dev/null @@ -1,3 +0,0 @@ -Solved common naming problem with middlewares that confusing too much developers -- now you can't see the `middleware` and `middlewares` attributes at the same point -because this functionality encapsulated to special interface. diff --git a/CHANGES/885.bugfix.rst b/CHANGES/885.bugfix.rst deleted file mode 100644 index 6d1bb095..00000000 --- a/CHANGES/885.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed CallbackData factory parsing IntEnum's diff --git a/CHANGES/889.feature.rst b/CHANGES/889.feature.rst deleted file mode 100644 index 8e58304c..00000000 --- a/CHANGES/889.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added possibility to get command magic result as handler argument diff --git a/CHANGES/890.feature.rst b/CHANGES/890.feature.rst deleted file mode 100644 index 10c60c05..00000000 --- a/CHANGES/890.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added full support of `Telegram Bot API 6.0 `_ diff --git a/Makefile b/Makefile index 8d9abab8..37a2b2b1 100644 --- a/Makefile +++ b/Makefile @@ -139,8 +139,10 @@ towncrier-draft-github: towncrier build --draft | pandoc - -o dist/release.md .PHONY: prepare-release -prepare-release: bump towncrier-draft-github towncrier-build +prepare-release: bump towncrier-build -.PHONY: tag-release -tag-release: - git tag v$(poetry version -s) +.PHONY: release +release: + git add . + git commit -m "Release $(shell poetry version -s)" + git tag v$(shell poetry version -s) diff --git a/README.rst b/README.rst index 99b88c07..df29b800 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ aiogram |beta badge| :target: https://pypi.python.org/pypi/aiogram :alt: Supported python versions -.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.7-blue.svg?logo=telegram +.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.0-blue.svg?logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API @@ -71,8 +71,8 @@ Features .. warning:: - It is strongly advised that you have prior experience working - with `asyncio `_ + It is strongly advised that you have prior experience working + with `asyncio `_ before beginning to use **aiogram**. If you have any questions, you can visit our community chats on Telegram: diff --git a/aiogram/__init__.py b/aiogram/__init__.py index d734cb83..50973dd6 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -39,5 +39,5 @@ __all__ = ( "flags", ) -__version__ = "3.0.0b2" -__api_version__ = "5.7" +__version__ = "3.0.0b3" +__api_version__ = "6.0" diff --git a/pyproject.toml b/pyproject.toml index ba67b272..8e85e24d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiogram" -version = "3.0.0-beta.2" +version = "3.0.0-beta.3" description = "Modern and fully asynchronous framework for Telegram Bot API" authors = [ "Alex Root Junior ", From 781e30c25b28689c90f1d3797e3e06da2bf87dcc Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 19 Apr 2022 22:18:10 +0300 Subject: [PATCH 21/35] Fixed changelog --- CHANGES.rst | 2 ++ CHANGES/839.bugix.rst | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 CHANGES/839.bugix.rst diff --git a/CHANGES.rst b/CHANGES.rst index 66602c32..1b08ff61 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,8 @@ Features Bugfixes -------- +- Fixed I18n lazy-proxy. Disabled caching. + `#839 `_ - Added parsing of spoiler message entity `#865 `_ - Fixed default `parse_mode` for `Message.copy_to()` method. diff --git a/CHANGES/839.bugix.rst b/CHANGES/839.bugix.rst deleted file mode 100644 index 89bbe247..00000000 --- a/CHANGES/839.bugix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed I18n lazy-proxy. Disabled caching. From 7bfc941a1e9a04b95d92bebaf3d9c7fe1aa4302e Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 19 Apr 2022 22:26:43 +0300 Subject: [PATCH 22/35] Fixed typy in WebApp docs page --- docs/utils/web_app.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/utils/web_app.rst b/docs/utils/web_app.rst index 82e9cc42..52e836d6 100644 --- a/docs/utils/web_app.rst +++ b/docs/utils/web_app.rst @@ -1,5 +1,5 @@ ====== -WebApะท +WebApp ====== Telegram Bot API 6.0 announces a revolution in the development of chatbots using WebApp feature. From 4fb77a3a2a30f0d39be0f968e63c3f948b553cf3 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 24 Apr 2022 04:19:19 +0300 Subject: [PATCH 23/35] Added possibility to combine filters or invert result (#895) * Added possibility to combine filters or invert result --- CHANGES/894.feature.rst | 7 ++ aiogram/dispatcher/event/event.py | 4 +- aiogram/dispatcher/event/handler.py | 15 ++-- aiogram/dispatcher/event/telegram.py | 30 +++++-- aiogram/dispatcher/filters/__init__.py | 4 + aiogram/dispatcher/filters/base.py | 4 +- aiogram/dispatcher/filters/logic.py | 87 +++++++++++++++++++ aiogram/dispatcher/middlewares/manager.py | 4 +- aiogram/dispatcher/router.py | 32 +++---- .../test_filters/test_logic.py | 37 ++++++++ 10 files changed, 184 insertions(+), 40 deletions(-) create mode 100644 CHANGES/894.feature.rst create mode 100644 aiogram/dispatcher/filters/logic.py create mode 100644 tests/test_dispatcher/test_filters/test_logic.py diff --git a/CHANGES/894.feature.rst b/CHANGES/894.feature.rst new file mode 100644 index 00000000..f89f4e07 --- /dev/null +++ b/CHANGES/894.feature.rst @@ -0,0 +1,7 @@ +Added possibility to combine filters or invert result + +Example: +.. code-block:: python + Text(text="demo") | Command(commands=["demo"]) + MyFilter() & AnotherFilter() + ~StateFilter(state='my-state') diff --git a/aiogram/dispatcher/event/event.py b/aiogram/dispatcher/event/event.py index ef87d329..a2e1165c 100644 --- a/aiogram/dispatcher/event/event.py +++ b/aiogram/dispatcher/event/event.py @@ -2,7 +2,7 @@ from __future__ import annotations from typing import Any, Callable, List -from .handler import CallbackType, HandlerObject, HandlerType +from .handler import CallbackType, HandlerObject class EventObserver: @@ -26,7 +26,7 @@ class EventObserver: def __init__(self) -> None: self.handlers: List[HandlerObject] = [] - def register(self, callback: HandlerType) -> None: + def register(self, callback: CallbackType) -> None: """ Register callback with filters """ diff --git a/aiogram/dispatcher/event/handler.py b/aiogram/dispatcher/event/handler.py index 813ddf51..af4cff37 100644 --- a/aiogram/dispatcher/event/handler.py +++ b/aiogram/dispatcher/event/handler.py @@ -3,24 +3,19 @@ import contextvars import inspect from dataclasses import dataclass, field from functools import partial -from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Tuple from magic_filter import MagicFilter -from aiogram.dispatcher.filters.base import BaseFilter from aiogram.dispatcher.flags.getter import extract_flags_from_object from aiogram.dispatcher.handler.base import BaseHandler -CallbackType = Callable[..., Awaitable[Any]] -SyncFilter = Callable[..., Any] -AsyncFilter = Callable[..., Awaitable[Any]] -FilterType = Union[SyncFilter, AsyncFilter, BaseFilter, MagicFilter] -HandlerType = Union[FilterType, Type[BaseHandler]] +CallbackType = Callable[..., Any] @dataclass class CallableMixin: - callback: HandlerType + callback: CallbackType awaitable: bool = field(init=False) spec: inspect.FullArgSpec = field(init=False) @@ -50,7 +45,7 @@ class CallableMixin: @dataclass class FilterObject(CallableMixin): - callback: FilterType + callback: CallbackType def __post_init__(self) -> None: # TODO: Make possibility to extract and explain magic from filter object. @@ -63,7 +58,7 @@ class FilterObject(CallableMixin): @dataclass class HandlerObject(CallableMixin): - callback: HandlerType + callback: CallbackType filters: Optional[List[FilterObject]] = None flags: Dict[str, Any] = field(default_factory=dict) diff --git a/aiogram/dispatcher/event/telegram.py b/aiogram/dispatcher/event/telegram.py index fcf3d7d2..ab815de7 100644 --- a/aiogram/dispatcher/event/telegram.py +++ b/aiogram/dispatcher/event/telegram.py @@ -12,7 +12,7 @@ from ...exceptions import FiltersResolveError from ...types import TelegramObject from ..filters.base import BaseFilter from .bases import REJECTED, UNHANDLED, MiddlewareType, SkipHandler -from .handler import CallbackType, FilterObject, FilterType, HandlerObject, HandlerType +from .handler import CallbackType, FilterObject, HandlerObject if TYPE_CHECKING: from aiogram.dispatcher.router import Router @@ -40,7 +40,7 @@ class TelegramEventObserver: # with dummy callback which never will be used self._handler = HandlerObject(callback=lambda: True, filters=[]) - def filter(self, *filters: FilterType, **bound_filters: Any) -> None: + def filter(self, *filters: CallbackType, **bound_filters: Any) -> None: """ Register filter for all handlers of this event observer @@ -51,7 +51,13 @@ class TelegramEventObserver: if self._handler.filters is None: self._handler.filters = [] self._handler.filters.extend( - [FilterObject(filter_) for filter_ in chain(resolved_filters, filters)] + [ + FilterObject(filter_) # type: ignore + for filter_ in chain( + resolved_filters, + filters, + ) + ] ) def bind_filter(self, bound_filter: Type[BaseFilter]) -> None: @@ -96,7 +102,7 @@ class TelegramEventObserver: def resolve_filters( self, - filters: Tuple[FilterType, ...], + filters: Tuple[CallbackType, ...], full_config: Dict[str, Any], ignore_default: bool = True, ) -> List[BaseFilter]: @@ -158,11 +164,11 @@ class TelegramEventObserver: def register( self, - callback: HandlerType, - *filters: FilterType, + callback: CallbackType, + *filters: CallbackType, flags: Optional[Dict[str, Any]] = None, **bound_filters: Any, - ) -> HandlerType: + ) -> CallbackType: """ Register event handler """ @@ -174,7 +180,13 @@ class TelegramEventObserver: self.handlers.append( HandlerObject( callback=callback, - filters=[FilterObject(filter_) for filter_ in chain(resolved_filters, filters)], + filters=[ + FilterObject(filter_) # type: ignore + for filter_ in chain( + resolved_filters, + filters, + ) + ], flags=flags, ) ) @@ -216,7 +228,7 @@ class TelegramEventObserver: return UNHANDLED def __call__( - self, *args: FilterType, flags: Optional[Dict[str, Any]] = None, **bound_filters: Any + self, *args: CallbackType, flags: Optional[Dict[str, Any]] = None, **bound_filters: Any ) -> Callable[[CallbackType], CallbackType]: """ Decorator for registering event handlers diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py index a38b57af..4caa137c 100644 --- a/aiogram/dispatcher/filters/__init__.py +++ b/aiogram/dispatcher/filters/__init__.py @@ -19,6 +19,7 @@ from .chat_member_updated import ( from .command import Command, CommandObject from .content_types import ContentTypesFilter from .exception import ExceptionMessageFilter, ExceptionTypeFilter +from .logic import and_f, invert_f, or_f from .magic_data import MagicData from .state import StateFilter from .text import Text @@ -47,6 +48,9 @@ __all__ = ( "IS_NOT_MEMBER", "JOIN_TRANSITION", "LEAVE_TRANSITION", + "and_f", + "or_f", + "invert_f", ) _ALL_EVENTS_FILTERS: Tuple[Type[BaseFilter], ...] = (MagicData,) diff --git a/aiogram/dispatcher/filters/base.py b/aiogram/dispatcher/filters/base.py index d2bb99cf..877f98f6 100644 --- a/aiogram/dispatcher/filters/base.py +++ b/aiogram/dispatcher/filters/base.py @@ -3,8 +3,10 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Union from pydantic import BaseModel +from aiogram.dispatcher.filters.logic import _LogicFilter -class BaseFilter(ABC, BaseModel): + +class BaseFilter(BaseModel, ABC, _LogicFilter): """ If you want to register own filters like builtin filters you will need to write subclass of this class with overriding the :code:`__call__` diff --git a/aiogram/dispatcher/filters/logic.py b/aiogram/dispatcher/filters/logic.py new file mode 100644 index 00000000..9a43956b --- /dev/null +++ b/aiogram/dispatcher/filters/logic.py @@ -0,0 +1,87 @@ +from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Union + +if TYPE_CHECKING: + from aiogram.dispatcher.event.handler import CallbackType, FilterObject + + +class _LogicFilter: + __call__: Callable[..., Awaitable[Union[bool, Dict[str, Any]]]] + + def __and__(self, other: "CallbackType") -> "_AndFilter": + return and_f(self, other) + + def __or__(self, other: "CallbackType") -> "_OrFilter": + return or_f(self, other) + + def __invert__(self) -> "_InvertFilter": + return invert_f(self) + + def __await__(self): # type: ignore # pragma: no cover + # Is needed only for inspection and this method is never be called + return self.__call__ + + +class _InvertFilter(_LogicFilter): + __slots__ = ("target",) + + def __init__(self, target: "FilterObject") -> None: + self.target = target + + async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]: + return not bool(await self.target.call(*args, **kwargs)) + + +class _AndFilter(_LogicFilter): + __slots__ = ("targets",) + + def __init__(self, *targets: "FilterObject") -> None: + self.targets = targets + + async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]: + final_result = {} + + for target in self.targets: + result = await target.call(*args, **kwargs) + if not result: + return False + if isinstance(result, dict): + final_result.update(result) + + if final_result: + return final_result + return True + + +class _OrFilter(_LogicFilter): + __slots__ = ("targets",) + + def __init__(self, *targets: "FilterObject") -> None: + self.targets = targets + + async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]: + for target in self.targets: + result = await target.call(*args, **kwargs) + if not result: + continue + if isinstance(result, dict): + return result + return bool(result) + return False + + +def and_f(target1: "CallbackType", target2: "CallbackType") -> _AndFilter: + from aiogram.dispatcher.event.handler import FilterObject + + return _AndFilter(FilterObject(target1), FilterObject(target2)) + + +def or_f(target1: "CallbackType", target2: "CallbackType") -> _OrFilter: + from aiogram.dispatcher.event.handler import FilterObject + + return _OrFilter(FilterObject(target1), FilterObject(target2)) + + +def invert_f(target: "CallbackType") -> _InvertFilter: + from aiogram.dispatcher.event.handler import FilterObject + + return _InvertFilter(FilterObject(target)) diff --git a/aiogram/dispatcher/middlewares/manager.py b/aiogram/dispatcher/middlewares/manager.py index 89892e77..9f132f58 100644 --- a/aiogram/dispatcher/middlewares/manager.py +++ b/aiogram/dispatcher/middlewares/manager.py @@ -2,7 +2,7 @@ import functools from typing import Any, Callable, Dict, List, Optional, Sequence, Union, overload from aiogram.dispatcher.event.bases import MiddlewareEventType, MiddlewareType, NextMiddlewareType -from aiogram.dispatcher.event.handler import HandlerType +from aiogram.dispatcher.event.handler import CallbackType from aiogram.types import TelegramObject @@ -49,7 +49,7 @@ class MiddlewareManager(Sequence[MiddlewareType[TelegramObject]]): @staticmethod def wrap_middlewares( - middlewares: Sequence[MiddlewareType[MiddlewareEventType]], handler: HandlerType + middlewares: Sequence[MiddlewareType[MiddlewareEventType]], handler: CallbackType ) -> NextMiddlewareType[MiddlewareEventType]: @functools.wraps(handler) def handler_wrapper(event: TelegramObject, kwargs: Dict[str, Any]) -> Any: diff --git a/aiogram/dispatcher/router.py b/aiogram/dispatcher/router.py index 32e82195..a1a20fba 100644 --- a/aiogram/dispatcher/router.py +++ b/aiogram/dispatcher/router.py @@ -8,7 +8,7 @@ from ..utils.imports import import_module from ..utils.warnings import CodeHasNoEffect from .event.bases import REJECTED, UNHANDLED from .event.event import EventObserver -from .event.handler import HandlerType +from .event.handler import CallbackType from .event.telegram import TelegramEventObserver from .filters import BUILTIN_FILTERS @@ -396,7 +396,7 @@ class Router: ) return self.errors - def register_message(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_message(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_message(...)` is deprecated and will be removed in version 3.2 " "use `Router.message.register(...)`", @@ -405,7 +405,7 @@ class Router: ) return self.message.register(*args, **kwargs) - def register_edited_message(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_edited_message(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_edited_message(...)` is deprecated and will be removed in version 3.2 " "use `Router.edited_message.register(...)`", @@ -414,7 +414,7 @@ class Router: ) return self.edited_message.register(*args, **kwargs) - def register_channel_post(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_channel_post(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_channel_post(...)` is deprecated and will be removed in version 3.2 " "use `Router.channel_post.register(...)`", @@ -423,7 +423,7 @@ class Router: ) return self.channel_post.register(*args, **kwargs) - def register_edited_channel_post(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_edited_channel_post(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_edited_channel_post(...)` is deprecated and will be removed in version 3.2 " "use `Router.edited_channel_post.register(...)`", @@ -432,7 +432,7 @@ class Router: ) return self.edited_channel_post.register(*args, **kwargs) - def register_inline_query(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_inline_query(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_inline_query(...)` is deprecated and will be removed in version 3.2 " "use `Router.inline_query.register(...)`", @@ -441,7 +441,7 @@ class Router: ) return self.inline_query.register(*args, **kwargs) - def register_chosen_inline_result(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_chosen_inline_result(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_chosen_inline_result(...)` is deprecated and will be removed in version 3.2 " "use `Router.chosen_inline_result.register(...)`", @@ -450,7 +450,7 @@ class Router: ) return self.chosen_inline_result.register(*args, **kwargs) - def register_callback_query(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_callback_query(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_callback_query(...)` is deprecated and will be removed in version 3.2 " "use `Router.callback_query.register(...)`", @@ -459,7 +459,7 @@ class Router: ) return self.callback_query.register(*args, **kwargs) - def register_shipping_query(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_shipping_query(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_shipping_query(...)` is deprecated and will be removed in version 3.2 " "use `Router.shipping_query.register(...)`", @@ -468,7 +468,7 @@ class Router: ) return self.shipping_query.register(*args, **kwargs) - def register_pre_checkout_query(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_pre_checkout_query(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_pre_checkout_query(...)` is deprecated and will be removed in version 3.2 " "use `Router.pre_checkout_query.register(...)`", @@ -477,7 +477,7 @@ class Router: ) return self.pre_checkout_query.register(*args, **kwargs) - def register_poll(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_poll(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_poll(...)` is deprecated and will be removed in version 3.2 " "use `Router.poll.register(...)`", @@ -486,7 +486,7 @@ class Router: ) return self.poll.register(*args, **kwargs) - def register_poll_answer(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_poll_answer(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_poll_answer(...)` is deprecated and will be removed in version 3.2 " "use `Router.poll_answer.register(...)`", @@ -495,7 +495,7 @@ class Router: ) return self.poll_answer.register(*args, **kwargs) - def register_my_chat_member(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_my_chat_member(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_my_chat_member(...)` is deprecated and will be removed in version 3.2 " "use `Router.my_chat_member.register(...)`", @@ -504,7 +504,7 @@ class Router: ) return self.my_chat_member.register(*args, **kwargs) - def register_chat_member(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_chat_member(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_chat_member(...)` is deprecated and will be removed in version 3.2 " "use `Router.chat_member.register(...)`", @@ -513,7 +513,7 @@ class Router: ) return self.chat_member.register(*args, **kwargs) - def register_chat_join_request(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_chat_join_request(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_chat_join_request(...)` is deprecated and will be removed in version 3.2 " "use `Router.chat_join_request.register(...)`", @@ -522,7 +522,7 @@ class Router: ) return self.chat_join_request.register(*args, **kwargs) - def register_errors(self, *args: Any, **kwargs: Any) -> HandlerType: + def register_errors(self, *args: Any, **kwargs: Any) -> CallbackType: warnings.warn( "`Router.register_errors(...)` is deprecated and will be removed in version 3.2 " "use `Router.errors.register(...)`", diff --git a/tests/test_dispatcher/test_filters/test_logic.py b/tests/test_dispatcher/test_filters/test_logic.py new file mode 100644 index 00000000..ccbf1cb5 --- /dev/null +++ b/tests/test_dispatcher/test_filters/test_logic.py @@ -0,0 +1,37 @@ +import pytest + +from aiogram.dispatcher.filters import Text, and_f, invert_f, or_f +from aiogram.dispatcher.filters.logic import _AndFilter, _InvertFilter, _OrFilter + + +class TestLogic: + @pytest.mark.parametrize( + "obj,case,result", + [ + [True, and_f(lambda t: t is True, lambda t: t is True), True], + [True, and_f(lambda t: t is True, lambda t: t is False), False], + [True, and_f(lambda t: t is False, lambda t: t is False), False], + [True, and_f(lambda t: {"t": t}, lambda t: t is False), False], + [True, and_f(lambda t: {"t": t}, lambda t: t is True), {"t": True}], + [True, or_f(lambda t: t is True, lambda t: t is True), True], + [True, or_f(lambda t: t is True, lambda t: t is False), True], + [True, or_f(lambda t: t is False, lambda t: t is False), False], + [True, or_f(lambda t: t is False, lambda t: t is True), True], + [True, or_f(lambda t: t is False, lambda t: {"t": t}), {"t": True}], + [True, or_f(lambda t: {"t": t}, lambda t: {"a": 42}), {"t": True}], + [True, invert_f(lambda t: t is False), True], + ], + ) + async def test_logic(self, obj, case, result): + assert await case(obj) == result + + @pytest.mark.parametrize( + "case,type_", + [ + [Text(text="test") | Text(text="test"), _OrFilter], + [Text(text="test") & Text(text="test"), _AndFilter], + [~Text(text="test"), _InvertFilter], + ], + ) + def test_dunder_methods(self, case, type_): + assert isinstance(case, type_) From f2e02e2a7c2e7579827d3b0a4d1539b0a95be525 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Mon, 25 Apr 2022 21:24:58 +0300 Subject: [PATCH 24/35] #896 Restrict including routers with strings (#897) * #896 Restrict including routers with strings * Remove imports util, bump dependencies --- CHANGES/896.misc.rst | 1 + aiogram/dispatcher/router.py | 3 --- aiogram/utils/imports.py | 23 ---------------------- poetry.lock | 14 +++++++------- pyproject.toml | 4 ++-- tests/test_dispatcher/test_router.py | 7 +------ tests/test_utils/test_imports.py | 29 ---------------------------- 7 files changed, 11 insertions(+), 70 deletions(-) create mode 100644 CHANGES/896.misc.rst delete mode 100644 aiogram/utils/imports.py delete mode 100644 tests/test_utils/test_imports.py diff --git a/CHANGES/896.misc.rst b/CHANGES/896.misc.rst new file mode 100644 index 00000000..9dbd6db4 --- /dev/null +++ b/CHANGES/896.misc.rst @@ -0,0 +1 @@ +Restrict including routers with strings diff --git a/aiogram/dispatcher/router.py b/aiogram/dispatcher/router.py index a1a20fba..9f1c0f77 100644 --- a/aiogram/dispatcher/router.py +++ b/aiogram/dispatcher/router.py @@ -4,7 +4,6 @@ import warnings from typing import Any, Dict, Generator, List, Optional, Set, Union from ..types import TelegramObject -from ..utils.imports import import_module from ..utils.warnings import CodeHasNoEffect from .event.bases import REJECTED, UNHANDLED from .event.event import EventObserver @@ -211,8 +210,6 @@ class Router: :param router: :return: """ - if isinstance(router, str): # Resolve import string - router = import_module(router) if not isinstance(router, Router): raise ValueError( f"router should be instance of Router not {type(router).__class__.__name__}" diff --git a/aiogram/utils/imports.py b/aiogram/utils/imports.py deleted file mode 100644 index edc0a6a0..00000000 --- a/aiogram/utils/imports.py +++ /dev/null @@ -1,23 +0,0 @@ -import importlib -from typing import Any - - -def import_module(target: str) -> Any: - if not isinstance(target, str): - raise ValueError(f"Target should be string not {type(target).__class__.__name__}") - - module_name, _, attr_name = target.partition(":") - if not module_name or not attr_name: - raise ValueError(f'Import string "{target}" must be in format ":"') - - try: - module = importlib.import_module(module_name) - except ImportError: - raise ValueError(f'Could not import module "{module_name}".') - - try: - attribute = getattr(module, attr_name) - except AttributeError: - raise ValueError(f'Module "{module_name}" has no attribute "{attr_name}"') - - return attribute diff --git a/poetry.lock b/poetry.lock index 0ac8ef45..e3ebed69 100644 --- a/poetry.lock +++ b/poetry.lock @@ -503,7 +503,7 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} [[package]] name = "magic-filter" -version = "1.0.6" +version = "1.0.7" description = "This package provides magic filter based on dynamic attribute getter" category = "main" optional = false @@ -794,7 +794,7 @@ diagrams = ["railroad-diagrams", "jinja2"] [[package]] name = "pytest" -version = "7.1.1" +version = "7.1.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false @@ -1398,7 +1398,7 @@ redis = ["redis"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "610623143deda7ddf0a604a0447178549a89c8914509d2c1bff4367917e8aaa1" +content-hash = "d3a0b716a9dcb5353de9ef0587a49025da4351eb68a4327c0b3c8a24791251ee" [metadata.files] aiofiles = [ @@ -1759,8 +1759,8 @@ livereload = [ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, ] magic-filter = [ - {file = "magic-filter-1.0.6.tar.gz", hash = "sha256:dd022b088df644b94fd087b4ef8bb3eabb7d96dfc93f59ae9a859ca26881ef61"}, - {file = "magic_filter-1.0.6-py3-none-any.whl", hash = "sha256:499a76a4c023fd6e90e49c4fca6edaf338725c3a923987a6dcdb52fdf4d93ed9"}, + {file = "magic-filter-1.0.7.tar.gz", hash = "sha256:b00fbe7321719290443a9c72a30314a4c29d8e676d42abfe228b41c5dc4e5d2f"}, + {file = "magic_filter-1.0.7-py3-none-any.whl", hash = "sha256:cf591b7f8c4a5037e162fb054ff84e6a7f6db05eb36f312f60b8bfefddc12294"}, ] markdown = [ {file = "Markdown-3.3.6-py3-none-any.whl", hash = "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3"}, @@ -2019,8 +2019,8 @@ pyparsing = [ {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] pytest = [ - {file = "pytest-7.1.1-py3-none-any.whl", hash = "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea"}, - {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"}, + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] pytest-aiohttp = [ {file = "pytest-aiohttp-1.0.4.tar.gz", hash = "sha256:39ff3a0d15484c01d1436cbedad575c6eafbf0f57cdf76fb94994c97b5b8c5a4"}, diff --git a/pyproject.toml b/pyproject.toml index 8e85e24d..14cdfc3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.8" -magic-filter = "^1.0.6" +magic-filter = "^1.0.7" aiohttp = "^3.8.1" pydantic = "^1.9.0" aiofiles = "^0.8.0" @@ -69,7 +69,7 @@ black = "^22.1.0" isort = "^5.10.1" flake8 = "^4.0.1" mypy = "^0.942" -pytest = "^7.0.1" +pytest = "^7.1.2" pytest-html = "^3.1.1" pytest-asyncio = "^0.18.1" pytest-lazy-fixture = "^0.6.3" diff --git a/tests/test_dispatcher/test_router.py b/tests/test_dispatcher/test_router.py index 56821f46..7f98e971 100644 --- a/tests/test_dispatcher/test_router.py +++ b/tests/test_dispatcher/test_router.py @@ -5,7 +5,6 @@ from aiogram.dispatcher.router import Router from aiogram.utils.warnings import CodeHasNoEffect pytestmark = pytest.mark.asyncio -importable_router = Router() class TestRouter: @@ -46,14 +45,10 @@ class TestRouter: with pytest.warns(CodeHasNoEffect): assert router1.include_router(router2) - def test_include_router_by_string(self): - router = Router() - router.include_router("tests.test_dispatcher.test_router:importable_router") - def test_include_router_by_string_bad_type(self): router = Router() with pytest.raises(ValueError, match=r"router should be instance of Router"): - router.include_router("tests.test_dispatcher.test_router:TestRouter") + router.include_router(self) def test_set_parent_router_bad_type(self): router = Router() diff --git a/tests/test_utils/test_imports.py b/tests/test_utils/test_imports.py deleted file mode 100644 index e877201c..00000000 --- a/tests/test_utils/test_imports.py +++ /dev/null @@ -1,29 +0,0 @@ -import pytest - -import aiogram -from aiogram.utils.imports import import_module - - -class TestImports: - def test_bad_type(self): - with pytest.raises(ValueError, match=r"Target should be string not"): - import_module(42) - - @pytest.mark.parametrize("value", ["module", "module:", ":attribute"]) - def test_bad_format(self, value): - with pytest.raises(ValueError, match='must be in format ":"'): - import_module(value) - - @pytest.mark.parametrize("value", ["module", "aiogram.KABOOM", "aiogram.KABOOM.TEST"]) - def test_bad_value(self, value): - with pytest.raises(ValueError, match="Could not import module"): - import_module(f"{value}:attribute") - - def test_has_no_attribute(self): - with pytest.raises(ValueError, match="has no attribute"): - import_module("aiogram:KABOOM") - - def test_imported(self): - value = import_module("aiogram:__version__") - isinstance(value, str) - assert value == aiogram.__version__ From 6ad242399b88f522060d9ce4578d9599e5137762 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Fri, 13 May 2022 23:52:13 +0300 Subject: [PATCH 25/35] #905 Added 3 missing content types (#906) * Added 3 missing content types * Added tests for 3 missing content types * More tests * Added changelog --- CHANGES/906.bugfix.rst | 5 ++++ aiogram/types/message.py | 9 +++++++ tests/test_api/test_types/test_message.py | 32 +++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 CHANGES/906.bugfix.rst diff --git a/CHANGES/906.bugfix.rst b/CHANGES/906.bugfix.rst new file mode 100644 index 00000000..1e727535 --- /dev/null +++ b/CHANGES/906.bugfix.rst @@ -0,0 +1,5 @@ +Added 3 missing content types: + +* proximity_alert_triggered +* supergroup_chat_created +* channel_chat_created diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 1fe8a4f5..74186eef 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -250,8 +250,14 @@ class Message(TelegramObject): return ContentType.DELETE_CHAT_PHOTO if self.group_chat_created: return ContentType.GROUP_CHAT_CREATED + if self.supergroup_chat_created: + return ContentType.SUPERGROUP_CHAT_CREATED + if self.channel_chat_created: + return ContentType.CHANNEL_CHAT_CREATED if self.passport_data: return ContentType.PASSPORT_DATA + if self.proximity_alert_triggered: + return ContentType.PROXIMITY_ALERT_TRIGGERED if self.poll: return ContentType.POLL if self.dice: @@ -1900,7 +1906,10 @@ class ContentType(helper.Helper): NEW_CHAT_PHOTO = helper.Item() # new_chat_photo DELETE_CHAT_PHOTO = helper.Item() # delete_chat_photo GROUP_CHAT_CREATED = helper.Item() # group_chat_created + SUPERGROUP_CHAT_CREATED = helper.Item() # supergroup_chat_created + CHANNEL_CHAT_CREATED = helper.Item() # channel_chat_created PASSPORT_DATA = helper.Item() # passport_data + PROXIMITY_ALERT_TRIGGERED = helper.Item() # proximity_alert_triggered POLL = helper.Item() # poll DICE = helper.Item() # dice MESSAGE_AUTO_DELETE_TIMER_CHANGED = helper.Item() # message_auto_delete_timer_changed diff --git a/tests/test_api/test_types/test_message.py b/tests/test_api/test_types/test_message.py index fcf357c4..cf1deae3 100644 --- a/tests/test_api/test_types/test_message.py +++ b/tests/test_api/test_types/test_message.py @@ -47,6 +47,7 @@ from aiogram.types import ( PhotoSize, Poll, PollOption, + ProximityAlertTriggered, Sticker, SuccessfulPayment, User, @@ -284,6 +285,20 @@ TEST_MESSAGE_GROUP_CHAT_CREATED = Message( chat=Chat(id=42, type="private"), from_user=User(id=42, is_bot=False, first_name="Test"), ) +TEST_MESSAGE_SUPERGROUP_CHAT_CREATED = Message( + message_id=42, + date=datetime.datetime.now(), + supergroup_chat_created=True, + chat=Chat(id=-10042, type="supergroup"), + from_user=User(id=42, is_bot=False, first_name="Test"), +) +TEST_MESSAGE_CHANNEL_CHAT_CREATED = Message( + message_id=42, + date=datetime.datetime.now(), + channel_chat_created=True, + chat=Chat(id=-10042, type="channel"), + from_user=User(id=42, is_bot=False, first_name="Test"), +) TEST_MESSAGE_PASSPORT_DATA = Message( message_id=42, date=datetime.datetime.now(), @@ -294,6 +309,17 @@ TEST_MESSAGE_PASSPORT_DATA = Message( chat=Chat(id=42, type="private"), from_user=User(id=42, is_bot=False, first_name="Test"), ) +TEST_MESSAGE_PROXIMITY_ALERT_TRIGGERED = Message( + message_id=42, + date=datetime.datetime.now(), + chat=Chat(id=42, type="supergroup"), + from_user=User(id=42, is_bot=False, first_name="Test"), + proximity_alert_triggered=ProximityAlertTriggered( + traveler=User(id=1, is_bot=False, first_name="Traveler"), + watcher=User(id=2, is_bot=False, first_name="Watcher"), + distance=42, + ), +) TEST_MESSAGE_POLL = Message( message_id=42, date=datetime.datetime.now(), @@ -404,7 +430,10 @@ class TestMessage: [TEST_MESSAGE_NEW_CHAT_PHOTO, ContentType.NEW_CHAT_PHOTO], [TEST_MESSAGE_DELETE_CHAT_PHOTO, ContentType.DELETE_CHAT_PHOTO], [TEST_MESSAGE_GROUP_CHAT_CREATED, ContentType.GROUP_CHAT_CREATED], + [TEST_MESSAGE_SUPERGROUP_CHAT_CREATED, ContentType.SUPERGROUP_CHAT_CREATED], + [TEST_MESSAGE_CHANNEL_CHAT_CREATED, ContentType.CHANNEL_CHAT_CREATED], [TEST_MESSAGE_PASSPORT_DATA, ContentType.PASSPORT_DATA], + [TEST_MESSAGE_PROXIMITY_ALERT_TRIGGERED, ContentType.PROXIMITY_ALERT_TRIGGERED], [TEST_MESSAGE_POLL, ContentType.POLL], [ TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED, @@ -553,7 +582,10 @@ class TestMessage: [TEST_MESSAGE_NEW_CHAT_PHOTO, None], [TEST_MESSAGE_DELETE_CHAT_PHOTO, None], [TEST_MESSAGE_GROUP_CHAT_CREATED, None], + [TEST_MESSAGE_SUPERGROUP_CHAT_CREATED, None], + [TEST_MESSAGE_CHANNEL_CHAT_CREATED, None], [TEST_MESSAGE_PASSPORT_DATA, None], + [TEST_MESSAGE_PROXIMITY_ALERT_TRIGGERED, None], [TEST_MESSAGE_POLL, SendPoll], [TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED, None], [TEST_MESSAGE_VIDEO_CHAT_STARTED, None], From 824b43c4363a325c84e1df9cb8cfbc73abb5351b Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 14 May 2022 17:27:36 +0300 Subject: [PATCH 26/35] #901 Fixed false-positive coercing of Union types in API methods (#912) * #901 Fixed false-positive coercing of Union types in API methods * Added default value for force_reply --- CHANGES/901.bugfix.rst | 1 + aiogram/methods/base.py | 1 + aiogram/types/force_reply.py | 4 +++- tests/test_api/test_methods/test_send_message.py | 7 ++++++- 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 CHANGES/901.bugfix.rst diff --git a/CHANGES/901.bugfix.rst b/CHANGES/901.bugfix.rst new file mode 100644 index 00000000..e920c152 --- /dev/null +++ b/CHANGES/901.bugfix.rst @@ -0,0 +1 @@ +Fixed false-positive coercing of Union types in API methods diff --git a/aiogram/methods/base.py b/aiogram/methods/base.py index 4d787a7b..2a42bd09 100644 --- a/aiogram/methods/base.py +++ b/aiogram/methods/base.py @@ -46,6 +46,7 @@ class TelegramMethod(abc.ABC, BaseModel, Generic[TelegramType]): allow_population_by_field_name = True arbitrary_types_allowed = True orm_mode = True + smart_union = True # https://github.com/aiogram/aiogram/issues/901 @root_validator(pre=True) def remove_unset(cls, values: Dict[str, Any]) -> Dict[str, Any]: diff --git a/aiogram/types/force_reply.py b/aiogram/types/force_reply.py index b69ad94c..bd8e8900 100644 --- a/aiogram/types/force_reply.py +++ b/aiogram/types/force_reply.py @@ -2,6 +2,8 @@ from __future__ import annotations from typing import Optional +from pydantic import Field + from .base import MutableTelegramObject @@ -19,7 +21,7 @@ class ForceReply(MutableTelegramObject): Source: https://core.telegram.org/bots/api#forcereply """ - force_reply: bool + force_reply: bool = Field(True, const=True) """Shows reply interface to the user, as if they manually selected the bot's message and tapped 'Reply'""" input_field_placeholder: Optional[str] = None """*Optional*. The placeholder to be shown in the input field when the reply is active; 1-64 characters""" diff --git a/tests/test_api/test_methods/test_send_message.py b/tests/test_api/test_methods/test_send_message.py index c2da672e..35324f34 100644 --- a/tests/test_api/test_methods/test_send_message.py +++ b/tests/test_api/test_methods/test_send_message.py @@ -3,7 +3,7 @@ import datetime import pytest from aiogram.methods import Request, SendMessage -from aiogram.types import Chat, Message +from aiogram.types import Chat, ForceReply, Message from tests.mocked_bot import MockedBot pytestmark = pytest.mark.asyncio @@ -43,3 +43,8 @@ class TestSendMessage: request: Request = bot.get_request() assert request.method == "sendMessage" assert response == prepare_result.result + + async def test_force_reply(self): + # https://github.com/aiogram/aiogram/issues/901 + method = SendMessage(text="test", chat_id=42, reply_markup=ForceReply()) + assert isinstance(method.reply_markup, ForceReply) From b3b320c1066317982d993a2894c185f0931e89f0 Mon Sep 17 00:00:00 2001 From: Kostiantyn Kriuchkov <36363097+Latand@users.noreply.github.com> Date: Fri, 10 Jun 2022 11:48:19 +0300 Subject: [PATCH 27/35] Small typo fix (#920) * Small typo fix Changed CommandPatterType to CommandPatternType Supposedly, the implied word is patterN * Added to CHANGELOG --- CHANGES/907.misc.rst | 1 + aiogram/dispatcher/filters/command.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 CHANGES/907.misc.rst diff --git a/CHANGES/907.misc.rst b/CHANGES/907.misc.rst new file mode 100644 index 00000000..5c7608e4 --- /dev/null +++ b/CHANGES/907.misc.rst @@ -0,0 +1 @@ +Changed CommandPatterType to CommandPatternType in `aiogram/dispatcher/filters/command.py` diff --git a/aiogram/dispatcher/filters/command.py b/aiogram/dispatcher/filters/command.py index 3690f884..b03554de 100644 --- a/aiogram/dispatcher/filters/command.py +++ b/aiogram/dispatcher/filters/command.py @@ -12,7 +12,7 @@ from aiogram.dispatcher.filters import BaseFilter from aiogram.types import Message from aiogram.utils.deep_linking import decode_payload -CommandPatterType = Union[str, re.Pattern] +CommandPatternType = Union[str, re.Pattern] class CommandException(Exception): @@ -26,7 +26,7 @@ class Command(BaseFilter): Works only with :class:`aiogram.types.message.Message` events which have the :code:`text`. """ - commands: Union[Sequence[CommandPatterType], CommandPatterType] + commands: Union[Sequence[CommandPatternType], CommandPatternType] """List of commands (string or compiled regexp patterns)""" commands_prefix: str = "/" """Prefix for command. Prefix is always is single char but here you can pass all of allowed prefixes, @@ -44,8 +44,8 @@ class Command(BaseFilter): @validator("commands", always=True) def _validate_commands( - cls, value: Union[Sequence[CommandPatterType], CommandPatterType] - ) -> Sequence[CommandPatterType]: + cls, value: Union[Sequence[CommandPatternType], CommandPatternType] + ) -> Sequence[CommandPatternType]: if isinstance(value, (str, re.Pattern)): value = [value] return value @@ -90,7 +90,7 @@ class Command(BaseFilter): raise CommandException("Mention did not match") def validate_command(self, command: CommandObject) -> CommandObject: - for allowed_command in cast(Sequence[CommandPatterType], self.commands): + for allowed_command in cast(Sequence[CommandPatternType], self.commands): # Command can be presented as regexp pattern or raw string # then need to validate that in different ways if isinstance(allowed_command, Pattern): # Regexp From adfc89f125e752f31af736833de79869e1b8e87f Mon Sep 17 00:00:00 2001 From: Andrew <11490628+andrew000@users.noreply.github.com> Date: Thu, 16 Jun 2022 21:43:32 +0300 Subject: [PATCH 28/35] Fix type hints for redis TTL params (#922) * Allow to use `int` and `datetime.timedelta` * Fix imports * Added changelog * Update CHANGES --- CHANGES/922.feature.rst | 1 + aiogram/dispatcher/fsm/storage/redis.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 CHANGES/922.feature.rst diff --git a/CHANGES/922.feature.rst b/CHANGES/922.feature.rst new file mode 100644 index 00000000..7ce5000c --- /dev/null +++ b/CHANGES/922.feature.rst @@ -0,0 +1 @@ +Fixed type hints for redis TTL params. diff --git a/aiogram/dispatcher/fsm/storage/redis.py b/aiogram/dispatcher/fsm/storage/redis.py index 36ac57c3..8d839c6c 100644 --- a/aiogram/dispatcher/fsm/storage/redis.py +++ b/aiogram/dispatcher/fsm/storage/redis.py @@ -5,6 +5,7 @@ from typing import Any, AsyncGenerator, Dict, Literal, Optional, cast from redis.asyncio.client import Redis from redis.asyncio.connection import ConnectionPool from redis.asyncio.lock import Lock +from redis.typing import ExpiryT from aiogram import Bot from aiogram.dispatcher.fsm.state import State @@ -90,8 +91,8 @@ class RedisStorage(BaseStorage): self, redis: Redis, key_builder: Optional[KeyBuilder] = None, - state_ttl: Optional[int] = None, - data_ttl: Optional[int] = None, + state_ttl: Optional[ExpiryT] = None, + data_ttl: Optional[ExpiryT] = None, lock_kwargs: Optional[Dict[str, Any]] = None, ) -> None: """ From 247ffbef69360235239d4eea486494acd2dd3291 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 25 Jun 2022 18:14:29 +0300 Subject: [PATCH 29/35] 3.x Bot API 6.1 (#937) Added support of Bot API 6.1 --- .apiversion | 2 +- CHANGES/936.misc.rst | 1 + README.rst | 2 +- aiogram/__init__.py | 4 +- aiogram/client/bot.py | 166 +++-- aiogram/methods/__init__.py | 2 + aiogram/methods/add_sticker_to_set.py | 2 +- aiogram/methods/answer_callback_query.py | 4 +- aiogram/methods/answer_web_app_query.py | 2 + aiogram/methods/create_chat_invite_link.py | 2 +- aiogram/methods/create_invoice_link.py | 65 ++ aiogram/methods/create_new_sticker_set.py | 4 +- aiogram/methods/edit_chat_invite_link.py | 2 +- aiogram/methods/edit_message_live_location.py | 2 +- aiogram/methods/get_file.py | 4 +- aiogram/methods/get_game_high_scores.py | 2 +- aiogram/methods/send_animation.py | 4 +- aiogram/methods/send_audio.py | 4 +- aiogram/methods/send_document.py | 4 +- aiogram/methods/send_game.py | 2 +- aiogram/methods/send_invoice.py | 10 +- aiogram/methods/send_photo.py | 2 +- aiogram/methods/send_sticker.py | 2 +- aiogram/methods/send_video.py | 6 +- aiogram/methods/send_video_note.py | 6 +- aiogram/methods/send_voice.py | 2 +- aiogram/methods/set_chat_menu_button.py | 2 +- aiogram/methods/set_sticker_set_thumb.py | 2 +- aiogram/methods/set_webhook.py | 14 +- aiogram/methods/upload_sticker_file.py | 2 +- aiogram/types/animation.py | 2 +- aiogram/types/audio.py | 2 +- aiogram/types/callback_query.py | 2 +- aiogram/types/chat.py | 8 +- aiogram/types/chat_invite_link.py | 2 +- aiogram/types/chosen_inline_result.py | 2 +- aiogram/types/document.py | 2 +- aiogram/types/encrypted_credentials.py | 2 +- aiogram/types/encrypted_passport_element.py | 2 +- aiogram/types/file.py | 4 +- aiogram/types/force_reply.py | 2 +- aiogram/types/inline_keyboard_button.py | 8 +- aiogram/types/inline_query.py | 2 +- .../inline_query_result_cached_mpeg4_gif.py | 2 +- aiogram/types/inline_query_result_document.py | 2 +- .../types/inline_query_result_mpeg4_gif.py | 2 +- aiogram/types/inline_query_result_video.py | 2 +- .../types/input_invoice_message_content.py | 10 +- aiogram/types/input_media_animation.py | 4 +- aiogram/types/input_media_audio.py | 4 +- aiogram/types/input_media_document.py | 4 +- aiogram/types/input_media_photo.py | 2 +- aiogram/types/input_media_video.py | 4 +- aiogram/types/keyboard_button.py | 2 +- aiogram/types/location.py | 2 +- aiogram/types/message.py | 10 +- aiogram/types/message_entity.py | 2 +- aiogram/types/passport_data.py | 2 +- aiogram/types/pre_checkout_query.py | 2 +- aiogram/types/reply_keyboard_markup.py | 2 +- aiogram/types/response_parameters.py | 2 +- aiogram/types/sent_web_app_message.py | 2 +- aiogram/types/shipping_address.py | 2 +- aiogram/types/sticker.py | 3 + aiogram/types/successful_payment.py | 2 +- aiogram/types/update.py | 6 +- aiogram/types/user.py | 4 + aiogram/types/video.py | 4 +- aiogram/types/voice.py | 2 +- aiogram/types/web_app_data.py | 4 +- aiogram/types/web_app_info.py | 2 +- aiogram/types/webhook_info.py | 8 +- aiogram/utils/i18n/core.py | 2 +- docs/api/methods/add_sticker_to_set.rst | 7 - docs/api/methods/answer_callback_query.rst | 7 - docs/api/methods/answer_inline_query.rst | 7 - .../api/methods/answer_pre_checkout_query.rst | 7 - docs/api/methods/answer_shipping_query.rst | 7 - docs/api/methods/answer_web_app_query.rst | 7 - .../api/methods/approve_chat_join_request.rst | 7 - docs/api/methods/ban_chat_member.rst | 7 - docs/api/methods/ban_chat_sender_chat.rst | 7 - docs/api/methods/close.rst | 7 - docs/api/methods/copy_message.rst | 7 - docs/api/methods/create_chat_invite_link.rst | 7 - docs/api/methods/create_invoice_link.rst | 44 ++ docs/api/methods/create_new_sticker_set.rst | 7 - .../api/methods/decline_chat_join_request.rst | 7 - docs/api/methods/delete_chat_photo.rst | 7 - docs/api/methods/delete_chat_sticker_set.rst | 7 - docs/api/methods/delete_message.rst | 7 - docs/api/methods/delete_my_commands.rst | 7 - docs/api/methods/delete_sticker_from_set.rst | 7 - docs/api/methods/delete_webhook.rst | 7 - docs/api/methods/edit_chat_invite_link.rst | 7 - docs/api/methods/edit_message_caption.rst | 7 - .../methods/edit_message_live_location.rst | 7 - docs/api/methods/edit_message_media.rst | 7 - .../api/methods/edit_message_reply_markup.rst | 7 - docs/api/methods/edit_message_text.rst | 7 - docs/api/methods/export_chat_invite_link.rst | 7 - docs/api/methods/forward_message.rst | 7 - docs/api/methods/get_chat.rst | 7 - docs/api/methods/get_chat_administrators.rst | 7 - docs/api/methods/get_chat_member.rst | 7 - docs/api/methods/get_chat_member_count.rst | 7 - docs/api/methods/get_chat_members_count.rst | 7 - docs/api/methods/get_chat_menu_button.rst | 7 - docs/api/methods/get_file.rst | 7 - docs/api/methods/get_game_high_scores.rst | 7 - docs/api/methods/get_me.rst | 7 - docs/api/methods/get_my_commands.rst | 7 - .../get_my_default_administrator_rights.rst | 7 - docs/api/methods/get_sticker_set.rst | 7 - docs/api/methods/get_updates.rst | 7 - docs/api/methods/get_user_profile_photos.rst | 7 - docs/api/methods/get_webhook_info.rst | 7 - docs/api/methods/index.rst | 1 + docs/api/methods/kick_chat_member.rst | 7 - docs/api/methods/leave_chat.rst | 7 - docs/api/methods/log_out.rst | 7 - docs/api/methods/pin_chat_message.rst | 7 - docs/api/methods/promote_chat_member.rst | 7 - docs/api/methods/restrict_chat_member.rst | 7 - docs/api/methods/revoke_chat_invite_link.rst | 7 - docs/api/methods/send_animation.rst | 7 - docs/api/methods/send_audio.rst | 7 - docs/api/methods/send_contact.rst | 7 - docs/api/methods/send_dice.rst | 7 - docs/api/methods/send_document.rst | 7 - docs/api/methods/send_game.rst | 7 - docs/api/methods/send_invoice.rst | 7 - docs/api/methods/send_location.rst | 7 - docs/api/methods/send_media_group.rst | 7 - docs/api/methods/send_message.rst | 7 - docs/api/methods/send_photo.rst | 7 - docs/api/methods/send_poll.rst | 7 - docs/api/methods/send_sticker.rst | 7 - docs/api/methods/send_venue.rst | 7 - docs/api/methods/send_video.rst | 7 - docs/api/methods/send_video_note.rst | 7 - docs/api/methods/send_voice.rst | 7 - .../set_chat_administrator_custom_title.rst | 7 - docs/api/methods/set_chat_description.rst | 7 - docs/api/methods/set_chat_menu_button.rst | 7 - docs/api/methods/set_chat_permissions.rst | 7 - docs/api/methods/set_chat_photo.rst | 7 - poetry.lock | 598 +++++------------- pyproject.toml | 24 +- .../test_methods/test_create_invoice_link.py | 43 ++ 150 files changed, 571 insertions(+), 1112 deletions(-) create mode 100644 CHANGES/936.misc.rst create mode 100644 aiogram/methods/create_invoice_link.py create mode 100644 docs/api/methods/create_invoice_link.rst create mode 100644 tests/test_api/test_methods/test_create_invoice_link.py diff --git a/.apiversion b/.apiversion index e0ea36fe..a435f5a5 100644 --- a/.apiversion +++ b/.apiversion @@ -1 +1 @@ -6.0 +6.1 diff --git a/CHANGES/936.misc.rst b/CHANGES/936.misc.rst new file mode 100644 index 00000000..0d48256c --- /dev/null +++ b/CHANGES/936.misc.rst @@ -0,0 +1 @@ +Added full support of `Bot API 6.1 `_ diff --git a/README.rst b/README.rst index df29b800..d0ad453c 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ aiogram |beta badge| :target: https://pypi.python.org/pypi/aiogram :alt: Supported python versions -.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.0-blue.svg?logo=telegram +.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.1-blue.svg?logo=telegram :target: https://core.telegram.org/bots/api :alt: Telegram Bot API diff --git a/aiogram/__init__.py b/aiogram/__init__.py index 50973dd6..221b53e4 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -39,5 +39,5 @@ __all__ = ( "flags", ) -__version__ = "3.0.0b3" -__api_version__ = "6.0" +__version__ = "3.0.0b4" +__api_version__ = "6.1" diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py index 16453574..e860a796 100644 --- a/aiogram/client/bot.py +++ b/aiogram/client/bot.py @@ -34,6 +34,7 @@ from ..methods import ( Close, CopyMessage, CreateChatInviteLink, + CreateInvoiceLink, CreateNewStickerSet, DeclineChatJoinRequest, DeleteChatPhoto, @@ -413,11 +414,12 @@ class Bot(ContextInstanceMixin["Bot"]): max_connections: Optional[int] = None, allowed_updates: Optional[List[str]] = None, drop_pending_updates: Optional[bool] = None, + secret_token: Optional[str] = None, request_timeout: Optional[int] = None, ) -> bool: """ - Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized :class:`aiogram.types.update.Update`. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns :code:`True` on success. - If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. :code:`https://www.example.com/`. Since nobody else knows your bot's token, you can be pretty sure it's us. + Use this method to specify a URL and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified URL, containing a JSON-serialized :class:`aiogram.types.update.Update`. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns :code:`True` on success. + If you'd like to make sure that the webhook was set by you, you can specify secret data in the parameter *secret_token*. If specified, the request will contain a header 'X-Telegram-Bot-Api-Secret-Token' with the secret token as content. **Notes** @@ -425,17 +427,18 @@ class Bot(ContextInstanceMixin["Bot"]): **2.** To use a self-signed certificate, you need to upload your `public key certificate `_ using *certificate* parameter. Please upload as InputFile, sending a String will not work. - **3.** Ports currently supported *for Webhooks*: **443, 80, 88, 8443**. - **NEW!** If you're having any trouble setting up webhooks, please check out this `amazing guide to Webhooks `_. + **3.** Ports currently supported *for webhooks*: **443, 80, 88, 8443**. + If you're having any trouble setting up webhooks, please check out this `amazing guide to webhooks `_. Source: https://core.telegram.org/bots/api#setwebhook - :param url: HTTPS url to send updates to. Use an empty string to remove webhook integration + :param url: HTTPS URL to send updates to. Use an empty string to remove webhook integration :param certificate: Upload your public key certificate so that the root certificate in use can be checked. See our `self-signed guide `_ for details. :param ip_address: The fixed IP address which will be used to send webhook requests instead of the IP address resolved through DNS - :param max_connections: Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to *40*. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. + :param max_connections: The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to *40*. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput. :param allowed_updates: A JSON-serialized list of the update types you want your bot to receive. For example, specify ['message', 'edited_channel_post', 'callback_query'] to only receive updates of these types. See :class:`aiogram.types.update.Update` for a complete list of available update types. Specify an empty list to receive all update types except *chat_member* (default). If not specified, the previous setting will be used. :param drop_pending_updates: Pass :code:`True` to drop all pending updates + :param secret_token: A secret token to be sent in a header 'X-Telegram-Bot-Api-Secret-Token' in every webhook request, 1-256 characters. Only characters :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed. The header is useful to ensure that the request comes from a webhook set by you. :param request_timeout: Request timeout :return: Returns True on success. """ @@ -446,6 +449,7 @@ class Bot(ContextInstanceMixin["Bot"]): max_connections=max_connections, allowed_updates=allowed_updates, drop_pending_updates=drop_pending_updates, + secret_token=secret_token, ) return await self(call, request_timeout=request_timeout) @@ -687,7 +691,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#sendphoto :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) - :param photo: Photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20. :ref:`More info on Sending Files ยป ` + :param photo: Photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20. :ref:`More information on Sending Files ยป ` :param caption: Photo caption (may also be used when resending photos by *file_id*), 0-1024 characters after entities parsing :param parse_mode: Mode for parsing entities in the photo caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* @@ -740,14 +744,14 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#sendaudio :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) - :param audio: Audio file to send. Pass a file_id as String to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` + :param audio: Audio file to send. Pass a file_id as String to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป ` :param caption: Audio caption, 0-1024 characters after entities parsing :param parse_mode: Mode for parsing entities in the audio caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* :param duration: Duration of the audio in seconds :param performer: Performer :param title: Track name - :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป ` + :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป ` :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message @@ -798,8 +802,8 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#senddocument :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) - :param document: File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` - :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป ` + :param document: File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป ` + :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป ` :param caption: Document caption (may also be used when resending documents by *file_id*), 0-1024 characters after entities parsing :param parse_mode: Mode for parsing entities in the document caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* @@ -850,16 +854,16 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> Message: """ - Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as :class:`aiogram.types.document.Document`). On success, the sent :class:`aiogram.types.message.Message` is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. + Use this method to send video files, Telegram clients support MPEG4 videos (other formats may be sent as :class:`aiogram.types.document.Document`). On success, the sent :class:`aiogram.types.message.Message` is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. Source: https://core.telegram.org/bots/api#sendvideo :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) - :param video: Video to send. Pass a file_id as String to send a video that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet, or upload a new video using multipart/form-data. :ref:`More info on Sending Files ยป ` + :param video: Video to send. Pass a file_id as String to send a video that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet, or upload a new video using multipart/form-data. :ref:`More information on Sending Files ยป ` :param duration: Duration of sent video in seconds :param width: Video width :param height: Video height - :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป ` + :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป ` :param caption: Video caption (may also be used when resending videos by *file_id*), 0-1024 characters after entities parsing :param parse_mode: Mode for parsing entities in the video caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* @@ -917,11 +921,11 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#sendanimation :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) - :param animation: Animation to send. Pass a file_id as String to send an animation that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation from the Internet, or upload a new animation using multipart/form-data. :ref:`More info on Sending Files ยป ` + :param animation: Animation to send. Pass a file_id as String to send an animation that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation from the Internet, or upload a new animation using multipart/form-data. :ref:`More information on Sending Files ยป ` :param duration: Duration of sent animation in seconds :param width: Animation width :param height: Animation height - :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป ` + :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป ` :param caption: Animation caption (may also be used when resending animation by *file_id*), 0-1024 characters after entities parsing :param parse_mode: Mode for parsing entities in the animation caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* @@ -974,7 +978,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#sendvoice :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) - :param voice: Audio file to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` + :param voice: Audio file to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป ` :param caption: Voice message caption, 0-1024 characters after entities parsing :param parse_mode: Mode for parsing entities in the voice message caption. See `formatting options `_ for more details. :param caption_entities: A JSON-serialized list of special entities that appear in the caption, which can be specified instead of *parse_mode* @@ -1019,15 +1023,15 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> Message: """ - As of `v.4.0 `_, Telegram clients support rounded square mp4 videos of up to 1 minute long. Use this method to send video messages. On success, the sent :class:`aiogram.types.message.Message` is returned. + As of `v.4.0 `_, Telegram clients support rounded square MPEG4 videos of up to 1 minute long. Use this method to send video messages. On success, the sent :class:`aiogram.types.message.Message` is returned. Source: https://core.telegram.org/bots/api#sendvideonote :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) - :param video_note: Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. :ref:`More info on Sending Files ยป `. Sending video notes by a URL is currently unsupported + :param video_note: Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. :ref:`More information on Sending Files ยป `. Sending video notes by a URL is currently unsupported :param duration: Duration of sent video in seconds :param length: Video width and height, i.e. diameter of the video message - :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป ` + :param thumb: Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป ` :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message @@ -1163,7 +1167,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param inline_message_id: Required if *chat_id* and *message_id* are not specified. Identifier of the inline message :param horizontal_accuracy: The radius of uncertainty for the location, measured in meters; 0-1500 :param heading: Direction in which the user is moving, in degrees. Must be between 1 and 360 if specified. - :param proximity_alert_radius: Maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified. + :param proximity_alert_radius: The maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified. :param reply_markup: A JSON-serialized object for a new `inline keyboard `_. :param request_timeout: Request timeout :return: On success, if the edited message is not an inline message, the edited Message is @@ -1487,12 +1491,12 @@ class Bot(ContextInstanceMixin["Bot"]): request_timeout: Optional[int] = None, ) -> File: """ - Use this method to get basic info about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a :class:`aiogram.types.file.File` object is returned. The file can then be downloaded via the link :code:`https://api.telegram.org/file/bot/`, where :code:`` is taken from the response. It is guaranteed that the link will be valid for at least 1 hour. When the link expires, a new one can be requested by calling :class:`aiogram.methods.get_file.GetFile` again. + Use this method to get basic information about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a :class:`aiogram.types.file.File` object is returned. The file can then be downloaded via the link :code:`https://api.telegram.org/file/bot/`, where :code:`` is taken from the response. It is guaranteed that the link will be valid for at least 1 hour. When the link expires, a new one can be requested by calling :class:`aiogram.methods.get_file.GetFile` again. **Note:** This function may not preserve the original file name and MIME type. You should save the file's MIME type and name (if available) when the File object is received. Source: https://core.telegram.org/bots/api#getfile - :param file_id: File identifier to get info about + :param file_id: File identifier to get information about :param request_timeout: Request timeout :return: On success, a File object is returned. """ @@ -1801,7 +1805,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) :param name: Invite link name; 0-32 characters :param expire_date: Point in time (Unix timestamp) when the link will expire - :param member_limit: Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999 + :param member_limit: The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999 :param creates_join_request: :code:`True`, if users joining the chat via the link need to be approved by chat administrators. If :code:`True`, *member_limit* can't be specified :param request_timeout: Request timeout :return: Returns the new invite link as ChatInviteLink object. @@ -1834,7 +1838,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param invite_link: The invite link to edit :param name: Invite link name; 0-32 characters :param expire_date: Point in time (Unix timestamp) when the link will expire - :param member_limit: Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999 + :param member_limit: The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999 :param creates_join_request: :code:`True`, if users joining the chat via the link need to be approved by chat administrators. If :code:`True`, *member_limit* can't be specified :param request_timeout: Request timeout :return: Returns the edited invite link as a ChatInviteLink object. @@ -2261,14 +2265,14 @@ class Bot(ContextInstanceMixin["Bot"]): """ Use this method to send answers to callback queries sent from `inline keyboards `_. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. On success, :code:`True` is returned. - Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via `@Botfather `_ and accept the terms. Otherwise, you may use links like :code:`t.me/your_bot?start=XXXX` that open your bot with a parameter. + Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via `@BotFather `_ and accept the terms. Otherwise, you may use links like :code:`t.me/your_bot?start=XXXX` that open your bot with a parameter. Source: https://core.telegram.org/bots/api#answercallbackquery :param callback_query_id: Unique identifier for the query to be answered :param text: Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters :param show_alert: If :code:`True`, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to *false*. - :param url: URL that will be opened by the user's client. If you have created a :class:`aiogram.types.game.Game` and accepted the conditions via `@Botfather `_, specify the URL that opens your game โ€” note that this will only work if the query comes from a `https://core.telegram.org/bots/api#inlinekeyboardbutton `_ *callback_game* button. + :param url: URL that will be opened by the user's client. If you have created a :class:`aiogram.types.game.Game` and accepted the conditions via `@BotFather `_, specify the URL that opens your game - note that this will only work if the query comes from a `https://core.telegram.org/bots/api#inlinekeyboardbutton `_ *callback_game* button. :param cache_time: The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. :param request_timeout: Request timeout :return: On success, True is returned. @@ -2364,7 +2368,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#setchatmenubutton :param chat_id: Unique identifier for the target private chat. If not specified, default bot's menu button will be changed - :param menu_button: A JSON-serialized object for the new bot's menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault` + :param menu_button: A JSON-serialized object for the bot's new menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault` :param request_timeout: Request timeout :return: Returns True on success. """ @@ -2666,7 +2670,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#sendsticker :param chat_id: Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`) - :param sticker: Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` + :param sticker: Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป ` :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message @@ -2717,7 +2721,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#uploadstickerfile :param user_id: User identifier of sticker file owner - :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. :ref:`More info on Sending Files ยป ` + :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. :ref:`More information on Sending Files ยป ` :param request_timeout: Request timeout :return: Returns the uploaded File on success. """ @@ -2746,10 +2750,10 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#createnewstickerset :param user_id: User identifier of created sticker set owner - :param name: Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_"`. :code:`` is case insensitive. 1-64 characters. + :param name: Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only English letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_"`. :code:`` is case insensitive. 1-64 characters. :param title: Sticker set title, 1-64 characters :param emojis: One or more emoji corresponding to the sticker - :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` + :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป ` :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements :param webm_sticker: **WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements :param contains_masks: Pass :code:`True`, if a set of mask stickers should be created @@ -2789,7 +2793,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param user_id: User identifier of sticker set owner :param name: Sticker set name :param emojis: One or more emoji corresponding to the sticker - :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป ` + :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป ` :param tgs_sticker: **TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements :param webm_sticker: **WEBM** video with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for technical requirements :param mask_position: A JSON-serialized object for position where the mask should be placed on faces @@ -2862,7 +2866,7 @@ class Bot(ContextInstanceMixin["Bot"]): :param name: Sticker set name :param user_id: User identifier of the sticker set owner - :param thumb: A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `. Animated sticker set thumbnails can't be uploaded via HTTP URL. + :param thumb: A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป `. Animated sticker set thumbnails can't be uploaded via HTTP URL. :param request_timeout: Request timeout :return: Returns True on success. """ @@ -2984,23 +2988,23 @@ class Bot(ContextInstanceMixin["Bot"]): :param title: Product name, 1-32 characters :param description: Product description, 1-255 characters :param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes. - :param provider_token: Payments provider token, obtained via `Botfather `_ + :param provider_token: Payment provider token, obtained via `@BotFather `_ :param currency: Three-letter ISO 4217 currency code, see `more on currencies `_ :param prices: Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) :param max_tip_amount: The maximum accepted amount for tips in the *smallest units* of the currency (integer, **not** float/double). For example, for a maximum tip of :code:`US$ 1.45` pass :code:`max_tip_amount = 145`. See the *exp* parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0 :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the *smallest units* of the currency (integer, **not** float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed *max_tip_amount*. :param start_parameter: Unique deep-linking parameter. If left empty, **forwarded copies** of the sent message will have a *Pay* button, allowing multiple users to pay directly from the forwarded message, using the same invoice. If non-empty, forwarded copies of the sent message will have a *URL* button with a deep link to the bot (instead of a *Pay* button), with the value used as the start parameter - :param provider_data: A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider. + :param provider_data: JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider. :param photo_url: URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for. - :param photo_size: Photo size + :param photo_size: Photo size in bytes :param photo_width: Photo width :param photo_height: Photo height :param need_name: Pass :code:`True`, if you require the user's full name to complete the order :param need_phone_number: Pass :code:`True`, if you require the user's phone number to complete the order :param need_email: Pass :code:`True`, if you require the user's email address to complete the order :param need_shipping_address: Pass :code:`True`, if you require the user's shipping address to complete the order - :param send_phone_number_to_provider: Pass :code:`True`, if user's phone number should be sent to provider - :param send_email_to_provider: Pass :code:`True`, if user's email address should be sent to provider + :param send_phone_number_to_provider: Pass :code:`True`, if the user's phone number should be sent to provider + :param send_email_to_provider: Pass :code:`True`, if the user's email address should be sent to provider :param is_flexible: Pass :code:`True`, if the final price depends on the shipping method :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. :param protect_content: Protects the contents of the sent message from forwarding and saving @@ -3041,6 +3045,82 @@ class Bot(ContextInstanceMixin["Bot"]): ) return await self(call, request_timeout=request_timeout) + async def create_invoice_link( + self, + title: str, + description: str, + payload: str, + provider_token: str, + currency: str, + prices: List[LabeledPrice], + max_tip_amount: Optional[int] = None, + suggested_tip_amounts: Optional[List[int]] = None, + provider_data: Optional[str] = None, + photo_url: Optional[str] = None, + photo_size: Optional[int] = None, + photo_width: Optional[int] = None, + photo_height: Optional[int] = None, + need_name: Optional[bool] = None, + need_phone_number: Optional[bool] = None, + need_email: Optional[bool] = None, + need_shipping_address: Optional[bool] = None, + send_phone_number_to_provider: Optional[bool] = None, + send_email_to_provider: Optional[bool] = None, + is_flexible: Optional[bool] = None, + request_timeout: Optional[int] = None, + ) -> str: + """ + Use this method to create a link for an invoice. Returns the created invoice link as *String* on success. + + Source: https://core.telegram.org/bots/api#createinvoicelink + + :param title: Product name, 1-32 characters + :param description: Product description, 1-255 characters + :param payload: Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes. + :param provider_token: Payment provider token, obtained via `BotFather `_ + :param currency: Three-letter ISO 4217 currency code, see `more on currencies `_ + :param prices: Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) + :param max_tip_amount: The maximum accepted amount for tips in the *smallest units* of the currency (integer, **not** float/double). For example, for a maximum tip of :code:`US$ 1.45` pass :code:`max_tip_amount = 145`. See the *exp* parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0 + :param suggested_tip_amounts: A JSON-serialized array of suggested amounts of tips in the *smallest units* of the currency (integer, **not** float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed *max_tip_amount*. + :param provider_data: JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider. + :param photo_url: URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. + :param photo_size: Photo size in bytes + :param photo_width: Photo width + :param photo_height: Photo height + :param need_name: Pass :code:`True`, if you require the user's full name to complete the order + :param need_phone_number: Pass :code:`True`, if you require the user's phone number to complete the order + :param need_email: Pass :code:`True`, if you require the user's email address to complete the order + :param need_shipping_address: Pass :code:`True`, if you require the user's shipping address to complete the order + :param send_phone_number_to_provider: Pass :code:`True`, if the user's phone number should be sent to the provider + :param send_email_to_provider: Pass :code:`True`, if the user's email address should be sent to the provider + :param is_flexible: Pass :code:`True`, if the final price depends on the shipping method + :param request_timeout: Request timeout + :return: Returns the created invoice link as String on success. + """ + call = CreateInvoiceLink( + title=title, + description=description, + payload=payload, + provider_token=provider_token, + currency=currency, + prices=prices, + max_tip_amount=max_tip_amount, + suggested_tip_amounts=suggested_tip_amounts, + provider_data=provider_data, + photo_url=photo_url, + photo_size=photo_size, + photo_width=photo_width, + photo_height=photo_height, + need_name=need_name, + need_phone_number=need_phone_number, + need_email=need_email, + need_shipping_address=need_shipping_address, + send_phone_number_to_provider=send_phone_number_to_provider, + send_email_to_provider=send_email_to_provider, + is_flexible=is_flexible, + ) + return await self(call, request_timeout=request_timeout) + async def answer_shipping_query( self, shipping_query_id: str, @@ -3146,7 +3226,7 @@ class Bot(ContextInstanceMixin["Bot"]): Source: https://core.telegram.org/bots/api#sendgame :param chat_id: Unique identifier for the target chat - :param game_short_name: Short name of the game, serves as the unique identifier for the game. Set up your games via `Botfather `_. + :param game_short_name: Short name of the game, serves as the unique identifier for the game. Set up your games via `@BotFather `_. :param disable_notification: Sends the message `silently `_. Users will receive a notification with no sound. :param protect_content: Protects the contents of the sent message from forwarding and saving :param reply_to_message_id: If the message is a reply, ID of the original message @@ -3216,7 +3296,7 @@ class Bot(ContextInstanceMixin["Bot"]): """ Use this method to get data for high score tables. Will return the score of the specified user and several of their neighbors in a game. On success, returns an *Array* of :class:`aiogram.types.game_high_score.GameHighScore` objects. - This method will currently return scores for the target user, plus two of their closest neighbors on each side. Will also return the top three users if the user and his neighbors are not among them. Please note that this behavior is subject to change. + This method will currently return scores for the target user, plus two of their closest neighbors on each side. Will also return the top three users if the user and their neighbors are not among them. Please note that this behavior is subject to change. Source: https://core.telegram.org/bots/api#getgamehighscores @@ -3228,8 +3308,8 @@ class Bot(ContextInstanceMixin["Bot"]): :return: Will return the score of the specified user and several of their neighbors in a game. On success, returns an Array of GameHighScore objects. This method will currently return scores for the target user, plus two of their closest neighbors - on each side. Will also return the top three users if the user and his neighbors - are not among them. + on each side. Will also return the top three users if the user and their + neighbors are not among them. """ call = GetGameHighScores( user_id=user_id, diff --git a/aiogram/methods/__init__.py b/aiogram/methods/__init__.py index f7b75066..72771afc 100644 --- a/aiogram/methods/__init__.py +++ b/aiogram/methods/__init__.py @@ -11,6 +11,7 @@ from .base import Request, Response, TelegramMethod from .close import Close from .copy_message import CopyMessage from .create_chat_invite_link import CreateChatInviteLink +from .create_invoice_link import CreateInvoiceLink from .create_new_sticker_set import CreateNewStickerSet from .decline_chat_join_request import DeclineChatJoinRequest from .delete_chat_photo import DeleteChatPhoto @@ -176,6 +177,7 @@ __all__ = ( "AnswerInlineQuery", "AnswerWebAppQuery", "SendInvoice", + "CreateInvoiceLink", "AnswerShippingQuery", "AnswerPreCheckoutQuery", "SetPassportDataErrors", diff --git a/aiogram/methods/add_sticker_to_set.py b/aiogram/methods/add_sticker_to_set.py index 7b676674..6fe7b7a1 100644 --- a/aiogram/methods/add_sticker_to_set.py +++ b/aiogram/methods/add_sticker_to_set.py @@ -25,7 +25,7 @@ class AddStickerToSet(TelegramMethod[bool]): emojis: str """One or more emoji corresponding to the sticker""" png_sticker: Optional[Union[InputFile, str]] = None - """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" + """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป `""" tgs_sticker: Optional[InputFile] = None """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements""" webm_sticker: Optional[InputFile] = None diff --git a/aiogram/methods/answer_callback_query.py b/aiogram/methods/answer_callback_query.py index 04f8282c..3059fc1c 100644 --- a/aiogram/methods/answer_callback_query.py +++ b/aiogram/methods/answer_callback_query.py @@ -12,7 +12,7 @@ class AnswerCallbackQuery(TelegramMethod[bool]): """ Use this method to send answers to callback queries sent from `inline keyboards `_. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. On success, :code:`True` is returned. - Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via `@Botfather `_ and accept the terms. Otherwise, you may use links like :code:`t.me/your_bot?start=XXXX` that open your bot with a parameter. + Alternatively, the user can be redirected to the specified Game URL. For this option to work, you must first create a game for your bot via `@BotFather `_ and accept the terms. Otherwise, you may use links like :code:`t.me/your_bot?start=XXXX` that open your bot with a parameter. Source: https://core.telegram.org/bots/api#answercallbackquery """ @@ -26,7 +26,7 @@ class AnswerCallbackQuery(TelegramMethod[bool]): show_alert: Optional[bool] = None """If :code:`True`, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to *false*.""" url: Optional[str] = None - """URL that will be opened by the user's client. If you have created a :class:`aiogram.types.game.Game` and accepted the conditions via `@Botfather `_, specify the URL that opens your game โ€” note that this will only work if the query comes from a `https://core.telegram.org/bots/api#inlinekeyboardbutton `_ *callback_game* button.""" + """URL that will be opened by the user's client. If you have created a :class:`aiogram.types.game.Game` and accepted the conditions via `@BotFather `_, specify the URL that opens your game - note that this will only work if the query comes from a `https://core.telegram.org/bots/api#inlinekeyboardbutton `_ *callback_game* button.""" cache_time: Optional[int] = None """The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0.""" diff --git a/aiogram/methods/answer_web_app_query.py b/aiogram/methods/answer_web_app_query.py index 3211ed38..c9315ee8 100644 --- a/aiogram/methods/answer_web_app_query.py +++ b/aiogram/methods/answer_web_app_query.py @@ -25,7 +25,9 @@ class AnswerWebAppQuery(TelegramMethod[SentWebAppMessage]): def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() + prepare_parse_mode( bot, data["result"], parse_mode_property="parse_mode", entities_property="entities" ) + return Request(method="answerWebAppQuery", data=data) diff --git a/aiogram/methods/create_chat_invite_link.py b/aiogram/methods/create_chat_invite_link.py index 965844d9..a9cf9144 100644 --- a/aiogram/methods/create_chat_invite_link.py +++ b/aiogram/methods/create_chat_invite_link.py @@ -26,7 +26,7 @@ class CreateChatInviteLink(TelegramMethod[ChatInviteLink]): expire_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None """Point in time (Unix timestamp) when the link will expire""" member_limit: Optional[int] = None - """Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999""" + """The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999""" creates_join_request: Optional[bool] = None """:code:`True`, if users joining the chat via the link need to be approved by chat administrators. If :code:`True`, *member_limit* can't be specified""" diff --git a/aiogram/methods/create_invoice_link.py b/aiogram/methods/create_invoice_link.py new file mode 100644 index 00000000..16208f27 --- /dev/null +++ b/aiogram/methods/create_invoice_link.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from ..types import LabeledPrice +from .base import Request, TelegramMethod + +if TYPE_CHECKING: + from ..client.bot import Bot + + +class CreateInvoiceLink(TelegramMethod[str]): + """ + Use this method to create a link for an invoice. Returns the created invoice link as *String* on success. + + Source: https://core.telegram.org/bots/api#createinvoicelink + """ + + __returning__ = str + + title: str + """Product name, 1-32 characters""" + description: str + """Product description, 1-255 characters""" + payload: str + """Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.""" + provider_token: str + """Payment provider token, obtained via `BotFather `_""" + currency: str + """Three-letter ISO 4217 currency code, see `more on currencies `_""" + prices: List[LabeledPrice] + """Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)""" + max_tip_amount: Optional[int] = None + """The maximum accepted amount for tips in the *smallest units* of the currency (integer, **not** float/double). For example, for a maximum tip of :code:`US$ 1.45` pass :code:`max_tip_amount = 145`. See the *exp* parameter in `currencies.json `_, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). Defaults to 0""" + suggested_tip_amounts: Optional[List[int]] = None + """A JSON-serialized array of suggested amounts of tips in the *smallest units* of the currency (integer, **not** float/double). At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed *max_tip_amount*.""" + provider_data: Optional[str] = None + """JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.""" + photo_url: Optional[str] = None + """URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service.""" + photo_size: Optional[int] = None + """Photo size in bytes""" + photo_width: Optional[int] = None + """Photo width""" + photo_height: Optional[int] = None + """Photo height""" + need_name: Optional[bool] = None + """Pass :code:`True`, if you require the user's full name to complete the order""" + need_phone_number: Optional[bool] = None + """Pass :code:`True`, if you require the user's phone number to complete the order""" + need_email: Optional[bool] = None + """Pass :code:`True`, if you require the user's email address to complete the order""" + need_shipping_address: Optional[bool] = None + """Pass :code:`True`, if you require the user's shipping address to complete the order""" + send_phone_number_to_provider: Optional[bool] = None + """Pass :code:`True`, if the user's phone number should be sent to the provider""" + send_email_to_provider: Optional[bool] = None + """Pass :code:`True`, if the user's email address should be sent to the provider""" + is_flexible: Optional[bool] = None + """Pass :code:`True`, if the final price depends on the shipping method""" + + def build_request(self, bot: Bot) -> Request: + data: Dict[str, Any] = self.dict() + + return Request(method="createInvoiceLink", data=data) diff --git a/aiogram/methods/create_new_sticker_set.py b/aiogram/methods/create_new_sticker_set.py index 5faab9ef..cb77cb97 100644 --- a/aiogram/methods/create_new_sticker_set.py +++ b/aiogram/methods/create_new_sticker_set.py @@ -21,13 +21,13 @@ class CreateNewStickerSet(TelegramMethod[bool]): user_id: int """User identifier of created sticker set owner""" name: str - """Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_"`. :code:`` is case insensitive. 1-64 characters.""" + """Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only English letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_"`. :code:`` is case insensitive. 1-64 characters.""" title: str """Sticker set title, 1-64 characters""" emojis: str """One or more emoji corresponding to the sticker""" png_sticker: Optional[Union[InputFile, str]] = None - """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" + """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป `""" tgs_sticker: Optional[InputFile] = None """**TGS** animation with the sticker, uploaded using multipart/form-data. See `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for technical requirements""" webm_sticker: Optional[InputFile] = None diff --git a/aiogram/methods/edit_chat_invite_link.py b/aiogram/methods/edit_chat_invite_link.py index 67354688..3bd3a4c3 100644 --- a/aiogram/methods/edit_chat_invite_link.py +++ b/aiogram/methods/edit_chat_invite_link.py @@ -28,7 +28,7 @@ class EditChatInviteLink(TelegramMethod[ChatInviteLink]): expire_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None """Point in time (Unix timestamp) when the link will expire""" member_limit: Optional[int] = None - """Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999""" + """The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999""" creates_join_request: Optional[bool] = None """:code:`True`, if users joining the chat via the link need to be approved by chat administrators. If :code:`True`, *member_limit* can't be specified""" diff --git a/aiogram/methods/edit_message_live_location.py b/aiogram/methods/edit_message_live_location.py index e8a34f19..52e6acfa 100644 --- a/aiogram/methods/edit_message_live_location.py +++ b/aiogram/methods/edit_message_live_location.py @@ -33,7 +33,7 @@ class EditMessageLiveLocation(TelegramMethod[Union[Message, bool]]): heading: Optional[int] = None """Direction in which the user is moving, in degrees. Must be between 1 and 360 if specified.""" proximity_alert_radius: Optional[int] = None - """Maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified.""" + """The maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified.""" reply_markup: Optional[InlineKeyboardMarkup] = None """A JSON-serialized object for a new `inline keyboard `_.""" diff --git a/aiogram/methods/get_file.py b/aiogram/methods/get_file.py index 163d484d..9b006296 100644 --- a/aiogram/methods/get_file.py +++ b/aiogram/methods/get_file.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class GetFile(TelegramMethod[File]): """ - Use this method to get basic info about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a :class:`aiogram.types.file.File` object is returned. The file can then be downloaded via the link :code:`https://api.telegram.org/file/bot/`, where :code:`` is taken from the response. It is guaranteed that the link will be valid for at least 1 hour. When the link expires, a new one can be requested by calling :class:`aiogram.methods.get_file.GetFile` again. + Use this method to get basic information about a file and prepare it for downloading. For the moment, bots can download files of up to 20MB in size. On success, a :class:`aiogram.types.file.File` object is returned. The file can then be downloaded via the link :code:`https://api.telegram.org/file/bot/`, where :code:`` is taken from the response. It is guaranteed that the link will be valid for at least 1 hour. When the link expires, a new one can be requested by calling :class:`aiogram.methods.get_file.GetFile` again. **Note:** This function may not preserve the original file name and MIME type. You should save the file's MIME type and name (if available) when the File object is received. Source: https://core.telegram.org/bots/api#getfile @@ -20,7 +20,7 @@ class GetFile(TelegramMethod[File]): __returning__ = File file_id: str - """File identifier to get info about""" + """File identifier to get information about""" def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() diff --git a/aiogram/methods/get_game_high_scores.py b/aiogram/methods/get_game_high_scores.py index 18a4eb2d..67df9338 100644 --- a/aiogram/methods/get_game_high_scores.py +++ b/aiogram/methods/get_game_high_scores.py @@ -13,7 +13,7 @@ class GetGameHighScores(TelegramMethod[List[GameHighScore]]): """ Use this method to get data for high score tables. Will return the score of the specified user and several of their neighbors in a game. On success, returns an *Array* of :class:`aiogram.types.game_high_score.GameHighScore` objects. - This method will currently return scores for the target user, plus two of their closest neighbors on each side. Will also return the top three users if the user and his neighbors are not among them. Please note that this behavior is subject to change. + This method will currently return scores for the target user, plus two of their closest neighbors on each side. Will also return the top three users if the user and their neighbors are not among them. Please note that this behavior is subject to change. Source: https://core.telegram.org/bots/api#getgamehighscores """ diff --git a/aiogram/methods/send_animation.py b/aiogram/methods/send_animation.py index 97b483a4..78801a1e 100644 --- a/aiogram/methods/send_animation.py +++ b/aiogram/methods/send_animation.py @@ -30,7 +30,7 @@ class SendAnimation(TelegramMethod[Message]): chat_id: Union[int, str] """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" animation: Union[InputFile, str] - """Animation to send. Pass a file_id as String to send an animation that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation from the Internet, or upload a new animation using multipart/form-data. :ref:`More info on Sending Files ยป `""" + """Animation to send. Pass a file_id as String to send an animation that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an animation from the Internet, or upload a new animation using multipart/form-data. :ref:`More information on Sending Files ยป `""" duration: Optional[int] = None """Duration of sent animation in seconds""" width: Optional[int] = None @@ -38,7 +38,7 @@ class SendAnimation(TelegramMethod[Message]): height: Optional[int] = None """Animation height""" thumb: Optional[Union[InputFile, str]] = None - """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """Animation caption (may also be used when resending animation by *file_id*), 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/methods/send_audio.py b/aiogram/methods/send_audio.py index 16b24723..4e31ed92 100644 --- a/aiogram/methods/send_audio.py +++ b/aiogram/methods/send_audio.py @@ -31,7 +31,7 @@ class SendAudio(TelegramMethod[Message]): chat_id: Union[int, str] """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" audio: Union[InputFile, str] - """Audio file to send. Pass a file_id as String to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" + """Audio file to send. Pass a file_id as String to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """Audio caption, 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET @@ -45,7 +45,7 @@ class SendAudio(TelegramMethod[Message]): title: Optional[str] = None """Track name""" thumb: Optional[Union[InputFile, str]] = None - """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" protect_content: Optional[bool] = None diff --git a/aiogram/methods/send_document.py b/aiogram/methods/send_document.py index 17f38dfb..a2de750d 100644 --- a/aiogram/methods/send_document.py +++ b/aiogram/methods/send_document.py @@ -30,9 +30,9 @@ class SendDocument(TelegramMethod[Message]): chat_id: Union[int, str] """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" document: Union[InputFile, str] - """File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" + """File to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป `""" thumb: Optional[Union[InputFile, str]] = None - """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """Document caption (may also be used when resending documents by *file_id*), 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/methods/send_game.py b/aiogram/methods/send_game.py index ba430845..416cd919 100644 --- a/aiogram/methods/send_game.py +++ b/aiogram/methods/send_game.py @@ -21,7 +21,7 @@ class SendGame(TelegramMethod[Message]): chat_id: int """Unique identifier for the target chat""" game_short_name: str - """Short name of the game, serves as the unique identifier for the game. Set up your games via `Botfather `_.""" + """Short name of the game, serves as the unique identifier for the game. Set up your games via `@BotFather `_.""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" protect_content: Optional[bool] = None diff --git a/aiogram/methods/send_invoice.py b/aiogram/methods/send_invoice.py index 95c61ef6..0e882699 100644 --- a/aiogram/methods/send_invoice.py +++ b/aiogram/methods/send_invoice.py @@ -27,7 +27,7 @@ class SendInvoice(TelegramMethod[Message]): payload: str """Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.""" provider_token: str - """Payments provider token, obtained via `Botfather `_""" + """Payment provider token, obtained via `@BotFather `_""" currency: str """Three-letter ISO 4217 currency code, see `more on currencies `_""" prices: List[LabeledPrice] @@ -39,11 +39,11 @@ class SendInvoice(TelegramMethod[Message]): start_parameter: Optional[str] = None """Unique deep-linking parameter. If left empty, **forwarded copies** of the sent message will have a *Pay* button, allowing multiple users to pay directly from the forwarded message, using the same invoice. If non-empty, forwarded copies of the sent message will have a *URL* button with a deep link to the bot (instead of a *Pay* button), with the value used as the start parameter""" provider_data: Optional[str] = None - """A JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.""" + """JSON-serialized data about the invoice, which will be shared with the payment provider. A detailed description of required fields should be provided by the payment provider.""" photo_url: Optional[str] = None """URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for.""" photo_size: Optional[int] = None - """Photo size""" + """Photo size in bytes""" photo_width: Optional[int] = None """Photo width""" photo_height: Optional[int] = None @@ -57,9 +57,9 @@ class SendInvoice(TelegramMethod[Message]): need_shipping_address: Optional[bool] = None """Pass :code:`True`, if you require the user's shipping address to complete the order""" send_phone_number_to_provider: Optional[bool] = None - """Pass :code:`True`, if user's phone number should be sent to provider""" + """Pass :code:`True`, if the user's phone number should be sent to provider""" send_email_to_provider: Optional[bool] = None - """Pass :code:`True`, if user's email address should be sent to provider""" + """Pass :code:`True`, if the user's email address should be sent to provider""" is_flexible: Optional[bool] = None """Pass :code:`True`, if the final price depends on the shipping method""" disable_notification: Optional[bool] = None diff --git a/aiogram/methods/send_photo.py b/aiogram/methods/send_photo.py index faf9353f..a4bb3d03 100644 --- a/aiogram/methods/send_photo.py +++ b/aiogram/methods/send_photo.py @@ -30,7 +30,7 @@ class SendPhoto(TelegramMethod[Message]): chat_id: Union[int, str] """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" photo: Union[InputFile, str] - """Photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20. :ref:`More info on Sending Files ยป `""" + """Photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. The photo must be at most 10 MB in size. The photo's width and height must not exceed 10000 in total. Width and height ratio must be at most 20. :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """Photo caption (may also be used when resending photos by *file_id*), 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/methods/send_sticker.py b/aiogram/methods/send_sticker.py index 573040e1..964f3e20 100644 --- a/aiogram/methods/send_sticker.py +++ b/aiogram/methods/send_sticker.py @@ -28,7 +28,7 @@ class SendSticker(TelegramMethod[Message]): chat_id: Union[int, str] """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" sticker: Union[InputFile, str] - """Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" + """Sticker to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a .WEBP file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป `""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" protect_content: Optional[bool] = None diff --git a/aiogram/methods/send_video.py b/aiogram/methods/send_video.py index 4ed09cf1..a41dbfe9 100644 --- a/aiogram/methods/send_video.py +++ b/aiogram/methods/send_video.py @@ -20,7 +20,7 @@ if TYPE_CHECKING: class SendVideo(TelegramMethod[Message]): """ - Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as :class:`aiogram.types.document.Document`). On success, the sent :class:`aiogram.types.message.Message` is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. + Use this method to send video files, Telegram clients support MPEG4 videos (other formats may be sent as :class:`aiogram.types.document.Document`). On success, the sent :class:`aiogram.types.message.Message` is returned. Bots can currently send video files of up to 50 MB in size, this limit may be changed in the future. Source: https://core.telegram.org/bots/api#sendvideo """ @@ -30,7 +30,7 @@ class SendVideo(TelegramMethod[Message]): chat_id: Union[int, str] """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" video: Union[InputFile, str] - """Video to send. Pass a file_id as String to send a video that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet, or upload a new video using multipart/form-data. :ref:`More info on Sending Files ยป `""" + """Video to send. Pass a file_id as String to send a video that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet, or upload a new video using multipart/form-data. :ref:`More information on Sending Files ยป `""" duration: Optional[int] = None """Duration of sent video in seconds""" width: Optional[int] = None @@ -38,7 +38,7 @@ class SendVideo(TelegramMethod[Message]): height: Optional[int] = None """Video height""" thumb: Optional[Union[InputFile, str]] = None - """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """Video caption (may also be used when resending videos by *file_id*), 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/methods/send_video_note.py b/aiogram/methods/send_video_note.py index 7431d582..d26557b7 100644 --- a/aiogram/methods/send_video_note.py +++ b/aiogram/methods/send_video_note.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: class SendVideoNote(TelegramMethod[Message]): """ - As of `v.4.0 `_, Telegram clients support rounded square mp4 videos of up to 1 minute long. Use this method to send video messages. On success, the sent :class:`aiogram.types.message.Message` is returned. + As of `v.4.0 `_, Telegram clients support rounded square MPEG4 videos of up to 1 minute long. Use this method to send video messages. On success, the sent :class:`aiogram.types.message.Message` is returned. Source: https://core.telegram.org/bots/api#sendvideonote """ @@ -28,13 +28,13 @@ class SendVideoNote(TelegramMethod[Message]): chat_id: Union[int, str] """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" video_note: Union[InputFile, str] - """Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. :ref:`More info on Sending Files ยป `. Sending video notes by a URL is currently unsupported""" + """Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. :ref:`More information on Sending Files ยป `. Sending video notes by a URL is currently unsupported""" duration: Optional[int] = None """Duration of sent video in seconds""" length: Optional[int] = None """Video width and height, i.e. diameter of the video message""" thumb: Optional[Union[InputFile, str]] = None - """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" disable_notification: Optional[bool] = None """Sends the message `silently `_. Users will receive a notification with no sound.""" protect_content: Optional[bool] = None diff --git a/aiogram/methods/send_voice.py b/aiogram/methods/send_voice.py index e753e9e1..afa9f75f 100644 --- a/aiogram/methods/send_voice.py +++ b/aiogram/methods/send_voice.py @@ -30,7 +30,7 @@ class SendVoice(TelegramMethod[Message]): chat_id: Union[int, str] """Unique identifier for the target chat or username of the target channel (in the format :code:`@channelusername`)""" voice: Union[InputFile, str] - """Audio file to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `""" + """Audio file to send. Pass a file_id as String to send a file that exists on the Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """Voice message caption, 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/methods/set_chat_menu_button.py b/aiogram/methods/set_chat_menu_button.py index 6578ec4e..25301431 100644 --- a/aiogram/methods/set_chat_menu_button.py +++ b/aiogram/methods/set_chat_menu_button.py @@ -21,7 +21,7 @@ class SetChatMenuButton(TelegramMethod[bool]): chat_id: Optional[int] = None """Unique identifier for the target private chat. If not specified, default bot's menu button will be changed""" menu_button: Optional[MenuButton] = None - """A JSON-serialized object for the new bot's menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault`""" + """A JSON-serialized object for the bot's new menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault`""" def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict() diff --git a/aiogram/methods/set_sticker_set_thumb.py b/aiogram/methods/set_sticker_set_thumb.py index 49ef7971..cbab57e4 100644 --- a/aiogram/methods/set_sticker_set_thumb.py +++ b/aiogram/methods/set_sticker_set_thumb.py @@ -23,7 +23,7 @@ class SetStickerSetThumb(TelegramMethod[bool]): user_id: int """User identifier of the sticker set owner""" thumb: Optional[Union[InputFile, str]] = None - """A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files ยป `. Animated sticker set thumbnails can't be uploaded via HTTP URL.""" + """A **PNG** image with the thumbnail, must be up to 128 kilobytes in size and have width and height exactly 100px, or a **TGS** animation with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#animated-sticker-requirements `_`https://core.telegram.org/stickers#animated-sticker-requirements `_ for animated sticker technical requirements, or a **WEBM** video with the thumbnail up to 32 kilobytes in size; see `https://core.telegram.org/stickers#video-sticker-requirements `_`https://core.telegram.org/stickers#video-sticker-requirements `_ for video sticker technical requirements. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More information on Sending Files ยป `. Animated sticker set thumbnails can't be uploaded via HTTP URL.""" def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"thumb"}) diff --git a/aiogram/methods/set_webhook.py b/aiogram/methods/set_webhook.py index 2b80dd17..34c14aaf 100644 --- a/aiogram/methods/set_webhook.py +++ b/aiogram/methods/set_webhook.py @@ -11,8 +11,8 @@ if TYPE_CHECKING: class SetWebhook(TelegramMethod[bool]): """ - Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized :class:`aiogram.types.update.Update`. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns :code:`True` on success. - If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the URL, e.g. :code:`https://www.example.com/`. Since nobody else knows your bot's token, you can be pretty sure it's us. + Use this method to specify a URL and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified URL, containing a JSON-serialized :class:`aiogram.types.update.Update`. In case of an unsuccessful request, we will give up after a reasonable amount of attempts. Returns :code:`True` on success. + If you'd like to make sure that the webhook was set by you, you can specify secret data in the parameter *secret_token*. If specified, the request will contain a header 'X-Telegram-Bot-Api-Secret-Token' with the secret token as content. **Notes** @@ -20,8 +20,8 @@ class SetWebhook(TelegramMethod[bool]): **2.** To use a self-signed certificate, you need to upload your `public key certificate `_ using *certificate* parameter. Please upload as InputFile, sending a String will not work. - **3.** Ports currently supported *for Webhooks*: **443, 80, 88, 8443**. - **NEW!** If you're having any trouble setting up webhooks, please check out this `amazing guide to Webhooks `_. + **3.** Ports currently supported *for webhooks*: **443, 80, 88, 8443**. + If you're having any trouble setting up webhooks, please check out this `amazing guide to webhooks `_. Source: https://core.telegram.org/bots/api#setwebhook """ @@ -29,17 +29,19 @@ class SetWebhook(TelegramMethod[bool]): __returning__ = bool url: str - """HTTPS url to send updates to. Use an empty string to remove webhook integration""" + """HTTPS URL to send updates to. Use an empty string to remove webhook integration""" certificate: Optional[InputFile] = None """Upload your public key certificate so that the root certificate in use can be checked. See our `self-signed guide `_ for details.""" ip_address: Optional[str] = None """The fixed IP address which will be used to send webhook requests instead of the IP address resolved through DNS""" max_connections: Optional[int] = None - """Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to *40*. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput.""" + """The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to *40*. Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput.""" allowed_updates: Optional[List[str]] = None """A JSON-serialized list of the update types you want your bot to receive. For example, specify ['message', 'edited_channel_post', 'callback_query'] to only receive updates of these types. See :class:`aiogram.types.update.Update` for a complete list of available update types. Specify an empty list to receive all update types except *chat_member* (default). If not specified, the previous setting will be used.""" drop_pending_updates: Optional[bool] = None """Pass :code:`True` to drop all pending updates""" + secret_token: Optional[str] = None + """A secret token to be sent in a header 'X-Telegram-Bot-Api-Secret-Token' in every webhook request, 1-256 characters. Only characters :code:`A-Z`, :code:`a-z`, :code:`0-9`, :code:`_` and :code:`-` are allowed. The header is useful to ensure that the request comes from a webhook set by you.""" def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"certificate"}) diff --git a/aiogram/methods/upload_sticker_file.py b/aiogram/methods/upload_sticker_file.py index 3d887abb..cb36ae4d 100644 --- a/aiogram/methods/upload_sticker_file.py +++ b/aiogram/methods/upload_sticker_file.py @@ -21,7 +21,7 @@ class UploadStickerFile(TelegramMethod[File]): user_id: int """User identifier of sticker file owner""" png_sticker: InputFile - """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. :ref:`More info on Sending Files ยป `""" + """**PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. :ref:`More information on Sending Files ยป `""" def build_request(self, bot: Bot) -> Request: data: Dict[str, Any] = self.dict(exclude={"png_sticker"}) diff --git a/aiogram/types/animation.py b/aiogram/types/animation.py index 54c8f4d2..ce3c034e 100644 --- a/aiogram/types/animation.py +++ b/aiogram/types/animation.py @@ -32,4 +32,4 @@ class Animation(TelegramObject): mime_type: Optional[str] = None """*Optional*. MIME type of the file as defined by sender""" file_size: Optional[int] = None - """*Optional*. File size in bytes""" + """*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.""" diff --git a/aiogram/types/audio.py b/aiogram/types/audio.py index 8d436f3e..fa0557cd 100644 --- a/aiogram/types/audio.py +++ b/aiogram/types/audio.py @@ -30,6 +30,6 @@ class Audio(TelegramObject): mime_type: Optional[str] = None """*Optional*. MIME type of the file as defined by sender""" file_size: Optional[int] = None - """*Optional*. File size in bytes""" + """*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.""" thumb: Optional[PhotoSize] = None """*Optional*. Thumbnail of the album cover to which the music file belongs""" diff --git a/aiogram/types/callback_query.py b/aiogram/types/callback_query.py index 08fddc8a..f9929bca 100644 --- a/aiogram/types/callback_query.py +++ b/aiogram/types/callback_query.py @@ -32,7 +32,7 @@ class CallbackQuery(TelegramObject): inline_message_id: Optional[str] = None """*Optional*. Identifier of the message sent via the bot in inline mode, that originated the query.""" data: Optional[str] = None - """*Optional*. Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field.""" + """*Optional*. Data associated with the callback button. Be aware that the message originated the query can contain no callback buttons with this data.""" game_short_name: Optional[str] = None """*Optional*. Short name of a `Game `_ to be returned, serves as the unique identifier for the game""" diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index c2df6f6b..1fb92f8a 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -36,7 +36,11 @@ class Chat(TelegramObject): bio: Optional[str] = None """*Optional*. Bio of the other party in a private chat. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" has_private_forwards: Optional[bool] = None - """*Optional*. True, if privacy settings of the other party in the private chat allows to use :code:`tg://user?id=` links only in chats with the user. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" + """*Optional*. :code:`True`, if privacy settings of the other party in the private chat allows to use :code:`tg://user?id=` links only in chats with the user. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" + join_to_send_messages: Optional[bool] = None + """*Optional*. :code:`True`, if users need to join the supergroup before they can send messages. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" + join_by_request: Optional[bool] = None + """*Optional*. :code:`True`, if all users directly joining the supergroup need to be approved by supergroup administrators. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" description: Optional[str] = None """*Optional*. Description, for groups, supergroups and channel chats. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" invite_link: Optional[str] = None @@ -50,7 +54,7 @@ class Chat(TelegramObject): message_auto_delete_time: Optional[int] = None """*Optional*. The time after which all messages sent to the chat will be automatically deleted; in seconds. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" has_protected_content: Optional[bool] = None - """*Optional*. True, if messages from the chat can't be forwarded to other chats. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" + """*Optional*. :code:`True`, if messages from the chat can't be forwarded to other chats. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" sticker_set_name: Optional[str] = None """*Optional*. For supergroups, name of group sticker set. Returned only in :class:`aiogram.methods.get_chat.GetChat`.""" can_set_sticker_set: Optional[bool] = None diff --git a/aiogram/types/chat_invite_link.py b/aiogram/types/chat_invite_link.py index 1654079d..33593c2d 100644 --- a/aiogram/types/chat_invite_link.py +++ b/aiogram/types/chat_invite_link.py @@ -31,6 +31,6 @@ class ChatInviteLink(TelegramObject): expire_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None """*Optional*. Point in time (Unix timestamp) when the link will expire or has been expired""" member_limit: Optional[int] = None - """*Optional*. Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999""" + """*Optional*. The maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999""" pending_join_request_count: Optional[int] = None """*Optional*. Number of pending join requests created using this link""" diff --git a/aiogram/types/chosen_inline_result.py b/aiogram/types/chosen_inline_result.py index 2144c0ca..0ec211f7 100644 --- a/aiogram/types/chosen_inline_result.py +++ b/aiogram/types/chosen_inline_result.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: class ChosenInlineResult(TelegramObject): """ Represents a `result `_ of an inline query that was chosen by the user and sent to their chat partner. - **Note:** It is necessary to enable `inline feedback `_ via `@Botfather `_ in order to receive these objects in updates. + **Note:** It is necessary to enable `inline feedback `_ via `@BotFather `_ in order to receive these objects in updates. Source: https://core.telegram.org/bots/api#choseninlineresult """ diff --git a/aiogram/types/document.py b/aiogram/types/document.py index 377241d3..db2433a8 100644 --- a/aiogram/types/document.py +++ b/aiogram/types/document.py @@ -26,4 +26,4 @@ class Document(TelegramObject): mime_type: Optional[str] = None """*Optional*. MIME type of the file as defined by sender""" file_size: Optional[int] = None - """*Optional*. File size in bytes""" + """*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.""" diff --git a/aiogram/types/encrypted_credentials.py b/aiogram/types/encrypted_credentials.py index 91fd7714..d14c2560 100644 --- a/aiogram/types/encrypted_credentials.py +++ b/aiogram/types/encrypted_credentials.py @@ -5,7 +5,7 @@ from .base import TelegramObject class EncryptedCredentials(TelegramObject): """ - Contains data required for decrypting and authenticating :class:`aiogram.types.encrypted_passport_element.EncryptedPassportElement`. See the `Telegram Passport Documentation `_ for a complete description of the data decryption and authentication processes. + Describes data required for decrypting and authenticating :class:`aiogram.types.encrypted_passport_element.EncryptedPassportElement`. See the `Telegram Passport Documentation `_ for a complete description of the data decryption and authentication processes. Source: https://core.telegram.org/bots/api#encryptedcredentials """ diff --git a/aiogram/types/encrypted_passport_element.py b/aiogram/types/encrypted_passport_element.py index 7b958f29..27506298 100644 --- a/aiogram/types/encrypted_passport_element.py +++ b/aiogram/types/encrypted_passport_element.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: class EncryptedPassportElement(TelegramObject): """ - Contains information about documents or other Telegram Passport elements shared with the bot by the user. + Describes documents or other Telegram Passport elements shared with the bot by the user. Source: https://core.telegram.org/bots/api#encryptedpassportelement """ diff --git a/aiogram/types/file.py b/aiogram/types/file.py index 9a0e3206..3e6e769e 100644 --- a/aiogram/types/file.py +++ b/aiogram/types/file.py @@ -9,7 +9,7 @@ class File(TelegramObject): """ This object represents a file ready to be downloaded. The file can be downloaded via the link :code:`https://api.telegram.org/file/bot/`. It is guaranteed that the link will be valid for at least 1 hour. When the link expires, a new one can be requested by calling :class:`aiogram.methods.get_file.GetFile`. - Maximum file size to download is 20 MB + The maximum file size to download is 20 MB Source: https://core.telegram.org/bots/api#file """ @@ -19,6 +19,6 @@ class File(TelegramObject): file_unique_id: str """Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file.""" file_size: Optional[int] = None - """*Optional*. File size in bytes, if known""" + """*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.""" file_path: Optional[str] = None """*Optional*. File path. Use :code:`https://api.telegram.org/file/bot/` to get the file.""" diff --git a/aiogram/types/force_reply.py b/aiogram/types/force_reply.py index bd8e8900..55f1b6c9 100644 --- a/aiogram/types/force_reply.py +++ b/aiogram/types/force_reply.py @@ -16,7 +16,7 @@ class ForceReply(MutableTelegramObject): - Explain the user how to send a command with parameters (e.g. /newpoll question answer1 answer2). May be appealing for hardcore users but lacks modern day polish. - Guide the user through a step-by-step process. 'Please send me your question', 'Cool, now let's add the first answer option', 'Great. Keep adding answer options, then send /done when you're ready'. - The last option is definitely more attractive. And if you use :class:`aiogram.types.force_reply.ForceReply` in your bot's questions, it will receive the user's answers even if it only receives replies, commands and mentions โ€” without any extra work for the user. + The last option is definitely more attractive. And if you use :class:`aiogram.types.force_reply.ForceReply` in your bot's questions, it will receive the user's answers even if it only receives replies, commands and mentions - without any extra work for the user. Source: https://core.telegram.org/bots/api#forcereply """ diff --git a/aiogram/types/inline_keyboard_button.py b/aiogram/types/inline_keyboard_button.py index aeb546f1..c4208b70 100644 --- a/aiogram/types/inline_keyboard_button.py +++ b/aiogram/types/inline_keyboard_button.py @@ -20,17 +20,17 @@ class InlineKeyboardButton(MutableTelegramObject): text: str """Label text on the button""" url: Optional[str] = None - """*Optional*. HTTP or tg:// url to be opened when the button is pressed. Links :code:`tg://user?id=` can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings.""" + """*Optional*. HTTP or tg:// URL to be opened when the button is pressed. Links :code:`tg://user?id=` can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings.""" callback_data: Optional[str] = None """*Optional*. Data to be sent in a `callback query `_ to the bot when button is pressed, 1-64 bytes""" web_app: Optional[WebAppInfo] = None """*Optional*. Description of the `Web App `_ that will be launched when the user presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the method :class:`aiogram.methods.answer_web_app_query.AnswerWebAppQuery`. Available only in private chats between a user and the bot.""" login_url: Optional[LoginUrl] = None - """*Optional*. An HTTP URL used to automatically authorize the user. Can be used as a replacement for the `Telegram Login Widget `_.""" + """*Optional*. An HTTPS URL used to automatically authorize the user. Can be used as a replacement for the `Telegram Login Widget `_.""" switch_inline_query: Optional[str] = None - """*Optional*. If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. Can be empty, in which case just the bot's username will be inserted.""" + """*Optional*. If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. May be empty, in which case just the bot's username will be inserted.""" switch_inline_query_current_chat: Optional[str] = None - """*Optional*. If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. Can be empty, in which case only the bot's username will be inserted.""" + """*Optional*. If set, pressing the button will insert the bot's username and the specified inline query in the current chat's input field. May be empty, in which case only the bot's username will be inserted.""" callback_game: Optional[CallbackGame] = None """*Optional*. Description of the game that will be launched when the user presses the button.""" pay: Optional[bool] = None diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py index 89602803..d5c674ae 100644 --- a/aiogram/types/inline_query.py +++ b/aiogram/types/inline_query.py @@ -29,7 +29,7 @@ class InlineQuery(TelegramObject): offset: str """Offset of the results to be returned, can be controlled by the bot""" chat_type: Optional[str] = None - """*Optional*. Type of the chat, from which the inline query was sent. Can be either 'sender' for a private chat with the inline query sender, 'private', 'group', 'supergroup', or 'channel'. The chat type should be always known for requests sent from official clients and most third-party clients, unless the request was sent from a secret chat""" + """*Optional*. Type of the chat from which the inline query was sent. Can be either 'sender' for a private chat with the inline query sender, 'private', 'group', 'supergroup', or 'channel'. The chat type should be always known for requests sent from official clients and most third-party clients, unless the request was sent from a secret chat""" location: Optional[Location] = None """*Optional*. Sender location, only for bots that request user location""" diff --git a/aiogram/types/inline_query_result_cached_mpeg4_gif.py b/aiogram/types/inline_query_result_cached_mpeg4_gif.py index 7076c187..0500603f 100644 --- a/aiogram/types/inline_query_result_cached_mpeg4_gif.py +++ b/aiogram/types/inline_query_result_cached_mpeg4_gif.py @@ -25,7 +25,7 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): id: str """Unique identifier for this result, 1-64 bytes""" mpeg4_file_id: str - """A valid file identifier for the MP4 file""" + """A valid file identifier for the MPEG4 file""" title: Optional[str] = None """*Optional*. Title for the result""" caption: Optional[str] = None diff --git a/aiogram/types/inline_query_result_document.py b/aiogram/types/inline_query_result_document.py index 40052009..7b35c69a 100644 --- a/aiogram/types/inline_query_result_document.py +++ b/aiogram/types/inline_query_result_document.py @@ -30,7 +30,7 @@ class InlineQueryResultDocument(InlineQueryResult): document_url: str """A valid URL for the file""" mime_type: str - """Mime type of the content of the file, either 'application/pdf' or 'application/zip'""" + """MIME type of the content of the file, either 'application/pdf' or 'application/zip'""" caption: Optional[str] = None """*Optional*. Caption of the document to be sent, 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/types/inline_query_result_mpeg4_gif.py b/aiogram/types/inline_query_result_mpeg4_gif.py index dc93b16a..242386f6 100644 --- a/aiogram/types/inline_query_result_mpeg4_gif.py +++ b/aiogram/types/inline_query_result_mpeg4_gif.py @@ -25,7 +25,7 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): id: str """Unique identifier for this result, 1-64 bytes""" mpeg4_url: str - """A valid URL for the MP4 file. File size must not exceed 1MB""" + """A valid URL for the MPEG4 file. File size must not exceed 1MB""" thumb_url: str """URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result""" mpeg4_width: Optional[int] = None diff --git a/aiogram/types/inline_query_result_video.py b/aiogram/types/inline_query_result_video.py index 3e8ef3f0..abd76c94 100644 --- a/aiogram/types/inline_query_result_video.py +++ b/aiogram/types/inline_query_result_video.py @@ -29,7 +29,7 @@ class InlineQueryResultVideo(InlineQueryResult): video_url: str """A valid URL for the embedded video player or video file""" mime_type: str - """Mime type of the content of video url, 'text/html' or 'video/mp4'""" + """MIME type of the content of the video URL, 'text/html' or 'video/mp4'""" thumb_url: str """URL of the thumbnail (JPEG only) for the video""" title: str diff --git a/aiogram/types/input_invoice_message_content.py b/aiogram/types/input_invoice_message_content.py index 301fbd81..8636cf07 100644 --- a/aiogram/types/input_invoice_message_content.py +++ b/aiogram/types/input_invoice_message_content.py @@ -22,7 +22,7 @@ class InputInvoiceMessageContent(InputMessageContent): payload: str """Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes.""" provider_token: str - """Payment provider token, obtained via `Botfather `_""" + """Payment provider token, obtained via `@BotFather `_""" currency: str """Three-letter ISO 4217 currency code, see `more on currencies `_""" prices: List[LabeledPrice] @@ -34,9 +34,9 @@ class InputInvoiceMessageContent(InputMessageContent): provider_data: Optional[str] = None """*Optional*. A JSON-serialized object for data about the invoice, which will be shared with the payment provider. A detailed description of the required fields should be provided by the payment provider.""" photo_url: Optional[str] = None - """*Optional*. URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. People like it better when they see what they are paying for.""" + """*Optional*. URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service.""" photo_size: Optional[int] = None - """*Optional*. Photo size""" + """*Optional*. Photo size in bytes""" photo_width: Optional[int] = None """*Optional*. Photo width""" photo_height: Optional[int] = None @@ -50,8 +50,8 @@ class InputInvoiceMessageContent(InputMessageContent): need_shipping_address: Optional[bool] = None """*Optional*. Pass :code:`True`, if you require the user's shipping address to complete the order""" send_phone_number_to_provider: Optional[bool] = None - """*Optional*. Pass :code:`True`, if user's phone number should be sent to provider""" + """*Optional*. Pass :code:`True`, if the user's phone number should be sent to provider""" send_email_to_provider: Optional[bool] = None - """*Optional*. Pass :code:`True`, if user's email address should be sent to provider""" + """*Optional*. Pass :code:`True`, if the user's email address should be sent to provider""" is_flexible: Optional[bool] = None """*Optional*. Pass :code:`True`, if the final price depends on the shipping method""" diff --git a/aiogram/types/input_media_animation.py b/aiogram/types/input_media_animation.py index 35f28509..7e25a8a2 100644 --- a/aiogram/types/input_media_animation.py +++ b/aiogram/types/input_media_animation.py @@ -22,9 +22,9 @@ class InputMediaAnimation(InputMedia): type: str = Field("animation", const=True) """Type of the result, must be *animation*""" media: Union[str, InputFile] - """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More info on Sending Files ยป `""" + """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files ยป `""" thumb: Optional[Union[InputFile, str]] = None - """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """*Optional*. Caption of the animation to be sent, 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/types/input_media_audio.py b/aiogram/types/input_media_audio.py index 0265f67a..35884192 100644 --- a/aiogram/types/input_media_audio.py +++ b/aiogram/types/input_media_audio.py @@ -22,9 +22,9 @@ class InputMediaAudio(InputMedia): type: str = Field("audio", const=True) """Type of the result, must be *audio*""" media: Union[str, InputFile] - """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More info on Sending Files ยป `""" + """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files ยป `""" thumb: Optional[Union[InputFile, str]] = None - """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """*Optional*. Caption of the audio to be sent, 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/types/input_media_document.py b/aiogram/types/input_media_document.py index f8b3b681..639be1ed 100644 --- a/aiogram/types/input_media_document.py +++ b/aiogram/types/input_media_document.py @@ -22,9 +22,9 @@ class InputMediaDocument(InputMedia): type: str = Field("document", const=True) """Type of the result, must be *document*""" media: Union[str, InputFile] - """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More info on Sending Files ยป `""" + """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files ยป `""" thumb: Optional[Union[InputFile, str]] = None - """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """*Optional*. Caption of the document to be sent, 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/types/input_media_photo.py b/aiogram/types/input_media_photo.py index 09aa2918..d824f5f5 100644 --- a/aiogram/types/input_media_photo.py +++ b/aiogram/types/input_media_photo.py @@ -22,7 +22,7 @@ class InputMediaPhoto(InputMedia): type: str = Field("photo", const=True) """Type of the result, must be *photo*""" media: Union[str, InputFile] - """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More info on Sending Files ยป `""" + """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """*Optional*. Caption of the photo to be sent, 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/types/input_media_video.py b/aiogram/types/input_media_video.py index b66a16c3..72149b84 100644 --- a/aiogram/types/input_media_video.py +++ b/aiogram/types/input_media_video.py @@ -22,9 +22,9 @@ class InputMediaVideo(InputMedia): type: str = Field("video", const=True) """Type of the result, must be *video*""" media: Union[str, InputFile] - """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More info on Sending Files ยป `""" + """File to send. Pass a file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass 'attach://' to upload a new one using multipart/form-data under name. :ref:`More information on Sending Files ยป `""" thumb: Optional[Union[InputFile, str]] = None - """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More info on Sending Files ยป `""" + """*Optional*. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, so you can pass 'attach://' if the thumbnail was uploaded using multipart/form-data under . :ref:`More information on Sending Files ยป `""" caption: Optional[str] = None """*Optional*. Caption of the video to be sent, 0-1024 characters after entities parsing""" parse_mode: Optional[str] = UNSET diff --git a/aiogram/types/keyboard_button.py b/aiogram/types/keyboard_button.py index bf8b0258..40c70420 100644 --- a/aiogram/types/keyboard_button.py +++ b/aiogram/types/keyboard_button.py @@ -15,7 +15,7 @@ class WebApp(MutableTelegramObject): class KeyboardButton(MutableTelegramObject): """ - This object represents one button of the reply keyboard. For simple text buttons *String* can be used instead of this object to specify text of the button. Optional fields *request_contact*, *request_location*, and *request_poll* are mutually exclusive. + This object represents one button of the reply keyboard. For simple text buttons *String* can be used instead of this object to specify text of the button. Optional fields *web_app*, *request_contact*, *request_location*, and *request_poll* are mutually exclusive. **Note:** *request_contact* and *request_location* options will only work in Telegram versions released after 9 April, 2016. Older clients will display *unsupported message*. **Note:** *request_poll* option will only work in Telegram versions released after 23 January, 2020. Older clients will display *unsupported message*. diff --git a/aiogram/types/location.py b/aiogram/types/location.py index 2c4a1f88..bac5fc70 100644 --- a/aiogram/types/location.py +++ b/aiogram/types/location.py @@ -23,4 +23,4 @@ class Location(TelegramObject): heading: Optional[int] = None """*Optional*. The direction in which user is moving, in degrees; 1-360. For active live locations only.""" proximity_alert_radius: Optional[int] = None - """*Optional*. Maximum distance for proximity alerts about approaching another chat member, in meters. For sent live locations only.""" + """*Optional*. The maximum distance for proximity alerts about approaching another chat member, in meters. For sent live locations only.""" diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 74186eef..f4b26087 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -88,7 +88,7 @@ class Message(TelegramObject): from_user: Optional[User] = Field(None, alias="from") """*Optional*. Sender of the message; empty for messages sent to channels. For backward compatibility, the field contains a fake sender user in non-channel chats, if the message was sent on behalf of a chat.""" sender_chat: Optional[Chat] = None - """*Optional*. Sender of the message, sent on behalf of a chat. For example, the channel itself for channel posts, the supergroup itself for messages from anonymous group administrators, the linked channel for messages automatically forwarded to the discussion group. For backward compatibility, the field *from* contains a fake sender user in non-channel chats, if the message was sent on behalf of a chat.""" + """*Optional*. Sender of the message, sent on behalf of a chat. For example, the channel itself for channel posts, the supergroup itself for messages from anonymous group administrators, the linked channel for messages automatically forwarded to the discussion group. For backward compatibility, the field *from* contains a fake sender user in non-channel chats, if the message was sent on behalf of a chat.""" forward_from: Optional[User] = None """*Optional*. For forwarded messages, sender of the original message""" forward_from_chat: Optional[Chat] = None @@ -102,7 +102,7 @@ class Message(TelegramObject): forward_date: Optional[int] = None """*Optional*. For forwarded messages, date the original message was sent in Unix time""" is_automatic_forward: Optional[bool] = None - """*Optional*. True, if the message is a channel post that was automatically forwarded to the connected discussion group""" + """*Optional*. :code:`True`, if the message is a channel post that was automatically forwarded to the connected discussion group""" reply_to_message: Optional[Message] = None """*Optional*. For replies, the original message. Note that the Message object in this field will not contain further *reply_to_message* fields even if it itself is a reply.""" via_bot: Optional[User] = None @@ -110,13 +110,13 @@ class Message(TelegramObject): edit_date: Optional[int] = None """*Optional*. Date the message was last edited in Unix time""" has_protected_content: Optional[bool] = None - """*Optional*. True, if the message can't be forwarded""" + """*Optional*. :code:`True`, if the message can't be forwarded""" media_group_id: Optional[str] = None """*Optional*. The unique identifier of a media message group this message belongs to""" author_signature: Optional[str] = None """*Optional*. Signature of the post author for messages in channels, or the custom title of an anonymous group administrator""" text: Optional[str] = None - """*Optional*. For text messages, the actual UTF-8 text of the message, 0-4096 characters""" + """*Optional*. For text messages, the actual UTF-8 text of the message""" entities: Optional[List[MessageEntity]] = None """*Optional*. For text messages, special entities like usernames, URLs, bot commands, etc. that appear in the text""" animation: Optional[Animation] = None @@ -136,7 +136,7 @@ class Message(TelegramObject): voice: Optional[Voice] = None """*Optional*. Message is a voice message, information about the file""" caption: Optional[str] = None - """*Optional*. Caption for the animation, audio, document, photo, video or voice, 0-1024 characters""" + """*Optional*. Caption for the animation, audio, document, photo, video or voice""" caption_entities: Optional[List[MessageEntity]] = None """*Optional*. For messages with a caption, special entities like usernames, URLs, bot commands, etc. that appear in the caption""" contact: Optional[Contact] = None diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index 28226286..d50f797a 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -24,7 +24,7 @@ class MessageEntity(MutableTelegramObject): length: int """Length of the entity in UTF-16 code units""" url: Optional[str] = None - """*Optional*. For 'text_link' only, url that will be opened after user taps on the text""" + """*Optional*. For 'text_link' only, URL that will be opened after user taps on the text""" user: Optional[User] = None """*Optional*. For 'text_mention' only, the mentioned user""" language: Optional[str] = None diff --git a/aiogram/types/passport_data.py b/aiogram/types/passport_data.py index 96608226..18523c05 100644 --- a/aiogram/types/passport_data.py +++ b/aiogram/types/passport_data.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: class PassportData(TelegramObject): """ - Contains information about Telegram Passport data shared with the bot by the user. + Describes Telegram Passport data shared with the bot by the user. Source: https://core.telegram.org/bots/api#passportdata """ diff --git a/aiogram/types/pre_checkout_query.py b/aiogram/types/pre_checkout_query.py index a95fb5f4..f2e49170 100644 --- a/aiogram/types/pre_checkout_query.py +++ b/aiogram/types/pre_checkout_query.py @@ -32,7 +32,7 @@ class PreCheckoutQuery(TelegramObject): shipping_option_id: Optional[str] = None """*Optional*. Identifier of the shipping option chosen by the user""" order_info: Optional[OrderInfo] = None - """*Optional*. Order info provided by the user""" + """*Optional*. Order information provided by the user""" def answer(self, ok: bool, error_message: Optional[str] = None) -> AnswerPreCheckoutQuery: """ diff --git a/aiogram/types/reply_keyboard_markup.py b/aiogram/types/reply_keyboard_markup.py index 6b8a65e3..334168bd 100644 --- a/aiogram/types/reply_keyboard_markup.py +++ b/aiogram/types/reply_keyboard_markup.py @@ -20,7 +20,7 @@ class ReplyKeyboardMarkup(MutableTelegramObject): resize_keyboard: Optional[bool] = None """*Optional*. Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). Defaults to *false*, in which case the custom keyboard is always of the same height as the app's standard keyboard.""" one_time_keyboard: Optional[bool] = None - """*Optional*. Requests clients to hide the keyboard as soon as it's been used. The keyboard will still be available, but clients will automatically display the usual letter-keyboard in the chat โ€“ the user can press a special button in the input field to see the custom keyboard again. Defaults to *false*.""" + """*Optional*. Requests clients to hide the keyboard as soon as it's been used. The keyboard will still be available, but clients will automatically display the usual letter-keyboard in the chat - the user can press a special button in the input field to see the custom keyboard again. Defaults to *false*.""" input_field_placeholder: Optional[str] = None """*Optional*. The placeholder to be shown in the input field when the keyboard is active; 1-64 characters""" selective: Optional[bool] = None diff --git a/aiogram/types/response_parameters.py b/aiogram/types/response_parameters.py index 5c722c9e..dd3531c2 100644 --- a/aiogram/types/response_parameters.py +++ b/aiogram/types/response_parameters.py @@ -7,7 +7,7 @@ from .base import TelegramObject class ResponseParameters(TelegramObject): """ - Contains information about why a request was unsuccessful. + Describes why a request was unsuccessful. Source: https://core.telegram.org/bots/api#responseparameters """ diff --git a/aiogram/types/sent_web_app_message.py b/aiogram/types/sent_web_app_message.py index 7295382c..7162d9b7 100644 --- a/aiogram/types/sent_web_app_message.py +++ b/aiogram/types/sent_web_app_message.py @@ -7,7 +7,7 @@ from .base import TelegramObject class SentWebAppMessage(TelegramObject): """ - Contains information about an inline message sent by a `Web App `_ on behalf of a user. + Describes an inline message sent by a `Web App `_ on behalf of a user. Source: https://core.telegram.org/bots/api#sentwebappmessage """ diff --git a/aiogram/types/shipping_address.py b/aiogram/types/shipping_address.py index 4ef322c7..948956ce 100644 --- a/aiogram/types/shipping_address.py +++ b/aiogram/types/shipping_address.py @@ -11,7 +11,7 @@ class ShippingAddress(TelegramObject): """ country_code: str - """ISO 3166-1 alpha-2 country code""" + """Two-letter ISO 3166-1 alpha-2 country code""" state: str """State, if applicable""" city: str diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py index 979d7e37..d10844f2 100644 --- a/aiogram/types/sticker.py +++ b/aiogram/types/sticker.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Optional from .base import TelegramObject if TYPE_CHECKING: + from .file import File from .mask_position import MaskPosition from .photo_size import PhotoSize @@ -34,6 +35,8 @@ class Sticker(TelegramObject): """*Optional*. Emoji associated with the sticker""" set_name: Optional[str] = None """*Optional*. Name of the sticker set to which the sticker belongs""" + premium_animation: Optional[File] = None + """*Optional*. Premium animation for the sticker, if the sticker is premium""" mask_position: Optional[MaskPosition] = None """*Optional*. For mask stickers, the position where the mask should be placed""" file_size: Optional[int] = None diff --git a/aiogram/types/successful_payment.py b/aiogram/types/successful_payment.py index d6e1ded7..d8b0e90c 100644 --- a/aiogram/types/successful_payment.py +++ b/aiogram/types/successful_payment.py @@ -28,4 +28,4 @@ class SuccessfulPayment(TelegramObject): shipping_option_id: Optional[str] = None """*Optional*. Identifier of the shipping option chosen by the user""" order_info: Optional[OrderInfo] = None - """*Optional*. Order info provided by the user""" + """*Optional*. Order information provided by the user""" diff --git a/aiogram/types/update.py b/aiogram/types/update.py index 493d95d5..8c7177f2 100644 --- a/aiogram/types/update.py +++ b/aiogram/types/update.py @@ -28,13 +28,13 @@ class Update(TelegramObject): """ update_id: int - """The update's unique identifier. Update identifiers start from a certain positive number and increase sequentially. This ID becomes especially handy if you're using `Webhooks `_, since it allows you to ignore repeated updates or to restore the correct update sequence, should they get out of order. If there are no new updates for at least a week, then identifier of the next update will be chosen randomly instead of sequentially.""" + """The update's unique identifier. Update identifiers start from a certain positive number and increase sequentially. This ID becomes especially handy if you're using `webhooks `_, since it allows you to ignore repeated updates or to restore the correct update sequence, should they get out of order. If there are no new updates for at least a week, then identifier of the next update will be chosen randomly instead of sequentially.""" message: Optional[Message] = None - """*Optional*. New incoming message of any kind โ€” text, photo, sticker, etc.""" + """*Optional*. New incoming message of any kind - text, photo, sticker, etc.""" edited_message: Optional[Message] = None """*Optional*. New version of a message that is known to the bot and was edited""" channel_post: Optional[Message] = None - """*Optional*. New incoming channel post of any kind โ€” text, photo, sticker, etc.""" + """*Optional*. New incoming channel post of any kind - text, photo, sticker, etc.""" edited_channel_post: Optional[Message] = None """*Optional*. New version of a channel post that is known to the bot and was edited""" inline_query: Optional[InlineQuery] = None diff --git a/aiogram/types/user.py b/aiogram/types/user.py index 3f700040..64eb5c73 100644 --- a/aiogram/types/user.py +++ b/aiogram/types/user.py @@ -24,6 +24,10 @@ class User(TelegramObject): """*Optional*. User's or bot's username""" language_code: Optional[str] = None """*Optional*. `IETF language tag `_ of the user's language""" + is_premium: Optional[bool] = None + """*Optional*. :code:`True`, if this user is a Telegram Premium user""" + added_to_attachment_menu: Optional[bool] = None + """*Optional*. :code:`True`, if this user added the bot to the attachment menu""" can_join_groups: Optional[bool] = None """*Optional*. :code:`True`, if the bot can be invited to groups. Returned only in :class:`aiogram.methods.get_me.GetMe`.""" can_read_all_group_messages: Optional[bool] = None diff --git a/aiogram/types/video.py b/aiogram/types/video.py index f245c53c..473527d1 100644 --- a/aiogram/types/video.py +++ b/aiogram/types/video.py @@ -30,6 +30,6 @@ class Video(TelegramObject): file_name: Optional[str] = None """*Optional*. Original filename as defined by sender""" mime_type: Optional[str] = None - """*Optional*. Mime type of a file as defined by sender""" + """*Optional*. MIME type of the file as defined by sender""" file_size: Optional[int] = None - """*Optional*. File size in bytes""" + """*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.""" diff --git a/aiogram/types/voice.py b/aiogram/types/voice.py index ed940ce8..53724ad0 100644 --- a/aiogram/types/voice.py +++ b/aiogram/types/voice.py @@ -21,4 +21,4 @@ class Voice(TelegramObject): mime_type: Optional[str] = None """*Optional*. MIME type of the file as defined by sender""" file_size: Optional[int] = None - """*Optional*. File size in bytes""" + """*Optional*. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value.""" diff --git a/aiogram/types/web_app_data.py b/aiogram/types/web_app_data.py index 6a108fef..09676292 100644 --- a/aiogram/types/web_app_data.py +++ b/aiogram/types/web_app_data.py @@ -5,7 +5,7 @@ from .base import TelegramObject class WebAppData(TelegramObject): """ - Contains data sent from a `Web App `_ to the bot. + Describes data sent from a `Web App `_ to the bot. Source: https://core.telegram.org/bots/api#webappdata """ @@ -13,4 +13,4 @@ class WebAppData(TelegramObject): data: str """The data. Be aware that a bad client can send arbitrary data in this field.""" button_text: str - """Text of the *web_app* keyboard button, from which the Web App was opened. Be aware that a bad client can send arbitrary data in this field.""" + """Text of the *web_app* keyboard button from which the Web App was opened. Be aware that a bad client can send arbitrary data in this field.""" diff --git a/aiogram/types/web_app_info.py b/aiogram/types/web_app_info.py index 9317fae9..b315846e 100644 --- a/aiogram/types/web_app_info.py +++ b/aiogram/types/web_app_info.py @@ -5,7 +5,7 @@ from .base import TelegramObject class WebAppInfo(TelegramObject): """ - Contains information about a `Web App `_. + Describes a `Web App `_. Source: https://core.telegram.org/bots/api#webappinfo """ diff --git a/aiogram/types/webhook_info.py b/aiogram/types/webhook_info.py index a3ec68e5..e08df0d6 100644 --- a/aiogram/types/webhook_info.py +++ b/aiogram/types/webhook_info.py @@ -1,14 +1,14 @@ from __future__ import annotations import datetime -from typing import List, Optional, Union +from typing import List, Optional from .base import TelegramObject class WebhookInfo(TelegramObject): """ - Contains information about the current status of a webhook. + Describes the current status of a webhook. Source: https://core.telegram.org/bots/api#webhookinfo """ @@ -21,13 +21,13 @@ class WebhookInfo(TelegramObject): """Number of updates awaiting delivery""" ip_address: Optional[str] = None """*Optional*. Currently used webhook IP address""" - last_error_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None + last_error_date: Optional[datetime.datetime] = None """*Optional*. Unix time for the most recent error that happened when trying to deliver an update via webhook""" last_error_message: Optional[str] = None """*Optional*. Error message in human-readable format for the most recent error that happened when trying to deliver an update via webhook""" last_synchronization_error_date: Optional[datetime.datetime] = None """*Optional*. Unix time of the most recent error that happened when trying to synchronize available updates with Telegram datacenters""" max_connections: Optional[int] = None - """*Optional*. Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery""" + """*Optional*. The maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery""" allowed_updates: Optional[List[str]] = None """*Optional*. A list of update types the bot is subscribed to. Defaults to all update types except *chat_member*""" diff --git a/aiogram/utils/i18n/core.py b/aiogram/utils/i18n/core.py index 830ead54..db7c7979 100644 --- a/aiogram/utils/i18n/core.py +++ b/aiogram/utils/i18n/core.py @@ -68,7 +68,7 @@ class I18n(ContextInstanceMixin["I18n"]): if os.path.exists(mo_path): with open(mo_path, "rb") as fp: - translations[name] = gettext.GNUTranslations(fp) # type: ignore + translations[name] = gettext.GNUTranslations(fp) elif os.path.exists(mo_path[:-2] + "po"): # pragma: no cover raise RuntimeError(f"Found locale '{name}' but this language is not compiled!") diff --git a/docs/api/methods/add_sticker_to_set.rst b/docs/api/methods/add_sticker_to_set.rst index 68d1cbc6..8c86d09a 100644 --- a/docs/api/methods/add_sticker_to_set.rst +++ b/docs/api/methods/add_sticker_to_set.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.add_sticker_to_set import AddStickerToSet` - alias: :code:`from aiogram.methods import AddStickerToSet` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await AddStickerToSet(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/answer_callback_query.rst b/docs/api/methods/answer_callback_query.rst index 1ffad5f5..3dc5096f 100644 --- a/docs/api/methods/answer_callback_query.rst +++ b/docs/api/methods/answer_callback_query.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.answer_callback_query import AnswerCallbackQuery` - alias: :code:`from aiogram.methods import AnswerCallbackQuery` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await AnswerCallbackQuery(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/answer_inline_query.rst b/docs/api/methods/answer_inline_query.rst index a2231c5b..193f9fdf 100644 --- a/docs/api/methods/answer_inline_query.rst +++ b/docs/api/methods/answer_inline_query.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.answer_inline_query import AnswerInlineQuery` - alias: :code:`from aiogram.methods import AnswerInlineQuery` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await AnswerInlineQuery(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/answer_pre_checkout_query.rst b/docs/api/methods/answer_pre_checkout_query.rst index c332f6e6..82157af2 100644 --- a/docs/api/methods/answer_pre_checkout_query.rst +++ b/docs/api/methods/answer_pre_checkout_query.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.answer_pre_checkout_query import AnswerPreCheckoutQuery` - alias: :code:`from aiogram.methods import AnswerPreCheckoutQuery` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await AnswerPreCheckoutQuery(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/answer_shipping_query.rst b/docs/api/methods/answer_shipping_query.rst index 2b3f5212..b9ddc36c 100644 --- a/docs/api/methods/answer_shipping_query.rst +++ b/docs/api/methods/answer_shipping_query.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.answer_shipping_query import AnswerShippingQuery` - alias: :code:`from aiogram.methods import AnswerShippingQuery` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await AnswerShippingQuery(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/answer_web_app_query.rst b/docs/api/methods/answer_web_app_query.rst index a608083f..884366ee 100644 --- a/docs/api/methods/answer_web_app_query.rst +++ b/docs/api/methods/answer_web_app_query.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.answer_web_app_query import AnswerWebAppQuery` - alias: :code:`from aiogram.methods import AnswerWebAppQuery` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: SentWebAppMessage = await AnswerWebAppQuery(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/approve_chat_join_request.rst b/docs/api/methods/approve_chat_join_request.rst index 8ac4643a..490c1d5b 100644 --- a/docs/api/methods/approve_chat_join_request.rst +++ b/docs/api/methods/approve_chat_join_request.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.approve_chat_join_request import ApproveChatJoinRequest` - alias: :code:`from aiogram.methods import ApproveChatJoinRequest` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await ApproveChatJoinRequest(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/ban_chat_member.rst b/docs/api/methods/ban_chat_member.rst index 0f8edf8b..b5ff4bfe 100644 --- a/docs/api/methods/ban_chat_member.rst +++ b/docs/api/methods/ban_chat_member.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.ban_chat_member import BanChatMember` - alias: :code:`from aiogram.methods import BanChatMember` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await BanChatMember(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/ban_chat_sender_chat.rst b/docs/api/methods/ban_chat_sender_chat.rst index 5c6182eb..435fd8b9 100644 --- a/docs/api/methods/ban_chat_sender_chat.rst +++ b/docs/api/methods/ban_chat_sender_chat.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.ban_chat_sender_chat import BanChatSenderChat` - alias: :code:`from aiogram.methods import BanChatSenderChat` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await BanChatSenderChat(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/close.rst b/docs/api/methods/close.rst index 2ae15875..6c315eb4 100644 --- a/docs/api/methods/close.rst +++ b/docs/api/methods/close.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.close import Close` - alias: :code:`from aiogram.methods import Close` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await Close(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/copy_message.rst b/docs/api/methods/copy_message.rst index a4f5c9b9..ac8acb3d 100644 --- a/docs/api/methods/copy_message.rst +++ b/docs/api/methods/copy_message.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.copy_message import CopyMessage` - alias: :code:`from aiogram.methods import CopyMessage` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: MessageId = await CopyMessage(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/create_chat_invite_link.rst b/docs/api/methods/create_chat_invite_link.rst index 787eded8..af2a47b5 100644 --- a/docs/api/methods/create_chat_invite_link.rst +++ b/docs/api/methods/create_chat_invite_link.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.create_chat_invite_link import CreateChatInviteLink` - alias: :code:`from aiogram.methods import CreateChatInviteLink` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: ChatInviteLink = await CreateChatInviteLink(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/create_invoice_link.rst b/docs/api/methods/create_invoice_link.rst new file mode 100644 index 00000000..5d25aae9 --- /dev/null +++ b/docs/api/methods/create_invoice_link.rst @@ -0,0 +1,44 @@ +################# +createInvoiceLink +################# + +Returns: :obj:`str` + +.. automodule:: aiogram.methods.create_invoice_link + :members: + :member-order: bysource + :undoc-members: True + + +Usage +===== + +As bot method +------------- + +.. code-block:: + + result: str = await bot.create_invoice_link(...) + + +Method as object +---------------- + +Imports: + +- :code:`from aiogram.methods.create_invoice_link import CreateInvoiceLink` +- alias: :code:`from aiogram.methods import CreateInvoiceLink` + +With specific bot +~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + result: str = await bot(CreateInvoiceLink(...)) + +As reply into Webhook in handler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + return CreateInvoiceLink(...) diff --git a/docs/api/methods/create_new_sticker_set.rst b/docs/api/methods/create_new_sticker_set.rst index 475c1c01..a118200b 100644 --- a/docs/api/methods/create_new_sticker_set.rst +++ b/docs/api/methods/create_new_sticker_set.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.create_new_sticker_set import CreateNewStickerSet` - alias: :code:`from aiogram.methods import CreateNewStickerSet` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await CreateNewStickerSet(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/decline_chat_join_request.rst b/docs/api/methods/decline_chat_join_request.rst index f367ec36..a0a82e99 100644 --- a/docs/api/methods/decline_chat_join_request.rst +++ b/docs/api/methods/decline_chat_join_request.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.decline_chat_join_request import DeclineChatJoinRequest` - alias: :code:`from aiogram.methods import DeclineChatJoinRequest` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await DeclineChatJoinRequest(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/delete_chat_photo.rst b/docs/api/methods/delete_chat_photo.rst index 637cef64..b80144d7 100644 --- a/docs/api/methods/delete_chat_photo.rst +++ b/docs/api/methods/delete_chat_photo.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.delete_chat_photo import DeleteChatPhoto` - alias: :code:`from aiogram.methods import DeleteChatPhoto` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await DeleteChatPhoto(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/delete_chat_sticker_set.rst b/docs/api/methods/delete_chat_sticker_set.rst index a06cc894..ef06d79d 100644 --- a/docs/api/methods/delete_chat_sticker_set.rst +++ b/docs/api/methods/delete_chat_sticker_set.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.delete_chat_sticker_set import DeleteChatStickerSet` - alias: :code:`from aiogram.methods import DeleteChatStickerSet` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await DeleteChatStickerSet(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/delete_message.rst b/docs/api/methods/delete_message.rst index 96b721e9..025954a8 100644 --- a/docs/api/methods/delete_message.rst +++ b/docs/api/methods/delete_message.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.delete_message import DeleteMessage` - alias: :code:`from aiogram.methods import DeleteMessage` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await DeleteMessage(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/delete_my_commands.rst b/docs/api/methods/delete_my_commands.rst index 5077f68c..58cfce2b 100644 --- a/docs/api/methods/delete_my_commands.rst +++ b/docs/api/methods/delete_my_commands.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.delete_my_commands import DeleteMyCommands` - alias: :code:`from aiogram.methods import DeleteMyCommands` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await DeleteMyCommands(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/delete_sticker_from_set.rst b/docs/api/methods/delete_sticker_from_set.rst index ac491710..a620464d 100644 --- a/docs/api/methods/delete_sticker_from_set.rst +++ b/docs/api/methods/delete_sticker_from_set.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.delete_sticker_from_set import DeleteStickerFromSet` - alias: :code:`from aiogram.methods import DeleteStickerFromSet` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await DeleteStickerFromSet(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/delete_webhook.rst b/docs/api/methods/delete_webhook.rst index b1f4d02c..b903296a 100644 --- a/docs/api/methods/delete_webhook.rst +++ b/docs/api/methods/delete_webhook.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.delete_webhook import DeleteWebhook` - alias: :code:`from aiogram.methods import DeleteWebhook` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await DeleteWebhook(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/edit_chat_invite_link.rst b/docs/api/methods/edit_chat_invite_link.rst index a9702e84..c72f1d10 100644 --- a/docs/api/methods/edit_chat_invite_link.rst +++ b/docs/api/methods/edit_chat_invite_link.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.edit_chat_invite_link import EditChatInviteLink` - alias: :code:`from aiogram.methods import EditChatInviteLink` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: ChatInviteLink = await EditChatInviteLink(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/edit_message_caption.rst b/docs/api/methods/edit_message_caption.rst index fb8a001a..f1a40a48 100644 --- a/docs/api/methods/edit_message_caption.rst +++ b/docs/api/methods/edit_message_caption.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.edit_message_caption import EditMessageCaption` - alias: :code:`from aiogram.methods import EditMessageCaption` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Union[Message, bool] = await EditMessageCaption(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/edit_message_live_location.rst b/docs/api/methods/edit_message_live_location.rst index 85022f03..3d9ec289 100644 --- a/docs/api/methods/edit_message_live_location.rst +++ b/docs/api/methods/edit_message_live_location.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.edit_message_live_location import EditMessageLiveLocation` - alias: :code:`from aiogram.methods import EditMessageLiveLocation` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Union[Message, bool] = await EditMessageLiveLocation(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/edit_message_media.rst b/docs/api/methods/edit_message_media.rst index 5e77467f..efd8a3cb 100644 --- a/docs/api/methods/edit_message_media.rst +++ b/docs/api/methods/edit_message_media.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.edit_message_media import EditMessageMedia` - alias: :code:`from aiogram.methods import EditMessageMedia` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Union[Message, bool] = await EditMessageMedia(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/edit_message_reply_markup.rst b/docs/api/methods/edit_message_reply_markup.rst index 34916d42..050af021 100644 --- a/docs/api/methods/edit_message_reply_markup.rst +++ b/docs/api/methods/edit_message_reply_markup.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.edit_message_reply_markup import EditMessageReplyMarkup` - alias: :code:`from aiogram.methods import EditMessageReplyMarkup` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Union[Message, bool] = await EditMessageReplyMarkup(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/edit_message_text.rst b/docs/api/methods/edit_message_text.rst index 32645b94..f75e944e 100644 --- a/docs/api/methods/edit_message_text.rst +++ b/docs/api/methods/edit_message_text.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.edit_message_text import EditMessageText` - alias: :code:`from aiogram.methods import EditMessageText` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Union[Message, bool] = await EditMessageText(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/export_chat_invite_link.rst b/docs/api/methods/export_chat_invite_link.rst index ea8c6fcd..90c02f36 100644 --- a/docs/api/methods/export_chat_invite_link.rst +++ b/docs/api/methods/export_chat_invite_link.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.export_chat_invite_link import ExportChatInviteLink` - alias: :code:`from aiogram.methods import ExportChatInviteLink` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: str = await ExportChatInviteLink(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/forward_message.rst b/docs/api/methods/forward_message.rst index fcf8fd59..2b02dfdc 100644 --- a/docs/api/methods/forward_message.rst +++ b/docs/api/methods/forward_message.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.forward_message import ForwardMessage` - alias: :code:`from aiogram.methods import ForwardMessage` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await ForwardMessage(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_chat.rst b/docs/api/methods/get_chat.rst index 79124ed2..dbc1143c 100644 --- a/docs/api/methods/get_chat.rst +++ b/docs/api/methods/get_chat.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_chat import GetChat` - alias: :code:`from aiogram.methods import GetChat` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Chat = await GetChat(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_chat_administrators.rst b/docs/api/methods/get_chat_administrators.rst index 558c948a..04f2b41b 100644 --- a/docs/api/methods/get_chat_administrators.rst +++ b/docs/api/methods/get_chat_administrators.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_chat_administrators import GetChatAdministrators` - alias: :code:`from aiogram.methods import GetChatAdministrators` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: List[Union[ChatMemberOwner, ChatMemberAdministrator, ChatMemberMember, ChatMemberRestricted, ChatMemberLeft, ChatMemberBanned]] = await GetChatAdministrators(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_chat_member.rst b/docs/api/methods/get_chat_member.rst index 13b58321..46ee5c04 100644 --- a/docs/api/methods/get_chat_member.rst +++ b/docs/api/methods/get_chat_member.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_chat_member import GetChatMember` - alias: :code:`from aiogram.methods import GetChatMember` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Union[ChatMemberOwner, ChatMemberAdministrator, ChatMemberMember, ChatMemberRestricted, ChatMemberLeft, ChatMemberBanned] = await GetChatMember(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_chat_member_count.rst b/docs/api/methods/get_chat_member_count.rst index 2bee4cd9..43858d92 100644 --- a/docs/api/methods/get_chat_member_count.rst +++ b/docs/api/methods/get_chat_member_count.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_chat_member_count import GetChatMemberCount` - alias: :code:`from aiogram.methods import GetChatMemberCount` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: int = await GetChatMemberCount(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_chat_members_count.rst b/docs/api/methods/get_chat_members_count.rst index 6286e845..1a11da5b 100644 --- a/docs/api/methods/get_chat_members_count.rst +++ b/docs/api/methods/get_chat_members_count.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_chat_members_count import GetChatMembersCount` - alias: :code:`from aiogram.methods import GetChatMembersCount` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: int = await GetChatMembersCount(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_chat_menu_button.rst b/docs/api/methods/get_chat_menu_button.rst index 8e3df76a..85a6c6a3 100644 --- a/docs/api/methods/get_chat_menu_button.rst +++ b/docs/api/methods/get_chat_menu_button.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_chat_menu_button import GetChatMenuButton` - alias: :code:`from aiogram.methods import GetChatMenuButton` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: MenuButton = await GetChatMenuButton(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_file.rst b/docs/api/methods/get_file.rst index ee96326b..eb9f3b7d 100644 --- a/docs/api/methods/get_file.rst +++ b/docs/api/methods/get_file.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_file import GetFile` - alias: :code:`from aiogram.methods import GetFile` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: File = await GetFile(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_game_high_scores.rst b/docs/api/methods/get_game_high_scores.rst index 4fb1f06e..d57d781d 100644 --- a/docs/api/methods/get_game_high_scores.rst +++ b/docs/api/methods/get_game_high_scores.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_game_high_scores import GetGameHighScores` - alias: :code:`from aiogram.methods import GetGameHighScores` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: List[GameHighScore] = await GetGameHighScores(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_me.rst b/docs/api/methods/get_me.rst index 7ae9d580..cf883046 100644 --- a/docs/api/methods/get_me.rst +++ b/docs/api/methods/get_me.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_me import GetMe` - alias: :code:`from aiogram.methods import GetMe` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: User = await GetMe(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_my_commands.rst b/docs/api/methods/get_my_commands.rst index 9dba9fe6..a67f1d88 100644 --- a/docs/api/methods/get_my_commands.rst +++ b/docs/api/methods/get_my_commands.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_my_commands import GetMyCommands` - alias: :code:`from aiogram.methods import GetMyCommands` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: List[BotCommand] = await GetMyCommands(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_my_default_administrator_rights.rst b/docs/api/methods/get_my_default_administrator_rights.rst index d73c54f8..5d28e41f 100644 --- a/docs/api/methods/get_my_default_administrator_rights.rst +++ b/docs/api/methods/get_my_default_administrator_rights.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_my_default_administrator_rights import GetMyDefaultAdministratorRights` - alias: :code:`from aiogram.methods import GetMyDefaultAdministratorRights` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: ChatAdministratorRights = await GetMyDefaultAdministratorRights(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_sticker_set.rst b/docs/api/methods/get_sticker_set.rst index a1feb38c..2b131b21 100644 --- a/docs/api/methods/get_sticker_set.rst +++ b/docs/api/methods/get_sticker_set.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_sticker_set import GetStickerSet` - alias: :code:`from aiogram.methods import GetStickerSet` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: StickerSet = await GetStickerSet(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_updates.rst b/docs/api/methods/get_updates.rst index 6ff33d31..7d14ee1e 100644 --- a/docs/api/methods/get_updates.rst +++ b/docs/api/methods/get_updates.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_updates import GetUpdates` - alias: :code:`from aiogram.methods import GetUpdates` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: List[Update] = await GetUpdates(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_user_profile_photos.rst b/docs/api/methods/get_user_profile_photos.rst index dab76989..3ab9fe88 100644 --- a/docs/api/methods/get_user_profile_photos.rst +++ b/docs/api/methods/get_user_profile_photos.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_user_profile_photos import GetUserProfilePhotos` - alias: :code:`from aiogram.methods import GetUserProfilePhotos` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: UserProfilePhotos = await GetUserProfilePhotos(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/get_webhook_info.rst b/docs/api/methods/get_webhook_info.rst index 519e1207..d038a261 100644 --- a/docs/api/methods/get_webhook_info.rst +++ b/docs/api/methods/get_webhook_info.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.get_webhook_info import GetWebhookInfo` - alias: :code:`from aiogram.methods import GetWebhookInfo` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: WebhookInfo = await GetWebhookInfo(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/index.rst b/docs/api/methods/index.rst index 6c778282..0fa45e7c 100644 --- a/docs/api/methods/index.rst +++ b/docs/api/methods/index.rst @@ -131,6 +131,7 @@ Payments :maxdepth: 1 send_invoice + create_invoice_link answer_shipping_query answer_pre_checkout_query diff --git a/docs/api/methods/kick_chat_member.rst b/docs/api/methods/kick_chat_member.rst index da73a535..5c80b773 100644 --- a/docs/api/methods/kick_chat_member.rst +++ b/docs/api/methods/kick_chat_member.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.kick_chat_member import KickChatMember` - alias: :code:`from aiogram.methods import KickChatMember` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await KickChatMember(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/leave_chat.rst b/docs/api/methods/leave_chat.rst index a78dce7e..c88ee520 100644 --- a/docs/api/methods/leave_chat.rst +++ b/docs/api/methods/leave_chat.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.leave_chat import LeaveChat` - alias: :code:`from aiogram.methods import LeaveChat` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await LeaveChat(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/log_out.rst b/docs/api/methods/log_out.rst index 0049c3f2..683cf19a 100644 --- a/docs/api/methods/log_out.rst +++ b/docs/api/methods/log_out.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.log_out import LogOut` - alias: :code:`from aiogram.methods import LogOut` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await LogOut(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/pin_chat_message.rst b/docs/api/methods/pin_chat_message.rst index 717f86a1..ec730278 100644 --- a/docs/api/methods/pin_chat_message.rst +++ b/docs/api/methods/pin_chat_message.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.pin_chat_message import PinChatMessage` - alias: :code:`from aiogram.methods import PinChatMessage` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await PinChatMessage(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/promote_chat_member.rst b/docs/api/methods/promote_chat_member.rst index 67bf3671..e3081d97 100644 --- a/docs/api/methods/promote_chat_member.rst +++ b/docs/api/methods/promote_chat_member.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.promote_chat_member import PromoteChatMember` - alias: :code:`from aiogram.methods import PromoteChatMember` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await PromoteChatMember(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/restrict_chat_member.rst b/docs/api/methods/restrict_chat_member.rst index a93c4c92..0cf2a5af 100644 --- a/docs/api/methods/restrict_chat_member.rst +++ b/docs/api/methods/restrict_chat_member.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.restrict_chat_member import RestrictChatMember` - alias: :code:`from aiogram.methods import RestrictChatMember` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await RestrictChatMember(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/revoke_chat_invite_link.rst b/docs/api/methods/revoke_chat_invite_link.rst index 530133f8..d643c901 100644 --- a/docs/api/methods/revoke_chat_invite_link.rst +++ b/docs/api/methods/revoke_chat_invite_link.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.revoke_chat_invite_link import RevokeChatInviteLink` - alias: :code:`from aiogram.methods import RevokeChatInviteLink` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: ChatInviteLink = await RevokeChatInviteLink(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_animation.rst b/docs/api/methods/send_animation.rst index 2e6284b5..4372fc33 100644 --- a/docs/api/methods/send_animation.rst +++ b/docs/api/methods/send_animation.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_animation import SendAnimation` - alias: :code:`from aiogram.methods import SendAnimation` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendAnimation(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_audio.rst b/docs/api/methods/send_audio.rst index ce023e84..8aa40c8f 100644 --- a/docs/api/methods/send_audio.rst +++ b/docs/api/methods/send_audio.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_audio import SendAudio` - alias: :code:`from aiogram.methods import SendAudio` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendAudio(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_contact.rst b/docs/api/methods/send_contact.rst index 48cae668..ce576007 100644 --- a/docs/api/methods/send_contact.rst +++ b/docs/api/methods/send_contact.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_contact import SendContact` - alias: :code:`from aiogram.methods import SendContact` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendContact(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_dice.rst b/docs/api/methods/send_dice.rst index 2774fd6d..dbb0fd05 100644 --- a/docs/api/methods/send_dice.rst +++ b/docs/api/methods/send_dice.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_dice import SendDice` - alias: :code:`from aiogram.methods import SendDice` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendDice(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_document.rst b/docs/api/methods/send_document.rst index 5134bcde..1fb86b4e 100644 --- a/docs/api/methods/send_document.rst +++ b/docs/api/methods/send_document.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_document import SendDocument` - alias: :code:`from aiogram.methods import SendDocument` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendDocument(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_game.rst b/docs/api/methods/send_game.rst index 149e3131..21cc7a8a 100644 --- a/docs/api/methods/send_game.rst +++ b/docs/api/methods/send_game.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_game import SendGame` - alias: :code:`from aiogram.methods import SendGame` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendGame(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_invoice.rst b/docs/api/methods/send_invoice.rst index 90132a46..e7fa0904 100644 --- a/docs/api/methods/send_invoice.rst +++ b/docs/api/methods/send_invoice.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_invoice import SendInvoice` - alias: :code:`from aiogram.methods import SendInvoice` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendInvoice(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_location.rst b/docs/api/methods/send_location.rst index 769d6724..d4dbfeb2 100644 --- a/docs/api/methods/send_location.rst +++ b/docs/api/methods/send_location.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_location import SendLocation` - alias: :code:`from aiogram.methods import SendLocation` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendLocation(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_media_group.rst b/docs/api/methods/send_media_group.rst index c962e049..b57701d7 100644 --- a/docs/api/methods/send_media_group.rst +++ b/docs/api/methods/send_media_group.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_media_group import SendMediaGroup` - alias: :code:`from aiogram.methods import SendMediaGroup` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: List[Message] = await SendMediaGroup(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_message.rst b/docs/api/methods/send_message.rst index c054b382..4f72f163 100644 --- a/docs/api/methods/send_message.rst +++ b/docs/api/methods/send_message.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_message import SendMessage` - alias: :code:`from aiogram.methods import SendMessage` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendMessage(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_photo.rst b/docs/api/methods/send_photo.rst index f6dfd0f5..38d54218 100644 --- a/docs/api/methods/send_photo.rst +++ b/docs/api/methods/send_photo.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_photo import SendPhoto` - alias: :code:`from aiogram.methods import SendPhoto` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendPhoto(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_poll.rst b/docs/api/methods/send_poll.rst index 7261399b..29e0a791 100644 --- a/docs/api/methods/send_poll.rst +++ b/docs/api/methods/send_poll.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_poll import SendPoll` - alias: :code:`from aiogram.methods import SendPoll` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendPoll(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_sticker.rst b/docs/api/methods/send_sticker.rst index 9c27417b..4a1212e8 100644 --- a/docs/api/methods/send_sticker.rst +++ b/docs/api/methods/send_sticker.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_sticker import SendSticker` - alias: :code:`from aiogram.methods import SendSticker` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendSticker(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_venue.rst b/docs/api/methods/send_venue.rst index f87100de..5a8e0d3c 100644 --- a/docs/api/methods/send_venue.rst +++ b/docs/api/methods/send_venue.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_venue import SendVenue` - alias: :code:`from aiogram.methods import SendVenue` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendVenue(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_video.rst b/docs/api/methods/send_video.rst index 8fe6ed40..2ee8a3dd 100644 --- a/docs/api/methods/send_video.rst +++ b/docs/api/methods/send_video.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_video import SendVideo` - alias: :code:`from aiogram.methods import SendVideo` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendVideo(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_video_note.rst b/docs/api/methods/send_video_note.rst index f10f534e..d53d91e0 100644 --- a/docs/api/methods/send_video_note.rst +++ b/docs/api/methods/send_video_note.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_video_note import SendVideoNote` - alias: :code:`from aiogram.methods import SendVideoNote` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendVideoNote(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/send_voice.rst b/docs/api/methods/send_voice.rst index b3583bb1..397a4e75 100644 --- a/docs/api/methods/send_voice.rst +++ b/docs/api/methods/send_voice.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.send_voice import SendVoice` - alias: :code:`from aiogram.methods import SendVoice` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: Message = await SendVoice(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/set_chat_administrator_custom_title.rst b/docs/api/methods/set_chat_administrator_custom_title.rst index ca73df29..7b3deaf0 100644 --- a/docs/api/methods/set_chat_administrator_custom_title.rst +++ b/docs/api/methods/set_chat_administrator_custom_title.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.set_chat_administrator_custom_title import SetChatAdministratorCustomTitle` - alias: :code:`from aiogram.methods import SetChatAdministratorCustomTitle` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await SetChatAdministratorCustomTitle(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/set_chat_description.rst b/docs/api/methods/set_chat_description.rst index 0625516c..2cb8f08e 100644 --- a/docs/api/methods/set_chat_description.rst +++ b/docs/api/methods/set_chat_description.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.set_chat_description import SetChatDescription` - alias: :code:`from aiogram.methods import SetChatDescription` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await SetChatDescription(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/set_chat_menu_button.rst b/docs/api/methods/set_chat_menu_button.rst index 6a60fae9..671313fe 100644 --- a/docs/api/methods/set_chat_menu_button.rst +++ b/docs/api/methods/set_chat_menu_button.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.set_chat_menu_button import SetChatMenuButton` - alias: :code:`from aiogram.methods import SetChatMenuButton` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await SetChatMenuButton(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/set_chat_permissions.rst b/docs/api/methods/set_chat_permissions.rst index b287a7e2..8e546011 100644 --- a/docs/api/methods/set_chat_permissions.rst +++ b/docs/api/methods/set_chat_permissions.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.set_chat_permissions import SetChatPermissions` - alias: :code:`from aiogram.methods import SetChatPermissions` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await SetChatPermissions(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/docs/api/methods/set_chat_photo.rst b/docs/api/methods/set_chat_photo.rst index 536696e8..99ff36c2 100644 --- a/docs/api/methods/set_chat_photo.rst +++ b/docs/api/methods/set_chat_photo.rst @@ -29,13 +29,6 @@ Imports: - :code:`from aiogram.methods.set_chat_photo import SetChatPhoto` - alias: :code:`from aiogram.methods import SetChatPhoto` -In handlers with current bot ----------------------------- - -.. code-block:: python - - result: bool = await SetChatPhoto(...) - With specific bot ~~~~~~~~~~~~~~~~~ diff --git a/poetry.lock b/poetry.lock index e3ebed69..50ccb364 100644 --- a/poetry.lock +++ b/poetry.lock @@ -58,14 +58,6 @@ category = "main" optional = true python-versions = "*" -[[package]] -name = "appnope" -version = "0.1.3" -description = "Disable App Nap on macOS >= 10.9" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "aresponses" version = "2.1.5" @@ -78,20 +70,6 @@ python-versions = ">=3.6" aiohttp = ">=3.1.0,<4.0.0" pytest-asyncio = "*" -[[package]] -name = "asttokens" -version = "2.0.5" -description = "Annotate AST trees with source code positions" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -six = "*" - -[package.extras] -test = ["astroid", "pytest"] - [[package]] name = "async-timeout" version = "4.0.2" @@ -141,14 +119,6 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] pytz = ">=2015.7" -[[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "beautifulsoup4" version = "4.11.1" @@ -191,7 +161,7 @@ name = "certifi" version = "2021.10.8" description = "Python package for providing Mozilla's CA Bundle." category = "main" -optional = false +optional = true python-versions = "*" [[package]] @@ -245,26 +215,18 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "coverage" -version = "6.3.2" +version = "6.4.1" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -category = "dev" -optional = false -python-versions = ">=3.5" - [[package]] name = "deprecated" version = "1.2.13" @@ -295,14 +257,6 @@ category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -[[package]] -name = "executing" -version = "0.8.3" -description = "Get the currently executing AST node of a frame, and other information" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "filelock" version = "3.6.0" @@ -338,16 +292,17 @@ python-versions = ">=3.7" [[package]] name = "furo" -version = "2022.4.7" +version = "2022.6.21" description = "A clean customisable Sphinx documentation theme." category = "main" optional = true -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] beautifulsoup4 = "*" -pygments = ">=2.7,<3.0" -sphinx = ">=4.0,<5.0" +pygments = "*" +sphinx = ">=4.0,<6.0" +sphinx-basic-ng = "*" [[package]] name = "identify" @@ -411,41 +366,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "ipython" -version = "8.2.0" -description = "IPython: Productive Interactive Computing" -category = "dev" -optional = false -python-versions = ">=3.8" - -[package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" -prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" -pygments = ">=2.4.0" -stack-data = "*" -traitlets = ">=5" - -[package.extras] -all = ["black", "Sphinx (>=1.3)", "ipykernel", "nbconvert", "nbformat", "ipywidgets", "notebook", "ipyparallel", "qtconsole", "pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "numpy (>=1.19)", "pandas", "trio"] -black = ["black"] -doc = ["Sphinx (>=1.3)"] -kernel = ["ipykernel"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] -test_extra = ["pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "trio"] - [[package]] name = "isort" version = "5.10.1" @@ -460,21 +380,6 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"] colors = ["colorama (>=0.4.3,<0.5.0)"] plugins = ["setuptools"] -[[package]] -name = "jedi" -version = "0.18.1" -description = "An autocompletion tool for Python that can be used for text editors." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -parso = ">=0.8.0,<0.9.0" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] - [[package]] name = "jinja2" version = "3.1.1" @@ -542,17 +447,6 @@ category = "main" optional = true python-versions = ">=3.7" -[[package]] -name = "matplotlib-inline" -version = "0.1.3" -description = "Inline Matplotlib backend for Jupyter" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -traitlets = "*" - [[package]] name = "mccabe" version = "0.6.1" @@ -571,7 +465,7 @@ python-versions = ">=3.7" [[package]] name = "mypy" -version = "0.942" +version = "0.961" description = "Optional static typing for Python" category = "dev" optional = false @@ -579,7 +473,7 @@ python-versions = ">=3.6" [package.dependencies] mypy-extensions = ">=0.4.3" -tomli = ">=1.1.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = ">=3.10" [package.extras] @@ -614,18 +508,6 @@ python-versions = ">=3.6" [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" -[[package]] -name = "parso" -version = "0.8.3" -description = "A Python Parser" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] - [[package]] name = "pathspec" version = "0.9.0" @@ -634,25 +516,6 @@ category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -[[package]] -name = "pexpect" -version = "4.8.0" -description = "Pexpect allows easy control of interactive console applications." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "platformdirs" version = "2.5.1" @@ -679,7 +542,7 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "2.18.1" +version = "2.19.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false @@ -693,36 +556,6 @@ pyyaml = ">=5.1" toml = "*" virtualenv = ">=20.0.8" -[[package]] -name = "prompt-toolkit" -version = "3.0.29" -description = "Library for building powerful interactive command lines in Python" -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "pure-eval" -version = "0.2.2" -description = "Safely evaluate AST nodes without side effects" -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -tests = ["pytest"] - [[package]] name = "py" version = "1.11.0" @@ -741,8 +574,8 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pydantic" -version = "1.9.0" -description = "Data validation and settings management using python 3.6 type hinting" +version = "1.9.1" +description = "Data validation and settings management using python type hints" category = "main" optional = false python-versions = ">=3.6.1" @@ -764,22 +597,22 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pygments" -version = "2.11.2" +version = "2.12.0" description = "Pygments is a syntax highlighting package written in Python." category = "main" -optional = false -python-versions = ">=3.5" +optional = true +python-versions = ">=3.6" [[package]] name = "pymdown-extensions" -version = "9.3" +version = "9.5" description = "Extension pack for Python Markdown." category = "main" optional = true python-versions = ">=3.7" [package.dependencies] -Markdown = ">=3.2" +markdown = ">=3.2" [[package]] name = "pyparsing" @@ -894,7 +727,7 @@ pytest = ">=7.1.1,<8.0.0" [[package]] name = "pytest-mock" -version = "3.7.0" +version = "3.8.1" description = "Thin-wrapper around the mock package for easier use with pytest" category = "dev" optional = false @@ -961,7 +794,7 @@ python-versions = ">=3.6" [[package]] name = "redis" -version = "4.2.2" +version = "4.3.3" description = "Python client for Redis database and key-value store" category = "main" optional = true @@ -994,36 +827,6 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] -[[package]] -name = "sentry-sdk" -version = "1.5.8" -description = "Python client for Sentry (https://sentry.io)" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -certifi = "*" -urllib3 = ">=1.10.0" - -[package.extras] -aiohttp = ["aiohttp (>=3.5)"] -beam = ["apache-beam (>=2.12)"] -bottle = ["bottle (>=0.12.13)"] -celery = ["celery (>=3)"] -chalice = ["chalice (>=1.16.0)"] -django = ["django (>=1.8)"] -falcon = ["falcon (>=1.4)"] -flask = ["flask (>=0.11)", "blinker (>=1.1)"] -httpx = ["httpx (>=0.16.0)"] -pure_eval = ["pure-eval", "executing", "asttokens"] -pyspark = ["pyspark (>=2.4.4)"] -quart = ["quart (>=0.16.1)", "blinker (>=1.1)"] -rq = ["rq (>=0.6)"] -sanic = ["sanic (>=0.8)"] -sqlalchemy = ["sqlalchemy (>=1.2)"] -tornado = ["tornado (>=5)"] - [[package]] name = "six" version = "1.16.0" @@ -1096,6 +899,20 @@ sphinx = "*" [package.extras] test = ["pytest", "pytest-cov"] +[[package]] +name = "sphinx-basic-ng" +version = "0.0.1a11" +description = "A modern skeleton for Sphinx themes." +category = "main" +optional = true +python-versions = ">=3.7" + +[package.dependencies] +sphinx = ">=4.0,<6.0" + +[package.extras] +docs = ["furo", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs", "ipython"] + [[package]] name = "sphinx-copybutton" version = "0.5.0" @@ -1227,22 +1044,6 @@ python-versions = ">=3.5" lint = ["flake8", "mypy", "docutils-stubs"] test = ["pytest"] -[[package]] -name = "stack-data" -version = "0.2.0" -description = "Extract data from python stack frames and tracebacks for informative displays" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -asttokens = "*" -executing = "*" -pure-eval = "*" - -[package.extras] -tests = ["pytest", "typeguard", "pygments", "littleutils", "cython"] - [[package]] name = "toml" version = "0.10.2" @@ -1285,31 +1086,20 @@ tomli = {version = "*", markers = "python_version >= \"3.6\""} [package.extras] dev = ["packaging"] -[[package]] -name = "traitlets" -version = "5.1.1" -description = "Traitlets Python configuration system" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -test = ["pytest"] - [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "urllib3" version = "1.26.9" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] @@ -1348,14 +1138,6 @@ six = ">=1.9.0,<2" docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] 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" -version = "0.2.5" -description = "Measures the displayed width of unicode strings in a terminal" -category = "dev" -optional = false -python-versions = "*" - [[package]] name = "wrapt" version = "1.14.0" @@ -1398,7 +1180,7 @@ redis = ["redis"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "d3a0b716a9dcb5353de9ef0587a49025da4351eb68a4327c0b3c8a24791251ee" +content-hash = "f236269d4c2c6589474cb1bd028ef42ebbb424bc77062db6c16f0a5b6e542d4c" [metadata.files] aiofiles = [ @@ -1491,18 +1273,10 @@ alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -appnope = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, -] aresponses = [ {file = "aresponses-2.1.5-py3-none-any.whl", hash = "sha256:06161209a39880aaf8ec3c67ab76d677aaea41944672e6a3e23a4464544879b1"}, {file = "aresponses-2.1.5.tar.gz", hash = "sha256:16535e5d24302eab194e15edd18b9e126e1fb70cd84049e63eb6b15c89e16936"}, ] -asttokens = [ - {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, - {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, -] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1523,10 +1297,6 @@ babel = [ {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, ] -backcall = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] beautifulsoup4 = [ {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, @@ -1580,51 +1350,47 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] coverage = [ - {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, - {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, - {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, - {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, - {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, - {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, - {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, - {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, - {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, - {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, - {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, - {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, - {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, - {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, - {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, - {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, - {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, - {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, - {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, - {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, - {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, - {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, - {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, - {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, - {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, - {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, - {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, - {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, - {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, - {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, - {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, - {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, - {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, - {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, - {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, - {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, - {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, - {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, - {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, - {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, - {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, -] -decorator = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1d5aa2703e1dab4ae6cf416eb0095304f49d004c39e9db1d86f57924f43006b"}, + {file = "coverage-6.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ce1b258493cbf8aec43e9b50d89982346b98e9ffdfaae8ae5793bc112fb0068"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c4e737f60c6936460c5be330d296dd5b48b3963f48634c53b3f7deb0f34ec4"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e65ef149028516c6d64461b95a8dbcfce95cfd5b9eb634320596173332ea84"}, + {file = "coverage-6.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f69718750eaae75efe506406c490d6fc5a6161d047206cc63ce25527e8a3adad"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e57816f8ffe46b1df8f12e1b348f06d164fd5219beba7d9433ba79608ef011cc"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:01c5615d13f3dd3aa8543afc069e5319cfa0c7d712f6e04b920431e5c564a749"}, + {file = "coverage-6.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ab269400706fab15981fd4bd5080c56bd5cc07c3bccb86aab5e1d5a88dc8f4"}, + {file = "coverage-6.4.1-cp310-cp310-win32.whl", hash = "sha256:a7f3049243783df2e6cc6deafc49ea123522b59f464831476d3d1448e30d72df"}, + {file = "coverage-6.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2ddcac99b2d2aec413e36d7a429ae9ebcadf912946b13ffa88e7d4c9b712d6"}, + {file = "coverage-6.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb73e0011b8793c053bfa85e53129ba5f0250fdc0392c1591fd35d915ec75c46"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106c16dfe494de3193ec55cac9640dd039b66e196e4641fa8ac396181578b982"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f4f3df85aa39da00fd3ec4b5abeb7407e82b68c7c5ad181308b0e2526da5d4"}, + {file = "coverage-6.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:961e2fb0680b4f5ad63234e0bf55dfb90d302740ae9c7ed0120677a94a1590cb"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cec3a0f75c8f1031825e19cd86ee787e87cf03e4fd2865c79c057092e69e3a3b"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:129cd05ba6f0d08a766d942a9ed4b29283aff7b2cccf5b7ce279d50796860bb3"}, + {file = "coverage-6.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bf5601c33213d3cb19d17a796f8a14a9eaa5e87629a53979a5981e3e3ae166f6"}, + {file = "coverage-6.4.1-cp37-cp37m-win32.whl", hash = "sha256:269eaa2c20a13a5bf17558d4dc91a8d078c4fa1872f25303dddcbba3a813085e"}, + {file = "coverage-6.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f02cbbf8119db68455b9d763f2f8737bb7db7e43720afa07d8eb1604e5c5ae28"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffa9297c3a453fba4717d06df579af42ab9a28022444cae7fa605af4df612d54"}, + {file = "coverage-6.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:145f296d00441ca703a659e8f3eb48ae39fb083baba2d7ce4482fb2723e050d9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d44996140af8b84284e5e7d398e589574b376fb4de8ccd28d82ad8e3bea13"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bd9a6fc18aab8d2e18f89b7ff91c0f34ff4d5e0ba0b33e989b3cd4194c81fd9"}, + {file = "coverage-6.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384f2a3652cef289e38100f2d037956194a837221edd520a7ee5b42d00cc605"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b3e07152b4563722be523e8cd0b209e0d1a373022cfbde395ebb6575bf6790d"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1480ff858b4113db2718848d7b2d1b75bc79895a9c22e76a221b9d8d62496428"}, + {file = "coverage-6.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:865d69ae811a392f4d06bde506d531f6a28a00af36f5c8649684a9e5e4a85c83"}, + {file = "coverage-6.4.1-cp38-cp38-win32.whl", hash = "sha256:664a47ce62fe4bef9e2d2c430306e1428ecea207ffd68649e3b942fa8ea83b0b"}, + {file = "coverage-6.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:26dff09fb0d82693ba9e6231248641d60ba606150d02ed45110f9ec26404ed1c"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9c80df769f5ec05ad21ea34be7458d1dc51ff1fb4b2219e77fe24edf462d6df"}, + {file = "coverage-6.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39ee53946bf009788108b4dd2894bf1349b4e0ca18c2016ffa7d26ce46b8f10d"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5b66caa62922531059bc5ac04f836860412f7f88d38a476eda0a6f11d4724f4"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd180ed867e289964404051a958f7cccabdeed423f91a899829264bb7974d3d3"}, + {file = "coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c08da0bd238f2970230c2a0d28ff0e99961598cb2e810245d7fc5afcf1254e8"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d42c549a8f41dc103a8004b9f0c433e2086add8a719da00e246e17cbe4056f72"}, + {file = "coverage-6.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:309ce4a522ed5fca432af4ebe0f32b21d6d7ccbb0f5fcc99290e71feba67c264"}, + {file = "coverage-6.4.1-cp39-cp39-win32.whl", hash = "sha256:fdb6f7bd51c2d1714cea40718f6149ad9be6a2ee7d93b19e9f00934c0f2a74d9"}, + {file = "coverage-6.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:342d4aefd1c3e7f620a13f4fe563154d808b69cccef415415aece4c786665397"}, + {file = "coverage-6.4.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:4803e7ccf93230accb928f3a68f00ffa80a88213af98ed338a57ad021ef06815"}, + {file = "coverage-6.4.1.tar.gz", hash = "sha256:4321f075095a096e70aff1d002030ee612b65a205a0a0f5b815280d5dc58100c"}, ] deprecated = [ {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, @@ -1638,10 +1404,6 @@ docutils = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] -executing = [ - {file = "executing-0.8.3-py2.py3-none-any.whl", hash = "sha256:d1eef132db1b83649a3905ca6dd8897f71ac6f8cac79a7e58a1a09cf137546c9"}, - {file = "executing-0.8.3.tar.gz", hash = "sha256:c6554e21c6b060590a6d3be4b82fb78f8f0194d809de5ea7df1c093763311501"}, -] filelock = [ {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, @@ -1712,8 +1474,8 @@ frozenlist = [ {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, ] furo = [ - {file = "furo-2022.4.7-py3-none-any.whl", hash = "sha256:7f3e3d2fb977483590f8ecb2c2cd511bd82661b79c18efb24de9558bc9cdf2d7"}, - {file = "furo-2022.4.7.tar.gz", hash = "sha256:96204ab7cd047e4b6c523996e0279c4c629a8fc31f4f109b2efd470c17f49c80"}, + {file = "furo-2022.6.21-py3-none-any.whl", hash = "sha256:061b68e323345e27fcba024cf33a1e77f3dfd8d9987410be822749a706e2add6"}, + {file = "furo-2022.6.21.tar.gz", hash = "sha256:9aa983b7488a4601d13113884bfb7254502c8729942e073a0acb87a5512af223"}, ] identify = [ {file = "identify-2.4.12-py2.py3-none-any.whl", hash = "sha256:5f06b14366bd1facb88b00540a1de05b69b310cbc2654db3c7e07fa3a4339323"}, @@ -1739,18 +1501,10 @@ iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] -ipython = [ - {file = "ipython-8.2.0-py3-none-any.whl", hash = "sha256:1b672bfd7a48d87ab203d9af8727a3b0174a4566b4091e9447c22fb63ea32857"}, - {file = "ipython-8.2.0.tar.gz", hash = "sha256:70e5eb132cac594a34b5f799bd252589009905f05104728aea6a403ec2519dc1"}, -] isort = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, ] -jedi = [ - {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, - {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, -] jinja2 = [ {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, @@ -1811,10 +1565,6 @@ markupsafe = [ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] -matplotlib-inline = [ - {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"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, @@ -1881,29 +1631,29 @@ multidict = [ {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, ] mypy = [ - {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, - {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, - {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, - {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, - {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, - {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, - {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, - {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, - {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, - {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, - {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, - {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, - {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, - {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, - {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, - {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, - {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, - {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, - {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, - {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, - {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, - {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, - {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, + {file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"}, + {file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"}, + {file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"}, + {file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"}, + {file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"}, + {file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"}, + {file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"}, + {file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"}, + {file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"}, + {file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"}, + {file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"}, + {file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"}, + {file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"}, + {file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"}, + {file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"}, + {file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"}, + {file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"}, + {file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"}, + {file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"}, + {file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"}, + {file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"}, + {file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"}, + {file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"}, ] mypy-extensions = [ {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, @@ -1917,22 +1667,10 @@ packaging = [ {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] -parso = [ - {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, - {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, -] pathspec = [ {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"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, -] -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.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, @@ -1942,20 +1680,8 @@ pluggy = [ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] pre-commit = [ - {file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"}, - {file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"}, -] -prompt-toolkit = [ - {file = "prompt_toolkit-3.0.29-py3-none-any.whl", hash = "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752"}, - {file = "prompt_toolkit-3.0.29.tar.gz", hash = "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7"}, -] -ptyprocess = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] -pure-eval = [ - {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, - {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, + {file = "pre_commit-2.19.0-py2.py3-none-any.whl", hash = "sha256:10c62741aa5704faea2ad69cb550ca78082efe5697d6f04e5710c3c229afdd10"}, + {file = "pre_commit-2.19.0.tar.gz", hash = "sha256:4233a1e38621c87d9dda9808c6606d7e7ba0e087cd56d3fe03202a01d2919615"}, ] py = [ {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, @@ -1966,53 +1692,53 @@ pycodestyle = [ {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] pydantic = [ - {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"}, - {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"}, - {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"}, - {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"}, - {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"}, - {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"}, - {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"}, - {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"}, - {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"}, - {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"}, - {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"}, - {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"}, - {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"}, - {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"}, - {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"}, - {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"}, - {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"}, - {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"}, - {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"}, - {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"}, - {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"}, - {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"}, - {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"}, - {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"}, - {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"}, - {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"}, - {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"}, - {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"}, - {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"}, - {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"}, - {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"}, - {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"}, - {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"}, - {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"}, - {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"}, + {file = "pydantic-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8098a724c2784bf03e8070993f6d46aa2eeca031f8d8a048dff277703e6e193"}, + {file = "pydantic-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320c64dd876e45254bdd350f0179da737463eea41c43bacbee9d8c9d1021f11"}, + {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18f3e912f9ad1bdec27fb06b8198a2ccc32f201e24174cec1b3424dda605a310"}, + {file = "pydantic-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c11951b404e08b01b151222a1cb1a9f0a860a8153ce8334149ab9199cd198131"}, + {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8bc541a405423ce0e51c19f637050acdbdf8feca34150e0d17f675e72d119580"}, + {file = "pydantic-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e565a785233c2d03724c4dc55464559639b1ba9ecf091288dd47ad9c629433bd"}, + {file = "pydantic-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4a88dcd6ff8fd47c18b3a3709a89adb39a6373f4482e04c1b765045c7e282fd"}, + {file = "pydantic-1.9.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:447d5521575f18e18240906beadc58551e97ec98142266e521c34968c76c8761"}, + {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985ceb5d0a86fcaa61e45781e567a59baa0da292d5ed2e490d612d0de5796918"}, + {file = "pydantic-1.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059b6c1795170809103a1538255883e1983e5b831faea6558ef873d4955b4a74"}, + {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d12f96b5b64bec3f43c8e82b4aab7599d0157f11c798c9f9c528a72b9e0b339a"}, + {file = "pydantic-1.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:ae72f8098acb368d877b210ebe02ba12585e77bd0db78ac04a1ee9b9f5dd2166"}, + {file = "pydantic-1.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:79b485767c13788ee314669008d01f9ef3bc05db9ea3298f6a50d3ef596a154b"}, + {file = "pydantic-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:494f7c8537f0c02b740c229af4cb47c0d39840b829ecdcfc93d91dcbb0779892"}, + {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e"}, + {file = "pydantic-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:969dd06110cb780da01336b281f53e2e7eb3a482831df441fb65dd30403f4608"}, + {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:177071dfc0df6248fd22b43036f936cfe2508077a72af0933d0c1fa269b18537"}, + {file = "pydantic-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9bcf8b6e011be08fb729d110f3e22e654a50f8a826b0575c7196616780683380"}, + {file = "pydantic-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a955260d47f03df08acf45689bd163ed9df82c0e0124beb4251b1290fa7ae728"}, + {file = "pydantic-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ce157d979f742a915b75f792dbd6aa63b8eccaf46a1005ba03aa8a986bde34a"}, + {file = "pydantic-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0bf07cab5b279859c253d26a9194a8906e6f4a210063b84b433cf90a569de0c1"}, + {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d93d4e95eacd313d2c765ebe40d49ca9dd2ed90e5b37d0d421c597af830c195"}, + {file = "pydantic-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1542636a39c4892c4f4fa6270696902acb186a9aaeac6f6cf92ce6ae2e88564b"}, + {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a9af62e9b5b9bc67b2a195ebc2c2662fdf498a822d62f902bf27cccb52dbbf49"}, + {file = "pydantic-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"}, + {file = "pydantic-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:9f659a5ee95c8baa2436d392267988fd0f43eb774e5eb8739252e5a7e9cf07e0"}, + {file = "pydantic-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b83ba3825bc91dfa989d4eed76865e71aea3a6ca1388b59fc801ee04c4d8d0d6"}, + {file = "pydantic-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1dd8fecbad028cd89d04a46688d2fcc14423e8a196d5b0a5c65105664901f810"}, + {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02eefd7087268b711a3ff4db528e9916ac9aa18616da7bca69c1871d0b7a091f"}, + {file = "pydantic-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb57ba90929bac0b6cc2af2373893d80ac559adda6933e562dcfb375029acee"}, + {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4ce9ae9e91f46c344bec3b03d6ee9612802682c1551aaf627ad24045ce090761"}, + {file = "pydantic-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72ccb318bf0c9ab97fc04c10c37683d9eea952ed526707fabf9ac5ae59b701fd"}, + {file = "pydantic-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:61b6760b08b7c395975d893e0b814a11cf011ebb24f7d869e7118f5a339a82e1"}, + {file = "pydantic-1.9.1-py3-none-any.whl", hash = "sha256:4988c0f13c42bfa9ddd2fe2f569c9d54646ce84adc5de84228cfe83396f3bd58"}, + {file = "pydantic-1.9.1.tar.gz", hash = "sha256:1ed987c3ff29fff7fd8c3ea3a3ea877ad310aae2ef9889a119e22d3f2db0691a"}, ] pyflakes = [ {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, ] pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, + {file = "Pygments-2.12.0-py3-none-any.whl", hash = "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519"}, + {file = "Pygments-2.12.0.tar.gz", hash = "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb"}, ] pymdown-extensions = [ - {file = "pymdown-extensions-9.3.tar.gz", hash = "sha256:a80553b243d3ed2d6c27723bcd64ca9887e560e6f4808baa96f36e93061eaf90"}, - {file = "pymdown_extensions-9.3-py3-none-any.whl", hash = "sha256:b37461a181c1c8103cfe1660081726a0361a8294cbfda88e5b02cefe976f0546"}, + {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"}, + {file = "pymdown_extensions-9.5.tar.gz", hash = "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0"}, ] pyparsing = [ {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, @@ -2048,8 +1774,8 @@ pytest-metadata = [ {file = "pytest_metadata-2.0.0-py3-none-any.whl", hash = "sha256:e25f1a77ed02baf1d83911604247a70d60d7dcb970aa12be38e1ed58d4d38e65"}, ] pytest-mock = [ - {file = "pytest-mock-3.7.0.tar.gz", hash = "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534"}, - {file = "pytest_mock-3.7.0-py3-none-any.whl", hash = "sha256:6cff27cec936bf81dc5ee87f07132b807bcda51106b5ec4b90a04331cba76231"}, + {file = "pytest-mock-3.8.1.tar.gz", hash = "sha256:2c6d756d5d3bf98e2e80797a959ca7f81f479e7d1f5f571611b0fdd6d1745240"}, + {file = "pytest_mock-3.8.1-py3-none-any.whl", hash = "sha256:d989f11ca4a84479e288b0cd1e6769d6ad0d3d7743dcc75e460d1416a5f2135a"}, ] pytest-mypy = [ {file = "pytest-mypy-0.9.1.tar.gz", hash = "sha256:9ffa3bf405c12c5c6be9e92e22bebb6ab2c91b9c32f45b0f0c93af473269ab5c"}, @@ -2099,17 +1825,13 @@ pyyaml = [ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] redis = [ - {file = "redis-4.2.2-py3-none-any.whl", hash = "sha256:4e95f4ec5f49e636efcf20061a5a9110c20852f607cfca6865c07aaa8a739ee2"}, - {file = "redis-4.2.2.tar.gz", hash = "sha256:0107dc8e98a4f1d1d4aa00100e044287f77121a1e6d2085545c4b7fa94a7a27f"}, + {file = "redis-4.3.3-py3-none-any.whl", hash = "sha256:f57f8df5d238a8ecf92f499b6b21467bfee6c13d89953c27edf1e2bc673622e7"}, + {file = "redis-4.3.3.tar.gz", hash = "sha256:2f7a57cf4af15cd543c4394bcbe2b9148db2606a37edba755368836e3a1d053e"}, ] requests = [ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, ] -sentry-sdk = [ - {file = "sentry-sdk-1.5.8.tar.gz", hash = "sha256:38fd16a92b5ef94203db3ece10e03bdaa291481dd7e00e77a148aa0302267d47"}, - {file = "sentry_sdk-1.5.8-py2.py3-none-any.whl", hash = "sha256:32af1a57954576709242beb8c373b3dbde346ac6bd616921def29d68846fb8c3"}, -] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -2130,6 +1852,10 @@ sphinx-autobuild = [ {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-basic-ng = [ + {file = "sphinx_basic_ng-0.0.1a11-py3-none-any.whl", hash = "sha256:9aecb5345816998789ef76658a83e3c0a12aafa14b17d40e28cd4aaeb94d1517"}, + {file = "sphinx_basic_ng-0.0.1a11.tar.gz", hash = "sha256:bf9a8fda0379c7d2ab51c9543f2b18e014b77fb295b49d64f3c1a910c863b34f"}, +] sphinx-copybutton = [ {file = "sphinx-copybutton-0.5.0.tar.gz", hash = "sha256:a0c059daadd03c27ba750da534a92a63e7a36a7736dcf684f26ee346199787f6"}, {file = "sphinx_copybutton-0.5.0-py3-none-any.whl", hash = "sha256:9684dec7434bd73f0eea58dda93f9bb879d24bff2d8b187b1f2ec08dfe7b5f48"}, @@ -2169,10 +1895,6 @@ sphinxcontrib-serializinghtml = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, ] -stack-data = [ - {file = "stack_data-0.2.0-py3-none-any.whl", hash = "sha256:999762f9c3132308789affa03e9271bbbe947bf78311851f4d485d8402ed858e"}, - {file = "stack_data-0.2.0.tar.gz", hash = "sha256:45692d41bd633a9503a5195552df22b583caf16f0b27c4e58c98d88c8b648e12"}, -] toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -2228,13 +1950,9 @@ towncrier = [ {file = "towncrier-21.9.0-py2.py3-none-any.whl", hash = "sha256:fc5a88a2a54988e3a8ed2b60d553599da8330f65722cc607c839614ed87e0f92"}, {file = "towncrier-21.9.0.tar.gz", hash = "sha256:9cb6f45c16e1a1eec9d0e7651165e7be60cd0ab81d13a5c96ca97a498ae87f48"}, ] -traitlets = [ - {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"}, - {file = "traitlets-5.1.1.tar.gz", hash = "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7"}, -] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] urllib3 = [ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, @@ -2262,10 +1980,6 @@ virtualenv = [ {file = "virtualenv-20.14.0-py2.py3-none-any.whl", hash = "sha256:1e8588f35e8b42c6ec6841a13c5e88239de1e6e4e4cedfd3916b306dc826ec66"}, {file = "virtualenv-20.14.0.tar.gz", hash = "sha256:8e5b402037287126e81ccde9432b95a8be5b19d36584f64957060a3488c11ca8"}, ] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] wrapt = [ {file = "wrapt-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7"}, {file = "wrapt-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c"}, diff --git a/pyproject.toml b/pyproject.toml index 14cdfc3b..ba5f8449 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aiogram" -version = "3.0.0-beta.3" +version = "3.0.0-beta.4" description = "Modern and fully asynchronous framework for Telegram Bot API" authors = [ "Alex Root Junior ", @@ -39,7 +39,7 @@ classifiers = [ python = "^3.8" magic-filter = "^1.0.7" aiohttp = "^3.8.1" -pydantic = "^1.9.0" +pydantic = "^1.9.1" aiofiles = "^0.8.0" # Fast uvloop = { version = "^0.16.0", markers = "sys_platform == 'darwin' or sys_platform == 'linux'", optional = true } @@ -48,32 +48,31 @@ Babel = { version = "^2.9.1", optional = true } # Proxy aiohttp-socks = { version = "^0.7.1", optional = true } # Redis -redis = { version = "^4.2.2", optional = true } +redis = {version = "^4.3.3", optional = true} # Docs Sphinx = { version = "^4.2.0", optional = true } -sphinx-intl = { version = "^2.0.1", optional = true } +sphinx-intl = {version = "^2.0.1", optional = true} sphinx-autobuild = { version = "^2021.3.14", optional = true } sphinx-copybutton = { version = "^0.5.0", optional = true } -furo = { version = "^2022.4.7", optional = true } +furo = {version = "^2022.6.21", optional = true} sphinx-prompt = { version = "^1.5.0", optional = true } Sphinx-Substitution-Extensions = { version = "^2020.9.30", optional = true } towncrier = { version = "^21.9.0", optional = true } pygments = { version = "^2.4", optional = true } -pymdown-extensions = { version = "^9.3", optional = true } +pymdown-extensions = {version = "^9.5", optional = true} markdown-include = { version = "^0.6", optional = true } -Pygments = { version = "^2.11.2", optional = true } +Pygments = {version = "^2.12.0", optional = true} [tool.poetry.dev-dependencies] -ipython = "^8.1.1" black = "^22.1.0" isort = "^5.10.1" flake8 = "^4.0.1" -mypy = "^0.942" +mypy = "^0.961" pytest = "^7.1.2" pytest-html = "^3.1.1" pytest-asyncio = "^0.18.1" pytest-lazy-fixture = "^0.6.3" -pytest-mock = "^3.7.0" +pytest-mock = "^3.8.1" pytest-mypy = "^0.9.1" pytest-cov = "^3.0.0" pytest-aiohttp = "^1.0.4" @@ -81,10 +80,9 @@ aresponses = "^2.1.5" asynctest = "^0.13.0" toml = "^0.10.2" -pre-commit = "^2.17.0" +pre-commit = "^2.19.0" packaging = "^21.3" -typing-extensions = "^4.1.1" -sentry-sdk = "^1.5.5" +typing-extensions = "^4.2.0" [tool.poetry.extras] diff --git a/tests/test_api/test_methods/test_create_invoice_link.py b/tests/test_api/test_methods/test_create_invoice_link.py new file mode 100644 index 00000000..2a3e16ac --- /dev/null +++ b/tests/test_api/test_methods/test_create_invoice_link.py @@ -0,0 +1,43 @@ +import pytest + +from aiogram.methods import CreateInvoiceLink, Request +from aiogram.types import LabeledPrice +from tests.mocked_bot import MockedBot + + +class TestCreateInvoiceLink: + @pytest.mark.asyncio + async def test_method(self, bot: MockedBot): + prepare_result = bot.add_result_for( + CreateInvoiceLink, ok=True, result="https://t.me/invoice/example" + ) + + response: str = await CreateInvoiceLink( + title="test", + description="test", + payload="test", + provider_token="test", + currency="BTC", + prices=[LabeledPrice(label="Test", amount=1)], + ) + request: Request = bot.get_request() + assert request.method == "createInvoiceLink" + assert response == prepare_result.result + + @pytest.mark.asyncio + async def test_bot_method(self, bot: MockedBot): + prepare_result = bot.add_result_for( + CreateInvoiceLink, ok=True, result="https://t.me/invoice/example" + ) + + response: str = await bot.create_invoice_link( + title="test", + description="test", + payload="test", + provider_token="test", + currency="BTC", + prices=[LabeledPrice(label="Test", amount=1)], + ) + request: Request = bot.get_request() + assert request.method == "createInvoiceLink" + assert response == prepare_result.result From 2c6d8ed5cba2fd855b6ad50c34282529f927eba2 Mon Sep 17 00:00:00 2001 From: Evgen Fil Date: Sun, 26 Jun 2022 00:31:19 +0500 Subject: [PATCH 30/35] Automatically put `3.x` label on new pull requests to `dev-3.x` (#940) --- .github/workflows/label_pr.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/label_pr.yaml diff --git a/.github/workflows/label_pr.yaml b/.github/workflows/label_pr.yaml new file mode 100644 index 00000000..dda333ae --- /dev/null +++ b/.github/workflows/label_pr.yaml @@ -0,0 +1,17 @@ +name: Label new pull request + +on: + pull_request_target: + types: + - opened + branches: + - dev-3.x + +jobs: + put-label: + runs-on: ubuntu-latest + steps: + - name: Add 3.x label + uses: andymckay/labeler@master + with: + add-labels: 3.x From fadb11515e389a1edf53a059883fd42b2336c62b Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 25 Jun 2022 22:47:08 +0300 Subject: [PATCH 31/35] Remove deprecated methods (#943) * #941 Removed deprecated methods --- CHANGES/941.misc.rst | 1 + aiogram/dispatcher/router.py | 290 +----------------- aiogram/types/message_entity.py | 9 - .../test_types/test_message_entity.py | 6 - tests/test_dispatcher/test_deprecated.py | 42 --- 5 files changed, 3 insertions(+), 345 deletions(-) create mode 100644 CHANGES/941.misc.rst delete mode 100644 tests/test_dispatcher/test_deprecated.py diff --git a/CHANGES/941.misc.rst b/CHANGES/941.misc.rst new file mode 100644 index 00000000..9eb14085 --- /dev/null +++ b/CHANGES/941.misc.rst @@ -0,0 +1 @@ +Removed deprecated :code:`router._handler` and :code:`router.register__handler` methods. diff --git a/aiogram/dispatcher/router.py b/aiogram/dispatcher/router.py index 9f1c0f77..da0b4426 100644 --- a/aiogram/dispatcher/router.py +++ b/aiogram/dispatcher/router.py @@ -1,17 +1,16 @@ from __future__ import annotations import warnings -from typing import Any, Dict, Generator, List, Optional, Set, Union +from typing import Any, Dict, Final, Generator, List, Optional, Set, Union from ..types import TelegramObject from ..utils.warnings import CodeHasNoEffect from .event.bases import REJECTED, UNHANDLED from .event.event import EventObserver -from .event.handler import CallbackType from .event.telegram import TelegramEventObserver from .filters import BUILTIN_FILTERS -INTERNAL_UPDATE_TYPES = frozenset({"update", "error"}) +INTERNAL_UPDATE_TYPES: Final[frozenset[str]] = frozenset({"update", "error"}) class Router: @@ -242,288 +241,3 @@ class Router: await self.shutdown.trigger(*args, **kwargs) for router in self.sub_routers: await router.emit_shutdown(*args, **kwargs) - - @property - def message_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.message_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.message(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.message - - @property - def edited_message_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.edited_message_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.edited_message(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.edited_message - - @property - def channel_post_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.channel_post_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.channel_post(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.channel_post - - @property - def edited_channel_post_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.edited_channel_post_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.edited_channel_post(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.edited_channel_post - - @property - def inline_query_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.inline_query_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.inline_query(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.inline_query - - @property - def chosen_inline_result_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.chosen_inline_result_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.chosen_inline_result(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.chosen_inline_result - - @property - def callback_query_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.callback_query_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.callback_query(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.callback_query - - @property - def shipping_query_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.shipping_query_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.shipping_query(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.shipping_query - - @property - def pre_checkout_query_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.pre_checkout_query_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.pre_checkout_query(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.pre_checkout_query - - @property - def poll_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.poll_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.poll(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.poll - - @property - def poll_answer_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.poll_answer_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.poll_answer(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.poll_answer - - @property - def my_chat_member_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.my_chat_member_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.my_chat_member(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.my_chat_member - - @property - def chat_member_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.chat_member_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.chat_member(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.chat_member - - @property - def chat_join_request_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.chat_join_request_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.chat_join_request(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.chat_join_request - - @property - def errors_handler(self) -> TelegramEventObserver: - warnings.warn( - "`Router.errors_handler(...)` is deprecated and will be removed in version 3.2 " - "use `Router.errors(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.errors - - def register_message(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_message(...)` is deprecated and will be removed in version 3.2 " - "use `Router.message.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.message.register(*args, **kwargs) - - def register_edited_message(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_edited_message(...)` is deprecated and will be removed in version 3.2 " - "use `Router.edited_message.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.edited_message.register(*args, **kwargs) - - def register_channel_post(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_channel_post(...)` is deprecated and will be removed in version 3.2 " - "use `Router.channel_post.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.channel_post.register(*args, **kwargs) - - def register_edited_channel_post(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_edited_channel_post(...)` is deprecated and will be removed in version 3.2 " - "use `Router.edited_channel_post.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.edited_channel_post.register(*args, **kwargs) - - def register_inline_query(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_inline_query(...)` is deprecated and will be removed in version 3.2 " - "use `Router.inline_query.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.inline_query.register(*args, **kwargs) - - def register_chosen_inline_result(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_chosen_inline_result(...)` is deprecated and will be removed in version 3.2 " - "use `Router.chosen_inline_result.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.chosen_inline_result.register(*args, **kwargs) - - def register_callback_query(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_callback_query(...)` is deprecated and will be removed in version 3.2 " - "use `Router.callback_query.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.callback_query.register(*args, **kwargs) - - def register_shipping_query(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_shipping_query(...)` is deprecated and will be removed in version 3.2 " - "use `Router.shipping_query.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.shipping_query.register(*args, **kwargs) - - def register_pre_checkout_query(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_pre_checkout_query(...)` is deprecated and will be removed in version 3.2 " - "use `Router.pre_checkout_query.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.pre_checkout_query.register(*args, **kwargs) - - def register_poll(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_poll(...)` is deprecated and will be removed in version 3.2 " - "use `Router.poll.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.poll.register(*args, **kwargs) - - def register_poll_answer(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_poll_answer(...)` is deprecated and will be removed in version 3.2 " - "use `Router.poll_answer.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.poll_answer.register(*args, **kwargs) - - def register_my_chat_member(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_my_chat_member(...)` is deprecated and will be removed in version 3.2 " - "use `Router.my_chat_member.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.my_chat_member.register(*args, **kwargs) - - def register_chat_member(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_chat_member(...)` is deprecated and will be removed in version 3.2 " - "use `Router.chat_member.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.chat_member.register(*args, **kwargs) - - def register_chat_join_request(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_chat_join_request(...)` is deprecated and will be removed in version 3.2 " - "use `Router.chat_join_request.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.chat_join_request.register(*args, **kwargs) - - def register_errors(self, *args: Any, **kwargs: Any) -> CallbackType: - warnings.warn( - "`Router.register_errors(...)` is deprecated and will be removed in version 3.2 " - "use `Router.errors.register(...)`", - DeprecationWarning, - stacklevel=2, - ) - return self.errors.register(*args, **kwargs) diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index d50f797a..7d090134 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -1,6 +1,5 @@ from __future__ import annotations -import warnings from typing import TYPE_CHECKING, Optional from ..utils.text_decorations import add_surrogates, remove_surrogates @@ -34,11 +33,3 @@ class MessageEntity(MutableTelegramObject): return remove_surrogates( add_surrogates(text)[self.offset * 2 : (self.offset + self.length) * 2] ) - - def get_text(self, text: str) -> str: - warnings.warn( - "Method `MessageEntity.get_text(...)` deprecated and will be removed in 3.2.\n" - " Use `MessageEntity.extract(...)` instead.", - DeprecationWarning, - ) - return self.extract(text=text) diff --git a/tests/test_api/test_types/test_message_entity.py b/tests/test_api/test_types/test_message_entity.py index 363824a5..572dbcb6 100644 --- a/tests/test_api/test_types/test_message_entity.py +++ b/tests/test_api/test_types/test_message_entity.py @@ -1,13 +1,7 @@ from aiogram.types import MessageEntity -from tests.deprecated import check_deprecated class TestMessageEntity: def test_extract(self): entity = MessageEntity(type="hashtag", length=4, offset=5) assert entity.extract("#foo #bar #baz") == "#bar" - - def test_get_text(self): - entity = MessageEntity(type="hashtag", length=4, offset=5) - with check_deprecated("3.2", exception=AttributeError): - assert entity.get_text("#foo #bar #baz") == "#bar" diff --git a/tests/test_dispatcher/test_deprecated.py b/tests/test_dispatcher/test_deprecated.py deleted file mode 100644 index 853e5902..00000000 --- a/tests/test_dispatcher/test_deprecated.py +++ /dev/null @@ -1,42 +0,0 @@ -import pytest - -from aiogram.dispatcher.event.telegram import TelegramEventObserver -from aiogram.dispatcher.router import Router -from tests.deprecated import check_deprecated - -OBSERVERS = { - "message", - "edited_message", - "channel_post", - "edited_channel_post", - "inline_query", - "chosen_inline_result", - "callback_query", - "shipping_query", - "pre_checkout_query", - "poll", - "poll_answer", - "my_chat_member", - "chat_member", - "chat_join_request", - "errors", -} - - -@pytest.mark.parametrize("observer_name", OBSERVERS) -def test_deprecated_handlers_name(observer_name: str): - router = Router() - - with check_deprecated("3.2", exception=AttributeError): - observer = getattr(router, f"{observer_name}_handler") - assert isinstance(observer, TelegramEventObserver) - - -@pytest.mark.parametrize("observer_name", OBSERVERS) -def test_deprecated_register_handlers(observer_name: str): - router = Router() - - with check_deprecated("3.2", exception=AttributeError): - register = getattr(router, f"register_{observer_name}") - register(lambda event: True) - assert callable(register) From b8893c0971a94a6b602c5abc7213f6d35506d0d2 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 26 Jun 2022 01:36:45 +0300 Subject: [PATCH 32/35] #944 Rename "extract()" to "extract_from()" for entities (#945) * #944 Rename "extract()" to "extract_from()" for entities --- CHANGES/944.misc.rst | 1 + aiogram/types/message_entity.py | 11 ++++++++++- tests/test_api/test_types/test_message_entity.py | 8 +++++++- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 CHANGES/944.misc.rst diff --git a/CHANGES/944.misc.rst b/CHANGES/944.misc.rst new file mode 100644 index 00000000..1dc64aaa --- /dev/null +++ b/CHANGES/944.misc.rst @@ -0,0 +1 @@ +`MessageEntity` method `get_text` was removed and `extract` was renamed to `extract_from` diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index 7d090134..fa6aea41 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from typing import TYPE_CHECKING, Optional from ..utils.text_decorations import add_surrogates, remove_surrogates @@ -29,7 +30,15 @@ class MessageEntity(MutableTelegramObject): language: Optional[str] = None """*Optional*. For 'pre' only, the programming language of the entity text""" - def extract(self, text: str) -> str: + def extract_from(self, text: str) -> str: return remove_surrogates( add_surrogates(text)[self.offset * 2 : (self.offset + self.length) * 2] ) + + def extract(self, text: str) -> str: + warnings.warn( + "Method `MessageEntity.extract(...)` deprecated and will be removed in 3.0b5.\n" + " Use `MessageEntity.extract_from(...)` instead.", + DeprecationWarning, + ) + return self.extract_from(text=text) diff --git a/tests/test_api/test_types/test_message_entity.py b/tests/test_api/test_types/test_message_entity.py index 572dbcb6..8dc4c0ea 100644 --- a/tests/test_api/test_types/test_message_entity.py +++ b/tests/test_api/test_types/test_message_entity.py @@ -1,7 +1,13 @@ from aiogram.types import MessageEntity +from tests.deprecated import check_deprecated class TestMessageEntity: + def test_extract_from(self): + entity = MessageEntity(type="hashtag", length=4, offset=5) + assert entity.extract_from("#foo #bar #baz") == "#bar" + def test_extract(self): entity = MessageEntity(type="hashtag", length=4, offset=5) - assert entity.extract("#foo #bar #baz") == "#bar" + with check_deprecated("3.0b5", exception=AttributeError): + assert entity.extract("#foo #bar #baz") == "#bar" From 70088cdd65431c234ceb61cf5c9664ee7a859961 Mon Sep 17 00:00:00 2001 From: Oleg A Date: Sun, 26 Jun 2022 01:50:27 +0300 Subject: [PATCH 33/35] Add `full_name` shortcut for `Chat` (#929) Added full_name for Chat --- CHANGES/929.feature.rst | 1 + aiogram/types/chat.py | 15 +++++++++++++++ tests/test_api/test_types/test_chat.py | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 CHANGES/929.feature.rst diff --git a/CHANGES/929.feature.rst b/CHANGES/929.feature.rst new file mode 100644 index 00000000..21764a59 --- /dev/null +++ b/CHANGES/929.feature.rst @@ -0,0 +1 @@ +Added `full_name` shortcut for `Chat` object diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 1fb92f8a..95d0ed7c 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -80,6 +80,21 @@ class Chat(TelegramObject): shift = int(-1 * pow(10, len(short_id) + 2)) return shift - self.id + @property + def full_name(self) -> str: + """Get full name of the Chat. + + For private chat it is first_name + last_name. + For other chat types it is title. + """ + if self.title is not None: + return self.title + + if self.last_name is not None: + return f"{self.first_name} {self.last_name}" + + return f"{self.first_name}" + def ban_sender_chat(self, sender_chat_id: int) -> BanChatSenderChat: from ..methods import BanChatSenderChat diff --git a/tests/test_api/test_types/test_chat.py b/tests/test_api/test_types/test_chat.py index 5fc9e081..e2f6bdca 100644 --- a/tests/test_api/test_types/test_chat.py +++ b/tests/test_api/test_types/test_chat.py @@ -1,3 +1,7 @@ +from typing import Optional + +from pytest import mark, param + from aiogram.types import Chat @@ -15,3 +19,24 @@ class TestChat: method = chat.unban_sender_chat(sender_chat_id=-1337) assert method.chat_id == chat.id assert method.sender_chat_id == -1337 + + @mark.parametrize( + "first,last,title,chat_type,result", + [ + param("First", None, None, "private", "First", id="private_first_only"), + param("First", "Last", None, "private", "First Last", id="private_with_last"), + param(None, None, "Title", "group", "Title", id="group_with_title"), + param(None, None, "Title", "supergroup", "Title", id="supergroup_with_title"), + param(None, None, "Title", "channel", "Title", id="channel_with_title"), + ], + ) + def test_full_name( + self, + first: Optional[str], + last: Optional[str], + title: Optional[str], + chat_type: str, + result: str, + ): + chat = Chat(id=42, first_name=first, last_name=last, title=title, type=chat_type) + assert chat.full_name == result From 2cb0fd1c4b25249abda614ee9cffe311325a5ca8 Mon Sep 17 00:00:00 2001 From: Evgen Fil Date: Sun, 26 Jun 2022 18:22:32 +0500 Subject: [PATCH 34/35] Improve changelog check action (#949) Make check faster when `skip news` label is present Add `name:` root field --- .github/workflows/pull_request_changelog.yml | 33 ++++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pull_request_changelog.yml b/.github/workflows/pull_request_changelog.yml index 538ea1ed..f803153e 100644 --- a/.github/workflows/pull_request_changelog.yml +++ b/.github/workflows/pull_request_changelog.yml @@ -1,3 +1,4 @@ +name: "Check that changes are described" on: pull_request_target: types: @@ -8,31 +9,32 @@ on: - "unlabeled" jobs: - check_changes: + changes-required: runs-on: ubuntu-latest - name: "Check that changes is described" + if: "!contains(github.event.pull_request.labels.*.name, 'skip news')" steps: - - name: "Checkout code" + - name: Checkout code uses: actions/checkout@master with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: '0' + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: python-version: "3.10" + - name: Install towncrier run: pip install towncrier - - name: "Check changelog" - if: "!contains(github.event.pull_request.labels.*.name, 'skip news')" + - name: Check changelog env: BASE_BRANCH: ${{ github.base_ref }} run: | git fetch --no-tags origin +refs/heads/${BASE_BRANCH}:refs/remotes/origin/${BASE_BRANCH} towncrier check --compare-with origin/${BASE_BRANCH} - - name: Find Comment + - name: Find bot comment if: "always()" uses: peter-evans/find-comment@v2 id: fc @@ -41,7 +43,7 @@ jobs: comment-author: 'github-actions[bot]' body-includes: Changelog - - name: Create fail comment + - name: Ask for changelog if: "failure()" uses: peter-evans/create-or-update-comment@v2 with: @@ -57,7 +59,7 @@ jobs: Read more at [Towncrier docs](https://towncrier.readthedocs.io/en/latest/quickstart.html#creating-news-fragments) - - name: Create success comment + - name: Changelog found if: "success()" uses: peter-evans/create-or-update-comment@v2 with: @@ -69,8 +71,19 @@ jobs: Thank you for adding a description of the changes + skip-news: + runs-on: ubuntu-latest + if: "contains(github.event.pull_request.labels.*.name, 'skip news')" + steps: + - name: Find bot comment + uses: peter-evans/find-comment@v2 + id: fc + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Changelog + - name: Comment when docs is not needed - if: "contains(github.event.pull_request.labels.*.name, 'skip news')" uses: peter-evans/create-or-update-comment@v2 with: edit-mode: replace @@ -79,4 +92,4 @@ jobs: body: | # :corn: Changelog is not needed. - This PR does not require a changelog in due to the `skip news` label. + This PR does not require a changelog because `skip news` label is present. From 416460e0136e0c67f3fc8a041a4aed845c9c3939 Mon Sep 17 00:00:00 2001 From: Evgen Fil Date: Sun, 26 Jun 2022 21:16:17 +0500 Subject: [PATCH 35/35] Skip running tests when there are no code or test changes (#948) * Skip running tests when there are no code or test changes * Allow config file changes to trigger tests --- .github/workflows/tests.yml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 73eac1b8..ba68db9b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,9 +4,29 @@ on: push: branches: - dev-3.x + paths: + - ".github/workflows/tests.yml" + - "aiogram/**" + - "tests/**" + - ".coveragerc" + - ".flake8" + - "codecov.yaml" + - "mypy.ini" + - "poetry.lock" + - "pyproject.toml" pull_request: branches: - dev-3.x + paths: + - ".github/workflows/tests.yml" + - "aiogram/**" + - "tests/**" + - ".coveragerc" + - ".flake8" + - "codecov.yaml" + - "mypy.ini" + - "poetry.lock" + - "pyproject.toml" jobs: build: @@ -24,7 +44,7 @@ jobs: - '3.10' defaults: - # Windows is sucks. Force use bash instead of PowerShell + # Windows sucks. Force use bash instead of PowerShell run: shell: bash