From 05148e2991aff07411243b1c4dc0f8e7b0c9424e Mon Sep 17 00:00:00 2001 From: Kurosawa <145038102+KurosawaAngel@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:10:16 +0500 Subject: [PATCH 01/29] add copy text widget --- src/aiogram_dialog/widgets/kbd/copy.py | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/aiogram_dialog/widgets/kbd/copy.py diff --git a/src/aiogram_dialog/widgets/kbd/copy.py b/src/aiogram_dialog/widgets/kbd/copy.py new file mode 100644 index 00000000..10befb2f --- /dev/null +++ b/src/aiogram_dialog/widgets/kbd/copy.py @@ -0,0 +1,37 @@ +from typing import Any + +from aiogram.types import CopyTextButton, InlineKeyboardButton + +from aiogram_dialog import DialogManager +from aiogram_dialog.api.internal import RawKeyboard +from aiogram_dialog.widgets.common import WhenCondition +from aiogram_dialog.widgets.kbd import Keyboard +from aiogram_dialog.widgets.text import Text + + +class CopyText(Keyboard): + def __init__( + self, + text: Text, + copy_text: Text, + when: WhenCondition = None, + ) -> None: + self._text = text + self._copy_text = copy_text + super().__init__(when=when) + + async def _render_keyboard( + self, + data: dict[str, Any], + manager: DialogManager, + ) -> RawKeyboard: + return [ + [ + InlineKeyboardButton( + text=await self._text.render_text(data, manager), + copy_text=CopyTextButton( + text=await self._copy_text.render_text(data, manager), + ), + ), + ], + ] From 6d2d8474482142894f1a82daa516f876873b7279 Mon Sep 17 00:00:00 2001 From: Kurosawa <145038102+KurosawaAngel@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:47:30 +0500 Subject: [PATCH 02/29] edit text to media --- src/aiogram_dialog/manager/message_manager.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index 773c9cb2..a48d2da0 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -130,13 +130,12 @@ def _message_changed( return False - def _can_edit(self, new_message: NewMessage, - old_message: OldMessage) -> bool: + def _can_edit(self, new_message: NewMessage, old_message: OldMessage) -> bool: # we cannot edit message if media appeared or removed return ( - self.had_media(old_message) == self.need_media(new_message) and - not self.had_reply_keyboard(old_message) and - not self.need_reply_keyboard(new_message) + not (self.had_media(old_message) and not self.need_media(new_message)) + and not self.had_reply_keyboard(old_message) + and not self.need_reply_keyboard(new_message) ) async def show_message( @@ -293,7 +292,10 @@ async def edit_message( self, bot: Bot, new_message: NewMessage, old_message: OldMessage, ) -> Message: if new_message.media: - if new_message.media.file_id == old_message.media_id: + if ( + old_message.media_id is not None + and new_message.media.file_id == old_message.media_id + ): return await self.edit_caption(bot, new_message, old_message) return await self.edit_media(bot, new_message, old_message) else: From fbacbf9635dd8f62decdef647387df91bb0f3182 Mon Sep 17 00:00:00 2001 From: Kurosawa <145038102+KurosawaAngel@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:04:05 +0500 Subject: [PATCH 03/29] fix flake --- src/aiogram_dialog/manager/message_manager.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index a48d2da0..c86879e4 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -130,13 +130,14 @@ def _message_changed( return False - def _can_edit(self, new_message: NewMessage, old_message: OldMessage) -> bool: - # we cannot edit message if media appeared or removed - return ( - not (self.had_media(old_message) and not self.need_media(new_message)) - and not self.had_reply_keyboard(old_message) - and not self.need_reply_keyboard(new_message) - ) + def _can_edit(self, new_message: NewMessage, + old_message: OldMessage) -> bool: + # we cannot edit message if media appeared or remove + return (not (self.had_media(old_message) and + not self.need_media(new_message)) and + not self.had_reply_keyboard(old_message) and + not self.need_reply_keyboard(new_message) + ) async def show_message( self, bot: Bot, new_message: NewMessage, @@ -293,8 +294,8 @@ async def edit_message( ) -> Message: if new_message.media: if ( - old_message.media_id is not None - and new_message.media.file_id == old_message.media_id + old_message.media_id is not None and + new_message.media.file_id == old_message.media_id ): return await self.edit_caption(bot, new_message, old_message) return await self.edit_media(bot, new_message, old_message) From 8f48142e806a0a32a2ed1ce1c621110f76203bc3 Mon Sep 17 00:00:00 2001 From: Kurosawa <145038102+KurosawaAngel@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:19:00 +0500 Subject: [PATCH 04/29] add widget to __init__ --- src/aiogram_dialog/widgets/kbd/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/aiogram_dialog/widgets/kbd/__init__.py b/src/aiogram_dialog/widgets/kbd/__init__.py index 07b67b98..ba4382e9 100644 --- a/src/aiogram_dialog/widgets/kbd/__init__.py +++ b/src/aiogram_dialog/widgets/kbd/__init__.py @@ -41,15 +41,20 @@ "ListGroup", "ManagedListGroup", "StubScroll", + "CopyText" ] from .base import Keyboard from .button import Button, SwitchInlineQuery, Url, WebApp from .calendar_kbd import ( - Calendar, CalendarConfig, CalendarScope, CalendarUserConfig, + Calendar, + CalendarConfig, + CalendarScope, + CalendarUserConfig, ManagedCalendar, ) from .checkbox import Checkbox, ManagedCheckbox +from .copy import CopyText from .counter import Counter, ManagedCounter from .group import Column, Group, Row from .list_group import ListGroup, ManagedListGroup From e5c45108f5c4ff08006a6000a1871e38ebc83672 Mon Sep 17 00:00:00 2001 From: Kurosawa <145038102+KurosawaAngel@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:52:00 +0500 Subject: [PATCH 05/29] better format --- src/aiogram_dialog/manager/message_manager.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index c86879e4..60a7aa3f 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -132,12 +132,13 @@ def _message_changed( def _can_edit(self, new_message: NewMessage, old_message: OldMessage) -> bool: - # we cannot edit message if media appeared or remove - return (not (self.had_media(old_message) and - not self.need_media(new_message)) and - not self.had_reply_keyboard(old_message) and - not self.need_reply_keyboard(new_message) - ) + # we cannot edit message if media removed + return ( + not self.had_media(old_message) or self.need_media(new_message) + ) and not ( + self.had_reply_keyboard(old_message) or + self.need_reply_keyboard(new_message) + ) async def show_message( self, bot: Bot, new_message: NewMessage, From c36ea7ced919910848026a47779bd1015e0c2847 Mon Sep 17 00:00:00 2001 From: Kurosawa <145038102+KurosawaAngel@users.noreply.github.com> Date: Wed, 6 Nov 2024 02:02:34 +0500 Subject: [PATCH 06/29] BETTER CODE FORMAT --- src/aiogram_dialog/manager/message_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index 60a7aa3f..0c0a766d 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -133,9 +133,9 @@ def _message_changed( def _can_edit(self, new_message: NewMessage, old_message: OldMessage) -> bool: # we cannot edit message if media removed - return ( - not self.had_media(old_message) or self.need_media(new_message) - ) and not ( + if self.had_media(old_message) and not self.need_media(new_message): + return False + return not ( self.had_reply_keyboard(old_message) or self.need_reply_keyboard(new_message) ) From a727f879f38d73a25b2ebedfdbde21caa3824f6d Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 00:44:08 +0300 Subject: [PATCH 07/29] drop support python 3.8 --- .github/workflows/setup.yml | 1 - docs/conf.py | 24 +++--- example/custom_media_url.py | 2 +- example/i18n/bot.py | 2 +- example/i18n/i18n_format.py | 4 +- example/i18n/i18n_middleware.py | 7 +- example/launch_modes.py | 2 +- example/list_group.py | 5 +- example/mega/bot.py | 2 +- example/mega/bot_dialogs/calendar.py | 7 +- example/mega/bot_dialogs/switch.py | 4 +- pyproject.toml | 15 ++-- requirements_dev.txt | 17 ----- requirements_doc.txt | 4 - src/aiogram_dialog/about.py | 2 +- src/aiogram_dialog/api/entities/access.py | 4 +- src/aiogram_dialog/api/entities/context.py | 6 +- src/aiogram_dialog/api/entities/stack.py | 4 +- src/aiogram_dialog/api/internal/manager.py | 4 +- src/aiogram_dialog/api/internal/widgets.py | 11 +-- src/aiogram_dialog/api/protocols/dialog.py | 8 +- src/aiogram_dialog/api/protocols/manager.py | 10 +-- src/aiogram_dialog/api/protocols/registry.py | 4 +- src/aiogram_dialog/context/intent_filter.py | 4 +- .../context/intent_middleware.py | 11 +-- src/aiogram_dialog/context/storage.py | 8 +- src/aiogram_dialog/dialog.py | 18 ++--- src/aiogram_dialog/manager/bg_manager.py | 4 +- src/aiogram_dialog/manager/manager.py | 12 +-- src/aiogram_dialog/manager/manager_factory.py | 4 +- .../manager/manager_middleware.py | 13 ++-- src/aiogram_dialog/manager/sub_manager.py | 10 +-- src/aiogram_dialog/setup.py | 5 +- src/aiogram_dialog/tools/preview.py | 32 +++++--- src/aiogram_dialog/tools/transitions.py | 8 +- src/aiogram_dialog/utils.py | 10 +-- src/aiogram_dialog/widgets/common/__init__.py | 18 ++++- src/aiogram_dialog/widgets/common/items.py | 7 +- src/aiogram_dialog/widgets/common/scroll.py | 7 +- src/aiogram_dialog/widgets/common/when.py | 12 +-- .../widgets/data/data_context.py | 6 +- src/aiogram_dialog/widgets/input/base.py | 8 +- src/aiogram_dialog/widgets/input/combined.py | 3 +- src/aiogram_dialog/widgets/input/text.py | 8 +- src/aiogram_dialog/widgets/kbd/button.py | 15 ++-- .../widgets/kbd/calendar_kbd.py | 73 ++++++++++--------- src/aiogram_dialog/widgets/kbd/checkbox.py | 7 +- src/aiogram_dialog/widgets/kbd/counter.py | 4 +- src/aiogram_dialog/widgets/kbd/group.py | 7 +- src/aiogram_dialog/widgets/kbd/list_group.py | 12 ++- src/aiogram_dialog/widgets/kbd/pager.py | 14 ++-- src/aiogram_dialog/widgets/kbd/request.py | 7 +- .../widgets/kbd/scrolling_group.py | 12 +-- src/aiogram_dialog/widgets/kbd/select.py | 44 ++++++----- src/aiogram_dialog/widgets/kbd/stub_scroll.py | 20 +++-- src/aiogram_dialog/widgets/media/dynamic.py | 3 +- src/aiogram_dialog/widgets/media/scroll.py | 9 ++- src/aiogram_dialog/widgets/text/base.py | 10 +-- src/aiogram_dialog/widgets/text/format.py | 4 +- src/aiogram_dialog/widgets/text/jinja.py | 12 +-- src/aiogram_dialog/widgets/text/list.py | 12 ++- src/aiogram_dialog/widgets/text/multi.py | 11 +-- src/aiogram_dialog/widgets/text/progress.py | 4 +- .../widgets/text/scrolling_text.py | 6 +- src/aiogram_dialog/widgets/utils.py | 11 +-- src/aiogram_dialog/widgets/widget_event.py | 3 +- src/aiogram_dialog/window.py | 17 ++--- tests/test_click.py | 4 +- 68 files changed, 348 insertions(+), 330 deletions(-) delete mode 100644 requirements_dev.txt delete mode 100644 requirements_doc.txt diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml index 3f4871c8..f9aeaeb7 100644 --- a/.github/workflows/setup.yml +++ b/.github/workflows/setup.yml @@ -21,7 +21,6 @@ jobs: os: - ubuntu-latest python-version: - - "3.8" - "3.9" - "3.10" - "3.11" diff --git a/docs/conf.py b/docs/conf.py index 72a8a1fe..7a3178ad 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,32 +12,32 @@ # # import os # import sys -# sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath(".")) import datetime # -- Project information ----------------------------------------------------- -project = 'aiogram-dialog' -copyright = f'{datetime.date.today().year}, Tishka17' -author = 'Tishka17' -master_doc = 'index' +project = "aiogram-dialog" +copyright = f"{datetime.date.today().year}, Tishka17" +author = "Tishka17" +master_doc = "index" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# extensions coming with Sphinx (named "sphinx.ext.*") or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx_copybutton', + "sphinx.ext.autodoc", + "sphinx_copybutton", ] autodoc_type_aliases = { } -autodoc_typehints = 'description' +autodoc_typehints = "description" # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -49,9 +49,9 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'furo' +html_theme = "furo" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] diff --git a/example/custom_media_url.py b/example/custom_media_url.py index 1f169e95..19b43fde 100644 --- a/example/custom_media_url.py +++ b/example/custom_media_url.py @@ -115,5 +115,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/i18n/bot.py b/example/i18n/bot.py index d737e9d5..aa908788 100644 --- a/example/i18n/bot.py +++ b/example/i18n/bot.py @@ -87,5 +87,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/i18n/i18n_format.py b/example/i18n/i18n_format.py index 2f4cd6f2..ddf44caa 100644 --- a/example/i18n/i18n_format.py +++ b/example/i18n/i18n_format.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Protocol +from typing import Any, Protocol from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import WhenCondition @@ -21,7 +21,7 @@ def __init__(self, text: str, when: WhenCondition = None): super().__init__(when) self.text = text - async def _render_text(self, data: Dict, manager: DialogManager) -> str: + async def _render_text(self, data: dict, manager: DialogManager) -> str: format_text = manager.middleware_data.get( I18N_FORMAT_KEY, default_format_text, ) diff --git a/example/i18n/i18n_middleware.py b/example/i18n/i18n_middleware.py index f897cb46..20fa32b2 100644 --- a/example/i18n/i18n_middleware.py +++ b/example/i18n/i18n_middleware.py @@ -1,4 +1,5 @@ -from typing import Any, Awaitable, Callable, Dict, Union +from typing import Any, Union +from collections.abc import Awaitable, Callable from aiogram.dispatcher.middlewares.base import BaseMiddleware from aiogram.types import CallbackQuery, Message @@ -9,7 +10,7 @@ class I18nMiddleware(BaseMiddleware): def __init__( self, - l10ns: Dict[str, FluentLocalization], + l10ns: dict[str, FluentLocalization], default_lang: str, ): super().__init__() @@ -19,7 +20,7 @@ def __init__( async def __call__( self, handler: Callable[ - [Union[Message, CallbackQuery], Dict[str, Any]], + [Union[Message, CallbackQuery], dict[str, Any]], Awaitable[Any], ], event: Union[Message, CallbackQuery], diff --git a/example/launch_modes.py b/example/launch_modes.py index fb94bff3..f68f63f4 100644 --- a/example/launch_modes.py +++ b/example/launch_modes.py @@ -93,5 +93,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/list_group.py b/example/list_group.py index e791a24b..a66a18ba 100644 --- a/example/list_group.py +++ b/example/list_group.py @@ -1,7 +1,6 @@ import asyncio import logging import os -from typing import Dict from aiogram import Bot, Dispatcher from aiogram.filters import CommandStart @@ -27,7 +26,7 @@ class DialogSG(StatesGroup): greeting = State() -def when_checked(data: Dict, widget, manager: SubManager) -> bool: +def when_checked(data: dict, widget, manager: SubManager) -> bool: # manager for our case is already adapted for current ListGroup row # so `.find` returns widget adapted for current row # if you need to find widgets outside the row, use `.find_in_parent` @@ -98,5 +97,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/mega/bot.py b/example/mega/bot.py index d373b80b..11d43bb7 100644 --- a/example/mega/bot.py +++ b/example/mega/bot.py @@ -93,5 +93,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/mega/bot_dialogs/calendar.py b/example/mega/bot_dialogs/calendar.py index 5101b89c..4f1bbe14 100644 --- a/example/mega/bot_dialogs/calendar.py +++ b/example/mega/bot_dialogs/calendar.py @@ -1,5 +1,4 @@ from datetime import date -from typing import Dict from aiogram import F from babel.dates import get_day_names, get_month_names @@ -25,7 +24,7 @@ async def _render_text(self, data, manager: DialogManager) -> str: selected_date: date = data["date"] locale = manager.event.from_user.language_code return get_day_names( - width="short", context='stand-alone', locale=locale, + width="short", context="stand-alone", locale=locale, )[selected_date.weekday()].title() @@ -49,12 +48,12 @@ async def _render_text(self, data, manager: DialogManager) -> str: selected_date: date = data["date"] locale = manager.event.from_user.language_code return get_month_names( - 'wide', context='stand-alone', locale=locale, + "wide", context="stand-alone", locale=locale, )[selected_date.month].title() class CustomCalendar(Calendar): - def _init_views(self) -> Dict[CalendarScope, CalendarScopeView]: + def _init_views(self) -> dict[CalendarScope, CalendarScopeView]: return { CalendarScope.DAYS: CalendarDaysView( self._item_callback_data, diff --git a/example/mega/bot_dialogs/switch.py b/example/mega/bot_dialogs/switch.py index 80cbd0a2..faf3a4d0 100644 --- a/example/mega/bot_dialogs/switch.py +++ b/example/mega/bot_dialogs/switch.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from aiogram_dialog import Dialog, DialogManager, Window from aiogram_dialog.widgets.kbd import Back, Checkbox, Next, Radio, Row @@ -14,7 +14,7 @@ async def data_getter( dialog_manager: DialogManager, **_kwargs, -) -> Dict[str, Any]: +) -> dict[str, Any]: return { "option": dialog_manager.find(CHECKBOX_ID).is_checked(), "emoji": dialog_manager.find(EMOJI_ID).get_checked(), diff --git a/pyproject.toml b/pyproject.toml index 8063d94b..84c18f36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=66.0"] +requires = ["setuptools==75.3.0"] build-backend = "setuptools.build_meta" [tool.setuptools] @@ -17,18 +17,21 @@ authors = [ ] license = { text = "Apache-2.0" } description = "Telegram bot UI framework on top of aiogram" -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ] dependencies = [ - 'aiogram>=3.5.0', - 'jinja2', - 'cachetools>=4.0.0,<6.0.0', - 'magic_filter', + "aiogram>=3.14.0", + "jinja2", + "cachetools>=4.0.0,<6.0.0", ] [project.optional-dependencies] tools = [ diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index faf62be7..00000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,17 +0,0 @@ -vulture -flake8==7.* -flake8-blind-except -flake8-bugbear -flake8-builtins -flake8-cognitive-complexity -flake8-comprehensions -flake8-docstrings -flake8-eradicate -flake8-import-order -flake8-mutable -flake8-polyfill -flake8-print - -pytest -pytest-asyncio -pytest-repeat diff --git a/requirements_doc.txt b/requirements_doc.txt deleted file mode 100644 index 54e78bcc..00000000 --- a/requirements_doc.txt +++ /dev/null @@ -1,4 +0,0 @@ -sphinx -sphinx-autodocgen -furo -sphinx-copybutton diff --git a/src/aiogram_dialog/about.py b/src/aiogram_dialog/about.py index fe0baced..c4162512 100644 --- a/src/aiogram_dialog/about.py +++ b/src/aiogram_dialog/about.py @@ -33,7 +33,7 @@ def about_dialog(): "Author: {{metadata['Author-email']}}\n" "\n" "{% for name, url in urls%}" - "{{name}}: {{url}}\n" + '{{name}}: {{url}}\n' "{% endfor %}" "", ), diff --git a/src/aiogram_dialog/api/entities/access.py b/src/aiogram_dialog/api/entities/access.py index bf49c19f..158b7926 100644 --- a/src/aiogram_dialog/api/entities/access.py +++ b/src/aiogram_dialog/api/entities/access.py @@ -1,8 +1,8 @@ from dataclasses import dataclass -from typing import Any, List +from typing import Any @dataclass class AccessSettings: - user_ids: List[int] + user_ids: list[int] custom: Any = None diff --git a/src/aiogram_dialog/api/entities/context.py b/src/aiogram_dialog/api/entities/context.py index b4882088..0f48af41 100644 --- a/src/aiogram_dialog/api/entities/context.py +++ b/src/aiogram_dialog/api/entities/context.py @@ -1,12 +1,12 @@ from dataclasses import dataclass, field -from typing import Dict, List, Optional, Union +from typing import Optional, Union from aiogram.fsm.state import State from .access import AccessSettings -Data = Union[Dict, List, int, str, float, None] -DataDict = Dict[str, Data] +Data = Union[dict, list, int, str, float, None] +DataDict = dict[str, Data] @dataclass(unsafe_hash=True) diff --git a/src/aiogram_dialog/api/entities/stack.py b/src/aiogram_dialog/api/entities/stack.py index 30fbd8cb..824830b1 100644 --- a/src/aiogram_dialog/api/entities/stack.py +++ b/src/aiogram_dialog/api/entities/stack.py @@ -2,7 +2,7 @@ import string import time from dataclasses import dataclass, field -from typing import List, Optional +from typing import Optional from aiogram.fsm.state import State @@ -38,7 +38,7 @@ def new_id(): @dataclass(unsafe_hash=True) class Stack: _id: str = field(compare=True, default_factory=new_id) - intents: List[str] = field(compare=False, default_factory=list) + intents: list[str] = field(compare=False, default_factory=list) last_message_id: Optional[int] = field(compare=False, default=None) last_reply_keyboard: bool = field(compare=False, default=False) last_media_id: Optional[str] = field(compare=False, default=None) diff --git a/src/aiogram_dialog/api/internal/manager.py b/src/aiogram_dialog/api/internal/manager.py index 73e399c5..145743c2 100644 --- a/src/aiogram_dialog/api/internal/manager.py +++ b/src/aiogram_dialog/api/internal/manager.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Dict, Protocol +from typing import Protocol from aiogram import Router @@ -12,7 +12,7 @@ class DialogManagerFactory(Protocol): @abstractmethod def __call__( - self, event: ChatEvent, data: Dict, + self, event: ChatEvent, data: dict, registry: DialogRegistryProtocol, router: Router, ) -> DialogManager: diff --git a/src/aiogram_dialog/api/internal/widgets.py b/src/aiogram_dialog/api/internal/widgets.py index df306af8..ba51d328 100644 --- a/src/aiogram_dialog/api/internal/widgets.py +++ b/src/aiogram_dialog/api/internal/widgets.py @@ -1,8 +1,9 @@ from abc import abstractmethod from typing import ( - Any, Awaitable, Callable, Dict, List, Optional, Protocol, + Any, Optional, Protocol, runtime_checkable, Union, ) +from collections.abc import Awaitable, Callable from aiogram.types import ( CallbackQuery, InlineKeyboardButton, KeyboardButton, Message, @@ -28,21 +29,21 @@ def find(self, widget_id: str) -> Optional["Widget"]: class TextWidget(Widget, Protocol): @abstractmethod async def render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: """Create text.""" raise NotImplementedError ButtonVariant = Union[InlineKeyboardButton, KeyboardButton] -RawKeyboard = List[List[ButtonVariant]] +RawKeyboard = list[list[ButtonVariant]] @runtime_checkable class KeyboardWidget(Widget, Protocol): @abstractmethod async def render_keyboard( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> RawKeyboard: """Create Inline keyboard contents.""" raise NotImplementedError @@ -89,7 +90,7 @@ async def process_message( raise NotImplementedError -DataGetter = Callable[..., Awaitable[Dict]] +DataGetter = Callable[..., Awaitable[dict]] @runtime_checkable diff --git a/src/aiogram_dialog/api/protocols/dialog.py b/src/aiogram_dialog/api/protocols/dialog.py index 941566dd..5960aa37 100644 --- a/src/aiogram_dialog/api/protocols/dialog.py +++ b/src/aiogram_dialog/api/protocols/dialog.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Any, Dict, List, Optional, Protocol, runtime_checkable, Type +from typing import Any, Optional, Protocol, runtime_checkable from aiogram.fsm.state import State, StatesGroup @@ -24,11 +24,11 @@ def states_group_name(self) -> str: raise NotImplementedError @abstractmethod - def states(self) -> List[State]: + def states(self) -> list[State]: raise NotImplementedError @abstractmethod - def states_group(self) -> Type[StatesGroup]: + def states_group(self) -> type[StatesGroup]: raise NotImplementedError @abstractmethod @@ -60,7 +60,7 @@ def find(self, widget_id) -> Any: @abstractmethod async def load_data( self, manager: DialogManager, - ) -> Dict: + ) -> dict: raise NotImplementedError @abstractmethod diff --git a/src/aiogram_dialog/api/protocols/manager.py b/src/aiogram_dialog/api/protocols/manager.py index 899c7e5e..e578ecc5 100644 --- a/src/aiogram_dialog/api/protocols/manager.py +++ b/src/aiogram_dialog/api/protocols/manager.py @@ -1,6 +1,6 @@ from abc import abstractmethod from enum import Enum -from typing import Any, Dict, Optional, Protocol, Union +from typing import Any, Optional, Protocol, Union from aiogram import Bot from aiogram.fsm.state import State @@ -46,7 +46,7 @@ async def switch_to( @abstractmethod async def update( self, - data: Dict, + data: dict, show_mode: Optional[ShowMode] = None, ) -> None: raise NotImplementedError @@ -91,13 +91,13 @@ async def mark_closed(self) -> None: @property @abstractmethod - def middleware_data(self) -> Dict: + def middleware_data(self) -> dict: """Middleware data.""" raise NotImplementedError @property @abstractmethod - def dialog_data(self) -> Dict: + def dialog_data(self) -> dict: """Dialog data for current context.""" raise NotImplementedError @@ -183,7 +183,7 @@ async def reset_stack(self, remove_keyboard: bool = True) -> None: raise NotImplementedError @abstractmethod - async def load_data(self) -> Dict: + async def load_data(self) -> dict: """Load data for current state.""" raise NotImplementedError diff --git a/src/aiogram_dialog/api/protocols/registry.py b/src/aiogram_dialog/api/protocols/registry.py index 5608c801..6a59095a 100644 --- a/src/aiogram_dialog/api/protocols/registry.py +++ b/src/aiogram_dialog/api/protocols/registry.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Dict, Protocol, Type, Union +from typing import Protocol, Union from aiogram.fsm.state import State, StatesGroup @@ -12,5 +12,5 @@ def find_dialog(self, state: Union[State, str]) -> DialogProtocol: raise NotImplementedError @abstractmethod - def states_groups(self) -> Dict[str, Type[StatesGroup]]: + def states_groups(self) -> dict[str, type[StatesGroup]]: raise NotImplementedError diff --git a/src/aiogram_dialog/context/intent_filter.py b/src/aiogram_dialog/context/intent_filter.py index 29309fdf..c38da0ec 100644 --- a/src/aiogram_dialog/context/intent_filter.py +++ b/src/aiogram_dialog/context/intent_filter.py @@ -1,4 +1,4 @@ -from typing import Optional, Type +from typing import Optional from aiogram.filters import BaseFilter from aiogram.fsm.state import StatesGroup @@ -9,7 +9,7 @@ class IntentFilter(BaseFilter): - def __init__(self, aiogd_intent_state_group: Optional[Type[StatesGroup]]): + def __init__(self, aiogd_intent_state_group: Optional[type[StatesGroup]]): self.aiogd_intent_state_group = aiogd_intent_state_group async def __call__(self, obj: TelegramObject, **kwargs) -> bool: diff --git a/src/aiogram_dialog/context/intent_middleware.py b/src/aiogram_dialog/context/intent_middleware.py index b781fa41..f19cc7a1 100644 --- a/src/aiogram_dialog/context/intent_middleware.py +++ b/src/aiogram_dialog/context/intent_middleware.py @@ -1,5 +1,6 @@ from logging import getLogger -from typing import Any, Awaitable, Callable, Dict, Optional +from typing import Any, Optional +from collections.abc import Awaitable, Callable from aiogram import Router from aiogram.dispatcher.event.bases import UNHANDLED @@ -159,7 +160,7 @@ def _check_outdated(self, intent_id: str, stack: Stack): f"Outdated intent id ({intent_id}) " f"for stack ({stack.id})", ) - elif intent_id != stack.last_intent_id(): + if intent_id != stack.last_intent_id(): raise OutdatedIntent( stack.id, f"Outdated intent id ({intent_id}) " @@ -444,7 +445,7 @@ def __init__( self.access_validator = access_validator def _is_error_supported( - self, event: ErrorEvent, data: Dict[str, Any], + self, event: ErrorEvent, data: dict[str, Any], ) -> bool: if isinstance(event, InvalidStackIdError): return False @@ -487,10 +488,10 @@ async def _load_stack( async def __call__( self, handler: Callable[ - [ErrorEvent, Dict[str, Any]], Awaitable[Any], + [ErrorEvent, dict[str, Any]], Awaitable[Any], ], event: ErrorEvent, - data: Dict[str, Any], + data: dict[str, Any], ) -> Any: error = event.exception if not self._is_error_supported(event, data): diff --git a/src/aiogram_dialog/context/storage.py b/src/aiogram_dialog/context/storage.py index 1269008a..b6c39e7d 100644 --- a/src/aiogram_dialog/context/storage.py +++ b/src/aiogram_dialog/context/storage.py @@ -1,6 +1,6 @@ from contextlib import AsyncExitStack from copy import copy -from typing import Dict, Optional, Type +from typing import Optional from aiogram import Bot from aiogram.fsm.state import State, StatesGroup @@ -24,7 +24,7 @@ def __init__( thread_id: Optional[int], business_connection_id: Optional[str], bot: Bot, - state_groups: Dict[str, Type[StatesGroup]], + state_groups: dict[str, type[StatesGroup]], ): self.storage = storage self.events_isolation = events_isolation @@ -164,7 +164,7 @@ def _state(self, state: str) -> State: raise UnknownState(f"Unknown state {state}") def _parse_access_settings( - self, raw: Optional[Dict], + self, raw: Optional[dict], ) -> Optional[AccessSettings]: if not raw: return None @@ -175,7 +175,7 @@ def _parse_access_settings( def _dump_access_settings( self, access_settings: Optional[AccessSettings], - ) -> Optional[Dict]: + ) -> Optional[dict]: if not access_settings: return None return { diff --git a/src/aiogram_dialog/dialog.py b/src/aiogram_dialog/dialog.py index 164d045c..5da26c73 100644 --- a/src/aiogram_dialog/dialog.py +++ b/src/aiogram_dialog/dialog.py @@ -1,12 +1,8 @@ +from collections.abc import Awaitable, Callable from logging import getLogger from typing import ( Any, - Awaitable, - Callable, - Dict, - List, Optional, - Type, TypeVar, Union, ) @@ -51,7 +47,7 @@ def __init__( ): super().__init__(name=name or windows[0].get_state().group.__name__) self._states_group = windows[0].get_state().group - self._states: List[State] = [] + self._states: list[State] = [] for w in windows: if w.get_state().group != self._states_group: raise ValueError( @@ -61,8 +57,8 @@ def __init__( if state in self._states: raise ValueError(f"Multiple windows with state {state}") self._states.append(state) - self.windows: Dict[State, WindowProtocol] = dict( - zip(self._states, windows), + self.windows: dict[State, WindowProtocol] = dict( + zip(self._states, windows, strict=False), ) self.on_start = on_start self.on_close = on_close @@ -79,7 +75,7 @@ def __init__( def launch_mode(self) -> LaunchMode: return self._launch_mode - def states(self) -> List[State]: + def states(self) -> list[State]: return self._states async def process_start( @@ -113,7 +109,7 @@ async def _current_window( async def load_data( self, manager: DialogManager, - ) -> Dict: + ) -> dict: data = await manager.load_data() data.update(await self.getter(**manager.middleware_data)) return data @@ -188,7 +184,7 @@ def _register_handlers(self) -> None: self.callback_query.register(self._callback_handler) self.message.register(self._message_handler) - def states_group(self) -> Type[StatesGroup]: + def states_group(self) -> type[StatesGroup]: return self._states_group def states_group_name(self) -> str: diff --git a/src/aiogram_dialog/manager/bg_manager.py b/src/aiogram_dialog/manager/bg_manager.py index 4e4adb34..dfd05c74 100644 --- a/src/aiogram_dialog/manager/bg_manager.py +++ b/src/aiogram_dialog/manager/bg_manager.py @@ -1,5 +1,5 @@ from logging import getLogger -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union from aiogram import Bot, Router from aiogram.fsm.state import State @@ -241,7 +241,7 @@ async def switch_to( async def update( self, - data: Dict, + data: dict, show_mode: Optional[ShowMode] = None, ) -> None: await self._load() diff --git a/src/aiogram_dialog/manager/manager.py b/src/aiogram_dialog/manager/manager.py index fc984f13..b310b381 100644 --- a/src/aiogram_dialog/manager/manager.py +++ b/src/aiogram_dialog/manager/manager.py @@ -1,6 +1,6 @@ from copy import deepcopy from logging import getLogger -from typing import Any, cast, Dict, Optional, Union +from typing import Any, cast, Optional, Union from aiogram import Router from aiogram.enums import ChatType @@ -55,7 +55,7 @@ def __init__( media_id_storage: MediaIdStorageProtocol, registry: DialogRegistryProtocol, router: Router, - data: Dict, + data: dict, ): self.disabled = False self.message_manager = message_manager @@ -81,12 +81,12 @@ def event(self) -> ChatEvent: return self._event @property - def middleware_data(self) -> Dict: + def middleware_data(self) -> dict: """Middleware data.""" return self._data @property - def dialog_data(self) -> Dict: + def dialog_data(self) -> dict: """Dialog data for current context.""" return self.current_context().dialog_data @@ -103,7 +103,7 @@ def check_disabled(self): "method to access methods from background tasks", ) - async def load_data(self) -> Dict: + async def load_data(self) -> dict: context = self.current_context() return { "dialog_data": context.dialog_data, @@ -468,7 +468,7 @@ def _calc_show_mode(self) -> ShowMode: async def update( self, - data: Dict, + data: dict, show_mode: Optional[ShowMode] = None, ) -> None: self.current_context().dialog_data.update(data) diff --git a/src/aiogram_dialog/manager/manager_factory.py b/src/aiogram_dialog/manager/manager_factory.py index a42f157c..aafa263a 100644 --- a/src/aiogram_dialog/manager/manager_factory.py +++ b/src/aiogram_dialog/manager/manager_factory.py @@ -1,5 +1,3 @@ -from typing import Dict - from aiogram import Router from aiogram_dialog.api.entities import ChatEvent @@ -21,7 +19,7 @@ def __init__( self.media_id_storage = media_id_storage def __call__( - self, event: ChatEvent, data: Dict, + self, event: ChatEvent, data: dict, registry: DialogRegistryProtocol, router: Router, ) -> DialogManager: diff --git a/src/aiogram_dialog/manager/manager_middleware.py b/src/aiogram_dialog/manager/manager_middleware.py index 4d9d4829..2166eaae 100644 --- a/src/aiogram_dialog/manager/manager_middleware.py +++ b/src/aiogram_dialog/manager/manager_middleware.py @@ -1,4 +1,5 @@ -from typing import Any, Awaitable, Callable, Dict, Union +from typing import Any, Union +from collections.abc import Awaitable, Callable from aiogram import Router from aiogram.dispatcher.middlewares.base import BaseMiddleware @@ -27,18 +28,18 @@ def __init__( self.router = router def _is_event_supported( - self, event: TelegramObject, data: Dict[str, Any], + self, event: TelegramObject, data: dict[str, Any], ) -> bool: return STORAGE_KEY in data async def __call__( self, handler: Callable[ - [Union[Update, DialogUpdateEvent], Dict[str, Any]], + [Union[Update, DialogUpdateEvent], dict[str, Any]], Awaitable[Any], ], event: ChatEvent, - data: Dict[str, Any], + data: dict[str, Any], ) -> Any: if self._is_event_supported(event, data): data[MANAGER_KEY] = self.dialog_manager_factory( @@ -67,11 +68,11 @@ def __init__( async def __call__( self, handler: Callable[ - [Union[TelegramObject, DialogUpdateEvent], Dict[str, Any]], + [Union[TelegramObject, DialogUpdateEvent], dict[str, Any]], Awaitable[TelegramObject], ], event: TelegramObject, - data: Dict[str, Any], + data: dict[str, Any], ) -> Any: data[BG_FACTORY_KEY] = self.bg_manager_factory return await handler(event, data) diff --git a/src/aiogram_dialog/manager/sub_manager.py b/src/aiogram_dialog/manager/sub_manager.py index 86069a34..6767e6dc 100644 --- a/src/aiogram_dialog/manager/sub_manager.py +++ b/src/aiogram_dialog/manager/sub_manager.py @@ -1,5 +1,5 @@ import dataclasses -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union from aiogram.fsm.state import State from aiogram.types import Message @@ -33,12 +33,12 @@ def event(self) -> ChatEvent: return self.manager.event @property - def middleware_data(self) -> Dict: + def middleware_data(self) -> dict: """Middleware data.""" return self.manager.middleware_data @property - def dialog_data(self) -> Dict: + def dialog_data(self) -> dict: """Dialog data for current context.""" return self.current_context().dialog_data @@ -74,7 +74,7 @@ async def answer_callback(self) -> None: async def reset_stack(self, remove_keyboard: bool = True) -> None: return await self.manager.reset_stack(remove_keyboard) - async def load_data(self) -> Dict: + async def load_data(self) -> dict: return await self.manager.load_data() def find(self, widget_id) -> Optional[Any]: @@ -133,7 +133,7 @@ async def switch_to( async def update( self, - data: Dict, + data: dict, show_mode: Optional[ShowMode] = None, ) -> None: self.current_context().dialog_data.update(data) diff --git a/src/aiogram_dialog/setup.py b/src/aiogram_dialog/setup.py index c7825799..7eb5eac5 100644 --- a/src/aiogram_dialog/setup.py +++ b/src/aiogram_dialog/setup.py @@ -1,4 +1,5 @@ -from typing import Callable, Dict, Iterable, Optional, Type, Union +from typing import Optional, Union +from collections.abc import Callable, Iterable from aiogram import Router from aiogram.dispatcher.event.telegram import TelegramEventObserver @@ -63,7 +64,7 @@ def find_dialog(self, state: Union[State, str]) -> DialogProtocol: f" (looking by state `{state}`)", ) from e - def states_groups(self) -> Dict[str, Type[StatesGroup]]: + def states_groups(self) -> dict[str, type[StatesGroup]]: self._ensure_loaded() return self._states_groups diff --git a/src/aiogram_dialog/tools/preview.py b/src/aiogram_dialog/tools/preview.py index 63b24188..aa2ae080 100644 --- a/src/aiogram_dialog/tools/preview.py +++ b/src/aiogram_dialog/tools/preview.py @@ -2,18 +2,26 @@ import logging from dataclasses import dataclass from datetime import datetime -from typing import Any, Dict, List, Optional, Type, Union +from typing import Any, Optional, Union from aiogram import Router from aiogram.fsm.state import State, StatesGroup from aiogram.types import ( - CallbackQuery, Chat, ContentType, InlineKeyboardMarkup, Message, - ReplyKeyboardMarkup, User, + CallbackQuery, + Chat, + ContentType, + InlineKeyboardMarkup, + Message, + ReplyKeyboardMarkup, + User, ) from jinja2 import Environment, PackageLoader, select_autoescape from aiogram_dialog import ( - BaseDialogManager, Dialog, DialogManager, DialogProtocol, + BaseDialogManager, + Dialog, + DialogManager, + DialogProtocol, ) from aiogram_dialog.api.entities import ( AccessSettings, @@ -47,8 +55,8 @@ class RenderWindow: message: str state: str state_name: str - keyboard: List[List[RenderButton]] - reply_keyboard: List[List[RenderButton]] + keyboard: list[list[RenderButton]] + reply_keyboard: list[list[RenderButton]] photo: Optional[str] text_input: Optional[RenderButton] attachment_input: Optional[RenderButton] @@ -57,7 +65,7 @@ class RenderWindow: @dataclass class RenderDialog: state_group: str - windows: List[RenderWindow] + windows: list[RenderWindow] class FakeManager(DialogManager): @@ -104,14 +112,14 @@ async def back(self, show_mode: Optional[ShowMode] = None) -> None: await self.switch_to(new_state, show_mode) @property - def middleware_data(self) -> Dict: + def middleware_data(self) -> dict: return self._data @property def event(self) -> ChatEvent: return self._event - async def load_data(self) -> Dict: + async def load_data(self) -> dict: return {} async def close_manager(self) -> None: @@ -131,7 +139,7 @@ def is_preview(self) -> bool: return True @property - def dialog_data(self) -> Dict: + def dialog_data(self) -> dict: return self.current_context().dialog_data def reset_context(self) -> None: @@ -208,7 +216,7 @@ def find(self, widget_id) -> Optional[Any]: async def update( self, - data: Dict, + data: dict, show_mode: Optional[ShowMode] = None, ) -> None: pass @@ -408,7 +416,7 @@ async def create_window( async def render_dialog( manager: FakeManager, - group: Type[StatesGroup], + group: type[StatesGroup], dialog: Dialog, simulate_events: bool, ) -> RenderDialog: diff --git a/src/aiogram_dialog/tools/transitions.py b/src/aiogram_dialog/tools/transitions.py index 161dd32d..3706658d 100644 --- a/src/aiogram_dialog/tools/transitions.py +++ b/src/aiogram_dialog/tools/transitions.py @@ -1,5 +1,5 @@ import os.path -from typing import Iterable, List, Sequence, Tuple +from collections.abc import Iterable, Sequence from aiogram import Router from aiogram.fsm.state import State @@ -46,7 +46,7 @@ def widget_edges(nodes, dialog, starts, current_state, kbd): def walk_keyboard( nodes, dialog, - starts: List[Tuple[State, State]], + starts: list[tuple[State, State]], current_state: State, keyboards: Sequence, ): @@ -59,7 +59,7 @@ def walk_keyboard( def find_starts( current_state, keyboards: Sequence, -) -> Iterable[Tuple[State, State]]: +) -> Iterable[tuple[State, State]]: for kbd in keyboards: if isinstance(kbd, Group): yield from find_starts(current_state, kbd.buttons) @@ -68,7 +68,7 @@ def find_starts( def render_window( - nodes: dict, dialog: Dialog, starts: List[Tuple[State, State]], + nodes: dict, dialog: Dialog, starts: list[tuple[State, State]], window: WindowProtocol, ): walk_keyboard( diff --git a/src/aiogram_dialog/utils.py b/src/aiogram_dialog/utils.py index 17682f63..023b4a77 100644 --- a/src/aiogram_dialog/utils.py +++ b/src/aiogram_dialog/utils.py @@ -1,5 +1,5 @@ from logging import getLogger -from typing import List, Optional, Tuple, Union +from typing import Optional, Union from aiogram.types import ( CallbackQuery, @@ -70,7 +70,7 @@ def join_reply_callback(text: str, callback_data: str) -> str: def split_reply_callback( data: Optional[str], -) -> Tuple[Optional[str], Optional[str]]: +) -> tuple[Optional[str], Optional[str]]: if not data: return None, None text = data.rstrip(REPLY_CALLBACK_SYMBOLS) @@ -103,8 +103,8 @@ def _transform_to_reply_button( def transform_to_reply_keyboard( - keyboard: List[List[Union[InlineKeyboardButton, KeyboardButton]]], -) -> List[List[KeyboardButton]]: + keyboard: list[list[Union[InlineKeyboardButton, KeyboardButton]]], +) -> list[list[KeyboardButton]]: new_kdb = [] for row in keyboard: new_row = [] @@ -186,7 +186,7 @@ def add_intent_id(keyboard: RawKeyboard, intent_id: str): ) -def remove_intent_id(callback_data: str) -> Tuple[Optional[str], str]: +def remove_intent_id(callback_data: str) -> tuple[Optional[str], str]: if CB_SEP in callback_data: intent_id, new_data = callback_data.split(CB_SEP, maxsplit=1) return intent_id, new_data diff --git a/src/aiogram_dialog/widgets/common/__init__.py b/src/aiogram_dialog/widgets/common/__init__.py index 7f0dd208..bc59f780 100644 --- a/src/aiogram_dialog/widgets/common/__init__.py +++ b/src/aiogram_dialog/widgets/common/__init__.py @@ -2,16 +2,26 @@ "Actionable", "BaseWidget", "ManagedWidget", - "BaseScroll", "ManagedScroll", - "OnPageChanged", "OnPageChangedVariants", "Scroll", "sync_scroll", - "true_condition", "Whenable", "WhenCondition", + "BaseScroll", + "ManagedScroll", + "OnPageChanged", + "OnPageChangedVariants", + "Scroll", + "sync_scroll", + "true_condition", + "Whenable", + "WhenCondition", ] from .action import Actionable from .base import BaseWidget from .managed import ManagedWidget from .scroll import ( - BaseScroll, ManagedScroll, OnPageChanged, OnPageChangedVariants, Scroll, + BaseScroll, + ManagedScroll, + OnPageChanged, + OnPageChangedVariants, + Scroll, sync_scroll, ) from .when import true_condition, Whenable, WhenCondition diff --git a/src/aiogram_dialog/widgets/common/items.py b/src/aiogram_dialog/widgets/common/items.py index 211a5b11..bac707ac 100644 --- a/src/aiogram_dialog/widgets/common/items.py +++ b/src/aiogram_dialog/widgets/common/items.py @@ -1,9 +1,10 @@ from operator import itemgetter -from typing import Callable, Dict, Sequence, Union +from typing import Union +from collections.abc import Callable, Sequence from magic_filter import MagicFilter -ItemsGetter = Callable[[Dict], Sequence] +ItemsGetter = Callable[[dict], Sequence] ItemsGetterVariant = Union[str, ItemsGetter, MagicFilter, Sequence] @@ -15,7 +16,7 @@ def identity(data) -> Sequence: def _get_magic_getter(f: MagicFilter) -> ItemsGetter: - def items_magic(data: Dict) -> Sequence: + def items_magic(data: dict) -> Sequence: items = f.resolve(data) if isinstance(items, Sequence): return items diff --git a/src/aiogram_dialog/widgets/common/scroll.py b/src/aiogram_dialog/widgets/common/scroll.py index 5c1ceb86..bd9e0e9e 100644 --- a/src/aiogram_dialog/widgets/common/scroll.py +++ b/src/aiogram_dialog/widgets/common/scroll.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod -from typing import Awaitable, Callable, Dict, Protocol, Sequence, Union +from typing import Protocol, Union +from collections.abc import Awaitable, Callable, Sequence from aiogram_dialog.api.entities import ChatEvent from aiogram_dialog.api.internal import Widget @@ -13,7 +14,7 @@ class Scroll(Widget, Protocol): @abstractmethod - async def get_page_count(self, data: Dict, manager: DialogManager) -> int: + async def get_page_count(self, data: dict, manager: DialogManager) -> int: raise NotImplementedError @abstractmethod @@ -32,7 +33,7 @@ def managed(self, manager: DialogManager) -> "ManagedScroll": class ManagedScroll(ManagedWidget[Scroll]): - async def get_page_count(self, data: Dict) -> int: + async def get_page_count(self, data: dict) -> int: return await self.widget.get_page_count(data, self.manager) async def get_page(self) -> int: diff --git a/src/aiogram_dialog/widgets/common/when.py b/src/aiogram_dialog/widgets/common/when.py index fec1eb81..24bf39d6 100644 --- a/src/aiogram_dialog/widgets/common/when.py +++ b/src/aiogram_dialog/widgets/common/when.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import abstractmethod -from typing import Dict, Protocol, Union +from typing import Protocol, Union from magic_filter import MagicFilter @@ -12,7 +12,7 @@ class Predicate(Protocol): @abstractmethod def __call__( self, - data: Dict, + data: dict, widget: Whenable, dialog_manager: DialogManager, ) -> bool: @@ -32,7 +32,7 @@ def __call__( def new_when_field(fieldname: str) -> Predicate: def when_field( - data: Dict, widget: "Whenable", manager: DialogManager, + data: dict, widget: "Whenable", manager: DialogManager, ) -> bool: return bool(data.get(fieldname)) @@ -41,14 +41,14 @@ def when_field( def new_when_magic(f: MagicFilter) -> Predicate: def when_magic( - data: Dict, widget: "Whenable", manager: DialogManager, + data: dict, widget: "Whenable", manager: DialogManager, ) -> bool: return f.resolve(data) return when_magic -def true_condition(data: Dict, widget: "Whenable", manager: DialogManager): +def true_condition(data: dict, widget: "Whenable", manager: DialogManager): return True @@ -64,5 +64,5 @@ def __init__(self, when: WhenCondition = None): else: self.condition = when - def is_(self, data: Dict, manager: DialogManager): + def is_(self, data: dict, manager: DialogManager): return self.condition(data, self, manager) diff --git a/src/aiogram_dialog/widgets/data/data_context.py b/src/aiogram_dialog/widgets/data/data_context.py index 4798b7ca..c3cbd941 100644 --- a/src/aiogram_dialog/widgets/data/data_context.py +++ b/src/aiogram_dialog/widgets/data/data_context.py @@ -1,12 +1,10 @@ -from typing import Dict, List - from aiogram_dialog.api.internal.widgets import DataGetter from aiogram_dialog.api.protocols import DialogManager class CompositeGetter: def __init__(self, *getters: DataGetter): - self.getters: List[DataGetter] = list(getters) + self.getters: list[DataGetter] = list(getters) async def __call__(self, **kwargs): data = {} @@ -16,7 +14,7 @@ async def __call__(self, **kwargs): class StaticGetter: - def __init__(self, data: Dict): + def __init__(self, data: dict): self.data = data async def __call__(self, **kwargs): diff --git a/src/aiogram_dialog/widgets/input/base.py b/src/aiogram_dialog/widgets/input/base.py index 32d3d143..ed893835 100644 --- a/src/aiogram_dialog/widgets/input/base.py +++ b/src/aiogram_dialog/widgets/input/base.py @@ -1,5 +1,6 @@ from abc import abstractmethod -from typing import Any, Awaitable, Callable, Optional, Sequence, Union +from typing import Any, Optional, Union +from collections.abc import Awaitable, Callable, Sequence from aiogram import F from aiogram.dispatcher.event.handler import FilterObject @@ -44,9 +45,8 @@ def __init__( if isinstance(content_types, str): if content_types != ContentType.ANY: filters.append(FilterObject(F.content_type == content_types)) - else: - if ContentType.ANY not in content_types: - filters.append(FilterObject(F.content_type.in_(content_types))) + elif ContentType.ANY not in content_types: + filters.append(FilterObject(F.content_type.in_(content_types))) if filter is not None: filters.append(FilterObject(filter)) self.filters = filters diff --git a/src/aiogram_dialog/widgets/input/combined.py b/src/aiogram_dialog/widgets/input/combined.py index 9b37f3a8..c9194624 100644 --- a/src/aiogram_dialog/widgets/input/combined.py +++ b/src/aiogram_dialog/widgets/input/combined.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Optional +from typing import Any, Optional +from collections.abc import Callable from aiogram.dispatcher.event.handler import FilterObject from aiogram.types import Message diff --git a/src/aiogram_dialog/widgets/input/text.py b/src/aiogram_dialog/widgets/input/text.py index 0d1f0761..cd87b3e6 100644 --- a/src/aiogram_dialog/widgets/input/text.py +++ b/src/aiogram_dialog/widgets/input/text.py @@ -2,8 +2,14 @@ from abc import abstractmethod from typing import ( - Any, Callable, Generic, Optional, Protocol, TypeVar, Union, + Any, + Generic, + Optional, + Protocol, + TypeVar, + Union, ) +from collections.abc import Callable from aiogram.dispatcher.event.handler import FilterObject from aiogram.types import ContentType, Message diff --git a/src/aiogram_dialog/widgets/kbd/button.py b/src/aiogram_dialog/widgets/kbd/button.py index 8329e5d2..37e5355a 100644 --- a/src/aiogram_dialog/widgets/kbd/button.py +++ b/src/aiogram_dialog/widgets/kbd/button.py @@ -1,4 +1,5 @@ -from typing import Awaitable, Callable, Dict, List, Optional, Union +from typing import Optional, Union +from collections.abc import Awaitable, Callable from aiogram.types import CallbackQuery, InlineKeyboardButton, WebAppInfo @@ -38,7 +39,7 @@ async def _process_own_callback( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: return [ @@ -65,7 +66,7 @@ def __init__( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: return [ @@ -80,8 +81,8 @@ async def _render_keyboard( class WebApp(Url): async def _render_keyboard( - self, data: Dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + self, data: dict, manager: DialogManager, + ) -> list[list[InlineKeyboardButton]]: text = await self.text.render_text(data, manager) web_app_url = await self.url.render_text(data, manager) @@ -104,9 +105,9 @@ def __init__( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: return [ [ InlineKeyboardButton( diff --git a/src/aiogram_dialog/widgets/kbd/calendar_kbd.py b/src/aiogram_dialog/widgets/kbd/calendar_kbd.py index a886f347..2ee1b07a 100644 --- a/src/aiogram_dialog/widgets/kbd/calendar_kbd.py +++ b/src/aiogram_dialog/widgets/kbd/calendar_kbd.py @@ -1,10 +1,16 @@ from __future__ import annotations +from collections.abc import Callable from dataclasses import dataclass from datetime import date, datetime, timedelta, timezone from enum import Enum from typing import ( - Any, Callable, Dict, List, Optional, Protocol, TypedDict, TypeVar, Union, + Any, + Optional, + Protocol, + TypedDict, + TypeVar, + Union, ) from aiogram.types import CallbackQuery, InlineKeyboardButton @@ -66,8 +72,7 @@ class CalendarScope(Enum): def raw_from_date(d: date) -> int: diff = d - EPOCH - raw_date = int(diff.total_seconds()) - return raw_date + return int(diff.total_seconds()) def date_from_raw(raw_date: int) -> date: @@ -161,9 +166,9 @@ async def render( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: """ Render keyboard for current scope. @@ -203,7 +208,7 @@ async def _render_date_button( self, selected_date: date, today: date, - data: Dict, + data: dict, manager: DialogManager, ) -> InlineKeyboardButton: current_data = { @@ -228,9 +233,9 @@ async def _render_days( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: keyboard = [] # align beginning start_date = offset.replace(day=1) # month beginning @@ -264,9 +269,9 @@ async def _render_days( async def _render_week_header( self, config: CalendarConfig, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[InlineKeyboardButton]: + ) -> list[InlineKeyboardButton]: week_range = range(config.firstweekday, config.firstweekday + 7) header = [] for week_day in week_range: @@ -286,9 +291,9 @@ async def _render_pager( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[InlineKeyboardButton]: + ) -> list[InlineKeyboardButton]: curr_month = offset.month next_month = (curr_month % 12) + 1 prev_month = (curr_month - 2) % 12 + 1 @@ -347,9 +352,9 @@ async def _render_header( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[InlineKeyboardButton]: + ) -> list[InlineKeyboardButton]: data = { "date": offset, "data": data, @@ -363,9 +368,9 @@ async def render( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: return [ await self._render_header(config, offset, data, manager), await self._render_week_header(config, data, manager), @@ -397,9 +402,9 @@ async def _render_pager( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[InlineKeyboardButton]: + ) -> list[InlineKeyboardButton]: curr_year = offset.year next_year = curr_year + 1 prev_year = curr_year - 1 @@ -467,7 +472,7 @@ async def _render_month_button( self, month: int, this_month: int, - data: Dict, + data: dict, offset: date, config: CalendarConfig, manager: DialogManager, @@ -498,9 +503,9 @@ async def _render_months( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: keyboard = [] today = get_today(config.timezone) if offset.year == today.year: @@ -519,7 +524,7 @@ async def _render_months( async def _render_header( self, config, offset, data, manager, - ) -> List[InlineKeyboardButton]: + ) -> list[InlineKeyboardButton]: data = { "date": offset, "data": data, @@ -533,9 +538,9 @@ async def render( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: return [ await self._render_header(config, offset, data, manager), *await self._render_months(config, offset, data, manager), @@ -562,9 +567,9 @@ async def _render_pager( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[InlineKeyboardButton]: + ) -> list[InlineKeyboardButton]: curr_year = offset.year next_year = curr_year + config.years_per_page prev_year = curr_year - config.years_per_page @@ -619,7 +624,7 @@ async def _render_year_button( self, year: int, this_year: int, - data: Dict, + data: dict, config: CalendarConfig, manager: DialogManager, ) -> InlineKeyboardButton: @@ -648,9 +653,9 @@ async def _render_years( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: keyboard = [] this_year = get_today(config.timezone).year years_columns = config.years_columns @@ -670,9 +675,9 @@ async def render( self, config: CalendarConfig, offset: date, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: return [ *await self._render_years(config, offset, data, manager), await self._render_pager(config, offset, data, manager), @@ -718,7 +723,7 @@ def __init__( CALLBACK_SCOPE_YEARS: self._handle_scope_years, } - def _init_views(self) -> Dict[CalendarScope, CalendarScopeView]: + def _init_views(self) -> dict[CalendarScope, CalendarScopeView]: """ Calendar scopes view initializer. @@ -734,7 +739,7 @@ def _init_views(self) -> Dict[CalendarScope, CalendarScopeView]: async def _get_user_config( self, - data: Dict, + data: dict, manager: DialogManager, ) -> CalendarUserConfig: """ @@ -790,7 +795,7 @@ def set_scope(self, new_scope: CalendarScope, data = self.get_widget_data(manager, {}) data["current_scope"] = new_scope.value - def managed(self, manager: DialogManager) -> "ManagedCalendar": + def managed(self, manager: DialogManager) -> ManagedCalendar: return ManagedCalendar(self, manager) async def _handle_scope_months( diff --git a/src/aiogram_dialog/widgets/kbd/checkbox.py b/src/aiogram_dialog/widgets/kbd/checkbox.py index 634f8e15..7b0c21c1 100644 --- a/src/aiogram_dialog/widgets/kbd/checkbox.py +++ b/src/aiogram_dialog/widgets/kbd/checkbox.py @@ -1,5 +1,6 @@ from abc import ABC, abstractmethod -from typing import Awaitable, Callable, Dict, Optional, Union +from typing import Optional, Union +from collections.abc import Awaitable, Callable from aiogram.types import CallbackQuery, InlineKeyboardButton @@ -41,7 +42,7 @@ def __init__( self.on_state_changed = ensure_event_processor(on_state_changed) async def _render_keyboard( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> RawKeyboard: checked = int(self.is_checked(manager)) # store current checked status in callback data @@ -70,7 +71,7 @@ async def _process_item_callback( return True def _is_text_checked( - self, data: Dict, case: Case, manager: DialogManager, + self, data: dict, case: Case, manager: DialogManager, ) -> bool: del data # unused del case # unused diff --git a/src/aiogram_dialog/widgets/kbd/counter.py b/src/aiogram_dialog/widgets/kbd/counter.py index 35f914c7..a034c1ee 100644 --- a/src/aiogram_dialog/widgets/kbd/counter.py +++ b/src/aiogram_dialog/widgets/kbd/counter.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Dict, Optional, Protocol, Union +from typing import Optional, Protocol, Union from aiogram.types import CallbackQuery, InlineKeyboardButton @@ -104,7 +104,7 @@ async def set_value(self, manager: DialogManager, async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: row = [] diff --git a/src/aiogram_dialog/widgets/kbd/group.py b/src/aiogram_dialog/widgets/kbd/group.py index c8d5ec23..02d9e6b8 100644 --- a/src/aiogram_dialog/widgets/kbd/group.py +++ b/src/aiogram_dialog/widgets/kbd/group.py @@ -1,5 +1,6 @@ from itertools import chain -from typing import Dict, Iterable, List, Optional +from typing import Optional +from collections.abc import Iterable from aiogram.types import CallbackQuery, InlineKeyboardButton @@ -33,7 +34,7 @@ def find(self, widget_id): async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: kbd: RawKeyboard = [] @@ -54,7 +55,7 @@ def _wrap_kbd( kbd: Iterable[InlineKeyboardButton], ) -> RawKeyboard: res: RawKeyboard = [] - row: List[ButtonVariant] = [] + row: list[ButtonVariant] = [] for b in kbd: row.append(b) if len(row) >= self.width: diff --git a/src/aiogram_dialog/widgets/kbd/list_group.py b/src/aiogram_dialog/widgets/kbd/list_group.py index d137d02d..499e1ce1 100644 --- a/src/aiogram_dialog/widgets/kbd/list_group.py +++ b/src/aiogram_dialog/widgets/kbd/list_group.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Optional, Union +from collections.abc import Callable from aiogram.types import CallbackQuery @@ -9,7 +10,10 @@ from aiogram_dialog.manager.sub_manager import SubManager from aiogram_dialog.widgets.common import ManagedWidget, WhenCondition from .base import Keyboard -from ..common.items import get_items_getter, ItemsGetterVariant +from aiogram_dialog.widgets.common.items import ( + get_items_getter, + ItemsGetterVariant, +) ItemIdGetter = Callable[[Any], Union[str, int]] @@ -29,7 +33,7 @@ def __init__( self.items_getter = get_items_getter(items) async def _render_keyboard( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> RawKeyboard: kbd: RawKeyboard = [] for pos, item in enumerate(self.items_getter(data)): @@ -40,7 +44,7 @@ async def _render_item( self, pos: int, item: Any, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: kbd: RawKeyboard = [] diff --git a/src/aiogram_dialog/widgets/kbd/pager.py b/src/aiogram_dialog/widgets/kbd/pager.py index 7d0b7ffa..e4958160 100644 --- a/src/aiogram_dialog/widgets/kbd/pager.py +++ b/src/aiogram_dialog/widgets/kbd/pager.py @@ -1,6 +1,6 @@ from abc import ABC from enum import Enum -from typing import Dict, TypedDict, Union +from typing import TypedDict, Union from aiogram.types import CallbackQuery, InlineKeyboardButton @@ -20,7 +20,7 @@ class PageDirection(Enum): class PagerData(TypedDict): - data: Dict + data: dict current_page: int current_page1: int pages: int @@ -107,7 +107,7 @@ async def _get_target_page( return min(last_page, current_page) async def _prepare_data( - self, data: Dict, + self, data: dict, target_page: int, current_page: int, pages: int, ) -> PagerPageData: @@ -122,7 +122,7 @@ async def _prepare_data( } async def render_keyboard( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> RawKeyboard: scroll = self._find_scroll(manager) pages = await scroll.get_page_count(data) @@ -229,7 +229,7 @@ def __init__( self.current_page_text = current_page_text async def _prepare_data( - self, data: Dict, + self, data: dict, current_page: int, pages: int, ) -> PagerData: return { @@ -240,7 +240,7 @@ async def _prepare_data( } async def _prepare_page_data( - self, data: Dict, target_page: int, + self, data: dict, target_page: int, ) -> PagerData: data = data.copy() data["target_page"] = target_page @@ -248,7 +248,7 @@ async def _prepare_page_data( return data async def render_keyboard( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> RawKeyboard: scroll = self._find_scroll(manager) pages = await scroll.get_page_count(data) diff --git a/src/aiogram_dialog/widgets/kbd/request.py b/src/aiogram_dialog/widgets/kbd/request.py index 026086c6..6dc9576e 100644 --- a/src/aiogram_dialog/widgets/kbd/request.py +++ b/src/aiogram_dialog/widgets/kbd/request.py @@ -1,4 +1,5 @@ -from typing import Callable, Dict, Union +from typing import Union +from collections.abc import Callable from aiogram.types import KeyboardButton @@ -19,7 +20,7 @@ def __init__( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: return [ @@ -43,7 +44,7 @@ def __init__( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: return [ diff --git a/src/aiogram_dialog/widgets/kbd/scrolling_group.py b/src/aiogram_dialog/widgets/kbd/scrolling_group.py index 4bc01ad7..015d6165 100644 --- a/src/aiogram_dialog/widgets/kbd/scrolling_group.py +++ b/src/aiogram_dialog/widgets/kbd/scrolling_group.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Optional from aiogram.types import CallbackQuery, InlineKeyboardButton @@ -37,7 +37,7 @@ def _get_page_count( async def _render_contents( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: return await super()._render_keyboard(data, manager) @@ -84,8 +84,8 @@ async def _render_pager( async def _render_page( self, page: int, - keyboard: List[List[InlineKeyboardButton]], - ) -> List[List[InlineKeyboardButton]]: + keyboard: list[list[InlineKeyboardButton]], + ) -> list[list[InlineKeyboardButton]]: pages = self._get_page_count(keyboard) last_page = pages - 1 current_page = min(last_page, page) @@ -95,7 +95,7 @@ async def _render_page( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: keyboard = await self._render_contents(data, manager) @@ -119,6 +119,6 @@ async def _process_item_callback( await self.set_page(callback, int(data), manager) return True - async def get_page_count(self, data: Dict, manager: DialogManager) -> int: + async def get_page_count(self, data: dict, manager: DialogManager) -> int: keyboard = await self._render_contents(data, manager) return self._get_page_count(keyboard=keyboard) diff --git a/src/aiogram_dialog/widgets/kbd/select.py b/src/aiogram_dialog/widgets/kbd/select.py index b3959760..5596c7a8 100644 --- a/src/aiogram_dialog/widgets/kbd/select.py +++ b/src/aiogram_dialog/widgets/kbd/select.py @@ -1,10 +1,8 @@ from abc import ABC, abstractmethod +from collections.abc import Callable from typing import ( Any, - Callable, - Dict, Generic, - List, Optional, Protocol, TypeVar, @@ -17,13 +15,16 @@ from aiogram_dialog.api.internal import RawKeyboard from aiogram_dialog.api.protocols import DialogManager, DialogProtocol from aiogram_dialog.widgets.common import ManagedWidget, WhenCondition +from aiogram_dialog.widgets.common.items import ( + get_items_getter, + ItemsGetterVariant, +) from aiogram_dialog.widgets.text import Case, Text from aiogram_dialog.widgets.widget_event import ( ensure_event_processor, WidgetEventProcessor, ) from .base import Keyboard -from ..common.items import get_items_getter, ItemsGetterVariant T = TypeVar("T") ManagedT = TypeVar("ManagedT") @@ -79,7 +80,7 @@ def __init__( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: return [ @@ -90,7 +91,7 @@ async def _render_keyboard( ] async def _render_button( - self, pos: int, item: Any, target_item: Any, data: Dict, + self, pos: int, item: Any, target_item: Any, data: dict, manager: DialogManager, ) -> InlineKeyboardButton: """ @@ -167,7 +168,7 @@ async def _process_on_state_changed( @abstractmethod def _is_text_checked( - self, data: Dict, case: Case, manager: DialogManager, + self, data: dict, case: Case, manager: DialogManager, ) -> bool: raise NotImplementedError @@ -272,7 +273,7 @@ def _preview_checked_id( return self.get_widget_data(manager, item_id) def _is_text_checked( - self, data: Dict, case: Case, manager: DialogManager, + self, data: dict, case: Case, manager: DialogManager, ) -> bool: item_id = self.item_id_getter(data["item"]) if manager.is_preview(): @@ -344,7 +345,7 @@ def __init__( self.max_selected = max_selected def _is_text_checked( - self, data: Dict, case: Case, manager: DialogManager, + self, data: dict, case: Case, manager: DialogManager, ) -> bool: item_id = str(self.item_id_getter(data["item"])) if manager.is_preview(): @@ -360,10 +361,10 @@ def is_checked( data = self._get_checked(manager) return str(item_id) in data - def _get_checked(self, manager: DialogManager) -> List[str]: + def _get_checked(self, manager: DialogManager) -> list[str]: return self.get_widget_data(manager, []) - def get_checked(self, manager: DialogManager) -> List[T]: + def get_checked(self, manager: DialogManager) -> list[T]: return [self.type_factory(item) for item in self._get_checked(manager)] async def reset_checked( @@ -379,18 +380,15 @@ async def set_checked( manager: DialogManager, ) -> None: item_id_str = str(item_id) - data: List = self._get_checked(manager) + data: list = self._get_checked(manager) changed = False if item_id_str in data: - if not checked: - if len(data) > self.min_selected: - data.remove(item_id_str) - changed = True - else: - if checked: - if self.max_selected == 0 or self.max_selected > len(data): - data.append(item_id_str) - changed = True + if not checked and len(data) > self.min_selected: + data.remove(item_id_str) + changed = True + elif checked and self.max_selected == 0 or self.max_selected > len(data): + data.append(item_id_str) + changed = True if changed: self.set_widget_data(manager, data) await self._process_on_state_changed(event, item_id_str, manager) @@ -415,7 +413,7 @@ def is_checked(self, item_id: T) -> bool: """Get if an item identified by ``item_id`` is checked.""" return self.widget.is_checked(item_id, self.manager) - def get_checked(self) -> List[T]: + def get_checked(self) -> list[T]: """Get a list of checked items ids.""" return self.widget.get_checked(self.manager) @@ -459,7 +457,7 @@ def __init__( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: items_it = iter(self.items_getter(data)) diff --git a/src/aiogram_dialog/widgets/kbd/stub_scroll.py b/src/aiogram_dialog/widgets/kbd/stub_scroll.py index 3e71625e..5d3b5178 100644 --- a/src/aiogram_dialog/widgets/kbd/stub_scroll.py +++ b/src/aiogram_dialog/widgets/kbd/stub_scroll.py @@ -1,18 +1,22 @@ -from typing import Callable, Dict, Union +from typing import Union +from collections.abc import Callable from magic_filter import MagicFilter from aiogram_dialog.api.internal import RawKeyboard from aiogram_dialog.api.protocols import DialogManager from .base import Keyboard -from ..common.scroll import BaseScroll, OnPageChangedVariants +from aiogram_dialog.widgets.common.scroll import ( + BaseScroll, + OnPageChangedVariants, +) -PagesGetter = Callable[[Dict, "StubScroll", DialogManager], int] +PagesGetter = Callable[[dict, "StubScroll", DialogManager], int] def new_pages_field(fieldname: str) -> PagesGetter: def pages_field( - data: Dict, widget: "StubScroll", manager: DialogManager, + data: dict, widget: "StubScroll", manager: DialogManager, ) -> int: return data.get(fieldname) @@ -21,7 +25,7 @@ def pages_field( def new_pages_magic(f: MagicFilter) -> PagesGetter: def pages_magic( - data: Dict, widget: "StubScroll", manager: DialogManager, + data: dict, widget: "StubScroll", manager: DialogManager, ) -> int: return f.resolve(data) @@ -30,7 +34,7 @@ def pages_magic( def new_pages_fixed(pages: int) -> PagesGetter: def pages_fixed( - data: Dict, widget: "StubScroll", manager: DialogManager, + data: dict, widget: "StubScroll", manager: DialogManager, ) -> int: return pages @@ -55,10 +59,10 @@ def __init__( async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, ) -> RawKeyboard: return [[]] - async def get_page_count(self, data: Dict, manager: DialogManager) -> int: + async def get_page_count(self, data: dict, manager: DialogManager) -> int: return self._pages(data, self, manager) diff --git a/src/aiogram_dialog/widgets/media/dynamic.py b/src/aiogram_dialog/widgets/media/dynamic.py index c4920739..3b602f7f 100644 --- a/src/aiogram_dialog/widgets/media/dynamic.py +++ b/src/aiogram_dialog/widgets/media/dynamic.py @@ -6,7 +6,8 @@ """ from operator import itemgetter -from typing import Callable, Optional, Union +from typing import Optional, Union +from collections.abc import Callable from aiogram_dialog import DialogManager from aiogram_dialog.api.entities import MediaAttachment diff --git a/src/aiogram_dialog/widgets/media/scroll.py b/src/aiogram_dialog/widgets/media/scroll.py index 9157d693..34bee7cb 100644 --- a/src/aiogram_dialog/widgets/media/scroll.py +++ b/src/aiogram_dialog/widgets/media/scroll.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional +from typing import Optional from aiogram_dialog.api.entities import MediaAttachment from aiogram_dialog.api.protocols import DialogManager @@ -6,7 +6,10 @@ BaseScroll, OnPageChangedVariants, WhenCondition, ) from .base import Media -from ..common.items import get_items_getter, ItemsGetterVariant +from aiogram_dialog.widgets.common.items import ( + get_items_getter, + ItemsGetterVariant, +) class MediaScroll(Media, BaseScroll): @@ -44,6 +47,6 @@ async def _render_media( manager, ) - async def get_page_count(self, data: Dict, manager: DialogManager) -> int: + async def get_page_count(self, data: dict, manager: DialogManager) -> int: items = self.items_getter(data) return len(items) diff --git a/src/aiogram_dialog/widgets/text/base.py b/src/aiogram_dialog/widgets/text/base.py index 4309c406..726f38da 100644 --- a/src/aiogram_dialog/widgets/text/base.py +++ b/src/aiogram_dialog/widgets/text/base.py @@ -1,5 +1,5 @@ from abc import abstractmethod -from typing import Dict, Optional, Union +from typing import Optional, Union from aiogram_dialog.api.internal import TextWidget from aiogram_dialog.api.protocols import DialogManager @@ -13,7 +13,7 @@ def __init__(self, when: WhenCondition = None): super().__init__(when=when) async def render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: """ Create text. @@ -69,7 +69,7 @@ def __init__(self, text: str, when: WhenCondition = None): self.text = text async def _render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: return self.text @@ -81,7 +81,7 @@ def __init__(self, *texts: Text, sep="\n", when: WhenCondition = None): self.sep = sep async def _render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: texts = [await t.render_text(data, manager) for t in self.texts] return self.sep.join(filter(None, texts)) @@ -123,7 +123,7 @@ def __init__(self, *texts: Text): self.texts = texts async def _render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: for text in self.texts: res = await text.render_text(data, manager) diff --git a/src/aiogram_dialog/widgets/text/format.py b/src/aiogram_dialog/widgets/text/format.py index d6caedbe..89e564ce 100644 --- a/src/aiogram_dialog/widgets/text/format.py +++ b/src/aiogram_dialog/widgets/text/format.py @@ -1,5 +1,3 @@ -from typing import Dict - from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import WhenCondition from .base import Text @@ -34,7 +32,7 @@ def __init__(self, text: str, when: WhenCondition = None): self.text = text async def _render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: if manager.is_preview(): return self.text.format_map(_FormatDataStub(data=data)) diff --git a/src/aiogram_dialog/widgets/text/jinja.py b/src/aiogram_dialog/widgets/text/jinja.py index 65a2348f..abe38781 100644 --- a/src/aiogram_dialog/widgets/text/jinja.py +++ b/src/aiogram_dialog/widgets/text/jinja.py @@ -1,14 +1,10 @@ import warnings from typing import ( Any, - Callable, - Dict, - Iterable, - Mapping, Optional, - Tuple, Union, ) +from collections.abc import Callable, Iterable, Mapping from aiogram import Bot, Dispatcher from jinja2 import BaseLoader, Environment @@ -20,7 +16,7 @@ JINJA_ENV_FIELD = "DialogsJinjaEnvironment" Filter = Callable[..., str] -Filters = Union[Iterable[Tuple[str, Filter]], Mapping[str, Filter]] +Filters = Union[Iterable[tuple[str, Filter]], Mapping[str, Filter]] class Jinja(Text): @@ -29,7 +25,7 @@ def __init__(self, text: str, when: WhenCondition = None): self.template_text = text async def _render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: if JINJA_ENV_FIELD in manager.middleware_data: env = manager.middleware_data[JINJA_ENV_FIELD] @@ -58,7 +54,7 @@ def _create_env( kwargs.setdefault("trim_blocks", True) if "loader" not in kwargs: kwargs["loader"] = StubLoader() - env = Environment(*args, **kwargs) + env = Environment(*args, **kwargs) # noqa: S701 if filters is not None: env.filters.update(filters) return env diff --git a/src/aiogram_dialog/widgets/text/list.py b/src/aiogram_dialog/widgets/text/list.py index b64a52d9..5dd7458e 100644 --- a/src/aiogram_dialog/widgets/text/list.py +++ b/src/aiogram_dialog/widgets/text/list.py @@ -1,11 +1,15 @@ -from typing import Any, Dict, Optional, Sequence +from typing import Any, Optional +from collections.abc import Sequence from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import ( BaseScroll, OnPageChangedVariants, WhenCondition, ) from .base import Text -from ..common.items import get_items_getter, ItemsGetterVariant +from aiogram_dialog.widgets.common.items import ( + get_items_getter, + ItemsGetterVariant, +) class List(Text, BaseScroll): @@ -27,7 +31,7 @@ def __init__( self.page_size = page_size async def _render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: items = self.items_getter(data) pages = self._get_page_count(items) @@ -57,7 +61,7 @@ async def _render_text( ] return self.sep.join(filter(None, texts)) - async def get_page_count(self, data: Dict, manager: DialogManager) -> int: + async def get_page_count(self, data: dict, manager: DialogManager) -> int: items = self.items_getter(data) return self._get_page_count(items) diff --git a/src/aiogram_dialog/widgets/text/multi.py b/src/aiogram_dialog/widgets/text/multi.py index 221e6be4..d5efc5ee 100644 --- a/src/aiogram_dialog/widgets/text/multi.py +++ b/src/aiogram_dialog/widgets/text/multi.py @@ -1,4 +1,5 @@ -from typing import Any, Callable, Dict, Hashable, Optional, Union +from typing import Any, Optional, Union +from collections.abc import Callable, Hashable from magic_filter import MagicFilter @@ -6,12 +7,12 @@ from aiogram_dialog.widgets.common import WhenCondition from .base import Text -Selector = Callable[[Dict, "Case", DialogManager], Hashable] +Selector = Callable[[dict, "Case", DialogManager], Hashable] def new_case_field(fieldname: str) -> Selector: def case_field( - data: Dict, widget: "Case", manager: DialogManager, + data: dict, widget: "Case", manager: DialogManager, ) -> Hashable: return data.get(fieldname) @@ -20,7 +21,7 @@ def case_field( def new_magic_selector(f: MagicFilter) -> Selector: def when_magic( - data: Dict, widget: "Case", manager: DialogManager, + data: dict, widget: "Case", manager: DialogManager, ) -> bool: return f.resolve(data) @@ -30,7 +31,7 @@ def when_magic( class Case(Text): def __init__( self, - texts: Dict[Any, Text], + texts: dict[Any, Text], selector: Union[str, Selector, MagicFilter], when: WhenCondition = None, ): diff --git a/src/aiogram_dialog/widgets/text/progress.py b/src/aiogram_dialog/widgets/text/progress.py index 1b5245f7..cb20ff14 100644 --- a/src/aiogram_dialog/widgets/text/progress.py +++ b/src/aiogram_dialog/widgets/text/progress.py @@ -1,5 +1,3 @@ -from typing import Dict - from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import WhenCondition from .base import Text @@ -21,7 +19,7 @@ def __init__( self.empty = empty async def _render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: if manager.is_preview(): percent = 15 diff --git a/src/aiogram_dialog/widgets/text/scrolling_text.py b/src/aiogram_dialog/widgets/text/scrolling_text.py index df5376a9..206fe076 100644 --- a/src/aiogram_dialog/widgets/text/scrolling_text.py +++ b/src/aiogram_dialog/widgets/text/scrolling_text.py @@ -1,5 +1,3 @@ -from typing import Dict - from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import ( BaseScroll, OnPageChangedVariants, WhenCondition, @@ -29,7 +27,7 @@ def _get_page_count( async def _render_contents( self, - data: Dict, + data: dict, manager: DialogManager, ) -> str: return await self.text.render_text(data, manager) @@ -44,6 +42,6 @@ async def _render_text(self, data, manager: DialogManager) -> str: return text[page_offset: page_offset + self.page_size] - async def get_page_count(self, data: Dict, manager: DialogManager) -> int: + async def get_page_count(self, data: dict, manager: DialogManager) -> int: text = await self._render_contents(data, manager) return self._get_page_count(text) diff --git a/src/aiogram_dialog/widgets/utils.py b/src/aiogram_dialog/widgets/utils.py index 3deb8807..3ce7e994 100644 --- a/src/aiogram_dialog/widgets/utils.py +++ b/src/aiogram_dialog/widgets/utils.py @@ -1,4 +1,5 @@ -from typing import Callable, Dict, List, Sequence, Tuple, Union +from typing import Union +from collections.abc import Callable, Sequence from aiogram_dialog.api.exceptions import InvalidWidgetType from aiogram_dialog.api.internal import DataGetter @@ -11,12 +12,12 @@ WidgetSrc = Union[str, Text, Keyboard, MessageHandlerFunc, Media, BaseInput] -SingleGetterBase = Union[DataGetter, Dict] +SingleGetterBase = Union[DataGetter, dict] GetterVariant = Union[ None, SingleGetterBase, - List[SingleGetterBase], - Tuple[SingleGetterBase, ...], + list[SingleGetterBase], + tuple[SingleGetterBase, ...], ] @@ -71,7 +72,7 @@ def ensure_media(widget: Union[Media, Sequence[Media]]) -> Media: def ensure_widgets( widgets: Sequence[WidgetSrc], -) -> Tuple[Text, Keyboard, Union[BaseInput, None], Media]: +) -> tuple[Text, Keyboard, Union[BaseInput, None], Media]: texts = [] keyboards = [] inputs = [] diff --git a/src/aiogram_dialog/widgets/widget_event.py b/src/aiogram_dialog/widgets/widget_event.py index 42823b05..d15cafe9 100644 --- a/src/aiogram_dialog/widgets/widget_event.py +++ b/src/aiogram_dialog/widgets/widget_event.py @@ -1,5 +1,6 @@ from abc import abstractmethod -from typing import Any, Callable, Union +from typing import Any, Union +from collections.abc import Callable from aiogram_dialog.api.entities import ChatEvent from aiogram_dialog.api.protocols import DialogManager diff --git a/src/aiogram_dialog/window.py b/src/aiogram_dialog/window.py index af02a0d2..d14078a6 100644 --- a/src/aiogram_dialog/window.py +++ b/src/aiogram_dialog/window.py @@ -1,5 +1,5 @@ from logging import getLogger -from typing import Any, cast, Dict, List, Optional +from typing import Any, cast, Optional from aiogram.fsm.state import State from aiogram.types import ( @@ -46,7 +46,7 @@ def __init__( markup_factory: MarkupFactory = _DEFAULT_MARKUP_FACTORY, parse_mode: Optional[str] = UNSET_PARSE_MODE, disable_web_page_preview: Optional[bool] = UNSET_DISABLE_WEB_PAGE_PREVIEW, # noqa: E501 - preview_add_transitions: Optional[List[Keyboard]] = None, + preview_add_transitions: Optional[list[Keyboard]] = None, preview_data: GetterVariant = None, ): ( @@ -67,18 +67,18 @@ def __init__( self.preview_add_transitions = preview_add_transitions async def render_text( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> str: return await self.text.render_text(data, manager) async def render_media( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> Optional[MediaAttachment]: if self.media: return await self.media.render_media(data, manager) async def render_kbd( - self, data: Dict, manager: DialogManager, + self, data: dict, manager: DialogManager, ) -> MarkupVariant: keyboard = await self.keyboard.render_keyboard(data, manager) return await self.markup_factory.render_markup( @@ -88,7 +88,7 @@ async def render_kbd( async def load_data( self, dialog: "DialogProtocol", manager: DialogManager, - ) -> Dict: + ) -> dict: data = await dialog.load_data(manager) data.update(await self.getter(**manager.middleware_data)) return data @@ -153,9 +153,8 @@ def get_state(self) -> State: def find(self, widget_id) -> Optional[Widget]: for root in (self.text, self.keyboard, self.on_message, self.media): - if root: - if found := root.find(widget_id): - return found + if root and (found := root.find(widget_id)): + return found return None def __repr__(self) -> str: diff --git a/tests/test_click.py b/tests/test_click.py index 3233dba9..0ec5d22f 100644 --- a/tests/test_click.py +++ b/tests/test_click.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from unittest.mock import Mock import pytest @@ -31,7 +31,7 @@ async def on_finish(event, button, manager: DialogManager) -> None: await manager.done() -async def second_getter(user_getter, **kwargs) -> Dict[str, Any]: +async def second_getter(user_getter, **kwargs) -> dict[str, Any]: return { "user": user_getter(), } From a5b3734bfed1a2f9f7055b0db889d6fbfb82c35e Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 00:47:06 +0300 Subject: [PATCH 08/29] hotfix: add accidentally deleted requirements --- requirements_dev.txt | 17 +++++++++++++++++ requirements_doc.txt | 4 ++++ 2 files changed, 21 insertions(+) create mode 100644 requirements_dev.txt create mode 100644 requirements_doc.txt diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 00000000..07c80a1e --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,17 @@ +vulture +flake8==7.* +flake8-blind-except +flake8-bugbear +flake8-builtins +flake8-cognitive-complexity +flake8-comprehensions +flake8-docstrings +flake8-eradicate +flake8-import-order +flake8-mutable +flake8-polyfill +flake8-print + +pytest +pytest-asyncio +pytest-repeat \ No newline at end of file diff --git a/requirements_doc.txt b/requirements_doc.txt new file mode 100644 index 00000000..76386171 --- /dev/null +++ b/requirements_doc.txt @@ -0,0 +1,4 @@ +sphinx +sphinx-autodocgen +furo +sphinx-copybutton \ No newline at end of file From 2667b20c8e0e1a7a2ada17cc54177dc21bc1ddb4 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 00:57:30 +0300 Subject: [PATCH 09/29] fix for flake 8 --- src/aiogram_dialog/__init__.py | 17 ++++++-- src/aiogram_dialog/api/entities/__init__.py | 10 +++-- .../api/entities/new_message.py | 5 ++- src/aiogram_dialog/api/entities/stack.py | 1 + src/aiogram_dialog/api/internal/__init__.py | 16 ++++++-- src/aiogram_dialog/api/internal/manager.py | 3 +- src/aiogram_dialog/api/internal/widgets.py | 14 +++++-- src/aiogram_dialog/api/internal/window.py | 1 + src/aiogram_dialog/api/protocols/__init__.py | 5 ++- src/aiogram_dialog/api/protocols/dialog.py | 5 ++- src/aiogram_dialog/api/protocols/manager.py | 7 +++- .../context/access_validator.py | 3 +- .../context/intent_middleware.py | 26 +++++++++---- src/aiogram_dialog/context/storage.py | 9 ++++- src/aiogram_dialog/dialog.py | 7 +++- src/aiogram_dialog/manager/bg_manager.py | 9 +++-- src/aiogram_dialog/manager/manager.py | 39 ++++++++++++++----- src/aiogram_dialog/manager/manager_factory.py | 7 +++- .../manager/manager_middleware.py | 8 ++-- src/aiogram_dialog/manager/message_manager.py | 9 ++++- src/aiogram_dialog/manager/sub_manager.py | 12 ++++-- src/aiogram_dialog/setup.py | 20 ++++++---- src/aiogram_dialog/test_tools/bot_client.py | 18 +++++++-- .../test_tools/memory_storage.py | 1 - .../test_tools/mock_message_manager.py | 10 ++++- src/aiogram_dialog/tools/preview.py | 2 +- src/aiogram_dialog/utils.py | 4 +- src/aiogram_dialog/widgets/common/__init__.py | 2 +- src/aiogram_dialog/widgets/common/action.py | 1 + src/aiogram_dialog/widgets/common/items.py | 2 +- src/aiogram_dialog/widgets/common/scroll.py | 6 ++- src/aiogram_dialog/widgets/input/base.py | 7 ++-- src/aiogram_dialog/widgets/input/combined.py | 6 ++- src/aiogram_dialog/widgets/input/text.py | 5 ++- src/aiogram_dialog/widgets/kbd/__init__.py | 5 ++- src/aiogram_dialog/widgets/kbd/button.py | 5 ++- .../widgets/kbd/calendar_kbd.py | 4 +- src/aiogram_dialog/widgets/kbd/checkbox.py | 5 ++- src/aiogram_dialog/widgets/kbd/counter.py | 2 +- src/aiogram_dialog/widgets/kbd/group.py | 3 +- src/aiogram_dialog/widgets/kbd/list_group.py | 10 +++-- src/aiogram_dialog/widgets/kbd/pager.py | 1 + src/aiogram_dialog/widgets/kbd/request.py | 3 +- .../widgets/kbd/scrolling_group.py | 5 ++- src/aiogram_dialog/widgets/kbd/select.py | 5 ++- src/aiogram_dialog/widgets/kbd/stub_scroll.py | 5 ++- .../widgets/markup/force_reply.py | 4 +- .../widgets/markup/inline_keyboard.py | 4 +- .../widgets/markup/reply_keyboard.py | 4 +- src/aiogram_dialog/widgets/media/dynamic.py | 2 +- src/aiogram_dialog/widgets/media/scroll.py | 9 +++-- src/aiogram_dialog/widgets/media/static.py | 1 + src/aiogram_dialog/widgets/text/base.py | 5 ++- src/aiogram_dialog/widgets/text/format.py | 1 + src/aiogram_dialog/widgets/text/jinja.py | 3 +- src/aiogram_dialog/widgets/text/list.py | 11 ++++-- src/aiogram_dialog/widgets/text/multi.py | 3 +- src/aiogram_dialog/widgets/text/progress.py | 1 + .../widgets/text/scrolling_text.py | 5 ++- src/aiogram_dialog/widgets/utils.py | 3 +- src/aiogram_dialog/widgets/widget_event.py | 2 +- src/aiogram_dialog/window.py | 9 +++-- 62 files changed, 296 insertions(+), 121 deletions(-) diff --git a/src/aiogram_dialog/__init__.py b/src/aiogram_dialog/__init__.py index d6d649e5..474f5717 100644 --- a/src/aiogram_dialog/__init__.py +++ b/src/aiogram_dialog/__init__.py @@ -22,12 +22,21 @@ import importlib.metadata as _metadata from .api.entities import ( - AccessSettings, ChatEvent, Data, DEFAULT_STACK_ID, GROUP_STACK_ID, - LaunchMode, ShowMode, StartMode, + DEFAULT_STACK_ID, + GROUP_STACK_ID, + AccessSettings, + ChatEvent, + Data, + LaunchMode, + ShowMode, + StartMode, ) from .api.protocols import ( - BaseDialogManager, BgManagerFactory, CancelEventProcessing, - DialogManager, DialogProtocol, + BaseDialogManager, + BgManagerFactory, + CancelEventProcessing, + DialogManager, + DialogProtocol, UnsetId, ) from .dialog import Dialog diff --git a/src/aiogram_dialog/api/entities/__init__.py b/src/aiogram_dialog/api/entities/__init__.py index 38b04326..4626999a 100644 --- a/src/aiogram_dialog/api/entities/__init__.py +++ b/src/aiogram_dialog/api/entities/__init__.py @@ -12,13 +12,17 @@ from .access import AccessSettings from .context import Context, Data -from .events import ChatEvent, EVENT_CONTEXT_KEY, EventContext +from .events import EVENT_CONTEXT_KEY, ChatEvent, EventContext from .launch_mode import LaunchMode from .media import MediaAttachment, MediaId from .modes import ShowMode, StartMode from .new_message import MarkupVariant, NewMessage, OldMessage, UnknownText from .stack import DEFAULT_STACK_ID, GROUP_STACK_ID, Stack from .update_event import ( - DIALOG_EVENT_NAME, DialogAction, DialogStartEvent, DialogSwitchEvent, - DialogUpdate, DialogUpdateEvent, + DIALOG_EVENT_NAME, + DialogAction, + DialogStartEvent, + DialogSwitchEvent, + DialogUpdate, + DialogUpdateEvent, ) diff --git a/src/aiogram_dialog/api/entities/new_message.py b/src/aiogram_dialog/api/entities/new_message.py index a0f16221..062f794f 100644 --- a/src/aiogram_dialog/api/entities/new_message.py +++ b/src/aiogram_dialog/api/entities/new_message.py @@ -3,7 +3,10 @@ from typing import Optional, Union from aiogram.types import ( - Chat, ForceReply, InlineKeyboardMarkup, ReplyKeyboardMarkup, + Chat, + ForceReply, + InlineKeyboardMarkup, + ReplyKeyboardMarkup, ReplyKeyboardRemove, ) diff --git a/src/aiogram_dialog/api/entities/stack.py b/src/aiogram_dialog/api/entities/stack.py index 824830b1..3256d018 100644 --- a/src/aiogram_dialog/api/entities/stack.py +++ b/src/aiogram_dialog/api/entities/stack.py @@ -7,6 +7,7 @@ from aiogram.fsm.state import State from aiogram_dialog.api.exceptions import DialogStackOverflow + from .access import AccessSettings from .context import Context, Data diff --git a/src/aiogram_dialog/api/internal/__init__.py b/src/aiogram_dialog/api/internal/__init__.py index c3e86d15..62dcf102 100644 --- a/src/aiogram_dialog/api/internal/__init__.py +++ b/src/aiogram_dialog/api/internal/__init__.py @@ -13,10 +13,20 @@ DialogManagerFactory, ) from .middleware import ( - CALLBACK_DATA_KEY, CONTEXT_KEY, EVENT_SIMULATED, STACK_KEY, STORAGE_KEY, + CALLBACK_DATA_KEY, + CONTEXT_KEY, + EVENT_SIMULATED, + STACK_KEY, + STORAGE_KEY, ) from .widgets import ( - ButtonVariant, DataGetter, InputWidget, KeyboardWidget, - MediaWidget, RawKeyboard, TextWidget, Widget, + ButtonVariant, + DataGetter, + InputWidget, + KeyboardWidget, + MediaWidget, + RawKeyboard, + TextWidget, + Widget, ) from .window import WindowProtocol diff --git a/src/aiogram_dialog/api/internal/manager.py b/src/aiogram_dialog/api/internal/manager.py index 145743c2..f5ed9d6c 100644 --- a/src/aiogram_dialog/api/internal/manager.py +++ b/src/aiogram_dialog/api/internal/manager.py @@ -5,7 +5,8 @@ from aiogram_dialog.api.entities import ChatEvent from aiogram_dialog.api.protocols import ( - DialogManager, DialogRegistryProtocol, + DialogManager, + DialogRegistryProtocol, ) diff --git a/src/aiogram_dialog/api/internal/widgets.py b/src/aiogram_dialog/api/internal/widgets.py index ba51d328..c3976817 100644 --- a/src/aiogram_dialog/api/internal/widgets.py +++ b/src/aiogram_dialog/api/internal/widgets.py @@ -1,12 +1,18 @@ from abc import abstractmethod +from collections.abc import Awaitable, Callable from typing import ( - Any, Optional, Protocol, - runtime_checkable, Union, + Any, + Optional, + Protocol, + Union, + runtime_checkable, ) -from collections.abc import Awaitable, Callable from aiogram.types import ( - CallbackQuery, InlineKeyboardButton, KeyboardButton, Message, + CallbackQuery, + InlineKeyboardButton, + KeyboardButton, + Message, ) from aiogram_dialog import DialogManager diff --git a/src/aiogram_dialog/api/internal/window.py b/src/aiogram_dialog/api/internal/window.py index 2dce844c..9deb8d59 100644 --- a/src/aiogram_dialog/api/internal/window.py +++ b/src/aiogram_dialog/api/internal/window.py @@ -9,6 +9,7 @@ from aiogram_dialog.api.entities import Data, NewMessage from aiogram_dialog.api.protocols import DialogProtocol + from .manager import DialogManager diff --git a/src/aiogram_dialog/api/protocols/__init__.py b/src/aiogram_dialog/api/protocols/__init__.py index 73f1ecb1..731dd00c 100644 --- a/src/aiogram_dialog/api/protocols/__init__.py +++ b/src/aiogram_dialog/api/protocols/__init__.py @@ -9,7 +9,10 @@ from .dialog import CancelEventProcessing, DialogProtocol from .manager import ( - BaseDialogManager, BgManagerFactory, DialogManager, UnsetId, + BaseDialogManager, + BgManagerFactory, + DialogManager, + UnsetId, ) from .media import MediaIdStorageProtocol from .message_manager import MessageManagerProtocol, MessageNotModified diff --git a/src/aiogram_dialog/api/protocols/dialog.py b/src/aiogram_dialog/api/protocols/dialog.py index 5960aa37..024dbfff 100644 --- a/src/aiogram_dialog/api/protocols/dialog.py +++ b/src/aiogram_dialog/api/protocols/dialog.py @@ -4,8 +4,11 @@ from aiogram.fsm.state import State, StatesGroup from aiogram_dialog.api.entities import ( - Data, LaunchMode, NewMessage, + Data, + LaunchMode, + NewMessage, ) + from .manager import DialogManager diff --git a/src/aiogram_dialog/api/protocols/manager.py b/src/aiogram_dialog/api/protocols/manager.py index e578ecc5..cffd5bdb 100644 --- a/src/aiogram_dialog/api/protocols/manager.py +++ b/src/aiogram_dialog/api/protocols/manager.py @@ -7,7 +7,12 @@ from aiogram_dialog.api.entities import ( AccessSettings, - ChatEvent, Context, Data, ShowMode, Stack, StartMode, + ChatEvent, + Context, + Data, + ShowMode, + Stack, + StartMode, ) diff --git a/src/aiogram_dialog/context/access_validator.py b/src/aiogram_dialog/context/access_validator.py index 1120be57..b9dbebe7 100644 --- a/src/aiogram_dialog/context/access_validator.py +++ b/src/aiogram_dialog/context/access_validator.py @@ -5,7 +5,8 @@ from aiogram_dialog import ChatEvent from aiogram_dialog.api.entities import ( - Context, Stack, + Context, + Stack, ) from aiogram_dialog.api.protocols import StackAccessValidator diff --git a/src/aiogram_dialog/context/intent_middleware.py b/src/aiogram_dialog/context/intent_middleware.py index f19cc7a1..069c9ff5 100644 --- a/src/aiogram_dialog/context/intent_middleware.py +++ b/src/aiogram_dialog/context/intent_middleware.py @@ -1,6 +1,6 @@ +from collections.abc import Awaitable, Callable from logging import getLogger from typing import Any, Optional -from collections.abc import Awaitable, Callable from aiogram import Router from aiogram.dispatcher.event.bases import UNHANDLED @@ -10,30 +10,40 @@ CallbackQuery, ChatJoinRequest, ChatMemberUpdated, - InaccessibleMessage, Message, + InaccessibleMessage, + Message, ) from aiogram.types.error_event import ErrorEvent from aiogram_dialog.api.entities import ( + DEFAULT_STACK_ID, + EVENT_CONTEXT_KEY, ChatEvent, Context, - DEFAULT_STACK_ID, DialogUpdateEvent, - EVENT_CONTEXT_KEY, EventContext, Stack, ) from aiogram_dialog.api.exceptions import ( - InvalidStackIdError, OutdatedIntent, UnknownIntent, UnknownState, + InvalidStackIdError, + OutdatedIntent, + UnknownIntent, + UnknownState, ) from aiogram_dialog.api.internal import ( - CALLBACK_DATA_KEY, CONTEXT_KEY, EVENT_SIMULATED, - ReplyCallbackQuery, STACK_KEY, STORAGE_KEY, + CALLBACK_DATA_KEY, + CONTEXT_KEY, + EVENT_SIMULATED, + STACK_KEY, + STORAGE_KEY, + ReplyCallbackQuery, ) from aiogram_dialog.api.protocols import ( - DialogRegistryProtocol, StackAccessValidator, + DialogRegistryProtocol, + StackAccessValidator, ) from aiogram_dialog.utils import remove_intent_id, split_reply_callback + from .storage import StorageProxy logger = getLogger(__name__) diff --git a/src/aiogram_dialog/context/storage.py b/src/aiogram_dialog/context/storage.py index b6c39e7d..bd99102a 100644 --- a/src/aiogram_dialog/context/storage.py +++ b/src/aiogram_dialog/context/storage.py @@ -5,11 +5,16 @@ from aiogram import Bot from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.storage.base import ( - BaseEventIsolation, BaseStorage, StorageKey, + BaseEventIsolation, + BaseStorage, + StorageKey, ) from aiogram_dialog.api.entities import ( - AccessSettings, Context, DEFAULT_STACK_ID, Stack, + DEFAULT_STACK_ID, + AccessSettings, + Context, + Stack, ) from aiogram_dialog.api.exceptions import UnknownIntent, UnknownState diff --git a/src/aiogram_dialog/dialog.py b/src/aiogram_dialog/dialog.py index 5da26c73..c9571c7e 100644 --- a/src/aiogram_dialog/dialog.py +++ b/src/aiogram_dialog/dialog.py @@ -18,12 +18,15 @@ ) from aiogram_dialog.api.internal import Widget, WindowProtocol from aiogram_dialog.api.protocols import ( - CancelEventProcessing, DialogManager, DialogProtocol, + CancelEventProcessing, + DialogManager, + DialogProtocol, ) + from .context.intent_filter import IntentFilter from .utils import remove_intent_id from .widgets.data import PreviewAwareGetter -from .widgets.utils import ensure_data_getter, GetterVariant +from .widgets.utils import GetterVariant, ensure_data_getter logger = getLogger(__name__) diff --git a/src/aiogram_dialog/manager/bg_manager.py b/src/aiogram_dialog/manager/bg_manager.py index dfd05c74..2d9c4136 100644 --- a/src/aiogram_dialog/manager/bg_manager.py +++ b/src/aiogram_dialog/manager/bg_manager.py @@ -6,9 +6,9 @@ from aiogram.types import Chat, User from aiogram_dialog.api.entities import ( + DEFAULT_STACK_ID, AccessSettings, Data, - DEFAULT_STACK_ID, DialogAction, DialogStartEvent, DialogSwitchEvent, @@ -19,10 +19,13 @@ StartMode, ) from aiogram_dialog.api.internal import ( - FakeChat, FakeUser, + FakeChat, + FakeUser, ) from aiogram_dialog.api.protocols import ( - BaseDialogManager, BgManagerFactory, UnsetId, + BaseDialogManager, + BgManagerFactory, + UnsetId, ) from aiogram_dialog.manager.updater import Updater from aiogram_dialog.utils import is_chat_loaded, is_user_loaded diff --git a/src/aiogram_dialog/manager/manager.py b/src/aiogram_dialog/manager/manager.py index b310b381..86837b03 100644 --- a/src/aiogram_dialog/manager/manager.py +++ b/src/aiogram_dialog/manager/manager.py @@ -1,49 +1,68 @@ from copy import deepcopy from logging import getLogger -from typing import Any, cast, Optional, Union +from typing import Any, Optional, Union, cast from aiogram import Router from aiogram.enums import ChatType from aiogram.fsm.state import State from aiogram.types import ( - CallbackQuery, Chat, ErrorEvent, Message, ReplyKeyboardMarkup, User, + CallbackQuery, + Chat, + ErrorEvent, + Message, + ReplyKeyboardMarkup, + User, ) from aiogram_dialog.api.entities import ( + DEFAULT_STACK_ID, + EVENT_CONTEXT_KEY, AccessSettings, ChatEvent, Context, Data, - DEFAULT_STACK_ID, - EVENT_CONTEXT_KEY, EventContext, LaunchMode, MediaId, NewMessage, + OldMessage, ShowMode, Stack, StartMode, + UnknownText, ) -from aiogram_dialog.api.entities import OldMessage, UnknownText from aiogram_dialog.api.exceptions import ( - IncorrectBackgroundError, InvalidKeyboardType, NoContextError, + IncorrectBackgroundError, + InvalidKeyboardType, + NoContextError, ) from aiogram_dialog.api.internal import ( - CONTEXT_KEY, EVENT_SIMULATED, FakeChat, FakeUser, - STACK_KEY, STORAGE_KEY, + CONTEXT_KEY, + EVENT_SIMULATED, + STACK_KEY, + STORAGE_KEY, + FakeChat, + FakeUser, ) from aiogram_dialog.api.protocols import ( - BaseDialogManager, DialogManager, DialogProtocol, DialogRegistryProtocol, - MediaIdStorageProtocol, MessageManagerProtocol, MessageNotModified, + BaseDialogManager, + DialogManager, + DialogProtocol, + DialogRegistryProtocol, + MediaIdStorageProtocol, + MessageManagerProtocol, + MessageNotModified, UnsetId, ) from aiogram_dialog.context.storage import StorageProxy from aiogram_dialog.utils import get_media_id + from .bg_manager import ( BgManager, coalesce_business_connection_id, coalesce_thread_id, ) + logger = getLogger(__name__) diff --git a/src/aiogram_dialog/manager/manager_factory.py b/src/aiogram_dialog/manager/manager_factory.py index aafa263a..ae364e14 100644 --- a/src/aiogram_dialog/manager/manager_factory.py +++ b/src/aiogram_dialog/manager/manager_factory.py @@ -3,9 +3,12 @@ from aiogram_dialog.api.entities import ChatEvent from aiogram_dialog.api.internal import DialogManagerFactory from aiogram_dialog.api.protocols import ( - DialogManager, DialogRegistryProtocol, - MediaIdStorageProtocol, MessageManagerProtocol, + DialogManager, + DialogRegistryProtocol, + MediaIdStorageProtocol, + MessageManagerProtocol, ) + from .manager import ManagerImpl diff --git a/src/aiogram_dialog/manager/manager_middleware.py b/src/aiogram_dialog/manager/manager_middleware.py index 2166eaae..041a7d71 100644 --- a/src/aiogram_dialog/manager/manager_middleware.py +++ b/src/aiogram_dialog/manager/manager_middleware.py @@ -1,14 +1,16 @@ -from typing import Any, Union from collections.abc import Awaitable, Callable +from typing import Any, Union from aiogram import Router from aiogram.dispatcher.middlewares.base import BaseMiddleware from aiogram.types import TelegramObject, Update from aiogram_dialog.api.entities import ChatEvent, DialogUpdateEvent -from aiogram_dialog.api.internal import DialogManagerFactory, STORAGE_KEY +from aiogram_dialog.api.internal import STORAGE_KEY, DialogManagerFactory from aiogram_dialog.api.protocols import ( - BgManagerFactory, DialogManager, DialogRegistryProtocol, + BgManagerFactory, + DialogManager, + DialogRegistryProtocol, ) MANAGER_KEY = "dialog_manager" diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index 773c9cb2..d3d76a9f 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -20,10 +20,15 @@ ) from aiogram_dialog.api.entities import ( - MediaAttachment, MediaId, NewMessage, OldMessage, ShowMode, + MediaAttachment, + MediaId, + NewMessage, + OldMessage, + ShowMode, ) from aiogram_dialog.api.protocols import ( - MessageManagerProtocol, MessageNotModified, + MessageManagerProtocol, + MessageNotModified, ) from aiogram_dialog.utils import get_media_id diff --git a/src/aiogram_dialog/manager/sub_manager.py b/src/aiogram_dialog/manager/sub_manager.py index 6767e6dc..5977cf5a 100644 --- a/src/aiogram_dialog/manager/sub_manager.py +++ b/src/aiogram_dialog/manager/sub_manager.py @@ -6,12 +6,18 @@ from aiogram_dialog.api.entities import ( AccessSettings, - ChatEvent, Data, ShowMode, StartMode, + ChatEvent, + Context, + Data, + ShowMode, + Stack, + StartMode, ) -from aiogram_dialog.api.entities import Context, Stack from aiogram_dialog.api.internal import Widget from aiogram_dialog.api.protocols import ( - BaseDialogManager, DialogManager, UnsetId, + BaseDialogManager, + DialogManager, + UnsetId, ) diff --git a/src/aiogram_dialog/setup.py b/src/aiogram_dialog/setup.py index 7eb5eac5..e26b12b5 100644 --- a/src/aiogram_dialog/setup.py +++ b/src/aiogram_dialog/setup.py @@ -1,9 +1,9 @@ -from typing import Optional, Union from collections.abc import Callable, Iterable +from typing import Optional, Union from aiogram import Router from aiogram.dispatcher.event.telegram import TelegramEventObserver -from aiogram.fsm.state import any_state, State, StatesGroup +from aiogram.fsm.state import State, StatesGroup, any_state from aiogram.fsm.storage.base import BaseEventIsolation from aiogram.fsm.storage.memory import SimpleEventIsolation @@ -11,23 +11,29 @@ from aiogram_dialog.api.exceptions import UnregisteredDialogError from aiogram_dialog.api.internal import DialogManagerFactory from aiogram_dialog.api.protocols import ( - BgManagerFactory, DialogProtocol, DialogRegistryProtocol, - MediaIdStorageProtocol, MessageManagerProtocol, StackAccessValidator, + BgManagerFactory, + DialogProtocol, + DialogRegistryProtocol, + MediaIdStorageProtocol, + MessageManagerProtocol, + StackAccessValidator, ) from aiogram_dialog.context.intent_middleware import ( - context_saver_middleware, - context_unlocker_middleware, IntentErrorMiddleware, IntentMiddlewareFactory, + context_saver_middleware, + context_unlocker_middleware, ) from aiogram_dialog.context.media_storage import MediaIdStorage from aiogram_dialog.manager.bg_manager import BgManagerFactoryImpl from aiogram_dialog.manager.manager_factory import DefaultManagerFactory from aiogram_dialog.manager.manager_middleware import ( - BgFactoryMiddleware, ManagerMiddleware, + BgFactoryMiddleware, + ManagerMiddleware, ) from aiogram_dialog.manager.message_manager import MessageManager from aiogram_dialog.manager.update_handler import handle_update + from .about import about_dialog from .context.access_validator import DefaultAccessValidator diff --git a/src/aiogram_dialog/test_tools/bot_client.py b/src/aiogram_dialog/test_tools/bot_client.py index 961b5a92..e0426870 100644 --- a/src/aiogram_dialog/test_tools/bot_client.py +++ b/src/aiogram_dialog/test_tools/bot_client.py @@ -5,10 +5,20 @@ from aiogram import Bot, Dispatcher from aiogram.methods import AnswerCallbackQuery, TelegramMethod from aiogram.types import ( - CallbackQuery, Chat, ChatJoinRequest, - ChatMemberAdministrator, ChatMemberBanned, ChatMemberLeft, - ChatMemberMember, ChatMemberOwner, ChatMemberRestricted, ChatMemberUpdated, - InlineKeyboardButton, Message, Update, User, + CallbackQuery, + Chat, + ChatJoinRequest, + ChatMemberAdministrator, + ChatMemberBanned, + ChatMemberLeft, + ChatMemberMember, + ChatMemberOwner, + ChatMemberRestricted, + ChatMemberUpdated, + InlineKeyboardButton, + Message, + Update, + User, ) from .keyboard import InlineButtonLocator diff --git a/src/aiogram_dialog/test_tools/memory_storage.py b/src/aiogram_dialog/test_tools/memory_storage.py index f8112c34..de2ecad3 100644 --- a/src/aiogram_dialog/test_tools/memory_storage.py +++ b/src/aiogram_dialog/test_tools/memory_storage.py @@ -6,7 +6,6 @@ from aiogram.fsm.state import State from aiogram.fsm.storage.base import BaseStorage, StorageKey - StateType = Optional[Union[str, State]] diff --git a/src/aiogram_dialog/test_tools/mock_message_manager.py b/src/aiogram_dialog/test_tools/mock_message_manager.py index 8a526da3..2fd2444e 100644 --- a/src/aiogram_dialog/test_tools/mock_message_manager.py +++ b/src/aiogram_dialog/test_tools/mock_message_manager.py @@ -5,14 +5,20 @@ from aiogram import Bot from aiogram.types import ( - Audio, CallbackQuery, Document, Message, PhotoSize, ReplyKeyboardMarkup, + Audio, + CallbackQuery, + Document, + Message, + PhotoSize, + ReplyKeyboardMarkup, Video, ) from aiogram_dialog import ShowMode from aiogram_dialog.api.entities import MediaAttachment, NewMessage, OldMessage from aiogram_dialog.api.protocols import ( - MessageManagerProtocol, MessageNotModified, + MessageManagerProtocol, + MessageNotModified, ) diff --git a/src/aiogram_dialog/tools/preview.py b/src/aiogram_dialog/tools/preview.py index aa2ae080..a9e1c190 100644 --- a/src/aiogram_dialog/tools/preview.py +++ b/src/aiogram_dialog/tools/preview.py @@ -24,13 +24,13 @@ DialogProtocol, ) from aiogram_dialog.api.entities import ( + EVENT_CONTEXT_KEY, AccessSettings, ChatEvent, Context, Data, DialogAction, DialogUpdateEvent, - EVENT_CONTEXT_KEY, EventContext, MediaAttachment, NewMessage, diff --git a/src/aiogram_dialog/utils.py b/src/aiogram_dialog/utils.py index 023b4a77..de1f7768 100644 --- a/src/aiogram_dialog/utils.py +++ b/src/aiogram_dialog/utils.py @@ -14,7 +14,9 @@ ) from aiogram_dialog.api.entities import ( - ChatEvent, DialogUpdateEvent, MediaId, + ChatEvent, + DialogUpdateEvent, + MediaId, ) from aiogram_dialog.api.internal import RawKeyboard diff --git a/src/aiogram_dialog/widgets/common/__init__.py b/src/aiogram_dialog/widgets/common/__init__.py index bc59f780..1dcad9bc 100644 --- a/src/aiogram_dialog/widgets/common/__init__.py +++ b/src/aiogram_dialog/widgets/common/__init__.py @@ -24,4 +24,4 @@ Scroll, sync_scroll, ) -from .when import true_condition, Whenable, WhenCondition +from .when import Whenable, WhenCondition, true_condition diff --git a/src/aiogram_dialog/widgets/common/action.py b/src/aiogram_dialog/widgets/common/action.py index 1246e78f..4f460049 100644 --- a/src/aiogram_dialog/widgets/common/action.py +++ b/src/aiogram_dialog/widgets/common/action.py @@ -3,6 +3,7 @@ from aiogram_dialog.api.exceptions import InvalidWidgetIdError from aiogram_dialog.api.protocols import DialogManager + from .base import BaseWidget ID_PATTERN = re.compile("^[a-zA-Z0-9_.]+$") diff --git a/src/aiogram_dialog/widgets/common/items.py b/src/aiogram_dialog/widgets/common/items.py index bac707ac..6419f3d4 100644 --- a/src/aiogram_dialog/widgets/common/items.py +++ b/src/aiogram_dialog/widgets/common/items.py @@ -1,6 +1,6 @@ +from collections.abc import Callable, Sequence from operator import itemgetter from typing import Union -from collections.abc import Callable, Sequence from magic_filter import MagicFilter diff --git a/src/aiogram_dialog/widgets/common/scroll.py b/src/aiogram_dialog/widgets/common/scroll.py index bd9e0e9e..42ba21ac 100644 --- a/src/aiogram_dialog/widgets/common/scroll.py +++ b/src/aiogram_dialog/widgets/common/scroll.py @@ -1,13 +1,15 @@ from abc import ABC, abstractmethod -from typing import Protocol, Union from collections.abc import Awaitable, Callable, Sequence +from typing import Protocol, Union from aiogram_dialog.api.entities import ChatEvent from aiogram_dialog.api.internal import Widget from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.widget_event import ( - ensure_event_processor, WidgetEventProcessor, + WidgetEventProcessor, + ensure_event_processor, ) + from .action import Actionable from .managed import ManagedWidget diff --git a/src/aiogram_dialog/widgets/input/base.py b/src/aiogram_dialog/widgets/input/base.py index ed893835..b9f3e1a3 100644 --- a/src/aiogram_dialog/widgets/input/base.py +++ b/src/aiogram_dialog/widgets/input/base.py @@ -1,6 +1,6 @@ from abc import abstractmethod -from typing import Any, Optional, Union from collections.abc import Awaitable, Callable, Sequence +from typing import Any, Optional, Union from aiogram import F from aiogram.dispatcher.event.handler import FilterObject @@ -8,12 +8,13 @@ from aiogram_dialog.api.internal import InputWidget from aiogram_dialog.api.protocols import ( - DialogManager, DialogProtocol, + DialogManager, + DialogProtocol, ) from aiogram_dialog.widgets.common import Actionable from aiogram_dialog.widgets.widget_event import ( - ensure_event_processor, WidgetEventProcessor, + ensure_event_processor, ) MessageHandlerFunc = Callable[ diff --git a/src/aiogram_dialog/widgets/input/combined.py b/src/aiogram_dialog/widgets/input/combined.py index c9194624..b0f8078c 100644 --- a/src/aiogram_dialog/widgets/input/combined.py +++ b/src/aiogram_dialog/widgets/input/combined.py @@ -1,12 +1,14 @@ -from typing import Any, Optional from collections.abc import Callable +from typing import Any, Optional from aiogram.dispatcher.event.handler import FilterObject from aiogram.types import Message from aiogram_dialog.api.protocols import ( - DialogManager, DialogProtocol, + DialogManager, + DialogProtocol, ) + from .base import BaseInput diff --git a/src/aiogram_dialog/widgets/input/text.py b/src/aiogram_dialog/widgets/input/text.py index cd87b3e6..27029008 100644 --- a/src/aiogram_dialog/widgets/input/text.py +++ b/src/aiogram_dialog/widgets/input/text.py @@ -1,6 +1,7 @@ from __future__ import annotations from abc import abstractmethod +from collections.abc import Callable from typing import ( Any, Generic, @@ -9,7 +10,6 @@ TypeVar, Union, ) -from collections.abc import Callable from aiogram.dispatcher.event.handler import FilterObject from aiogram.types import ContentType, Message @@ -17,9 +17,10 @@ from aiogram_dialog.api.protocols import DialogManager, DialogProtocol from aiogram_dialog.widgets.common import ManagedWidget from aiogram_dialog.widgets.widget_event import ( - ensure_event_processor, WidgetEventProcessor, + ensure_event_processor, ) + from .base import BaseInput T = TypeVar("T") diff --git a/src/aiogram_dialog/widgets/kbd/__init__.py b/src/aiogram_dialog/widgets/kbd/__init__.py index 07b67b98..52dfc1ee 100644 --- a/src/aiogram_dialog/widgets/kbd/__init__.py +++ b/src/aiogram_dialog/widgets/kbd/__init__.py @@ -46,7 +46,10 @@ from .base import Keyboard from .button import Button, SwitchInlineQuery, Url, WebApp from .calendar_kbd import ( - Calendar, CalendarConfig, CalendarScope, CalendarUserConfig, + Calendar, + CalendarConfig, + CalendarScope, + CalendarUserConfig, ManagedCalendar, ) from .checkbox import Checkbox, ManagedCheckbox diff --git a/src/aiogram_dialog/widgets/kbd/button.py b/src/aiogram_dialog/widgets/kbd/button.py index 37e5355a..21f99dca 100644 --- a/src/aiogram_dialog/widgets/kbd/button.py +++ b/src/aiogram_dialog/widgets/kbd/button.py @@ -1,5 +1,5 @@ -from typing import Optional, Union from collections.abc import Awaitable, Callable +from typing import Optional, Union from aiogram.types import CallbackQuery, InlineKeyboardButton, WebAppInfo @@ -8,9 +8,10 @@ from aiogram_dialog.widgets.common import WhenCondition from aiogram_dialog.widgets.text import Text from aiogram_dialog.widgets.widget_event import ( - ensure_event_processor, WidgetEventProcessor, + ensure_event_processor, ) + from .base import Keyboard OnClick = Callable[[CallbackQuery, "Button", DialogManager], Awaitable] diff --git a/src/aiogram_dialog/widgets/kbd/calendar_kbd.py b/src/aiogram_dialog/widgets/kbd/calendar_kbd.py index 2ee1b07a..7d4aafa1 100644 --- a/src/aiogram_dialog/widgets/kbd/calendar_kbd.py +++ b/src/aiogram_dialog/widgets/kbd/calendar_kbd.py @@ -21,8 +21,10 @@ from aiogram_dialog.widgets.common import ManagedWidget, WhenCondition from aiogram_dialog.widgets.text import Format, Text from aiogram_dialog.widgets.widget_event import ( - ensure_event_processor, WidgetEventProcessor, + WidgetEventProcessor, + ensure_event_processor, ) + from .base import Keyboard EPOCH = date(1970, 1, 1) diff --git a/src/aiogram_dialog/widgets/kbd/checkbox.py b/src/aiogram_dialog/widgets/kbd/checkbox.py index 7b0c21c1..973b040d 100644 --- a/src/aiogram_dialog/widgets/kbd/checkbox.py +++ b/src/aiogram_dialog/widgets/kbd/checkbox.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -from typing import Optional, Union from collections.abc import Awaitable, Callable +from typing import Optional, Union from aiogram.types import CallbackQuery, InlineKeyboardButton @@ -10,9 +10,10 @@ from aiogram_dialog.widgets.common import ManagedWidget, WhenCondition from aiogram_dialog.widgets.text import Case, Text from aiogram_dialog.widgets.widget_event import ( - ensure_event_processor, WidgetEventProcessor, + ensure_event_processor, ) + from .base import Keyboard OnStateChanged = Callable[ diff --git a/src/aiogram_dialog/widgets/kbd/counter.py b/src/aiogram_dialog/widgets/kbd/counter.py index a034c1ee..86271f7a 100644 --- a/src/aiogram_dialog/widgets/kbd/counter.py +++ b/src/aiogram_dialog/widgets/kbd/counter.py @@ -10,8 +10,8 @@ from aiogram_dialog.widgets.kbd.base import Keyboard from aiogram_dialog.widgets.text import Const, Format, Text from aiogram_dialog.widgets.widget_event import ( - ensure_event_processor, WidgetEventProcessor, + ensure_event_processor, ) diff --git a/src/aiogram_dialog/widgets/kbd/group.py b/src/aiogram_dialog/widgets/kbd/group.py index 02d9e6b8..ef526b52 100644 --- a/src/aiogram_dialog/widgets/kbd/group.py +++ b/src/aiogram_dialog/widgets/kbd/group.py @@ -1,12 +1,13 @@ +from collections.abc import Iterable from itertools import chain from typing import Optional -from collections.abc import Iterable from aiogram.types import CallbackQuery, InlineKeyboardButton from aiogram_dialog.api.internal import ButtonVariant, RawKeyboard from aiogram_dialog.api.protocols import DialogManager, DialogProtocol from aiogram_dialog.widgets.common import WhenCondition + from .base import Keyboard diff --git a/src/aiogram_dialog/widgets/kbd/list_group.py b/src/aiogram_dialog/widgets/kbd/list_group.py index 499e1ce1..16454705 100644 --- a/src/aiogram_dialog/widgets/kbd/list_group.py +++ b/src/aiogram_dialog/widgets/kbd/list_group.py @@ -1,20 +1,22 @@ -from typing import Any, Optional, Union from collections.abc import Callable +from typing import Any, Optional, Union from aiogram.types import CallbackQuery from aiogram_dialog.api.internal import RawKeyboard, Widget from aiogram_dialog.api.protocols import ( - DialogManager, DialogProtocol, + DialogManager, + DialogProtocol, ) from aiogram_dialog.manager.sub_manager import SubManager from aiogram_dialog.widgets.common import ManagedWidget, WhenCondition -from .base import Keyboard from aiogram_dialog.widgets.common.items import ( - get_items_getter, ItemsGetterVariant, + get_items_getter, ) +from .base import Keyboard + ItemIdGetter = Callable[[Any], Union[str, int]] diff --git a/src/aiogram_dialog/widgets/kbd/pager.py b/src/aiogram_dialog/widgets/kbd/pager.py index e4958160..b5c016ec 100644 --- a/src/aiogram_dialog/widgets/kbd/pager.py +++ b/src/aiogram_dialog/widgets/kbd/pager.py @@ -8,6 +8,7 @@ from aiogram_dialog.api.protocols import DialogManager, DialogProtocol from aiogram_dialog.widgets.common import ManagedScroll, Scroll, WhenCondition from aiogram_dialog.widgets.text import Const, Format, Text + from .base import Keyboard diff --git a/src/aiogram_dialog/widgets/kbd/request.py b/src/aiogram_dialog/widgets/kbd/request.py index 6dc9576e..5625652f 100644 --- a/src/aiogram_dialog/widgets/kbd/request.py +++ b/src/aiogram_dialog/widgets/kbd/request.py @@ -1,11 +1,12 @@ -from typing import Union from collections.abc import Callable +from typing import Union from aiogram.types import KeyboardButton from aiogram_dialog.api.internal import RawKeyboard from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.text import Text + from .base import Keyboard diff --git a/src/aiogram_dialog/widgets/kbd/scrolling_group.py b/src/aiogram_dialog/widgets/kbd/scrolling_group.py index 015d6165..acf6d168 100644 --- a/src/aiogram_dialog/widgets/kbd/scrolling_group.py +++ b/src/aiogram_dialog/widgets/kbd/scrolling_group.py @@ -5,8 +5,11 @@ from aiogram_dialog.api.internal import RawKeyboard from aiogram_dialog.api.protocols import DialogManager, DialogProtocol from aiogram_dialog.widgets.common import ( - BaseScroll, OnPageChangedVariants, WhenCondition, + BaseScroll, + OnPageChangedVariants, + WhenCondition, ) + from .base import Keyboard from .group import Group diff --git a/src/aiogram_dialog/widgets/kbd/select.py b/src/aiogram_dialog/widgets/kbd/select.py index 5596c7a8..320df9d9 100644 --- a/src/aiogram_dialog/widgets/kbd/select.py +++ b/src/aiogram_dialog/widgets/kbd/select.py @@ -16,14 +16,15 @@ from aiogram_dialog.api.protocols import DialogManager, DialogProtocol from aiogram_dialog.widgets.common import ManagedWidget, WhenCondition from aiogram_dialog.widgets.common.items import ( - get_items_getter, ItemsGetterVariant, + get_items_getter, ) from aiogram_dialog.widgets.text import Case, Text from aiogram_dialog.widgets.widget_event import ( - ensure_event_processor, WidgetEventProcessor, + ensure_event_processor, ) + from .base import Keyboard T = TypeVar("T") diff --git a/src/aiogram_dialog/widgets/kbd/stub_scroll.py b/src/aiogram_dialog/widgets/kbd/stub_scroll.py index 5d3b5178..7910f1ca 100644 --- a/src/aiogram_dialog/widgets/kbd/stub_scroll.py +++ b/src/aiogram_dialog/widgets/kbd/stub_scroll.py @@ -1,16 +1,17 @@ -from typing import Union from collections.abc import Callable +from typing import Union from magic_filter import MagicFilter from aiogram_dialog.api.internal import RawKeyboard from aiogram_dialog.api.protocols import DialogManager -from .base import Keyboard from aiogram_dialog.widgets.common.scroll import ( BaseScroll, OnPageChangedVariants, ) +from .base import Keyboard + PagesGetter = Callable[[dict, "StubScroll", DialogManager], int] diff --git a/src/aiogram_dialog/widgets/markup/force_reply.py b/src/aiogram_dialog/widgets/markup/force_reply.py index 5673ca70..0c6be743 100644 --- a/src/aiogram_dialog/widgets/markup/force_reply.py +++ b/src/aiogram_dialog/widgets/markup/force_reply.py @@ -4,7 +4,9 @@ from aiogram_dialog import DialogManager from aiogram_dialog.api.internal.widgets import ( - MarkupFactory, MarkupVariant, RawKeyboard, + MarkupFactory, + MarkupVariant, + RawKeyboard, ) from aiogram_dialog.widgets.text import Text diff --git a/src/aiogram_dialog/widgets/markup/inline_keyboard.py b/src/aiogram_dialog/widgets/markup/inline_keyboard.py index 47352a8a..2e918ca6 100644 --- a/src/aiogram_dialog/widgets/markup/inline_keyboard.py +++ b/src/aiogram_dialog/widgets/markup/inline_keyboard.py @@ -2,7 +2,9 @@ from aiogram_dialog import DialogManager from aiogram_dialog.api.internal.widgets import ( - MarkupFactory, MarkupVariant, RawKeyboard, + MarkupFactory, + MarkupVariant, + RawKeyboard, ) from aiogram_dialog.utils import add_intent_id diff --git a/src/aiogram_dialog/widgets/markup/reply_keyboard.py b/src/aiogram_dialog/widgets/markup/reply_keyboard.py index a5dca48a..74a8891f 100644 --- a/src/aiogram_dialog/widgets/markup/reply_keyboard.py +++ b/src/aiogram_dialog/widgets/markup/reply_keyboard.py @@ -4,7 +4,9 @@ from aiogram_dialog import DialogManager from aiogram_dialog.api.internal.widgets import ( - MarkupFactory, MarkupVariant, RawKeyboard, + MarkupFactory, + MarkupVariant, + RawKeyboard, ) from aiogram_dialog.utils import add_intent_id, transform_to_reply_keyboard from aiogram_dialog.widgets.text import Text diff --git a/src/aiogram_dialog/widgets/media/dynamic.py b/src/aiogram_dialog/widgets/media/dynamic.py index 3b602f7f..3a0523f2 100644 --- a/src/aiogram_dialog/widgets/media/dynamic.py +++ b/src/aiogram_dialog/widgets/media/dynamic.py @@ -5,9 +5,9 @@ https://github.com/SamWarden/aiogram_dialog_extras """ +from collections.abc import Callable from operator import itemgetter from typing import Optional, Union -from collections.abc import Callable from aiogram_dialog import DialogManager from aiogram_dialog.api.entities import MediaAttachment diff --git a/src/aiogram_dialog/widgets/media/scroll.py b/src/aiogram_dialog/widgets/media/scroll.py index 34bee7cb..5c5a5b93 100644 --- a/src/aiogram_dialog/widgets/media/scroll.py +++ b/src/aiogram_dialog/widgets/media/scroll.py @@ -3,14 +3,17 @@ from aiogram_dialog.api.entities import MediaAttachment from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import ( - BaseScroll, OnPageChangedVariants, WhenCondition, + BaseScroll, + OnPageChangedVariants, + WhenCondition, ) -from .base import Media from aiogram_dialog.widgets.common.items import ( - get_items_getter, ItemsGetterVariant, + get_items_getter, ) +from .base import Media + class MediaScroll(Media, BaseScroll): def __init__( diff --git a/src/aiogram_dialog/widgets/media/static.py b/src/aiogram_dialog/widgets/media/static.py index 43f1af7d..8053d150 100644 --- a/src/aiogram_dialog/widgets/media/static.py +++ b/src/aiogram_dialog/widgets/media/static.py @@ -7,6 +7,7 @@ from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import WhenCondition from aiogram_dialog.widgets.text import Const, Text + from .base import Media diff --git a/src/aiogram_dialog/widgets/text/base.py b/src/aiogram_dialog/widgets/text/base.py index 726f38da..f5e80877 100644 --- a/src/aiogram_dialog/widgets/text/base.py +++ b/src/aiogram_dialog/widgets/text/base.py @@ -4,7 +4,10 @@ from aiogram_dialog.api.internal import TextWidget from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import ( - BaseWidget, true_condition, Whenable, WhenCondition, + BaseWidget, + Whenable, + WhenCondition, + true_condition, ) diff --git a/src/aiogram_dialog/widgets/text/format.py b/src/aiogram_dialog/widgets/text/format.py index 89e564ce..035e1d17 100644 --- a/src/aiogram_dialog/widgets/text/format.py +++ b/src/aiogram_dialog/widgets/text/format.py @@ -1,5 +1,6 @@ from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import WhenCondition + from .base import Text diff --git a/src/aiogram_dialog/widgets/text/jinja.py b/src/aiogram_dialog/widgets/text/jinja.py index abe38781..3a9edc47 100644 --- a/src/aiogram_dialog/widgets/text/jinja.py +++ b/src/aiogram_dialog/widgets/text/jinja.py @@ -1,16 +1,17 @@ import warnings +from collections.abc import Callable, Iterable, Mapping from typing import ( Any, Optional, Union, ) -from collections.abc import Callable, Iterable, Mapping from aiogram import Bot, Dispatcher from jinja2 import BaseLoader, Environment from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import WhenCondition + from .base import Text JINJA_ENV_FIELD = "DialogsJinjaEnvironment" diff --git a/src/aiogram_dialog/widgets/text/list.py b/src/aiogram_dialog/widgets/text/list.py index 5dd7458e..f3930f54 100644 --- a/src/aiogram_dialog/widgets/text/list.py +++ b/src/aiogram_dialog/widgets/text/list.py @@ -1,16 +1,19 @@ -from typing import Any, Optional from collections.abc import Sequence +from typing import Any, Optional from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import ( - BaseScroll, OnPageChangedVariants, WhenCondition, + BaseScroll, + OnPageChangedVariants, + WhenCondition, ) -from .base import Text from aiogram_dialog.widgets.common.items import ( - get_items_getter, ItemsGetterVariant, + get_items_getter, ) +from .base import Text + class List(Text, BaseScroll): def __init__( diff --git a/src/aiogram_dialog/widgets/text/multi.py b/src/aiogram_dialog/widgets/text/multi.py index d5efc5ee..d7facd0a 100644 --- a/src/aiogram_dialog/widgets/text/multi.py +++ b/src/aiogram_dialog/widgets/text/multi.py @@ -1,10 +1,11 @@ -from typing import Any, Optional, Union from collections.abc import Callable, Hashable +from typing import Any, Optional, Union from magic_filter import MagicFilter from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import WhenCondition + from .base import Text Selector = Callable[[dict, "Case", DialogManager], Hashable] diff --git a/src/aiogram_dialog/widgets/text/progress.py b/src/aiogram_dialog/widgets/text/progress.py index cb20ff14..6f492f26 100644 --- a/src/aiogram_dialog/widgets/text/progress.py +++ b/src/aiogram_dialog/widgets/text/progress.py @@ -1,5 +1,6 @@ from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import WhenCondition + from .base import Text diff --git a/src/aiogram_dialog/widgets/text/scrolling_text.py b/src/aiogram_dialog/widgets/text/scrolling_text.py index 206fe076..488b9b29 100644 --- a/src/aiogram_dialog/widgets/text/scrolling_text.py +++ b/src/aiogram_dialog/widgets/text/scrolling_text.py @@ -1,7 +1,10 @@ from aiogram_dialog.api.protocols import DialogManager from aiogram_dialog.widgets.common import ( - BaseScroll, OnPageChangedVariants, WhenCondition, + BaseScroll, + OnPageChangedVariants, + WhenCondition, ) + from .base import Text diff --git a/src/aiogram_dialog/widgets/utils.py b/src/aiogram_dialog/widgets/utils.py index 3ce7e994..16aa047d 100644 --- a/src/aiogram_dialog/widgets/utils.py +++ b/src/aiogram_dialog/widgets/utils.py @@ -1,8 +1,9 @@ -from typing import Union from collections.abc import Callable, Sequence +from typing import Union from aiogram_dialog.api.exceptions import InvalidWidgetType from aiogram_dialog.api.internal import DataGetter + from .data.data_context import CompositeGetter, StaticGetter from .input import BaseInput, CombinedInput, MessageHandlerFunc, MessageInput from .kbd import Group, Keyboard diff --git a/src/aiogram_dialog/widgets/widget_event.py b/src/aiogram_dialog/widgets/widget_event.py index d15cafe9..f3c31da8 100644 --- a/src/aiogram_dialog/widgets/widget_event.py +++ b/src/aiogram_dialog/widgets/widget_event.py @@ -1,6 +1,6 @@ from abc import abstractmethod -from typing import Any, Union from collections.abc import Callable +from typing import Any, Union from aiogram_dialog.api.entities import ChatEvent from aiogram_dialog.api.protocols import DialogManager diff --git a/src/aiogram_dialog/window.py b/src/aiogram_dialog/window.py index d14078a6..618a23eb 100644 --- a/src/aiogram_dialog/window.py +++ b/src/aiogram_dialog/window.py @@ -1,11 +1,11 @@ from logging import getLogger -from typing import Any, cast, Optional +from typing import Any, Optional, cast from aiogram.fsm.state import State from aiogram.types import ( + UNSET_PARSE_MODE, CallbackQuery, Message, - UNSET_PARSE_MODE, ) from aiogram.types.base import UNSET_DISABLE_WEB_PAGE_PREVIEW @@ -17,6 +17,7 @@ NewMessage, ) from aiogram_dialog.api.internal import Widget, WindowProtocol + from .api.entities import Data from .api.internal.widgets import MarkupFactory from .api.protocols import DialogManager, DialogProtocol @@ -25,10 +26,10 @@ from .widgets.kbd import Keyboard from .widgets.markup.inline_keyboard import InlineKeyboardFactory from .widgets.utils import ( - ensure_data_getter, - ensure_widgets, GetterVariant, WidgetSrc, + ensure_data_getter, + ensure_widgets, ) logger = getLogger(__name__) From 3d8d3cf164afadba52e9cdd8eebf297c3af0b637 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 01:08:49 +0300 Subject: [PATCH 10/29] fix imports --- example/custom_media_url.py | 10 +++++----- example/i18n/bot.py | 6 +++++- example/i18n/i18n_middleware.py | 4 ++-- example/input_media_group.py | 6 +++++- example/launch_modes.py | 7 ++++++- example/list_group.py | 15 +++++++++++---- example/loading.py | 8 ++++++-- example/mega/bot.py | 2 +- example/mega/bot_dialogs/calendar.py | 15 +++++++++++---- example/mega/bot_dialogs/common.py | 1 + example/mega/bot_dialogs/counter.py | 1 + example/mega/bot_dialogs/layouts.py | 11 +++++++++-- example/mega/bot_dialogs/main.py | 1 + example/mega/bot_dialogs/mutltiwidget.py | 1 + example/mega/bot_dialogs/reply_buttons.py | 8 ++++++-- example/mega/bot_dialogs/scrolls.py | 16 ++++++++++++---- example/mega/bot_dialogs/select.py | 9 +++++++-- example/mega/bot_dialogs/switch.py | 1 + example/multistack.py | 7 +++++-- example/scrolls.py | 22 ++++++++++++++++------ example/simple.py | 10 +++++++--- example/subdialog.py | 17 +++++++++++++---- example/wizard.py | 6 +++++- src/aiogram_dialog/widgets/kbd/select.py | 2 +- 24 files changed, 138 insertions(+), 48 deletions(-) diff --git a/example/custom_media_url.py b/example/custom_media_url.py index 19b43fde..67acc22b 100644 --- a/example/custom_media_url.py +++ b/example/custom_media_url.py @@ -4,17 +4,18 @@ from io import BytesIO from typing import Union - from aiogram import Bot, Dispatcher from aiogram.filters import CommandStart from aiogram.fsm.state import State, StatesGroup from aiogram.types import BufferedInputFile, ContentType, InputFile, Message from PIL import Image, ImageDraw, ImageFont - from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, - StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.api.entities import MediaAttachment from aiogram_dialog.manager.message_manager import MessageManager @@ -22,7 +23,6 @@ from aiogram_dialog.widgets.media import StaticMedia from aiogram_dialog.widgets.text import Const - src_dir = os.path.normpath(os.path.join(__file__, os.path.pardir)) API_TOKEN = os.getenv("BOT_TOKEN") diff --git a/example/i18n/bot.py b/example/i18n/bot.py index aa908788..949d9445 100644 --- a/example/i18n/bot.py +++ b/example/i18n/bot.py @@ -18,7 +18,11 @@ from i18n_middleware import I18nMiddleware from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.widgets.kbd import Button, Cancel, Row diff --git a/example/i18n/i18n_middleware.py b/example/i18n/i18n_middleware.py index 20fa32b2..e0cfaf5b 100644 --- a/example/i18n/i18n_middleware.py +++ b/example/i18n/i18n_middleware.py @@ -1,5 +1,5 @@ -from typing import Any, Union from collections.abc import Awaitable, Callable +from typing import Any, Union from aiogram.dispatcher.middlewares.base import BaseMiddleware from aiogram.types import CallbackQuery, Message @@ -24,7 +24,7 @@ async def __call__( Awaitable[Any], ], event: Union[Message, CallbackQuery], - data: Dict[str, Any], + data: dict[str, Any], ) -> Any: # some language/locale retrieving logic if event.from_user: diff --git a/example/input_media_group.py b/example/input_media_group.py index e54efac5..c5e1939a 100644 --- a/example/input_media_group.py +++ b/example/input_media_group.py @@ -10,7 +10,11 @@ from aiogram.types import CallbackQuery, Message from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.api.entities import MediaAttachment, MediaId from aiogram_dialog.widgets.common import ManagedScroll diff --git a/example/launch_modes.py b/example/launch_modes.py index f68f63f4..78aab3ec 100644 --- a/example/launch_modes.py +++ b/example/launch_modes.py @@ -9,7 +9,12 @@ from aiogram.types import Message from aiogram_dialog import ( - Dialog, DialogManager, LaunchMode, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + LaunchMode, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.widgets.kbd import Cancel, Row, Start from aiogram_dialog.widgets.text import Const, Format diff --git a/example/list_group.py b/example/list_group.py index a66a18ba..37df5f07 100644 --- a/example/list_group.py +++ b/example/list_group.py @@ -9,13 +9,20 @@ from aiogram.types import Message from aiogram_dialog import ( - Dialog, DialogManager, LaunchMode, - setup_dialogs, StartMode, SubManager, + Dialog, + DialogManager, + LaunchMode, + StartMode, + SubManager, Window, + setup_dialogs, ) from aiogram_dialog.widgets.kbd import ( - Checkbox, ListGroup, - ManagedCheckbox, Radio, Row, + Checkbox, + ListGroup, + ManagedCheckbox, + Radio, + Row, ) from aiogram_dialog.widgets.text import Const, Format diff --git a/example/loading.py b/example/loading.py index fd810716..f8b43e8f 100644 --- a/example/loading.py +++ b/example/loading.py @@ -9,8 +9,12 @@ from aiogram.types import CallbackQuery, Message from aiogram_dialog import ( - BaseDialogManager, Dialog, DialogManager, - setup_dialogs, StartMode, Window, + BaseDialogManager, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.widgets.kbd import Button from aiogram_dialog.widgets.text import Const, Multi, Progress diff --git a/example/mega/bot.py b/example/mega/bot.py index 11d43bb7..c73ed60f 100644 --- a/example/mega/bot.py +++ b/example/mega/bot.py @@ -18,7 +18,7 @@ from bot_dialogs.select import selects_dialog from bot_dialogs.switch import switch_dialog -from aiogram_dialog import DialogManager, setup_dialogs, ShowMode, StartMode +from aiogram_dialog import DialogManager, ShowMode, StartMode, setup_dialogs from aiogram_dialog.api.exceptions import UnknownIntent diff --git a/example/mega/bot_dialogs/calendar.py b/example/mega/bot_dialogs/calendar.py index 4f1bbe14..b1e8a113 100644 --- a/example/mega/bot_dialogs/calendar.py +++ b/example/mega/bot_dialogs/calendar.py @@ -5,14 +5,21 @@ from aiogram_dialog import ChatEvent, Dialog, DialogManager, Window from aiogram_dialog.widgets.kbd import ( - Calendar, CalendarScope, ManagedCalendar, SwitchTo, + Calendar, + CalendarScope, + ManagedCalendar, + SwitchTo, ) from aiogram_dialog.widgets.kbd.calendar_kbd import ( - CalendarDaysView, CalendarMonthView, - CalendarScopeView, CalendarYearsView, - DATE_TEXT, TODAY_TEXT, + DATE_TEXT, + TODAY_TEXT, + CalendarDaysView, + CalendarMonthView, + CalendarScopeView, + CalendarYearsView, ) from aiogram_dialog.widgets.text import Const, Format, Text + from . import states from .common import MAIN_MENU_BUTTON diff --git a/example/mega/bot_dialogs/common.py b/example/mega/bot_dialogs/common.py index 89718bdd..3cf5ab2e 100644 --- a/example/mega/bot_dialogs/common.py +++ b/example/mega/bot_dialogs/common.py @@ -1,5 +1,6 @@ from aiogram_dialog.widgets.kbd import Start from aiogram_dialog.widgets.text import Const + from . import states MAIN_MENU_BUTTON = Start( diff --git a/example/mega/bot_dialogs/counter.py b/example/mega/bot_dialogs/counter.py index 445d8066..a5efad82 100644 --- a/example/mega/bot_dialogs/counter.py +++ b/example/mega/bot_dialogs/counter.py @@ -3,6 +3,7 @@ from aiogram_dialog import Dialog, DialogManager, Window from aiogram_dialog.widgets.kbd import Counter, ManagedCounter from aiogram_dialog.widgets.text import Const, Progress + from . import states from .common import MAIN_MENU_BUTTON diff --git a/example/mega/bot_dialogs/layouts.py b/example/mega/bot_dialogs/layouts.py index 6e7f7082..a56e04f2 100644 --- a/example/mega/bot_dialogs/layouts.py +++ b/example/mega/bot_dialogs/layouts.py @@ -1,10 +1,17 @@ from aiogram_dialog import ( - Dialog, Window, + Dialog, + Window, ) from aiogram_dialog.widgets.kbd import ( - Button, Column, Group, Row, Select, SwitchTo, + Button, + Column, + Group, + Row, + Select, + SwitchTo, ) from aiogram_dialog.widgets.text import Const, Format + from . import states from .common import MAIN_MENU_BUTTON diff --git a/example/mega/bot_dialogs/main.py b/example/mega/bot_dialogs/main.py index b6a3c8dd..7305ddf5 100644 --- a/example/mega/bot_dialogs/main.py +++ b/example/mega/bot_dialogs/main.py @@ -2,6 +2,7 @@ from aiogram_dialog.about import about_aiogram_dialog_button from aiogram_dialog.widgets.kbd import Start from aiogram_dialog.widgets.text import Const + from . import states main_dialog = Dialog( diff --git a/example/mega/bot_dialogs/mutltiwidget.py b/example/mega/bot_dialogs/mutltiwidget.py index c9f07b03..3bc0f2a1 100644 --- a/example/mega/bot_dialogs/mutltiwidget.py +++ b/example/mega/bot_dialogs/mutltiwidget.py @@ -1,6 +1,7 @@ from aiogram_dialog import Dialog, Window from aiogram_dialog.widgets.kbd import Checkbox, Counter, Multiselect, Radio from aiogram_dialog.widgets.text import Const, Format + from . import states from .common import MAIN_MENU_BUTTON diff --git a/example/mega/bot_dialogs/reply_buttons.py b/example/mega/bot_dialogs/reply_buttons.py index 1d4a341c..a9024da1 100644 --- a/example/mega/bot_dialogs/reply_buttons.py +++ b/example/mega/bot_dialogs/reply_buttons.py @@ -1,10 +1,14 @@ from aiogram_dialog import Dialog, Window from aiogram_dialog.widgets.kbd import ( - Checkbox, Radio, RequestContact, - RequestLocation, Row, + Checkbox, + Radio, + RequestContact, + RequestLocation, + Row, ) from aiogram_dialog.widgets.markup.reply_keyboard import ReplyKeyboardFactory from aiogram_dialog.widgets.text import Const, Format + from . import states from .common import MAIN_MENU_BUTTON diff --git a/example/mega/bot_dialogs/scrolls.py b/example/mega/bot_dialogs/scrolls.py index 3e8c264a..cb474a30 100644 --- a/example/mega/bot_dialogs/scrolls.py +++ b/example/mega/bot_dialogs/scrolls.py @@ -4,13 +4,21 @@ from aiogram_dialog import Dialog, DialogManager, Window from aiogram_dialog.widgets.common import sync_scroll from aiogram_dialog.widgets.kbd import ( - CurrentPage, FirstPage, LastPage, - Multiselect, NextPage, NumberedPager, - PrevPage, Row, ScrollingGroup, - StubScroll, SwitchTo, + CurrentPage, + FirstPage, + LastPage, + Multiselect, + NextPage, + NumberedPager, + PrevPage, + Row, + ScrollingGroup, + StubScroll, + SwitchTo, ) from aiogram_dialog.widgets.media import StaticMedia from aiogram_dialog.widgets.text import Const, Format, List, ScrollingText + from . import states from .common import MAIN_MENU_BUTTON diff --git a/example/mega/bot_dialogs/select.py b/example/mega/bot_dialogs/select.py index 169e537f..438fef8e 100644 --- a/example/mega/bot_dialogs/select.py +++ b/example/mega/bot_dialogs/select.py @@ -5,10 +5,15 @@ from aiogram_dialog import Dialog, DialogManager, Window from aiogram_dialog.widgets.kbd import ( - Column, Multiselect, Radio, - Select, SwitchTo, Toggle, + Column, + Multiselect, + Radio, + Select, + SwitchTo, + Toggle, ) from aiogram_dialog.widgets.text import Const, Format, List + from . import states from .common import MAIN_MENU_BUTTON diff --git a/example/mega/bot_dialogs/switch.py b/example/mega/bot_dialogs/switch.py index faf3a4d0..4848e61a 100644 --- a/example/mega/bot_dialogs/switch.py +++ b/example/mega/bot_dialogs/switch.py @@ -3,6 +3,7 @@ from aiogram_dialog import Dialog, DialogManager, Window from aiogram_dialog.widgets.kbd import Back, Checkbox, Next, Radio, Row from aiogram_dialog.widgets.text import Case, Const, Format + from . import states from .common import MAIN_MENU_BUTTON diff --git a/example/multistack.py b/example/multistack.py index 14e70ca3..cb780e4d 100644 --- a/example/multistack.py +++ b/example/multistack.py @@ -11,8 +11,11 @@ from aiogram.types import CallbackQuery, Message from aiogram_dialog import ( - Dialog, DialogManager, - setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.widgets.input import MessageInput from aiogram_dialog.widgets.kbd import Button, Cancel, Multiselect, Start diff --git a/example/scrolls.py b/example/scrolls.py index 349eacf6..70eec81b 100644 --- a/example/scrolls.py +++ b/example/scrolls.py @@ -11,14 +11,24 @@ from aiogram.types import Message from aiogram_dialog import ( - Dialog, DialogManager, - setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.widgets.kbd import ( - CurrentPage, FirstPage, LastPage, - Multiselect, NextPage, NumberedPager, - PrevPage, Row, ScrollingGroup, - StubScroll, SwitchTo, + CurrentPage, + FirstPage, + LastPage, + Multiselect, + NextPage, + NumberedPager, + PrevPage, + Row, + ScrollingGroup, + StubScroll, + SwitchTo, ) from aiogram_dialog.widgets.text import Const, Format, List, ScrollingText diff --git a/example/simple.py b/example/simple.py index 7a939a61..ae6826d5 100644 --- a/example/simple.py +++ b/example/simple.py @@ -11,9 +11,13 @@ from redis.asyncio.client import Redis from aiogram_dialog import ( - ChatEvent, Dialog, DialogManager, - setup_dialogs, ShowMode, - StartMode, Window, + ChatEvent, + Dialog, + DialogManager, + ShowMode, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.api.exceptions import UnknownIntent, UnknownState from aiogram_dialog.widgets.input import MessageInput diff --git a/example/subdialog.py b/example/subdialog.py index cb1a3102..c169277e 100644 --- a/example/subdialog.py +++ b/example/subdialog.py @@ -10,14 +10,23 @@ from aiogram.types import CallbackQuery, Message from aiogram_dialog import ( - Data, Dialog, DialogManager, - setup_dialogs, StartMode, Window, + Data, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.tools import render_preview, render_transitions from aiogram_dialog.widgets.input import MessageInput from aiogram_dialog.widgets.kbd import ( - Back, Button, Cancel, - Group, Next, Row, Start, + Back, + Button, + Cancel, + Group, + Next, + Row, + Start, ) from aiogram_dialog.widgets.text import Const, Format, Multi diff --git a/example/wizard.py b/example/wizard.py index 8cea2a17..b1017ea0 100644 --- a/example/wizard.py +++ b/example/wizard.py @@ -8,7 +8,11 @@ from aiogram.types import Message from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.widgets.input import TextInput from aiogram_dialog.widgets.kbd import Checkbox, Next, SwitchTo diff --git a/src/aiogram_dialog/widgets/kbd/select.py b/src/aiogram_dialog/widgets/kbd/select.py index 320df9d9..2fe2c70d 100644 --- a/src/aiogram_dialog/widgets/kbd/select.py +++ b/src/aiogram_dialog/widgets/kbd/select.py @@ -387,7 +387,7 @@ async def set_checked( if not checked and len(data) > self.min_selected: data.remove(item_id_str) changed = True - elif checked and self.max_selected == 0 or self.max_selected > len(data): + elif checked and self.max_selected == 0 or self.max_selected > len(data): # noqa: E501 data.append(item_id_str) changed = True if changed: From c150675fe8cbf6af813ebf4c4dadadd9d65f8e23 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 11:09:54 +0300 Subject: [PATCH 11/29] drop flake8, add ruff, add uv for workflows --- .flake8 | 41 ---------- .github/workflows/setup.yml | 13 ++-- .ruff.toml | 74 +++++++++++++++++++ example/input_media_group.py | 4 +- example/list_group.py | 8 +- example/loading.py | 4 +- example/mega/bot.py | 2 +- example/mega/bot_dialogs/select.py | 16 ++-- example/multistack.py | 2 +- example/scrolls.py | 2 +- example/simple.py | 2 +- example/subdialog.py | 2 +- example/wizard.py | 2 +- requirements_dev.txt | 14 +--- .../context/intent_middleware.py | 11 ++- src/aiogram_dialog/context/media_storage.py | 2 +- src/aiogram_dialog/context/storage.py | 8 +- src/aiogram_dialog/dialog.py | 3 +- src/aiogram_dialog/manager/manager.py | 8 +- src/aiogram_dialog/manager/message_manager.py | 7 +- src/aiogram_dialog/manager/updater.py | 2 +- .../test_tools/memory_storage.py | 8 +- .../test_tools/mock_message_manager.py | 8 +- src/aiogram_dialog/tools/preview.py | 32 ++++---- src/aiogram_dialog/utils.py | 15 ++-- src/aiogram_dialog/widgets/common/when.py | 6 +- .../widgets/kbd/calendar_kbd.py | 2 +- src/aiogram_dialog/widgets/kbd/counter.py | 2 +- src/aiogram_dialog/widgets/kbd/group.py | 4 +- src/aiogram_dialog/widgets/kbd/list_group.py | 2 +- src/aiogram_dialog/widgets/kbd/select.py | 12 +-- src/aiogram_dialog/widgets/media/static.py | 2 +- src/aiogram_dialog/window.py | 5 +- tests/test_click.py | 6 +- tests/test_create.py | 2 +- tests/test_events.py | 6 +- tests/test_group.py | 11 ++- tests/test_nested_transitions.py | 6 +- tests/test_transitions.py | 6 +- tests/widgets/kbd/test_group.py | 6 +- tests/widgets/media/test_media_message.py | 2 +- 41 files changed, 208 insertions(+), 162 deletions(-) delete mode 100644 .flake8 create mode 100644 .ruff.toml diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 8a4bef99..00000000 --- a/.flake8 +++ /dev/null @@ -1,41 +0,0 @@ -[flake8] -count=True -statistics=True -show-source=True - -max-line-length=79 -import-order-style=smarkets -application-import-names=aiogram_dialog -exclude= - .venv, - docs, - benchmarks -docstring-convention=pep257 -ignore= - # A002 argument "object" is shadowing a python builtin - A002, - # A003 class attribute "id" is shadowing a python builtin - A003, - # A005 A module is shadowing a Python builtin module - A005, - # D100 Missing docstring in public module - D100, - # D101 Missing docstring in public class - D101, - # D102 Missing docstring in public method - D102, - # D103 Missing docstring in public function - D103, - # D104 Missing docstring in public package - D104, - # D105 Missing docstring in magic method - D105, - # D107 Missing docstring in __init__ - D107, - # W504 line break after binary operator - W504, -max-cognitive-complexity=10 -max-complexity=10 -per-file-ignores= - tests/**:D1,E800,A003 - **/__init__.py:F401 diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml index f9aeaeb7..41847be8 100644 --- a/.github/workflows/setup.yml +++ b/.github/workflows/setup.yml @@ -33,15 +33,16 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v3 + - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install . -r requirements_dev.txt - pip install diagrams + uv pip install . -r requirements_dev.txt + uv pip install diagrams - - name: Run flake8 - run: | - python -m flake8 src/aiogram_dialog tests example + - name: Run Ruff + run: ruff check src/aiogram_dialog tests example - name: Run tests run: | diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 00000000..6d1b7998 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,74 @@ +line-length = 79 +target-version="py39" +src = ["src"] + +include = ["src/**.py", "tests/**.py", "example/**.py"] +exclude = ["docs"] + +lint.select = ["ALL"] +lint.ignore = [ + "ARG", + "ANN", + "D", + "DTZ", + "TD", + "EM101", + "EM102", + "PT001", + "PT023", + "SIM108", + "SIM114", + "TRY003", + "TCH003", + "PLW2901", + "RET505", + "PLR0913", + "UP038", + "TCH001", + "SIM103", + "ISC003", + "A002", + "FA100", + "TRY400", + "FBT001", + "FBT002", + "S311", + "N818", + "B904", + "FIX002", + "RUF012", + "TCH002", + "TRY201", + "ISC002", + "ASYNC230", + "UP007", + "N804", + "B008", + "BLE001", + "RUF009", + "PYI034", +] + +[lint.per-file-ignores] +"src/aiogram_dialog/tools/**" = [ + "S101", + "SLF001", + "PTH", +] +"src/aiogram_dialog/test_tools/**"= [ + "S101", + "PTH" +] +"tests/**" = [ + "TID252", + "PLR2004", + "S101", + "INP001", + "FBT003", +] +"example/**" = [ + "INP001", + "ERA001", + "RUF001", + "PTH", +] \ No newline at end of file diff --git a/example/input_media_group.py b/example/input_media_group.py index c5e1939a..ba608e7b 100644 --- a/example/input_media_group.py +++ b/example/input_media_group.py @@ -61,7 +61,7 @@ async def getter(dialog_manager: DialogManager, **kwargs) -> dict: ) else: media = MediaAttachment( - url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Image_not_available.png/800px-Image_not_available.png?20210219185637", # noqa: E501 + url="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d1/Image_not_available.png/800px-Image_not_available.png?20210219185637", type=ContentType.PHOTO, ) return { @@ -109,5 +109,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/list_group.py b/example/list_group.py index 37df5f07..7104f8a1 100644 --- a/example/list_group.py +++ b/example/list_group.py @@ -67,8 +67,8 @@ async def data_getter(*args, **kwargs): item_id_getter=str, items=["black", "white"], # Alternatives: - # items=F["data"]["colors"], # noqa: E800 - # items=lambda d: d["data"]["colors"], # noqa: E800 + # items=F["data"]["colors"], + # items=lambda d: d["data"]["colors"], when=when_checked, ), ), @@ -76,8 +76,8 @@ async def data_getter(*args, **kwargs): item_id_getter=str, items=["apple", "orange", "pear"], # Alternatives: - # items=F["fruits"], # noqa: E800 - # items=lambda d: d["fruits"], # noqa: E800 + # items=F["fruits"], + # items=lambda d: d["fruits"], ), state=DialogSG.greeting, getter=data_getter, diff --git a/example/loading.py b/example/loading.py index f8b43e8f..baf0359b 100644 --- a/example/loading.py +++ b/example/loading.py @@ -57,7 +57,7 @@ async def start_bg( manager: DialogManager, ): await manager.start(Bg.progress) - asyncio.create_task(background(callback, manager.bg())) + asyncio.create_task(background(callback, manager.bg())) # noqa: RUF006 async def background(callback: CallbackQuery, manager: BaseDialogManager): @@ -99,5 +99,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/mega/bot.py b/example/mega/bot.py index c73ed60f..851342d3 100644 --- a/example/mega/bot.py +++ b/example/mega/bot.py @@ -40,7 +40,7 @@ async def on_unknown_intent(event: ErrorEvent, dialog_manager: DialogManager): "Redirecting to main menu.", ) if event.update.callback_query.message: - try: + try: # noqa: SIM105 await event.update.callback_query.message.delete() except TelegramBadRequest: pass # whatever diff --git a/example/mega/bot_dialogs/select.py b/example/mega/bot_dialogs/select.py index 438fef8e..f85767e2 100644 --- a/example/mega/bot_dialogs/select.py +++ b/example/mega/bot_dialogs/select.py @@ -94,8 +94,8 @@ async def on_item_selected( field=Format("+ {item.emoji} {item.name} - {item.id}"), items=FRUITS_KEY, # Alternatives: - # items=lambda d: d[OTHER_KEY][FRUITS_KEY], # noqa: E800 - # items=F[OTHER_KEY][FRUITS_KEY], # noqa: E800 + # items=lambda d: d[OTHER_KEY][FRUITS_KEY], + # items=F[OTHER_KEY][FRUITS_KEY], ), Column( Select( @@ -103,8 +103,8 @@ async def on_item_selected( id="sel", items=FRUITS_KEY, # Alternatives: - # items=lambda d: d[OTHER_KEY][FRUITS_KEY], # noqa: E800 - # items=F[OTHER_KEY][FRUITS_KEY], # noqa: E800 + # items=lambda d: d[OTHER_KEY][FRUITS_KEY], + # items=F[OTHER_KEY][FRUITS_KEY], item_id_getter=fruit_id_getter, on_click=on_item_selected, ), @@ -123,8 +123,8 @@ async def on_item_selected( id="radio", items=FRUITS_KEY, # Alternatives: - # items=lambda d: d[OTHER_KEY][FRUITS_KEY], # noqa: E800 - # items=F[OTHER_KEY][FRUITS_KEY], # noqa: E800 + # items=lambda d: d[OTHER_KEY][FRUITS_KEY], + # items=F[OTHER_KEY][FRUITS_KEY], item_id_getter=fruit_id_getter, ), ), @@ -143,8 +143,8 @@ async def on_item_selected( id="multi", items=FRUITS_KEY, # Alternatives: - # items=lambda d: d[OTHER_KEY][FRUITS_KEY], # noqa: E800 - # items=F[OTHER_KEY][FRUITS_KEY], # noqa: E800 + # items=lambda d: d[OTHER_KEY][FRUITS_KEY], + # items=F[OTHER_KEY][FRUITS_KEY], item_id_getter=fruit_id_getter, ), ), diff --git a/example/multistack.py b/example/multistack.py index cb780e4d..de1e5ca2 100644 --- a/example/multistack.py +++ b/example/multistack.py @@ -112,5 +112,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/scrolls.py b/example/scrolls.py index 70eec81b..d6e7bf95 100644 --- a/example/scrolls.py +++ b/example/scrolls.py @@ -233,5 +233,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/simple.py b/example/simple.py index ae6826d5..13bd9c41 100644 --- a/example/simple.py +++ b/example/simple.py @@ -176,5 +176,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/subdialog.py b/example/subdialog.py index c169277e..ac8cab21 100644 --- a/example/subdialog.py +++ b/example/subdialog.py @@ -165,5 +165,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/example/wizard.py b/example/wizard.py index b1017ea0..47e606dc 100644 --- a/example/wizard.py +++ b/example/wizard.py @@ -127,5 +127,5 @@ async def main(): await dp.start_polling(bot) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/requirements_dev.txt b/requirements_dev.txt index 07c80a1e..543ae4f4 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,17 +1,5 @@ vulture -flake8==7.* -flake8-blind-except -flake8-bugbear -flake8-builtins -flake8-cognitive-complexity -flake8-comprehensions -flake8-docstrings -flake8-eradicate -flake8-import-order -flake8-mutable -flake8-polyfill -flake8-print - +ruff==0.7.2 pytest pytest-asyncio pytest-repeat \ No newline at end of file diff --git a/src/aiogram_dialog/context/intent_middleware.py b/src/aiogram_dialog/context/intent_middleware.py index 069c9ff5..34de801d 100644 --- a/src/aiogram_dialog/context/intent_middleware.py +++ b/src/aiogram_dialog/context/intent_middleware.py @@ -129,6 +129,7 @@ def event_context_from_error(event: ErrorEvent) -> EventContext: return event_context_from_chat_join(event.update.chat_join_request) elif event.update.callback_query: return event_context_from_callback(event.update.callback_query) + raise ValueError("Unsupported event type in ErrorEvent.update") class InaccessibleBusinessMessage(InaccessibleMessage): @@ -150,7 +151,7 @@ def __init__( def storage_proxy( self, event_context: EventContext, fsm_storage: BaseStorage, ) -> StorageProxy: - proxy = StorageProxy( + return StorageProxy( bot=event_context.bot, storage=fsm_storage, events_isolation=self.events_isolation, @@ -160,7 +161,6 @@ def storage_proxy( thread_id=event_context.thread_id, business_connection_id=event_context.business_connection_id, ) - return proxy def _check_outdated(self, intent_id: str, stack: Stack): """Check if intent id is outdated for stack.""" @@ -186,8 +186,7 @@ async def _load_stack( ) -> Optional[Stack]: if stack_id is None: raise InvalidStackIdError("Both stack id and intent id are None") - stack = await proxy.load_stack(stack_id) - return stack + return await proxy.load_stack(stack_id) async def _load_context_by_stack( self, @@ -209,7 +208,7 @@ async def _load_context_by_stack( else: try: context = await proxy.load_context(stack.last_intent_id()) - except: # noqa: B001,B901,E722 + except: await proxy.unlock() raise @@ -244,7 +243,7 @@ async def _load_context_by_intent( return try: self._check_outdated(intent_id, stack) - except: # noqa: B001,B901,E722 + except: await proxy.unlock() raise diff --git a/src/aiogram_dialog/context/media_storage.py b/src/aiogram_dialog/context/media_storage.py index 9a972d2f..6ba290da 100644 --- a/src/aiogram_dialog/context/media_storage.py +++ b/src/aiogram_dialog/context/media_storage.py @@ -29,5 +29,5 @@ async def save_media_id( media_id: MediaId, ) -> None: if not path and not url: - return None + return self.cache[(path, url, type)] = media_id diff --git a/src/aiogram_dialog/context/storage.py b/src/aiogram_dialog/context/storage.py index bd99102a..e1c22fd6 100644 --- a/src/aiogram_dialog/context/storage.py +++ b/src/aiogram_dialog/context/storage.py @@ -142,9 +142,11 @@ def _fixed_stack_id(self, stack_id: str) -> str: if stack_id != DEFAULT_STACK_ID: return stack_id # private chat has chat_id=user_id and no business connection - if self.user_id in (None, self.chat_id): - if self.business_connection_id is None: - return stack_id + if ( + self.user_id in (None, self.chat_id) and + self.business_connection_id is None + ): + return stack_id return f"<{self.user_id}>" def _stack_key(self, stack_id: str) -> StorageKey: diff --git a/src/aiogram_dialog/dialog.py b/src/aiogram_dialog/dialog.py index c9571c7e..6b2ea5dc 100644 --- a/src/aiogram_dialog/dialog.py +++ b/src/aiogram_dialog/dialog.py @@ -120,8 +120,7 @@ async def load_data( async def render(self, manager: DialogManager) -> NewMessage: logger.debug("Dialog render (%s)", self) window = await self._current_window(manager) - new_message = await window.render(self, manager) - return new_message + return await window.render(self, manager) async def _message_handler( self, message: Message, dialog_manager: DialogManager, diff --git a/src/aiogram_dialog/manager/manager.py b/src/aiogram_dialog/manager/manager.py index 86837b03..680ef54e 100644 --- a/src/aiogram_dialog/manager/manager.py +++ b/src/aiogram_dialog/manager/manager.py @@ -207,9 +207,9 @@ async def done( async def answer_callback(self) -> None: if not isinstance(self.event, CallbackQuery): - return + return None if self.is_event_simulated(): - return + return None return await self.message_manager.answer_callback( bot=self._data["bot"], callback_query=self.event, @@ -308,7 +308,7 @@ async def _process_launch_mode( ): if new_dialog.launch_mode in (LaunchMode.EXCLUSIVE, LaunchMode.ROOT): await self.reset_stack(remove_keyboard=False) - if new_dialog.launch_mode is LaunchMode.SINGLE_TOP: + if new_dialog.launch_mode is LaunchMode.SINGLE_TOP: # noqa: SIM102 if new_dialog is old_dialog: await self.storage().remove_context(self.current_stack().pop()) self._data[CONTEXT_KEY] = None @@ -466,7 +466,7 @@ def _save_last_message(self, message: OldMessage): stack.last_media_unique_id = message.media_uniq_id stack.last_reply_keyboard = message.has_reply_keyboard - def _calc_show_mode(self) -> ShowMode: + def _calc_show_mode(self) -> ShowMode: # noqa: PLR0911 if self.show_mode is not ShowMode.AUTO: return self.show_mode if self.middleware_data["event_chat"].type != ChatType.PRIVATE: diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index d3d76a9f..b6d8db72 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -200,7 +200,7 @@ async def remove_kbd( old_message: Optional[OldMessage], ) -> Optional[Message]: if show_mode is ShowMode.NO_UPDATE: - return + return None if show_mode is ShowMode.DELETE_AND_SEND and old_message: return await self.remove_message_safe(bot, old_message, None) return await self._remove_kbd(bot, old_message, None) @@ -214,6 +214,7 @@ async def _remove_kbd( if self.had_reply_keyboard(old_message): if not self.need_reply_keyboard(new_message): return await self.remove_reply_kbd(bot, old_message) + return None else: return await self.remove_inline_kbd(bot, old_message) @@ -221,7 +222,7 @@ async def remove_inline_kbd( self, bot: Bot, old_message: Optional[OldMessage], ) -> Optional[Message]: if not old_message: - return + return None logger.debug("remove_inline_kbd in %s", old_message.chat) try: return await bot.edit_message_reply_markup( @@ -243,7 +244,7 @@ async def remove_reply_kbd( self, bot: Bot, old_message: Optional[OldMessage], ) -> Optional[Message]: if not old_message: - return + return None logger.debug("remove_reply_kbd in %s", old_message.chat) return await self.send_text( bot=bot, diff --git a/src/aiogram_dialog/manager/updater.py b/src/aiogram_dialog/manager/updater.py index df8926f7..fa44ecad 100644 --- a/src/aiogram_dialog/manager/updater.py +++ b/src/aiogram_dialog/manager/updater.py @@ -16,7 +16,7 @@ def __init__(self, dp: Router): async def notify(self, bot: Bot, update: DialogUpdate) -> None: def callback(): - asyncio.create_task( + asyncio.create_task( # noqa: RUF006 self._process_update(bot, update), ) diff --git a/src/aiogram_dialog/test_tools/memory_storage.py b/src/aiogram_dialog/test_tools/memory_storage.py index de2ecad3..d0ffba70 100644 --- a/src/aiogram_dialog/test_tools/memory_storage.py +++ b/src/aiogram_dialog/test_tools/memory_storage.py @@ -1,7 +1,7 @@ import json from collections import defaultdict from dataclasses import dataclass -from typing import Any, Dict, Optional, Union +from typing import Any, Optional, Union from aiogram.fsm.state import State from aiogram.fsm.storage.base import BaseStorage, StorageKey @@ -16,7 +16,7 @@ class MemoryStorageRecord: class JsonMemoryStorage(BaseStorage): - storage: Dict[StorageKey, MemoryStorageRecord] + storage: dict[StorageKey, MemoryStorageRecord] def __init__(self) -> None: self.storage = defaultdict(MemoryStorageRecord) @@ -36,8 +36,8 @@ async def set_state( async def get_state(self, key: StorageKey) -> Optional[str]: return self.storage[key].state - async def set_data(self, key: StorageKey, data: Dict[str, Any]) -> None: + async def set_data(self, key: StorageKey, data: dict[str, Any]) -> None: self.storage[key].data = json.dumps(data) - async def get_data(self, key: StorageKey) -> Dict[str, Any]: + async def get_data(self, key: StorageKey) -> dict[str, Any]: return json.loads(self.storage[key].data) diff --git a/src/aiogram_dialog/test_tools/mock_message_manager.py b/src/aiogram_dialog/test_tools/mock_message_manager.py index 2fd2444e..6c5fbf36 100644 --- a/src/aiogram_dialog/test_tools/mock_message_manager.py +++ b/src/aiogram_dialog/test_tools/mock_message_manager.py @@ -1,6 +1,6 @@ from copy import deepcopy from datetime import datetime -from typing import Optional, Set +from typing import Optional from uuid import uuid4 from aiogram import Bot @@ -57,7 +57,7 @@ def file_unique_id(media: MediaAttachment) -> str: class MockMessageManager(MessageManagerProtocol): def __init__(self): - self.answered_callbacks: Set[str] = set() + self.answered_callbacks: set[str] = set() self.sent_messages = [] self.last_message_id = 0 @@ -85,9 +85,9 @@ async def remove_kbd( old_message: Optional[OldMessage], ) -> Optional[Message]: if not old_message: - return + return None if show_mode in (ShowMode.DELETE_AND_SEND, ShowMode.NO_UPDATE): - return + return None assert isinstance(old_message, OldMessage) message = Message( diff --git a/src/aiogram_dialog/tools/preview.py b/src/aiogram_dialog/tools/preview.py index a9e1c190..75d7b269 100644 --- a/src/aiogram_dialog/tools/preview.py +++ b/src/aiogram_dialog/tools/preview.py @@ -269,7 +269,7 @@ async def create_button( manager.set_state(state) try: await dialog._callback_handler(callback_query, dialog_manager=manager) - except Exception: # noqa: B902 + except Exception: logging.debug("Click %s", callback) state = manager.current_context().state return RenderButton(title=title, state=state.state) @@ -297,7 +297,7 @@ async def render_input( manager.set_state(state) try: await dialog._message_handler(message, dialog_manager=manager) - except Exception: # noqa: B902 + except Exception: logging.debug("Input %s", content_type) if state == manager.current_context().state: @@ -319,22 +319,20 @@ async def render_inline_keyboard( dialog: Dialog, simulate_events: bool, ): - keyboard = [] - for row in reply_markup.inline_keyboard: - keyboard_row = [] - for button in row: - keyboard_row.append( - await create_button( - title=button.text, - callback=button.callback_data, - manager=manager, - dialog=dialog, - state=state, - simulate_events=simulate_events, - ), + return [ + [ + await create_button( + title=button.text, + callback=button.callback_data, + manager=manager, + dialog=dialog, + state=state, + simulate_events=simulate_events, ) - keyboard.append(keyboard_row) - return keyboard + for button in row + ] + for row in reply_markup.inline_keyboard + ] async def render_reply_keyboard( diff --git a/src/aiogram_dialog/utils.py b/src/aiogram_dialog/utils.py index de1f7768..6ddb7cde 100644 --- a/src/aiogram_dialog/utils.py +++ b/src/aiogram_dialog/utils.py @@ -83,7 +83,7 @@ def split_reply_callback( def decode_reply_callback(data: str) -> str: bytes_data = bytes( _decode_reply_callback_byte(little, big) - for little, big in zip(data[::2], data[1::2]) + for little, big in zip(data[::2], data[1::2], strict=False) ) return bytes_data.decode("utf-8") @@ -107,13 +107,10 @@ def _transform_to_reply_button( def transform_to_reply_keyboard( keyboard: list[list[Union[InlineKeyboardButton, KeyboardButton]]], ) -> list[list[KeyboardButton]]: - new_kdb = [] - for row in keyboard: - new_row = [] - new_kdb.append(new_row) - for button in row: - new_row.append(_transform_to_reply_button(button)) - return new_kdb + return [ + [_transform_to_reply_button(button) for button in row] + for row in keyboard + ] def get_chat(event: ChatEvent) -> Chat: @@ -126,6 +123,8 @@ def get_chat(event: ChatEvent) -> Chat: if not event.message: return Chat(id=event.from_user.id, type="") return event.message.chat + else: + raise TypeError def is_chat_loaded(chat: Chat) -> bool: diff --git a/src/aiogram_dialog/widgets/common/when.py b/src/aiogram_dialog/widgets/common/when.py index 24bf39d6..5dd9a391 100644 --- a/src/aiogram_dialog/widgets/common/when.py +++ b/src/aiogram_dialog/widgets/common/when.py @@ -32,7 +32,7 @@ def __call__( def new_when_field(fieldname: str) -> Predicate: def when_field( - data: dict, widget: "Whenable", manager: DialogManager, + data: dict, widget: Whenable, manager: DialogManager, ) -> bool: return bool(data.get(fieldname)) @@ -41,14 +41,14 @@ def when_field( def new_when_magic(f: MagicFilter) -> Predicate: def when_magic( - data: dict, widget: "Whenable", manager: DialogManager, + data: dict, widget: Whenable, manager: DialogManager, ) -> bool: return f.resolve(data) return when_magic -def true_condition(data: dict, widget: "Whenable", manager: DialogManager): +def true_condition(data: dict, widget: Whenable, manager: DialogManager): return True diff --git a/src/aiogram_dialog/widgets/kbd/calendar_kbd.py b/src/aiogram_dialog/widgets/kbd/calendar_kbd.py index 7d4aafa1..89399e01 100644 --- a/src/aiogram_dialog/widgets/kbd/calendar_kbd.py +++ b/src/aiogram_dialog/widgets/kbd/calendar_kbd.py @@ -254,7 +254,7 @@ async def _render_days( end_date += timedelta(days=days_till_week_end) # add days today = get_today(config.timezone) - for offset in range(0, (end_date - start_date).days, 7): + for offset in range(0, (end_date - start_date).days, 7): # noqa: PLR1704 row = [] for row_offset in range(7): days_offset = timedelta(days=(offset + row_offset)) diff --git a/src/aiogram_dialog/widgets/kbd/counter.py b/src/aiogram_dialog/widgets/kbd/counter.py index 86271f7a..abc243fd 100644 --- a/src/aiogram_dialog/widgets/kbd/counter.py +++ b/src/aiogram_dialog/widgets/kbd/counter.py @@ -20,7 +20,7 @@ class OnCounterEvent(Protocol): async def __call__( self, event: ChatEvent, - counter: "ManagedCounter", # noqa: F841 + counter: "ManagedCounter", # noqa: F841, RUF100 dialog_manager: DialogManager, ): raise NotImplementedError diff --git a/src/aiogram_dialog/widgets/kbd/group.py b/src/aiogram_dialog/widgets/kbd/group.py index ef526b52..a8757977 100644 --- a/src/aiogram_dialog/widgets/kbd/group.py +++ b/src/aiogram_dialog/widgets/kbd/group.py @@ -16,7 +16,7 @@ def __init__( self, *buttons: Keyboard, id: Optional[str] = None, - width: int = None, + width: Optional[int] = None, when: WhenCondition = None, ): super().__init__(id=id, when=when) @@ -24,7 +24,7 @@ def __init__( self.width = width def find(self, widget_id): - widget = super(Group, self).find(widget_id) + widget = super().find(widget_id) if widget: return widget for btn in self.buttons: diff --git a/src/aiogram_dialog/widgets/kbd/list_group.py b/src/aiogram_dialog/widgets/kbd/list_group.py index 16454705..05087112 100644 --- a/src/aiogram_dialog/widgets/kbd/list_group.py +++ b/src/aiogram_dialog/widgets/kbd/list_group.py @@ -95,7 +95,7 @@ async def _process_item_callback( widget_id=self.widget_id, item_id=item_id, ) - for b in self.buttons: + for b in self.buttons: # noqa: RET503 if await b.process_callback(callback, dialog, sub_manager): return True diff --git a/src/aiogram_dialog/widgets/kbd/select.py b/src/aiogram_dialog/widgets/kbd/select.py index 2fe2c70d..f9efd8d2 100644 --- a/src/aiogram_dialog/widgets/kbd/select.py +++ b/src/aiogram_dialog/widgets/kbd/select.py @@ -38,7 +38,7 @@ class OnItemStateChanged(Protocol[ManagedT, T]): async def __call__( self, event: ChatEvent, - select: ManagedT, # noqa: F841 + select: ManagedT, dialog_manager: DialogManager, data: T, /, @@ -51,7 +51,7 @@ class OnItemClick(Protocol[ManagedT, T]): async def __call__( self, event: CallbackQuery, - select: ManagedT, # noqa: F841 + select: ManagedT, dialog_manager: DialogManager, data: T, /, @@ -143,7 +143,7 @@ def __init__( on_state_changed: Union[ OnItemStateChanged[ManagedT, T], WidgetEventProcessor, None, ] = None, - when: Union[str, Callable] = None, + when: Optional[Union[str, Callable]] = None, ): text = Case( {True: checked_text, False: unchecked_text}, @@ -229,7 +229,7 @@ def __init__( OnItemStateChanged["ManagedRadio[T]", T], WidgetEventProcessor, None, ] = None, - when: Union[str, Callable] = None, + when: Optional[Union[str, Callable]] = None, ): super().__init__( @@ -331,7 +331,7 @@ def __init__( OnItemStateChanged["ManagedMultiselect[T]", T], WidgetEventProcessor, None, ] = None, - when: Union[str, Callable] = None, + when: Optional[Union[str, Callable]] = None, ): super().__init__( checked_text=checked_text, @@ -447,7 +447,7 @@ def __init__( OnItemStateChanged["ManagedToggle[T]", T], WidgetEventProcessor, None, ] = None, - when: Union[str, Callable] = None, + when: Optional[Union[str, Callable]] = None, ): super().__init__( checked_text=text, unchecked_text=text, diff --git a/src/aiogram_dialog/widgets/media/static.py b/src/aiogram_dialog/widgets/media/static.py index 8053d150..239fb48e 100644 --- a/src/aiogram_dialog/widgets/media/static.py +++ b/src/aiogram_dialog/widgets/media/static.py @@ -19,7 +19,7 @@ def __init__( url: Union[Text, str, None] = None, type: ContentType = ContentType.PHOTO, use_pipe: bool = False, - media_params: dict = None, + media_params: Optional[dict] = None, when: WhenCondition = None, ): super().__init__(when=when) diff --git a/src/aiogram_dialog/window.py b/src/aiogram_dialog/window.py index 618a23eb..832f30fc 100644 --- a/src/aiogram_dialog/window.py +++ b/src/aiogram_dialog/window.py @@ -77,6 +77,7 @@ async def render_media( ) -> Optional[MediaAttachment]: if self.media: return await self.media.render_media(data, manager) + return None async def render_kbd( self, data: dict, manager: DialogManager, @@ -128,7 +129,7 @@ async def render( chat = manager.middleware_data["event_chat"] try: current_data = await self.load_data(dialog, manager) - except Exception: # noqa: B902 + except Exception: logger.error("Cannot get window data for state %s", self.state) raise try: @@ -145,7 +146,7 @@ async def render( disable_web_page_preview=self.disable_web_page_preview, media=await self.render_media(current_data, manager), ) - except Exception: # noqa: B902 + except Exception: logger.error("Cannot render window for state %s", self.state) raise diff --git a/tests/test_click.py b/tests/test_click.py index 0ec5d22f..54ab6f29 100644 --- a/tests/test_click.py +++ b/tests/test_click.py @@ -8,7 +8,11 @@ from aiogram.types import Message from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.test_tools import BotClient, MockMessageManager from aiogram_dialog.test_tools.keyboard import InlineButtonTextLocator diff --git a/tests/test_create.py b/tests/test_create.py index 7d6764f9..5a69a4e1 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -1,7 +1,7 @@ from aiogram import Dispatcher from aiogram.fsm.state import State, StatesGroup -from aiogram_dialog import Dialog, setup_dialogs, Window +from aiogram_dialog import Dialog, Window, setup_dialogs from aiogram_dialog.widgets.text import Format diff --git a/tests/test_events.py b/tests/test_events.py index 9a9c5017..aa29ec50 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -7,7 +7,11 @@ from aiogram.types import ChatMemberMember, ChatMemberOwner from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.test_tools import BotClient, MockMessageManager from aiogram_dialog.test_tools.memory_storage import JsonMemoryStorage diff --git a/tests/test_group.py b/tests/test_group.py index daba4b59..89166fa9 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -7,9 +7,13 @@ from aiogram.fsm.state import State, StatesGroup from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) -from aiogram_dialog.api.entities import AccessSettings, GROUP_STACK_ID +from aiogram_dialog.api.entities import GROUP_STACK_ID, AccessSettings from aiogram_dialog.test_tools import BotClient, MockMessageManager from aiogram_dialog.test_tools.keyboard import InlineButtonTextLocator from aiogram_dialog.test_tools.memory_storage import JsonMemoryStorage @@ -159,7 +163,8 @@ async def test_same_user(dp, client, message_manager): async def test_shared_stack(dp, client, second_client, message_manager): dp.message.register(start_shared, CommandStart()) await client.send("/start") - await asyncio.sleep(0.01) # synchronization workaround, fixme + await asyncio.sleep(0.02) # synchronization workaround, fixme + first_message = message_manager.one_message() assert first_message.text == "stub" message_manager.reset_history() diff --git a/tests/test_nested_transitions.py b/tests/test_nested_transitions.py index 53c77c22..82678663 100644 --- a/tests/test_nested_transitions.py +++ b/tests/test_nested_transitions.py @@ -5,7 +5,11 @@ from aiogram.types import Message from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.test_tools import BotClient, MockMessageManager from aiogram_dialog.test_tools.keyboard import InlineButtonTextLocator diff --git a/tests/test_transitions.py b/tests/test_transitions.py index 64ef53bc..bc844bd9 100644 --- a/tests/test_transitions.py +++ b/tests/test_transitions.py @@ -5,7 +5,11 @@ from aiogram.types import Message from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.test_tools import BotClient, MockMessageManager from aiogram_dialog.test_tools.keyboard import InlineButtonTextLocator diff --git a/tests/widgets/kbd/test_group.py b/tests/widgets/kbd/test_group.py index 7d7b7c60..5dd094bf 100644 --- a/tests/widgets/kbd/test_group.py +++ b/tests/widgets/kbd/test_group.py @@ -7,7 +7,11 @@ from aiogram.types import Message from aiogram_dialog import ( - Dialog, DialogManager, setup_dialogs, StartMode, Window, + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, ) from aiogram_dialog.test_tools import BotClient, MockMessageManager from aiogram_dialog.test_tools.keyboard import InlineButtonTextLocator diff --git a/tests/widgets/media/test_media_message.py b/tests/widgets/media/test_media_message.py index 6d9087be..174ade26 100644 --- a/tests/widgets/media/test_media_message.py +++ b/tests/widgets/media/test_media_message.py @@ -8,9 +8,9 @@ from aiogram_dialog import ( Dialog, DialogManager, - setup_dialogs, StartMode, Window, + setup_dialogs, ) from aiogram_dialog.test_tools import BotClient, MockMessageManager from aiogram_dialog.widgets.media.static import StaticMedia From 8f0070d111b74f43cb733b79e52b4646480c7586 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 11:11:31 +0300 Subject: [PATCH 12/29] fix workflow --- .github/workflows/setup.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml index 41847be8..f2fd1847 100644 --- a/.github/workflows/setup.yml +++ b/.github/workflows/setup.yml @@ -38,8 +38,8 @@ jobs: - name: Install dependencies run: | - uv pip install . -r requirements_dev.txt - uv pip install diagrams + uv pip install . -r requirements_dev.txt --system + uv pip install diagrams --system - name: Run Ruff run: ruff check src/aiogram_dialog tests example From 06c911c68850be59f841fc95b61fb3743b996917 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 11:17:40 +0300 Subject: [PATCH 13/29] fix code for python 3.9 --- src/aiogram_dialog/dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aiogram_dialog/dialog.py b/src/aiogram_dialog/dialog.py index 6b2ea5dc..a40891c9 100644 --- a/src/aiogram_dialog/dialog.py +++ b/src/aiogram_dialog/dialog.py @@ -61,7 +61,7 @@ def __init__( raise ValueError(f"Multiple windows with state {state}") self._states.append(state) self.windows: dict[State, WindowProtocol] = dict( - zip(self._states, windows, strict=False), + zip(self._states, windows), ) self.on_start = on_start self.on_close = on_close From 9c2a5936b4ebe6ca00af849ec40a38a54ee47aac Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 11:25:20 +0300 Subject: [PATCH 14/29] fix tests for python 3.9 --- src/aiogram_dialog/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aiogram_dialog/utils.py b/src/aiogram_dialog/utils.py index 6ddb7cde..57a9831a 100644 --- a/src/aiogram_dialog/utils.py +++ b/src/aiogram_dialog/utils.py @@ -83,7 +83,7 @@ def split_reply_callback( def decode_reply_callback(data: str) -> str: bytes_data = bytes( _decode_reply_callback_byte(little, big) - for little, big in zip(data[::2], data[1::2], strict=False) + for little, big in zip(data[::2], data[1::2]) ) return bytes_data.decode("utf-8") From 6897cc64917ab1946ee15be55cebfc5d3b1779fa Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 17:06:42 +0300 Subject: [PATCH 15/29] fix: improve code readability --- src/aiogram_dialog/widgets/kbd/select.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/aiogram_dialog/widgets/kbd/select.py b/src/aiogram_dialog/widgets/kbd/select.py index f9efd8d2..5d4571d9 100644 --- a/src/aiogram_dialog/widgets/kbd/select.py +++ b/src/aiogram_dialog/widgets/kbd/select.py @@ -352,7 +352,7 @@ def _is_text_checked( if manager.is_preview(): return ( # just stupid way to make it differ in preview - ord(item_id[-1]) % 2 == 1 + ord(item_id[-1]) % 2 == 1 ) return self.is_checked(item_id, manager) @@ -387,9 +387,11 @@ async def set_checked( if not checked and len(data) > self.min_selected: data.remove(item_id_str) changed = True - elif checked and self.max_selected == 0 or self.max_selected > len(data): # noqa: E501 - data.append(item_id_str) - changed = True + else: # noqa: PLR5501 + if checked: # noqa: SIM102 + if self.max_selected == 0 or self.max_selected > len(data): + data.append(item_id_str) + changed = True if changed: self.set_widget_data(manager, data) await self._process_on_state_changed(event, item_id_str, manager) From c8cfd0aeabd258671909913405649eb8c1616518 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 17:21:15 +0300 Subject: [PATCH 16/29] fix: enable ignored ruff rules: PT001, PT023, PLW2901, B904, RUF012, N804, B008, RUF009 and sort rules by name --- .ruff.toml | 48 ++++++++++++--------------- src/aiogram_dialog/context/storage.py | 2 +- tests/test_events.py | 6 ++-- tests/test_group.py | 8 ++--- tests/test_nested_transitions.py | 6 ++-- tests/test_transitions.py | 6 ++-- tests/widgets/conftest.py | 2 +- tests/widgets/text/test_jinja.py | 2 +- 8 files changed, 37 insertions(+), 43 deletions(-) diff --git a/.ruff.toml b/.ruff.toml index 6d1b7998..dfb7e3d4 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -7,48 +7,42 @@ exclude = ["docs"] lint.select = ["ALL"] lint.ignore = [ - "ARG", "ANN", + "ARG", "D", "DTZ", "TD", + "A002", + "ASYNC230", + "BLE001", "EM101", "EM102", - "PT001", - "PT023", - "SIM108", - "SIM114", - "TRY003", - "TCH003", - "PLW2901", - "RET505", - "PLR0913", - "UP038", - "TCH001", - "SIM103", - "ISC003", - "A002", "FA100", - "TRY400", "FBT001", "FBT002", - "S311", - "N818", - "B904", "FIX002", - "RUF012", + "ISC002", + "ISC003", + "N818", + "PLR0913", + "PLW2901", + "PYI034", + "RET505", + "S311", + "SIM103", + "SIM108", + "SIM114", + "TCH001", "TCH002", + "TCH003", + "TRY003", "TRY201", - "ISC002", - "ASYNC230", + "TRY400", "UP007", - "N804", - "B008", - "BLE001", - "RUF009", - "PYI034", + "UP038", ] + [lint.per-file-ignores] "src/aiogram_dialog/tools/**" = [ "S101", diff --git a/src/aiogram_dialog/context/storage.py b/src/aiogram_dialog/context/storage.py index e1c22fd6..f7766161 100644 --- a/src/aiogram_dialog/context/storage.py +++ b/src/aiogram_dialog/context/storage.py @@ -167,7 +167,7 @@ def _state(self, state: str) -> State: if real_state.state == state: return real_state except KeyError: - raise UnknownState(f"Unknown state group {group}") + raise UnknownState(f"Unknown state group {group}") from None raise UnknownState(f"Unknown state {state}") def _parse_access_settings( diff --git a/tests/test_events.py b/tests/test_events.py index aa29ec50..b2d17b25 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -32,12 +32,12 @@ async def start(event: Any, dialog_manager: DialogManager): await dialog_manager.start(MainSG.start, mode=StartMode.RESET_STACK) -@pytest.fixture() +@pytest.fixture def message_manager(): return MockMessageManager() -@pytest.fixture() +@pytest.fixture def dp(message_manager): dp = Dispatcher(storage=JsonMemoryStorage()) dp.include_router(Dialog(window)) @@ -45,7 +45,7 @@ def dp(message_manager): return dp -@pytest.fixture() +@pytest.fixture def client(dp): return BotClient(dp) diff --git a/tests/test_group.py b/tests/test_group.py index 89166fa9..5c8d0651 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -47,12 +47,12 @@ async def add_shared(event: Any, dialog_manager: DialogManager): )) -@pytest.fixture() +@pytest.fixture def message_manager(): return MockMessageManager() -@pytest.fixture() +@pytest.fixture def dp(message_manager): dp = Dispatcher(storage=JsonMemoryStorage()) dp.include_router(Dialog(window)) @@ -60,12 +60,12 @@ def dp(message_manager): return dp -@pytest.fixture() +@pytest.fixture def client(dp): return BotClient(dp, chat_id=-1, user_id=1, chat_type="group") -@pytest.fixture() +@pytest.fixture def second_client(dp): return BotClient(dp, chat_id=-1, user_id=2, chat_type="group") diff --git a/tests/test_nested_transitions.py b/tests/test_nested_transitions.py index 82678663..e8d96180 100644 --- a/tests/test_nested_transitions.py +++ b/tests/test_nested_transitions.py @@ -46,17 +46,17 @@ async def on_process_result_sub(_, __, dialog_manager: DialogManager): await dialog_manager.done() -@pytest.fixture() +@pytest.fixture def message_manager() -> MockMessageManager: return MockMessageManager() -@pytest.fixture() +@pytest.fixture def client(dp) -> BotClient: return BotClient(dp) -@pytest.fixture() +@pytest.fixture def dp(message_manager: MockMessageManager): dp = Dispatcher(storage=JsonMemoryStorage()) dp.message.register(start, CommandStart()) diff --git a/tests/test_transitions.py b/tests/test_transitions.py index bc844bd9..9334dedd 100644 --- a/tests/test_transitions.py +++ b/tests/test_transitions.py @@ -31,17 +31,17 @@ async def start(message: Message, dialog_manager: DialogManager): await dialog_manager.start(MainSG.start, mode=StartMode.RESET_STACK) -@pytest.fixture() +@pytest.fixture def message_manager() -> MockMessageManager: return MockMessageManager() -@pytest.fixture() +@pytest.fixture def client(dp) -> BotClient: return BotClient(dp) -@pytest.fixture() +@pytest.fixture def dp(message_manager: MockMessageManager): dp = Dispatcher(storage=JsonMemoryStorage()) dp.message.register(start, CommandStart()) diff --git a/tests/widgets/conftest.py b/tests/widgets/conftest.py index 70b5cc02..16f2912f 100644 --- a/tests/widgets/conftest.py +++ b/tests/widgets/conftest.py @@ -7,7 +7,7 @@ from aiogram_dialog.api.entities import Context -@pytest.fixture() +@pytest.fixture def mock_manager() -> DialogManager: manager = MagicMock() context = Context( diff --git a/tests/widgets/text/test_jinja.py b/tests/widgets/text/test_jinja.py index 5a94f200..47c1cb7e 100644 --- a/tests/widgets/text/test_jinja.py +++ b/tests/widgets/text/test_jinja.py @@ -4,7 +4,7 @@ from aiogram_dialog.widgets.text import Jinja -@pytest.fixture() +@pytest.fixture def mock_manager(mock_manager) -> DialogManager: mock_manager.middleware_data = {} return mock_manager From 90475d9b305c3d15089ba35eb8a6ff29fa4a86dd Mon Sep 17 00:00:00 2001 From: Andrey Tikhonov <17@itishka.org> Date: Wed, 6 Nov 2024 15:27:41 +0100 Subject: [PATCH 17/29] Update to restart pipeline --- src/aiogram_dialog/widgets/kbd/copy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aiogram_dialog/widgets/kbd/copy.py b/src/aiogram_dialog/widgets/kbd/copy.py index 10befb2f..c755180f 100644 --- a/src/aiogram_dialog/widgets/kbd/copy.py +++ b/src/aiogram_dialog/widgets/kbd/copy.py @@ -16,9 +16,9 @@ def __init__( copy_text: Text, when: WhenCondition = None, ) -> None: + super().__init__(when=when) self._text = text self._copy_text = copy_text - super().__init__(when=when) async def _render_keyboard( self, From 2ed7d6b5962dc4207fcbb7109555e5427a83da69 Mon Sep 17 00:00:00 2001 From: Andrey Tikhonov <17@itishka.org> Date: Wed, 6 Nov 2024 15:34:18 +0100 Subject: [PATCH 18/29] fix trailing comma --- src/aiogram_dialog/widgets/kbd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aiogram_dialog/widgets/kbd/__init__.py b/src/aiogram_dialog/widgets/kbd/__init__.py index ba4382e9..c955d3b5 100644 --- a/src/aiogram_dialog/widgets/kbd/__init__.py +++ b/src/aiogram_dialog/widgets/kbd/__init__.py @@ -41,7 +41,7 @@ "ListGroup", "ManagedListGroup", "StubScroll", - "CopyText" + "CopyText", ] from .base import Keyboard From fc7e852a3e0112a5b838f37bc8f5e5bafdc4b01a Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Wed, 6 Nov 2024 17:39:41 +0300 Subject: [PATCH 19/29] drop python 3.8 from docs examples --- .../example.py | 18 ++++++++--------- docs/widgets/hiding/example.py | 12 +++++------ docs/widgets/keyboard/calendar/custom.py | 15 ++++++++------ docs/widgets/text/case/example.py | 20 +++++++++---------- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/docs/widgets/custom_widgets/switch_inline_query_current_chat/example.py b/docs/widgets/custom_widgets/switch_inline_query_current_chat/example.py index d62182d4..ddd57de0 100644 --- a/docs/widgets/custom_widgets/switch_inline_query_current_chat/example.py +++ b/docs/widgets/custom_widgets/switch_inline_query_current_chat/example.py @@ -1,9 +1,7 @@ -from typing import Dict, List - -from aiogram.filters.state import StatesGroup, State +from aiogram.filters.state import State, StatesGroup from aiogram.types import InlineKeyboardButton -from aiogram_dialog import Dialog, Window -from aiogram_dialog import DialogManager + +from aiogram_dialog import Dialog, DialogManager, Window from aiogram_dialog.widgets.kbd import SwitchInlineQuery from aiogram_dialog.widgets.text import Const @@ -11,9 +9,9 @@ class SwitchInlineQueryCurrentChat(SwitchInlineQuery): async def _render_keyboard( self, - data: Dict, + data: dict, manager: DialogManager, - ) -> List[List[InlineKeyboardButton]]: + ) -> list[list[InlineKeyboardButton]]: return [ [ InlineKeyboardButton( @@ -34,8 +32,8 @@ class MySG(StatesGroup): Window( SwitchInlineQueryCurrentChat( Const("Some search"), # Button text - Const("query") # additional query. Optional + Const("query"), # additional query. Optional ), - state=MySG.main - ) + state=MySG.main, + ), ) diff --git a/docs/widgets/hiding/example.py b/docs/widgets/hiding/example.py index de3cd53a..5b1c777a 100644 --- a/docs/widgets/hiding/example.py +++ b/docs/widgets/hiding/example.py @@ -1,11 +1,9 @@ -from typing import Dict - -from aiogram.filters.state import StatesGroup, State +from aiogram.filters.state import State, StatesGroup from magic_filter import F -from aiogram_dialog import Window, DialogManager +from aiogram_dialog import DialogManager, Window from aiogram_dialog.widgets.common import Whenable -from aiogram_dialog.widgets.kbd import Button, Row, Group +from aiogram_dialog.widgets.kbd import Button, Group, Row from aiogram_dialog.widgets.text import Const, Format, Multi @@ -20,7 +18,7 @@ async def get_data(**kwargs): } -def is_tishka17(data: Dict, widget: Whenable, manager: DialogManager): +def is_tishka17(data: dict, widget: Whenable, manager: DialogManager): return data.get("name") == "Tishka17" @@ -28,7 +26,7 @@ def is_tishka17(data: Dict, widget: Whenable, manager: DialogManager): Multi( Const("Hello"), Format("{name}", when="extended"), - sep=" " + sep=" ", ), Group( Row( diff --git a/docs/widgets/keyboard/calendar/custom.py b/docs/widgets/keyboard/calendar/custom.py index d3eb30aa..efb8a1b0 100644 --- a/docs/widgets/keyboard/calendar/custom.py +++ b/docs/widgets/keyboard/calendar/custom.py @@ -1,17 +1,20 @@ -from typing import Dict - from aiogram_dialog import DialogManager from aiogram_dialog.widgets.kbd import ( - Calendar, CalendarScope, CalendarUserConfig, + Calendar, + CalendarScope, + CalendarUserConfig, ) from aiogram_dialog.widgets.kbd.calendar_kbd import ( - CalendarDaysView, CalendarMonthView, CalendarScopeView, CalendarYearsView, + CalendarDaysView, + CalendarMonthView, + CalendarScopeView, + CalendarYearsView, ) from aiogram_dialog.widgets.text import Const, Format class CustomCalendar(Calendar): - def _init_views(self) -> Dict[CalendarScope, CalendarScopeView]: + def _init_views(self) -> dict[CalendarScope, CalendarScopeView]: return { CalendarScope.DAYS: CalendarDaysView( self._item_callback_data, self.config, @@ -28,7 +31,7 @@ def _init_views(self) -> Dict[CalendarScope, CalendarScopeView]: async def _get_user_config( self, - data: Dict, + data: dict, manager: DialogManager, ) -> CalendarUserConfig: return CalendarUserConfig( diff --git a/docs/widgets/text/case/example.py b/docs/widgets/text/case/example.py index 0181477a..cbe72d5e 100644 --- a/docs/widgets/text/case/example.py +++ b/docs/widgets/text/case/example.py @@ -1,11 +1,11 @@ -from typing import Any, Dict +from typing import Any +from aiogram.filters.state import State, StatesGroup from magic_filter import F -from aiogram.filters.state import StatesGroup, State +from aiogram_dialog import Dialog, DialogManager, Window +from aiogram_dialog.widgets.text import Case, Const, Format -from aiogram_dialog import Window, DialogManager, Dialog -from aiogram_dialog.widgets.text import Const, Format, Case class MySG(StatesGroup): window1 = State() @@ -35,7 +35,7 @@ async def get_data(**kwargs): # The result of this function will be used to select wich option of ``Case`` widget to show. # # `text2` will produce text `42 is even!` -def parity_selector(data: Dict, case: Case, manager: DialogManager): +def parity_selector(data: dict, case: Case, manager: DialogManager): return data["number"] % 2 @@ -63,8 +63,8 @@ def parity_selector(data: Dict, case: Case, manager: DialogManager): async def on_dialog_start(start_data: Any, manager: DialogManager): - manager.dialog_data['user'] = { - 'test_result': True, + manager.dialog_data["user"] = { + "test_result": True, } @@ -74,7 +74,7 @@ async def on_dialog_start(start_data: Any, manager: DialogManager): text2, text3, state=MySG.window1, - getter=get_data + getter=get_data, ), - on_start=on_dialog_start -) \ No newline at end of file + on_start=on_dialog_start, +) From a60844d67a5c4524f9946d7681f676e5f9e083b4 Mon Sep 17 00:00:00 2001 From: Kostiantyn Kriuchkov <36363097+Latand@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:09:16 +0200 Subject: [PATCH 20/29] Update message_manager.py --- src/aiogram_dialog/manager/message_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index 386d9b37..7f740a8b 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -238,6 +238,8 @@ async def remove_inline_kbd( pass elif "message to edit not found" in err.message: pass + elif "MESSAGE_ID_INVALID" in err.message: + pass else: raise err From f72f37959542eb6fc94ae6739e3ca9f9de4d96b6 Mon Sep 17 00:00:00 2001 From: Kurosawa <145038102+KurosawaAngel@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:57:47 +0500 Subject: [PATCH 21/29] fix edit voice --- src/aiogram_dialog/api/entities/new_message.py | 2 ++ src/aiogram_dialog/api/entities/stack.py | 2 ++ src/aiogram_dialog/manager/manager.py | 6 +++++- src/aiogram_dialog/manager/message_manager.py | 11 +++++++++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/aiogram_dialog/api/entities/new_message.py b/src/aiogram_dialog/api/entities/new_message.py index 062f794f..4ac6883a 100644 --- a/src/aiogram_dialog/api/entities/new_message.py +++ b/src/aiogram_dialog/api/entities/new_message.py @@ -2,6 +2,7 @@ from enum import Enum from typing import Optional, Union +from aiogram.enums import ContentType from aiogram.types import ( Chat, ForceReply, @@ -30,6 +31,7 @@ class OldMessage: text: Union[str, None, UnknownText] = None has_reply_keyboard: bool = False business_connection_id: Optional[str] = None + content_type: Optional[ContentType] = None @dataclass diff --git a/src/aiogram_dialog/api/entities/stack.py b/src/aiogram_dialog/api/entities/stack.py index 3256d018..27b3f01f 100644 --- a/src/aiogram_dialog/api/entities/stack.py +++ b/src/aiogram_dialog/api/entities/stack.py @@ -4,6 +4,7 @@ from dataclasses import dataclass, field from typing import Optional +from aiogram.enums import ContentType from aiogram.fsm.state import State from aiogram_dialog.api.exceptions import DialogStackOverflow @@ -47,6 +48,7 @@ class Stack: last_income_media_group_id: Optional[str] = field( compare=False, default=None, ) + content_type: Optional[ContentType] = field(compare=False, default=None) access_settings: Optional[AccessSettings] = None @property diff --git a/src/aiogram_dialog/manager/manager.py b/src/aiogram_dialog/manager/manager.py index 680ef54e..79cef84b 100644 --- a/src/aiogram_dialog/manager/manager.py +++ b/src/aiogram_dialog/manager/manager.py @@ -421,6 +421,7 @@ def _get_message_from_callback( chat=event_context.chat, message_id=current_message.message_id, business_connection_id=event_context.business_connection_id, + content_type=current_message.content_type, ) elif not stack or not stack.last_message_id: return None @@ -433,6 +434,7 @@ def _get_message_from_callback( chat=event_context.chat, message_id=stack.last_message_id, business_connection_id=event_context.business_connection_id, + content_type=stack.content_type, ) def _get_last_message(self) -> Optional[OldMessage]: @@ -457,14 +459,16 @@ def _get_last_message(self) -> Optional[OldMessage]: chat=event_context.chat, message_id=stack.last_message_id, business_connection_id=event_context.business_connection_id, + content_type=stack.content_type, ) - def _save_last_message(self, message: OldMessage): + def _save_last_message(self, message: OldMessage) -> None: stack = self.current_stack() stack.last_message_id = message.message_id stack.last_media_id = message.media_id stack.last_media_unique_id = message.media_uniq_id stack.last_reply_keyboard = message.has_reply_keyboard + stack.content_type = message.content_type def _calc_show_mode(self) -> ShowMode: # noqa: PLR0911 if self.show_mode is not ShowMode.AUTO: diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index 7f740a8b..128f730d 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -71,6 +71,7 @@ def _combine(sent_message: NewMessage, message_result: Message) -> OldMessage: media_uniq_id=(media_id.file_unique_id if media_id else None), media_id=(media_id.file_id if media_id else None), business_connection_id=message_result.business_connection_id, + content_type=message_result.content_type, ) @@ -116,6 +117,9 @@ def need_reply_keyboard(self, new_message: Optional[NewMessage]) -> bool: return False return isinstance(new_message.reply_markup, ReplyKeyboardMarkup) + def had_voice(self, old_message: OldMessage) -> bool: + return old_message.content_type == ContentType.VOICE + def _message_changed( self, new_message: NewMessage, old_message: OldMessage, ) -> bool: @@ -140,9 +144,12 @@ def _can_edit(self, new_message: NewMessage, # we cannot edit message if media removed if self.had_media(old_message) and not self.need_media(new_message): return False + # we cannot edit a message if there was voice + if self.had_voice(old_message): + return False return not ( - self.had_reply_keyboard(old_message) or - self.need_reply_keyboard(new_message) + self.had_reply_keyboard(old_message) + or self.need_reply_keyboard(new_message) ) async def show_message( From 5552ee2acf958df25b7e32f92f1ea177a40b2310 Mon Sep 17 00:00:00 2001 From: Kurosawa <145038102+KurosawaAngel@users.noreply.github.com> Date: Thu, 7 Nov 2024 23:42:53 +0500 Subject: [PATCH 22/29] add voice added check --- src/aiogram_dialog/manager/message_manager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index 128f730d..f5836157 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -120,6 +120,12 @@ def need_reply_keyboard(self, new_message: Optional[NewMessage]) -> bool: def had_voice(self, old_message: OldMessage) -> bool: return old_message.content_type == ContentType.VOICE + def need_voice(self, new_message: NewMessage) -> bool: + return ( + new_message.media is not None + and new_message.media.type == ContentType.VOICE + ) + def _message_changed( self, new_message: NewMessage, old_message: OldMessage, ) -> bool: @@ -145,7 +151,7 @@ def _can_edit(self, new_message: NewMessage, if self.had_media(old_message) and not self.need_media(new_message): return False # we cannot edit a message if there was voice - if self.had_voice(old_message): + if self.had_voice(old_message) or self.need_voice(new_message): return False return not ( self.had_reply_keyboard(old_message) From 460a04f0522d01f0d7c1fd4ca3a4d003ddc5f43c Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Mon, 11 Nov 2024 17:32:06 +0300 Subject: [PATCH 23/29] add tests for python 3.13 --- .github/workflows/setup.yml | 1 + pyproject.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml index f2fd1847..993e3cf7 100644 --- a/.github/workflows/setup.yml +++ b/.github/workflows/setup.yml @@ -25,6 +25,7 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" steps: - uses: actions/checkout@v2 diff --git a/pyproject.toml b/pyproject.toml index 84c18f36..d45f2889 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", @@ -32,6 +33,7 @@ dependencies = [ "aiogram>=3.14.0", "jinja2", "cachetools>=4.0.0,<6.0.0", + "diagrams>=0.24.1", ] [project.optional-dependencies] tools = [ From b7856331ed998a4a09f1f0405aaf35b4a3714a74 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Mon, 11 Nov 2024 17:39:30 +0300 Subject: [PATCH 24/29] fix pyproject --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d45f2889..14b83f88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ dependencies = [ "aiogram>=3.14.0", "jinja2", "cachetools>=4.0.0,<6.0.0", - "diagrams>=0.24.1", ] [project.optional-dependencies] tools = [ From d1b2e5ca4009dafed872bdc06295a988b6c73371 Mon Sep 17 00:00:00 2001 From: Andrey Tikhonov <17@itishka.org> Date: Wed, 11 Dec 2024 23:41:52 +0100 Subject: [PATCH 25/29] link preview prototype --- .../api/entities/new_message.py | 2 + src/aiogram_dialog/api/internal/__init__.py | 3 +- src/aiogram_dialog/api/internal/widgets.py | 11 ++++ src/aiogram_dialog/manager/message_manager.py | 13 ++-- .../widgets/link_preview/__init__.py | 3 + .../widgets/link_preview/base.py | 61 +++++++++++++++++++ src/aiogram_dialog/widgets/utils.py | 25 +++++++- src/aiogram_dialog/window.py | 12 ++++ 8 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 src/aiogram_dialog/widgets/link_preview/__init__.py create mode 100644 src/aiogram_dialog/widgets/link_preview/base.py diff --git a/src/aiogram_dialog/api/entities/new_message.py b/src/aiogram_dialog/api/entities/new_message.py index 4ac6883a..e360563a 100644 --- a/src/aiogram_dialog/api/entities/new_message.py +++ b/src/aiogram_dialog/api/entities/new_message.py @@ -7,6 +7,7 @@ Chat, ForceReply, InlineKeyboardMarkup, + LinkPreviewOptions, ReplyKeyboardMarkup, ReplyKeyboardRemove, ) @@ -45,3 +46,4 @@ class NewMessage: show_mode: ShowMode = ShowMode.AUTO disable_web_page_preview: Optional[bool] = None media: Optional[MediaAttachment] = None + link_preview_options: Optional[LinkPreviewOptions] = None diff --git a/src/aiogram_dialog/api/internal/__init__.py b/src/aiogram_dialog/api/internal/__init__.py index 62dcf102..2dc28901 100644 --- a/src/aiogram_dialog/api/internal/__init__.py +++ b/src/aiogram_dialog/api/internal/__init__.py @@ -4,7 +4,7 @@ "CALLBACK_DATA_KEY", "CONTEXT_KEY", "EVENT_SIMULATED", "STACK_KEY", "STORAGE_KEY", "ButtonVariant", "DataGetter", "InputWidget", "KeyboardWidget", - "MediaWidget", "RawKeyboard", "TextWidget", "Widget", + "LinkPreviewWidget", "MediaWidget", "RawKeyboard", "TextWidget", "Widget", "WindowProtocol", ] @@ -24,6 +24,7 @@ DataGetter, InputWidget, KeyboardWidget, + LinkPreviewWidget, MediaWidget, RawKeyboard, TextWidget, diff --git a/src/aiogram_dialog/api/internal/widgets.py b/src/aiogram_dialog/api/internal/widgets.py index c3976817..8472b216 100644 --- a/src/aiogram_dialog/api/internal/widgets.py +++ b/src/aiogram_dialog/api/internal/widgets.py @@ -12,6 +12,7 @@ CallbackQuery, InlineKeyboardButton, KeyboardButton, + LinkPreviewOptions, Message, ) @@ -41,6 +42,16 @@ async def render_text( raise NotImplementedError +@runtime_checkable +class LinkPreviewWidget(Widget, Protocol): + @abstractmethod + async def render_link_preview( + self, data: dict, manager: DialogManager, + ) -> Optional[LinkPreviewOptions]: + """Create link preview.""" + raise NotImplementedError + + ButtonVariant = Union[InlineKeyboardButton, KeyboardButton] RawKeyboard = list[list[ButtonVariant]] diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index f5836157..1d729e6d 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -129,10 +129,13 @@ def need_voice(self, new_message: NewMessage) -> bool: def _message_changed( self, new_message: NewMessage, old_message: OldMessage, ) -> bool: - if new_message.text != old_message.text: - return True - # we cannot actually compare reply keyboards - if new_message.reply_markup or old_message.has_reply_keyboard: + if ( + (new_message.text != old_message.text) or + # we cannot actually compare reply keyboards + (new_message.reply_markup or old_message.has_reply_keyboard) or + # we do not know if link preview changed + new_message.link_preview_options + ): return True if self.had_media(old_message) != self.need_media(new_message): @@ -349,6 +352,7 @@ async def edit_text( reply_markup=new_message.reply_markup, parse_mode=new_message.parse_mode, disable_web_page_preview=new_message.disable_web_page_preview, + link_preview_options=new_message.link_preview_options, ) async def edit_media( @@ -395,6 +399,7 @@ async def send_text(self, bot: Bot, new_message: NewMessage) -> Message: disable_web_page_preview=new_message.disable_web_page_preview, reply_markup=new_message.reply_markup, parse_mode=new_message.parse_mode, + link_preview_options=new_message.link_preview_options, ) async def send_media(self, bot: Bot, new_message: NewMessage) -> Message: diff --git a/src/aiogram_dialog/widgets/link_preview/__init__.py b/src/aiogram_dialog/widgets/link_preview/__init__.py new file mode 100644 index 00000000..1d848cca --- /dev/null +++ b/src/aiogram_dialog/widgets/link_preview/__init__.py @@ -0,0 +1,3 @@ +__all__ = ["LinkPreviewBase", "LinkPreview"] + +from .base import LinkPreview, LinkPreviewBase diff --git a/src/aiogram_dialog/widgets/link_preview/base.py b/src/aiogram_dialog/widgets/link_preview/base.py new file mode 100644 index 00000000..ac9f40ef --- /dev/null +++ b/src/aiogram_dialog/widgets/link_preview/base.py @@ -0,0 +1,61 @@ +from typing import Optional + +from aiogram.types import LinkPreviewOptions + +from aiogram_dialog import DialogManager +from aiogram_dialog.api.internal import LinkPreviewWidget, TextWidget +from aiogram_dialog.widgets.common import BaseWidget, Whenable, WhenCondition + + +class LinkPreviewBase(Whenable, BaseWidget, LinkPreviewWidget): + def __init__(self, when: WhenCondition = None): + super().__init__(when=when) + + async def render_link_preview( + self, data: dict, manager: DialogManager, + ) -> Optional[LinkPreviewOptions]: + if not self.is_(data, manager): + return None + return await self._render_link_preview(data, manager) + + async def _render_link_preview( + self, data: dict, manager: DialogManager, + ) -> Optional[LinkPreviewOptions]: + return None + + +class LinkPreview(LinkPreviewBase): + def __init__( + self, + url: TextWidget, + is_disabled: bool = False, + prefer_small_media: bool = False, + prefer_large_media: bool = False, + show_above_text: bool = False, + when: WhenCondition = None, + ): + super().__init__(when=when) + self.url = url + self.is_disabled = is_disabled + self.prefer_small_media = prefer_small_media + self.prefer_large_media = prefer_large_media + self.show_above_text = show_above_text + + async def render_link_preview( + self, data: dict, manager: DialogManager, + ) -> Optional[LinkPreviewOptions]: + if not self.is_(data, manager): + return None + return await self._render_link_preview(data, manager) + + async def _render_link_preview( + self, data: dict, manager: DialogManager, + ) -> Optional[LinkPreviewOptions]: + url = await self.url.render_text(data, manager) + return LinkPreviewOptions( + url=url, + is_disabled=self.is_disabled, + prefer_small_media=self.prefer_small_media, + prefer_large_media=self.prefer_large_media, + show_above_text=self.show_above_text, + ) diff --git a/src/aiogram_dialog/widgets/utils.py b/src/aiogram_dialog/widgets/utils.py index 16aa047d..4fd98b9f 100644 --- a/src/aiogram_dialog/widgets/utils.py +++ b/src/aiogram_dialog/widgets/utils.py @@ -2,16 +2,19 @@ from typing import Union from aiogram_dialog.api.exceptions import InvalidWidgetType -from aiogram_dialog.api.internal import DataGetter +from aiogram_dialog.api.internal import DataGetter, LinkPreviewWidget from .data.data_context import CompositeGetter, StaticGetter from .input import BaseInput, CombinedInput, MessageHandlerFunc, MessageInput from .kbd import Group, Keyboard +from .link_preview import LinkPreviewBase from .media import Media from .text import Format, Multi, Text from .widget_event import WidgetEventProcessor -WidgetSrc = Union[str, Text, Keyboard, MessageHandlerFunc, Media, BaseInput] +WidgetSrc = Union[ + str, Text, Keyboard, MessageHandlerFunc, Media, BaseInput, LinkPreviewBase, +] SingleGetterBase = Union[DataGetter, dict] GetterVariant = Union[ @@ -71,13 +74,26 @@ def ensure_media(widget: Union[Media, Sequence[Media]]) -> Media: return Media() +def ensure_link_preview( + widget: Union[LinkPreviewWidget, Sequence[LinkPreviewWidget]], +) -> LinkPreviewWidget: + if isinstance(widget, LinkPreviewWidget): + return widget + if len(widget) > 1: + raise ValueError("Only one link preview widget is supported") + if len(widget) == 1: + return widget[0] + return LinkPreviewBase() + + def ensure_widgets( widgets: Sequence[WidgetSrc], -) -> tuple[Text, Keyboard, Union[BaseInput, None], Media]: +) -> tuple[Text, Keyboard, Union[BaseInput, None], Media, LinkPreviewWidget]: texts = [] keyboards = [] inputs = [] media = [] + link_preview = [] for w in widgets: if isinstance(w, (str, Text)): @@ -88,6 +104,8 @@ def ensure_widgets( inputs.append(ensure_input(w)) elif isinstance(w, Media): media.append(ensure_media(w)) + elif isinstance(w, LinkPreviewBase): + link_preview.append(ensure_link_preview(w)) else: raise InvalidWidgetType( f"Cannot add widget of type {type(w)}. " @@ -99,6 +117,7 @@ def ensure_widgets( ensure_keyboard(keyboards), ensure_input(inputs), ensure_media(media), + ensure_link_preview(link_preview), ) diff --git a/src/aiogram_dialog/window.py b/src/aiogram_dialog/window.py index 832f30fc..c4eaf58c 100644 --- a/src/aiogram_dialog/window.py +++ b/src/aiogram_dialog/window.py @@ -5,6 +5,7 @@ from aiogram.types import ( UNSET_PARSE_MODE, CallbackQuery, + LinkPreviewOptions, Message, ) from aiogram.types.base import UNSET_DISABLE_WEB_PAGE_PREVIEW @@ -55,6 +56,7 @@ def __init__( self.keyboard, self.on_message, self.media, + self.link_preview, ) = ensure_widgets(widgets) self.getter = PreviewAwareGetter( ensure_data_getter(getter), @@ -87,6 +89,13 @@ async def render_kbd( data, manager, keyboard, ) + async def render_link_preview( + self, data: dict, manager: DialogManager, + ) -> Optional[LinkPreviewOptions]: + if self.link_preview: + return await self.link_preview.render_link_preview(data, manager) + return None + async def load_data( self, dialog: "DialogProtocol", manager: DialogManager, @@ -145,6 +154,9 @@ async def render( parse_mode=self.parse_mode, disable_web_page_preview=self.disable_web_page_preview, media=await self.render_media(current_data, manager), + link_preview_options=await self.render_link_preview( + current_data, manager, + ), ) except Exception: logger.error("Cannot render window for state %s", self.state) From bd56910a19d168b7c2758dc1b874ca180c385c15 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Mon, 16 Dec 2024 01:20:15 +0300 Subject: [PATCH 26/29] make `url` optional and add example for `LinkPreview` widget --- example/link_preview.py | 95 +++++++++++++++++++ .../widgets/link_preview/base.py | 5 +- 2 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 example/link_preview.py diff --git a/example/link_preview.py b/example/link_preview.py new file mode 100644 index 00000000..0ea3eed6 --- /dev/null +++ b/example/link_preview.py @@ -0,0 +1,95 @@ +from aiogram import Bot, Dispatcher +from aiogram.filters import Command +from aiogram.filters.state import State, StatesGroup +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.types import Message + +from aiogram_dialog import ( + Dialog, + DialogManager, + StartMode, + Window, + setup_dialogs, +) +from aiogram_dialog.widgets.kbd import SwitchTo +from aiogram_dialog.widgets.link_preview import LinkPreview +from aiogram_dialog.widgets.text import Const, Format + + +async def photo_getter(**_): + return {"photo_url": "https://nplus1.ru/news/2024/05/23/voyager-1-science-data"} + + +class SG(StatesGroup): + MAIN = State() + IS_DISABLED = State() + SMALL_MEDIA = State() + LARGE_MEDIA = State() + SHOW_ABOVE_TEXT = State() + + +BACK = SwitchTo(Const("back"), "_back", SG.MAIN) + + +dialog = Dialog( + Window( + Format("Default\n{photo_url}"), + SwitchTo( + Const("disable"), "_disable", SG.IS_DISABLED, + ), + SwitchTo( + Const("prefer small media"), "_prefer_small_media", SG.SMALL_MEDIA, + ), + SwitchTo( + Const("prefer large media"), "_prefer_large_media", SG.LARGE_MEDIA, + ), + SwitchTo( + Const("show above text"), "_show_above_text", SG.SHOW_ABOVE_TEXT, + ), + getter=photo_getter, + state=SG.MAIN, + ), + Window( + Format("{photo_url}"), + LinkPreview(is_disabled=True), + BACK, + state=SG.IS_DISABLED, + getter=photo_getter, + ), + Window( + Const("prefer small media"), + LinkPreview(Format("{photo_url}"), prefer_small_media=True), + BACK, + state=SG.SMALL_MEDIA, + getter=photo_getter, + ), + Window( + Const("prefer large media"), + LinkPreview(Format("{photo_url}"), prefer_large_media=True), + BACK, + state=SG.LARGE_MEDIA, + getter=photo_getter, + ), + Window( + Const("show above text"), + LinkPreview(Format("{photo_url}"), show_above_text=True), + BACK, + state=SG.SHOW_ABOVE_TEXT, + getter=photo_getter, + ), +) + +storage = MemoryStorage() +bot = Bot("token") +dp = Dispatcher(storage=storage) +dp.include_router(dialog) +setup_dialogs(dp) + + +@dp.message(Command("start")) +async def start(message: Message, dialog_manager: DialogManager): + await dialog_manager.start(SG.MAIN, mode=StartMode.RESET_STACK) + + +if __name__ == "__main__": + dp.run_polling(bot, skip_updates=True) diff --git a/src/aiogram_dialog/widgets/link_preview/base.py b/src/aiogram_dialog/widgets/link_preview/base.py index ac9f40ef..7a2fe925 100644 --- a/src/aiogram_dialog/widgets/link_preview/base.py +++ b/src/aiogram_dialog/widgets/link_preview/base.py @@ -27,7 +27,7 @@ async def _render_link_preview( class LinkPreview(LinkPreviewBase): def __init__( self, - url: TextWidget, + url: Optional[TextWidget] = None, is_disabled: bool = False, prefer_small_media: bool = False, prefer_large_media: bool = False, @@ -51,9 +51,8 @@ async def render_link_preview( async def _render_link_preview( self, data: dict, manager: DialogManager, ) -> Optional[LinkPreviewOptions]: - url = await self.url.render_text(data, manager) return LinkPreviewOptions( - url=url, + url=await self.url.render_text(data, manager) if self.url else None, is_disabled=self.is_disabled, prefer_small_media=self.prefer_small_media, prefer_large_media=self.prefer_large_media, From 22dd47b63479f12930d1f4c2cefa82d1a8b7f590 Mon Sep 17 00:00:00 2001 From: chiri <2alivemafia@gmail.com> Date: Mon, 16 Dec 2024 02:18:15 +0300 Subject: [PATCH 27/29] add docs to LinkPreview widget --- docs/conf.py | 5 +---- docs/widgets/index.rst | 8 +++++--- docs/widgets/link_preview/example.py | 27 +++++++++++++++++++++++++++ docs/widgets/link_preview/index.rst | 23 +++++++++++++++++++++++ 4 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 docs/widgets/link_preview/example.py create mode 100644 docs/widgets/link_preview/index.rst diff --git a/docs/conf.py b/docs/conf.py index 7a3178ad..c8410d17 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,13 +13,10 @@ # import os # import sys # sys.path.insert(0, os.path.abspath(".")) - -import datetime - # -- Project information ----------------------------------------------------- project = "aiogram-dialog" -copyright = f"{datetime.date.today().year}, Tishka17" +copyright = "%Y, Tishka17" author = "Tishka17" master_doc = "index" diff --git a/docs/widgets/index.rst b/docs/widgets/index.rst index c11c0664..5678ef7a 100644 --- a/docs/widgets/index.rst +++ b/docs/widgets/index.rst @@ -4,13 +4,14 @@ Widgets and Rendering Base information ******************** -Currently there are 4 kinds of widgets: :ref:`texts `, :ref:`keyboards `, -:ref:`input `, :ref:`media` and you can create your own :ref:`widgets`. +Currently there are 5 kinds of widgets: :ref:`texts `, :ref:`keyboards `, +:ref:`input `, :ref:`media`, :ref:`link preview` and you can create your own :ref:`widgets`. * **Texts** used to render text anywhere in dialog. It can be message text, button title and so on. * **Keyboards** represent parts of ``InlineKeyboard`` * **Media** represent media attachment to message * **Input** allows to process incoming messages from user. Is has no representation. +* **Link Preview** used to manage link previews in messages. Widgets can display static (e.g. ``Const``) and dynamic (e.g. ``Format``) content. To use dynamic data you have to set it. See :ref:`passing data `. @@ -37,5 +38,6 @@ Also there are 2 general types: keyboard/index input/index media/index + link_preview/index hiding/index - custom_widgets/index + custom_widgets/index \ No newline at end of file diff --git a/docs/widgets/link_preview/example.py b/docs/widgets/link_preview/example.py new file mode 100644 index 00000000..3f9bbed4 --- /dev/null +++ b/docs/widgets/link_preview/example.py @@ -0,0 +1,27 @@ +from aiogram.filters.state import State, StatesGroup + +from aiogram_dialog import Window +from aiogram_dialog.widgets.link_preview import LinkPreview +from aiogram_dialog.widgets.text import Const + + +class SG(StatesGroup): + MAIN = State() + SECOND = State() + + +window = Window( + Const("https://nplus1.ru/news/2024/05/23/voyager-1-science-data"), + LinkPreview(is_disabled=True), + state=SG.MAIN, +) + +second_window = Window( + Const("some text"), + LinkPreview( + url=Const("https://nplus1.ru/news/2024/05/23/voyager-1-science-data"), + prefer_small_media=True, + show_above_text=True, + ), + state=SG.MAIN, +) diff --git a/docs/widgets/link_preview/index.rst b/docs/widgets/link_preview/index.rst new file mode 100644 index 00000000..0c552bfc --- /dev/null +++ b/docs/widgets/link_preview/index.rst @@ -0,0 +1,23 @@ +.. _link_preview: + +LinkPreview +************* + +The **LinkPreview** widget is used to manage link previews in messages. + +Parameters: + +* ``url``: A ``TextWidget`` with URL to be used in the link preview. If not provided, the first URL found in the message will be used. +* ``is_disabled``: that controls whether the link preview is displayed. If ``True``, the preview will be disabled. +* ``prefer_small_media``: that controls if the media in the link preview should be displayed in a smaller size. Ignored if media size change is not supported. +* ``prefer_large_media``: that controls if the media in the link preview should be enlarged. Ignored if media size change is not supported. +* ``show_above_text``: that specifies whether the link preview should be displayed above the message text. If ``True``, link preview be displayed above the message text. + + +Code example: + +.. literalinclude:: ./example.py + +.. autoclass:: aiogram_dialog.widgets.link_preview.LinkPreview + :special-members: __init__ + :members: render_link_preview, _render_link_preview From 74d0d6e3b40d5b82c10a8896c25bc25965457159 Mon Sep 17 00:00:00 2001 From: Andrey Tikhonov <17@itishka.org> Date: Mon, 16 Dec 2024 10:54:06 +0100 Subject: [PATCH 28/29] remove disable_web_page_preview usage, move example to mega bot --- example/link_preview.py | 95 ------------------- example/loading.py | 2 + example/mega/bot.py | 2 + example/mega/bot_dialogs/link_preview.py | 82 ++++++++++++++++ example/mega/bot_dialogs/main.py | 5 + example/mega/bot_dialogs/states.py | 8 ++ .../api/entities/new_message.py | 1 - src/aiogram_dialog/manager/message_manager.py | 3 - src/aiogram_dialog/widgets/utils.py | 14 ++- src/aiogram_dialog/window.py | 18 +++- 10 files changed, 124 insertions(+), 106 deletions(-) delete mode 100644 example/link_preview.py create mode 100644 example/mega/bot_dialogs/link_preview.py diff --git a/example/link_preview.py b/example/link_preview.py deleted file mode 100644 index 0ea3eed6..00000000 --- a/example/link_preview.py +++ /dev/null @@ -1,95 +0,0 @@ -from aiogram import Bot, Dispatcher -from aiogram.filters import Command -from aiogram.filters.state import State, StatesGroup -from aiogram.fsm.storage.memory import MemoryStorage -from aiogram.types import Message - -from aiogram_dialog import ( - Dialog, - DialogManager, - StartMode, - Window, - setup_dialogs, -) -from aiogram_dialog.widgets.kbd import SwitchTo -from aiogram_dialog.widgets.link_preview import LinkPreview -from aiogram_dialog.widgets.text import Const, Format - - -async def photo_getter(**_): - return {"photo_url": "https://nplus1.ru/news/2024/05/23/voyager-1-science-data"} - - -class SG(StatesGroup): - MAIN = State() - IS_DISABLED = State() - SMALL_MEDIA = State() - LARGE_MEDIA = State() - SHOW_ABOVE_TEXT = State() - - -BACK = SwitchTo(Const("back"), "_back", SG.MAIN) - - -dialog = Dialog( - Window( - Format("Default\n{photo_url}"), - SwitchTo( - Const("disable"), "_disable", SG.IS_DISABLED, - ), - SwitchTo( - Const("prefer small media"), "_prefer_small_media", SG.SMALL_MEDIA, - ), - SwitchTo( - Const("prefer large media"), "_prefer_large_media", SG.LARGE_MEDIA, - ), - SwitchTo( - Const("show above text"), "_show_above_text", SG.SHOW_ABOVE_TEXT, - ), - getter=photo_getter, - state=SG.MAIN, - ), - Window( - Format("{photo_url}"), - LinkPreview(is_disabled=True), - BACK, - state=SG.IS_DISABLED, - getter=photo_getter, - ), - Window( - Const("prefer small media"), - LinkPreview(Format("{photo_url}"), prefer_small_media=True), - BACK, - state=SG.SMALL_MEDIA, - getter=photo_getter, - ), - Window( - Const("prefer large media"), - LinkPreview(Format("{photo_url}"), prefer_large_media=True), - BACK, - state=SG.LARGE_MEDIA, - getter=photo_getter, - ), - Window( - Const("show above text"), - LinkPreview(Format("{photo_url}"), show_above_text=True), - BACK, - state=SG.SHOW_ABOVE_TEXT, - getter=photo_getter, - ), -) - -storage = MemoryStorage() -bot = Bot("token") -dp = Dispatcher(storage=storage) -dp.include_router(dialog) -setup_dialogs(dp) - - -@dp.message(Command("start")) -async def start(message: Message, dialog_manager: DialogManager): - await dialog_manager.start(SG.MAIN, mode=StartMode.RESET_STACK) - - -if __name__ == "__main__": - dp.run_polling(bot, skip_updates=True) diff --git a/example/loading.py b/example/loading.py index baf0359b..52f3394f 100644 --- a/example/loading.py +++ b/example/loading.py @@ -17,6 +17,7 @@ setup_dialogs, ) from aiogram_dialog.widgets.kbd import Button +from aiogram_dialog.widgets.link_preview import LinkPreview from aiogram_dialog.widgets.text import Const, Multi, Progress API_TOKEN = os.getenv("BOT_TOKEN") @@ -75,6 +76,7 @@ async def background(callback: CallbackQuery, manager: BaseDialogManager): Window( Const("Press button to start processing"), Button(Const("Start"), id="start", on_click=start_bg), + LinkPreview(url=Const("http://ya.ru")), state=MainSG.main, ), ) diff --git a/example/mega/bot.py b/example/mega/bot.py index 851342d3..7ba54e8a 100644 --- a/example/mega/bot.py +++ b/example/mega/bot.py @@ -11,6 +11,7 @@ from bot_dialogs.calendar import calendar_dialog from bot_dialogs.counter import counter_dialog from bot_dialogs.layouts import layouts_dialog +from bot_dialogs.link_preview import link_preview_dialog from bot_dialogs.main import main_dialog from bot_dialogs.mutltiwidget import multiwidget_dialog from bot_dialogs.reply_buttons import reply_kbd_dialog @@ -68,6 +69,7 @@ async def on_unknown_intent(event: ErrorEvent, dialog_manager: DialogManager): multiwidget_dialog, switch_dialog, reply_kbd_dialog, + link_preview_dialog, ) diff --git a/example/mega/bot_dialogs/link_preview.py b/example/mega/bot_dialogs/link_preview.py new file mode 100644 index 00000000..511be3e6 --- /dev/null +++ b/example/mega/bot_dialogs/link_preview.py @@ -0,0 +1,82 @@ +from aiogram_dialog import ( + Dialog, + Window, +) +from aiogram_dialog.widgets.kbd import SwitchTo +from aiogram_dialog.widgets.link_preview import LinkPreview +from aiogram_dialog.widgets.text import Const, Format +from . import states +from .common import MAIN_MENU_BUTTON + + +async def links_getter(**_): + return { + "main": "https://en.wikipedia.org/wiki/HTML_element", + "photo": "https://en.wikipedia.org/wiki/Hyperlink", + } + + +LinkPreview_MAIN_MENU_BUTTON = SwitchTo( + text=Const("Back"), id="back", state=states.LinkPreview.MAIN, +) +COMMON_TEXT = Format( + "This is demo of different link preview options.\n" + "Link in text: {main}\n" + "Link in preview can be different\n\n" + "Current mode is:", +) + +BACK = SwitchTo(Const("back"), "_back", states.LinkPreview.MAIN) + +link_preview_dialog = Dialog( + Window( + COMMON_TEXT, + Format("Default"), + SwitchTo( + Const("disable"), "_disable", states.LinkPreview.IS_DISABLED, + ), + SwitchTo( + Const("prefer small media"), "_prefer_small_media", + states.LinkPreview.SMALL_MEDIA, + ), + SwitchTo( + Const("prefer large media"), "_prefer_large_media", + states.LinkPreview.LARGE_MEDIA, + ), + SwitchTo( + Const("show above text"), "_show_above_text", + states.LinkPreview.SHOW_ABOVE_TEXT, + ), + MAIN_MENU_BUTTON, + state=states.LinkPreview.MAIN, + ), + Window( + COMMON_TEXT, + Const("is_disabled=True"), + LinkPreview(is_disabled=True), + LinkPreview_MAIN_MENU_BUTTON, + state=states.LinkPreview.IS_DISABLED, + ), + Window( + COMMON_TEXT, + Const("prefer_small_media=True"), + LinkPreview(Format("{photo}"), prefer_small_media=True), + LinkPreview_MAIN_MENU_BUTTON, + state=states.LinkPreview.SMALL_MEDIA, + ), + Window( + COMMON_TEXT, + Const("prefer_large_media=True"), + LinkPreview(Format("{photo}"), prefer_large_media=True), + LinkPreview_MAIN_MENU_BUTTON, + state=states.LinkPreview.LARGE_MEDIA, + ), + Window( + COMMON_TEXT, + Const("show_above_text=True"), + LinkPreview(Format("{photo}"), show_above_text=True), + LinkPreview_MAIN_MENU_BUTTON, + state=states.LinkPreview.SHOW_ABOVE_TEXT, + ), + getter=links_getter, +) diff --git a/example/mega/bot_dialogs/main.py b/example/mega/bot_dialogs/main.py index 7305ddf5..0b541bf7 100644 --- a/example/mega/bot_dialogs/main.py +++ b/example/mega/bot_dialogs/main.py @@ -44,6 +44,11 @@ id="switch", state=states.Switch.MAIN, ), + Start( + text=Const("🔗 Link Preview"), + id="linkpreview", + state=states.LinkPreview.MAIN, + ), Start( text=Const("⌨️ Reply keyboard"), id="reply", diff --git a/example/mega/bot_dialogs/states.py b/example/mega/bot_dialogs/states.py index cb9d48a2..a18f1bd3 100644 --- a/example/mega/bot_dialogs/states.py +++ b/example/mega/bot_dialogs/states.py @@ -52,3 +52,11 @@ class Switch(StatesGroup): MAIN = State() INPUT = State() LAST = State() + + +class LinkPreview(StatesGroup): + MAIN = State() + IS_DISABLED = State() + SMALL_MEDIA = State() + LARGE_MEDIA = State() + SHOW_ABOVE_TEXT = State() diff --git a/src/aiogram_dialog/api/entities/new_message.py b/src/aiogram_dialog/api/entities/new_message.py index e360563a..6cc071cf 100644 --- a/src/aiogram_dialog/api/entities/new_message.py +++ b/src/aiogram_dialog/api/entities/new_message.py @@ -44,6 +44,5 @@ class NewMessage: reply_markup: Optional[MarkupVariant] = None parse_mode: Optional[str] = None show_mode: ShowMode = ShowMode.AUTO - disable_web_page_preview: Optional[bool] = None media: Optional[MediaAttachment] = None link_preview_options: Optional[LinkPreviewOptions] = None diff --git a/src/aiogram_dialog/manager/message_manager.py b/src/aiogram_dialog/manager/message_manager.py index 1d729e6d..2ad38c09 100644 --- a/src/aiogram_dialog/manager/message_manager.py +++ b/src/aiogram_dialog/manager/message_manager.py @@ -351,7 +351,6 @@ async def edit_text( text=new_message.text, reply_markup=new_message.reply_markup, parse_mode=new_message.parse_mode, - disable_web_page_preview=new_message.disable_web_page_preview, link_preview_options=new_message.link_preview_options, ) @@ -367,7 +366,6 @@ async def edit_media( caption=new_message.text, reply_markup=new_message.reply_markup, parse_mode=new_message.parse_mode, - disable_web_page_preview=new_message.disable_web_page_preview, media=await self.get_media_source(new_message.media, bot), **new_message.media.kwargs, ) @@ -396,7 +394,6 @@ async def send_text(self, bot: Bot, new_message: NewMessage) -> Message: text=new_message.text, message_thread_id=new_message.thread_id, business_connection_id=new_message.business_connection_id, - disable_web_page_preview=new_message.disable_web_page_preview, reply_markup=new_message.reply_markup, parse_mode=new_message.parse_mode, link_preview_options=new_message.link_preview_options, diff --git a/src/aiogram_dialog/widgets/utils.py b/src/aiogram_dialog/widgets/utils.py index 4fd98b9f..3da9d10a 100644 --- a/src/aiogram_dialog/widgets/utils.py +++ b/src/aiogram_dialog/widgets/utils.py @@ -1,5 +1,5 @@ from collections.abc import Callable, Sequence -from typing import Union +from typing import Union, Optional from aiogram_dialog.api.exceptions import InvalidWidgetType from aiogram_dialog.api.internal import DataGetter, LinkPreviewWidget @@ -76,19 +76,25 @@ def ensure_media(widget: Union[Media, Sequence[Media]]) -> Media: def ensure_link_preview( widget: Union[LinkPreviewWidget, Sequence[LinkPreviewWidget]], -) -> LinkPreviewWidget: +) -> Optional[LinkPreviewWidget]: if isinstance(widget, LinkPreviewWidget): return widget if len(widget) > 1: raise ValueError("Only one link preview widget is supported") if len(widget) == 1: return widget[0] - return LinkPreviewBase() + return None def ensure_widgets( widgets: Sequence[WidgetSrc], -) -> tuple[Text, Keyboard, Union[BaseInput, None], Media, LinkPreviewWidget]: +) -> tuple[ + Text, + Keyboard, + Optional[BaseInput], + Media, + Optional[LinkPreviewWidget], +]: texts = [] keyboards = [] inputs = [] diff --git a/src/aiogram_dialog/window.py b/src/aiogram_dialog/window.py index c4eaf58c..aeafd45a 100644 --- a/src/aiogram_dialog/window.py +++ b/src/aiogram_dialog/window.py @@ -1,3 +1,4 @@ +import warnings from logging import getLogger from typing import Any, Optional, cast @@ -25,6 +26,7 @@ from .dialog import OnResultEvent from .widgets.data import PreviewAwareGetter from .widgets.kbd import Keyboard +from .widgets.link_preview import LinkPreview from .widgets.markup.inline_keyboard import InlineKeyboardFactory from .widgets.utils import ( GetterVariant, @@ -47,7 +49,7 @@ def __init__( on_process_result: Optional[OnResultEvent] = None, markup_factory: MarkupFactory = _DEFAULT_MARKUP_FACTORY, parse_mode: Optional[str] = UNSET_PARSE_MODE, - disable_web_page_preview: Optional[bool] = UNSET_DISABLE_WEB_PAGE_PREVIEW, # noqa: E501 + disable_web_page_preview: Optional[bool] = None, # noqa: E501 preview_add_transitions: Optional[list[Keyboard]] = None, preview_data: GetterVariant = None, ): @@ -66,8 +68,19 @@ def __init__( self.on_process_result = on_process_result self.markup_factory = markup_factory self.parse_mode = parse_mode - self.disable_web_page_preview = disable_web_page_preview self.preview_add_transitions = preview_add_transitions + if disable_web_page_preview is not None: + if self.link_preview: + raise ValueError( + "Cannot use LinkPreview widget " + "together with disable_web_page_preview", + ) + warnings.warn( + "disable_web_page_preview is deprecated, " + "use `LinkPreview` widget instead", + category=DeprecationWarning, + ) + self.link_preview = LinkPreview(is_disabled=True) async def render_text( self, data: dict, manager: DialogManager, @@ -152,7 +165,6 @@ async def render( text=await self.render_text(current_data, manager), reply_markup=await self.render_kbd(current_data, manager), parse_mode=self.parse_mode, - disable_web_page_preview=self.disable_web_page_preview, media=await self.render_media(current_data, manager), link_preview_options=await self.render_link_preview( current_data, manager, From eacc7bcc7b47e4cf94a843cfad27c552768c6eb5 Mon Sep 17 00:00:00 2001 From: Andrey Tikhonov <17@itishka.org> Date: Mon, 16 Dec 2024 17:37:57 +0100 Subject: [PATCH 29/29] Fix linter --- example/mega/bot_dialogs/link_preview.py | 1 + src/aiogram_dialog/widgets/link_preview/base.py | 6 +++++- src/aiogram_dialog/widgets/utils.py | 2 +- src/aiogram_dialog/window.py | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/example/mega/bot_dialogs/link_preview.py b/example/mega/bot_dialogs/link_preview.py index 511be3e6..d353fc43 100644 --- a/example/mega/bot_dialogs/link_preview.py +++ b/example/mega/bot_dialogs/link_preview.py @@ -5,6 +5,7 @@ from aiogram_dialog.widgets.kbd import SwitchTo from aiogram_dialog.widgets.link_preview import LinkPreview from aiogram_dialog.widgets.text import Const, Format + from . import states from .common import MAIN_MENU_BUTTON diff --git a/src/aiogram_dialog/widgets/link_preview/base.py b/src/aiogram_dialog/widgets/link_preview/base.py index 7a2fe925..299b711b 100644 --- a/src/aiogram_dialog/widgets/link_preview/base.py +++ b/src/aiogram_dialog/widgets/link_preview/base.py @@ -52,7 +52,11 @@ async def _render_link_preview( self, data: dict, manager: DialogManager, ) -> Optional[LinkPreviewOptions]: return LinkPreviewOptions( - url=await self.url.render_text(data, manager) if self.url else None, + url=( + await self.url.render_text(data, manager) + if self.url + else None + ), is_disabled=self.is_disabled, prefer_small_media=self.prefer_small_media, prefer_large_media=self.prefer_large_media, diff --git a/src/aiogram_dialog/widgets/utils.py b/src/aiogram_dialog/widgets/utils.py index 3da9d10a..977d4984 100644 --- a/src/aiogram_dialog/widgets/utils.py +++ b/src/aiogram_dialog/widgets/utils.py @@ -1,5 +1,5 @@ from collections.abc import Callable, Sequence -from typing import Union, Optional +from typing import Optional, Union from aiogram_dialog.api.exceptions import InvalidWidgetType from aiogram_dialog.api.internal import DataGetter, LinkPreviewWidget diff --git a/src/aiogram_dialog/window.py b/src/aiogram_dialog/window.py index aeafd45a..035835dd 100644 --- a/src/aiogram_dialog/window.py +++ b/src/aiogram_dialog/window.py @@ -9,7 +9,6 @@ LinkPreviewOptions, Message, ) -from aiogram.types.base import UNSET_DISABLE_WEB_PAGE_PREVIEW from aiogram_dialog.api.entities import ( EVENT_CONTEXT_KEY, @@ -49,7 +48,7 @@ def __init__( on_process_result: Optional[OnResultEvent] = None, markup_factory: MarkupFactory = _DEFAULT_MARKUP_FACTORY, parse_mode: Optional[str] = UNSET_PARSE_MODE, - disable_web_page_preview: Optional[bool] = None, # noqa: E501 + disable_web_page_preview: Optional[bool] = None, preview_add_transitions: Optional[list[Keyboard]] = None, preview_data: GetterVariant = None, ): @@ -79,6 +78,7 @@ def __init__( "disable_web_page_preview is deprecated, " "use `LinkPreview` widget instead", category=DeprecationWarning, + stacklevel=2, ) self.link_preview = LinkPreview(is_disabled=True)