Deprecate filters factory (#976)

* Deprecate filters factory

* Added changelog

* Update filters usage in docs and examples
This commit is contained in:
Alex Root Junior 2022-08-14 18:40:41 +03:00 committed by GitHub
parent c1341ba2df
commit 0e0dbe7e59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 67 additions and 23 deletions

1
CHANGES/942.misc.rst Normal file
View file

@ -0,0 +1 @@
Deprecated filters factory. It will be removed in next Beta (3.0b5)

View file

@ -11,6 +11,7 @@ from aiogram.dispatcher.middlewares.manager import MiddlewareManager
from aiogram.filters.base import BaseFilter
from ...exceptions import FiltersResolveError
from ...filters import BUILTIN_FILTERS_SET
from ...types import TelegramObject
from .bases import REJECTED, UNHANDLED, MiddlewareType, SkipHandler
from .handler import CallbackType, FilterObject, HandlerObject
@ -24,7 +25,7 @@ class TelegramEventObserver:
Event observer for Telegram events
Here you can register handler with filters or bounded filters which can be used as keyword arguments instead of writing full references when you register new handlers.
This observer will stops event propagation when first handler is pass.
This observer will stop event propagation when first handler is pass.
"""
def __init__(self, router: Router, event_name: str) -> None:
@ -41,14 +42,16 @@ class TelegramEventObserver:
# with dummy callback which never will be used
self._handler = HandlerObject(callback=lambda: True, filters=[])
def filter(self, *filters: CallbackType, **bound_filters: Any) -> None:
def filter(self, *filters: CallbackType, _stacklevel: int = 2, **bound_filters: Any) -> None:
"""
Register filter for all handlers of this event observer
:param filters: positional filters
:param bound_filters: keyword filters
"""
resolved_filters = self.resolve_filters(filters, bound_filters)
resolved_filters = self.resolve_filters(
filters, bound_filters, _stacklevel=_stacklevel + 1
)
if self._handler.filters is None:
self._handler.filters = []
self._handler.filters.extend(
@ -67,14 +70,18 @@ class TelegramEventObserver:
:param bound_filter:
"""
# TODO: This functionality should be deprecated in the future
# in due to bound filter has uncontrollable ordering and
# makes debugging process is harder that explicit using filters
if not isclass(bound_filter) or not issubclass(bound_filter, BaseFilter):
raise TypeError(
"bound_filter() argument 'bound_filter' must be subclass of BaseFilter"
)
if bound_filter not in BUILTIN_FILTERS_SET:
warnings.warn(
category=DeprecationWarning,
message="filters factory deprecated and will be removed in 3.0b5,"
" use filters directly instead (Example: "
f"`{bound_filter.__name__}(<argument>=<value>)` instead of `<argument>=<value>`)",
stacklevel=2,
)
self.filters.append(bound_filter)
def _resolve_filters_chain(self) -> Generator[Type[BaseFilter], None, None]:
@ -106,6 +113,7 @@ class TelegramEventObserver:
filters: Tuple[CallbackType, ...],
full_config: Dict[str, Any],
ignore_default: bool = True,
_stacklevel: int = 2,
) -> List[BaseFilter]:
"""
Resolve keyword filters via filters factory
@ -164,11 +172,11 @@ class TelegramEventObserver:
if bound_filters:
warnings.warn(
category=DeprecationWarning,
message="Filters factory deprecated and will be removed in Beta 5. "
message="Filters factory deprecated and will be removed in 3.0b5.\n"
"Use filters directly, for example instead of "
"`@router.message(commands=['help']')` "
"use `@router.message(Command(commands=['help'])`",
stacklevel=3,
stacklevel=_stacklevel,
)
return bound_filters
@ -177,6 +185,7 @@ class TelegramEventObserver:
callback: CallbackType,
*filters: CallbackType,
flags: Optional[Dict[str, Any]] = None,
_stacklevel: int = 2,
**bound_filters: Any,
) -> CallbackType:
"""
@ -184,7 +193,12 @@ class TelegramEventObserver:
"""
if flags is None:
flags = {}
resolved_filters = self.resolve_filters(filters, bound_filters, ignore_default=False)
resolved_filters = self.resolve_filters(
filters,
bound_filters,
ignore_default=False,
_stacklevel=_stacklevel + 1,
)
for resolved_filter in resolved_filters:
resolved_filter.update_handler_flags(flags=flags)
self.handlers.append(
@ -238,14 +252,20 @@ class TelegramEventObserver:
return UNHANDLED
def __call__(
self, *args: CallbackType, flags: Optional[Dict[str, Any]] = None, **bound_filters: Any
self,
*args: CallbackType,
flags: Optional[Dict[str, Any]] = None,
_stacklevel: int = 2,
**bound_filters: Any,
) -> Callable[[CallbackType], CallbackType]:
"""
Decorator for registering event handlers
"""
def wrapper(callback: CallbackType) -> CallbackType:
self.register(callback, *args, flags=flags, **bound_filters)
self.register(
callback, *args, flags=flags, **bound_filters, _stacklevel=_stacklevel + 1
)
return callback
return wrapper

View file

@ -1,3 +1,4 @@
from itertools import chain
from typing import Dict, Tuple, Type
from .base import BaseFilter
@ -134,3 +135,5 @@ BUILTIN_FILTERS: Dict[str, Tuple[Type[BaseFilter], ...]] = {
*_ALL_EVENTS_FILTERS,
),
}
BUILTIN_FILTERS_SET = set(chain.from_iterable(BUILTIN_FILTERS.values()))

View file

@ -86,10 +86,10 @@ Handle user leave or join events
from aiogram.filters import IS_MEMBER, IS_NOT_MEMBER
@router.chat_member(member_status_changed=IS_MEMBER >> IS_NOT_MEMBER)
@router.chat_member(ChatMemberUpdatedFilter(member_status_changed=IS_MEMBER >> IS_NOT_MEMBER))
async def on_user_leave(event: ChatMemberUpdated): ...
@router.chat_member(member_status_changed=IS_NOT_MEMBER >> IS_MEMBER)
@router.chat_member(ChatMemberUpdatedFilter(member_status_changed=IS_NOT_MEMBER >> IS_MEMBER))
async def on_user_join(event: ChatMemberUpdated): ...
Or construct your own terms via using pre-defined set of statuses and transitions.

View file

@ -21,8 +21,7 @@ Usage
1. Filter single variant of commands: :code:`Command(commands=["start"])` or :code:`Command(commands="start")`
2. Handle command by regexp pattern: :code:`Command(commands=[re.compile(r"item_(\d+)")])`
3. Match command by multiple variants: :code:`Command(commands=["item", re.compile(r"item_(\d+)")])`
4. Handle commands in public chats intended for other bots: :code:`Command(commands=["command"], commands)`
5. As keyword argument in registerer: :code:`@router.message(commands=["help"])`
4. Handle commands in public chats intended for other bots: :code:`Command(commands=["command"], commands_ignore_mention=True)`
.. warning::

View file

@ -2,6 +2,13 @@
Filtering events
================
.. danger::
Note that the design of filters will be changed in 3.0b5
`Read more >> <https://github.com/aiogram/aiogram/issues/942>`_
Filters is needed for routing updates to the specific handler.
Searching of handler is always stops on first match set of filters are pass.

View file

@ -17,7 +17,7 @@ Or used from filters factory by passing corresponding arguments to handler regis
Usage
=====
#. :code:`magic_data=F.event.from_user.id == F.config.admin_id` (Note that :code:`config` should be passed from middleware)
#. :code:`MagicData(magic_data=F.event.from_user.id == F.config.admin_id)` (Note that :code:`config` should be passed from middleware)
Allowed handlers

View file

@ -1,6 +1,7 @@
import logging
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiogram.types import Message
TOKEN = "42:TOKEN"
@ -9,7 +10,7 @@ dp = Dispatcher()
logger = logging.getLogger(__name__)
@dp.message(commands=["start"])
@dp.message(Command(commands=["start"]))
async def command_start_handler(message: Message) -> None:
"""
This handler receive messages with `/start` command

View file

@ -5,6 +5,7 @@ from os import getenv
from typing import Any, Dict
from aiogram import Bot, Dispatcher, F, Router, html
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import KeyboardButton, Message, ReplyKeyboardMarkup, ReplyKeyboardRemove
@ -18,7 +19,7 @@ class Form(StatesGroup):
language = State()
@form_router.message(commands=["start"])
@form_router.message(Command(commands=["start"]))
async def command_start(message: Message, state: FSMContext) -> None:
await state.set_state(Form.name)
await message.answer(
@ -27,7 +28,7 @@ async def command_start(message: Message, state: FSMContext) -> None:
)
@form_router.message(commands=["cancel"])
@form_router.message(Command(commands=["cancel"]))
@form_router.message(F.text.casefold() == "cancel")
async def cancel_handler(message: Message, state: FSMContext) -> None:
"""

View file

@ -1,6 +1,7 @@
import logging
from aiogram import Bot, Dispatcher, Router
from aiogram.filters import Command
from aiogram.types import (
CallbackQuery,
ChatMemberUpdated,
@ -16,7 +17,7 @@ logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
@dp.message(commands=["start"])
@dp.message(Command(commands=["start"]))
async def command_start_handler(message: Message) -> None:
"""
This handler receive messages with `/start` command
@ -71,7 +72,7 @@ async def my_chat_member_change(chat_member: ChatMemberUpdated, bot: Bot) -> Non
def main() -> None:
# Initialize Bot instance with an default parse mode which will be passed to all API calls
# Initialize Bot instance with a default parse mode which will be passed to all API calls
bot = Bot(TOKEN, parse_mode="HTML")
sub_router.include_router(deep_dark_router)

View file

@ -9,8 +9,9 @@ from aiogram.dispatcher.event.handler import HandlerObject
from aiogram.dispatcher.event.telegram import TelegramEventObserver
from aiogram.dispatcher.router import Router
from aiogram.exceptions import FiltersResolveError
from aiogram.filters import BaseFilter
from aiogram.filters import BaseFilter, Command
from aiogram.types import Chat, Message, User
from tests.deprecated import check_deprecated
pytestmark = pytest.mark.asyncio
@ -368,3 +369,13 @@ class TestTelegramEventObserver:
r2.message.register(handler)
assert await r1.message.trigger(None) is REJECTED
def test_deprecated_bind_filter(self):
router = Router()
with check_deprecated("3.0b5", exception=AttributeError):
router.message.bind_filter(MyFilter1)
def test_deprecated_resolve_filters(self):
router = Router()
with check_deprecated("3.0b5", exception=AttributeError):
router.message.resolve_filters([Command], full_config={"commands": ["test"]})