mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Deprecate filters factory (#976)
* Deprecate filters factory * Added changelog * Update filters usage in docs and examples
This commit is contained in:
parent
c1341ba2df
commit
0e0dbe7e59
11 changed files with 67 additions and 23 deletions
1
CHANGES/942.misc.rst
Normal file
1
CHANGES/942.misc.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Deprecated filters factory. It will be removed in next Beta (3.0b5)
|
||||||
|
|
@ -11,6 +11,7 @@ from aiogram.dispatcher.middlewares.manager import MiddlewareManager
|
||||||
from aiogram.filters.base import BaseFilter
|
from aiogram.filters.base import BaseFilter
|
||||||
|
|
||||||
from ...exceptions import FiltersResolveError
|
from ...exceptions import FiltersResolveError
|
||||||
|
from ...filters import BUILTIN_FILTERS_SET
|
||||||
from ...types import TelegramObject
|
from ...types import TelegramObject
|
||||||
from .bases import REJECTED, UNHANDLED, MiddlewareType, SkipHandler
|
from .bases import REJECTED, UNHANDLED, MiddlewareType, SkipHandler
|
||||||
from .handler import CallbackType, FilterObject, HandlerObject
|
from .handler import CallbackType, FilterObject, HandlerObject
|
||||||
|
|
@ -24,7 +25,7 @@ class TelegramEventObserver:
|
||||||
Event observer for Telegram events
|
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.
|
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:
|
def __init__(self, router: Router, event_name: str) -> None:
|
||||||
|
|
@ -41,14 +42,16 @@ class TelegramEventObserver:
|
||||||
# with dummy callback which never will be used
|
# with dummy callback which never will be used
|
||||||
self._handler = HandlerObject(callback=lambda: True, filters=[])
|
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
|
Register filter for all handlers of this event observer
|
||||||
|
|
||||||
:param filters: positional filters
|
:param filters: positional filters
|
||||||
:param bound_filters: keyword 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:
|
if self._handler.filters is None:
|
||||||
self._handler.filters = []
|
self._handler.filters = []
|
||||||
self._handler.filters.extend(
|
self._handler.filters.extend(
|
||||||
|
|
@ -67,14 +70,18 @@ class TelegramEventObserver:
|
||||||
|
|
||||||
:param bound_filter:
|
: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):
|
if not isclass(bound_filter) or not issubclass(bound_filter, BaseFilter):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"bound_filter() argument 'bound_filter' must be subclass of BaseFilter"
|
"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)
|
self.filters.append(bound_filter)
|
||||||
|
|
||||||
def _resolve_filters_chain(self) -> Generator[Type[BaseFilter], None, None]:
|
def _resolve_filters_chain(self) -> Generator[Type[BaseFilter], None, None]:
|
||||||
|
|
@ -106,6 +113,7 @@ class TelegramEventObserver:
|
||||||
filters: Tuple[CallbackType, ...],
|
filters: Tuple[CallbackType, ...],
|
||||||
full_config: Dict[str, Any],
|
full_config: Dict[str, Any],
|
||||||
ignore_default: bool = True,
|
ignore_default: bool = True,
|
||||||
|
_stacklevel: int = 2,
|
||||||
) -> List[BaseFilter]:
|
) -> List[BaseFilter]:
|
||||||
"""
|
"""
|
||||||
Resolve keyword filters via filters factory
|
Resolve keyword filters via filters factory
|
||||||
|
|
@ -164,11 +172,11 @@ class TelegramEventObserver:
|
||||||
if bound_filters:
|
if bound_filters:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
category=DeprecationWarning,
|
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 "
|
"Use filters directly, for example instead of "
|
||||||
"`@router.message(commands=['help']')` "
|
"`@router.message(commands=['help']')` "
|
||||||
"use `@router.message(Command(commands=['help'])`",
|
"use `@router.message(Command(commands=['help'])`",
|
||||||
stacklevel=3,
|
stacklevel=_stacklevel,
|
||||||
)
|
)
|
||||||
return bound_filters
|
return bound_filters
|
||||||
|
|
||||||
|
|
@ -177,6 +185,7 @@ class TelegramEventObserver:
|
||||||
callback: CallbackType,
|
callback: CallbackType,
|
||||||
*filters: CallbackType,
|
*filters: CallbackType,
|
||||||
flags: Optional[Dict[str, Any]] = None,
|
flags: Optional[Dict[str, Any]] = None,
|
||||||
|
_stacklevel: int = 2,
|
||||||
**bound_filters: Any,
|
**bound_filters: Any,
|
||||||
) -> CallbackType:
|
) -> CallbackType:
|
||||||
"""
|
"""
|
||||||
|
|
@ -184,7 +193,12 @@ class TelegramEventObserver:
|
||||||
"""
|
"""
|
||||||
if flags is None:
|
if flags is None:
|
||||||
flags = {}
|
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:
|
for resolved_filter in resolved_filters:
|
||||||
resolved_filter.update_handler_flags(flags=flags)
|
resolved_filter.update_handler_flags(flags=flags)
|
||||||
self.handlers.append(
|
self.handlers.append(
|
||||||
|
|
@ -238,14 +252,20 @@ class TelegramEventObserver:
|
||||||
return UNHANDLED
|
return UNHANDLED
|
||||||
|
|
||||||
def __call__(
|
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]:
|
) -> Callable[[CallbackType], CallbackType]:
|
||||||
"""
|
"""
|
||||||
Decorator for registering event handlers
|
Decorator for registering event handlers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(callback: CallbackType) -> CallbackType:
|
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 callback
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from itertools import chain
|
||||||
from typing import Dict, Tuple, Type
|
from typing import Dict, Tuple, Type
|
||||||
|
|
||||||
from .base import BaseFilter
|
from .base import BaseFilter
|
||||||
|
|
@ -134,3 +135,5 @@ BUILTIN_FILTERS: Dict[str, Tuple[Type[BaseFilter], ...]] = {
|
||||||
*_ALL_EVENTS_FILTERS,
|
*_ALL_EVENTS_FILTERS,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BUILTIN_FILTERS_SET = set(chain.from_iterable(BUILTIN_FILTERS.values()))
|
||||||
|
|
|
||||||
|
|
@ -86,10 +86,10 @@ Handle user leave or join events
|
||||||
|
|
||||||
from aiogram.filters import IS_MEMBER, IS_NOT_MEMBER
|
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): ...
|
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): ...
|
async def on_user_join(event: ChatMemberUpdated): ...
|
||||||
|
|
||||||
Or construct your own terms via using pre-defined set of statuses and transitions.
|
Or construct your own terms via using pre-defined set of statuses and transitions.
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@ Usage
|
||||||
1. Filter single variant of commands: :code:`Command(commands=["start"])` or :code:`Command(commands="start")`
|
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+)")])`
|
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+)")])`
|
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)`
|
4. Handle commands in public chats intended for other bots: :code:`Command(commands=["command"], commands_ignore_mention=True)`
|
||||||
5. As keyword argument in registerer: :code:`@router.message(commands=["help"])`
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@
|
||||||
Filtering events
|
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.
|
Filters is needed for routing updates to the specific handler.
|
||||||
Searching of handler is always stops on first match set of filters are pass.
|
Searching of handler is always stops on first match set of filters are pass.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ Or used from filters factory by passing corresponding arguments to handler regis
|
||||||
Usage
|
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
|
Allowed handlers
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
|
from aiogram.filters import Command
|
||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
|
|
||||||
TOKEN = "42:TOKEN"
|
TOKEN = "42:TOKEN"
|
||||||
|
|
@ -9,7 +10,7 @@ dp = Dispatcher()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dp.message(commands=["start"])
|
@dp.message(Command(commands=["start"]))
|
||||||
async def command_start_handler(message: Message) -> None:
|
async def command_start_handler(message: Message) -> None:
|
||||||
"""
|
"""
|
||||||
This handler receive messages with `/start` command
|
This handler receive messages with `/start` command
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from os import getenv
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, F, Router, html
|
from aiogram import Bot, Dispatcher, F, Router, html
|
||||||
|
from aiogram.filters import Command
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
from aiogram.types import KeyboardButton, Message, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
from aiogram.types import KeyboardButton, Message, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||||
|
|
@ -18,7 +19,7 @@ class Form(StatesGroup):
|
||||||
language = State()
|
language = State()
|
||||||
|
|
||||||
|
|
||||||
@form_router.message(commands=["start"])
|
@form_router.message(Command(commands=["start"]))
|
||||||
async def command_start(message: Message, state: FSMContext) -> None:
|
async def command_start(message: Message, state: FSMContext) -> None:
|
||||||
await state.set_state(Form.name)
|
await state.set_state(Form.name)
|
||||||
await message.answer(
|
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")
|
@form_router.message(F.text.casefold() == "cancel")
|
||||||
async def cancel_handler(message: Message, state: FSMContext) -> None:
|
async def cancel_handler(message: Message, state: FSMContext) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, Router
|
from aiogram import Bot, Dispatcher, Router
|
||||||
|
from aiogram.filters import Command
|
||||||
from aiogram.types import (
|
from aiogram.types import (
|
||||||
CallbackQuery,
|
CallbackQuery,
|
||||||
ChatMemberUpdated,
|
ChatMemberUpdated,
|
||||||
|
|
@ -16,7 +17,7 @@ logger = logging.getLogger(__name__)
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
@dp.message(commands=["start"])
|
@dp.message(Command(commands=["start"]))
|
||||||
async def command_start_handler(message: Message) -> None:
|
async def command_start_handler(message: Message) -> None:
|
||||||
"""
|
"""
|
||||||
This handler receive messages with `/start` command
|
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:
|
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")
|
bot = Bot(TOKEN, parse_mode="HTML")
|
||||||
|
|
||||||
sub_router.include_router(deep_dark_router)
|
sub_router.include_router(deep_dark_router)
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ from aiogram.dispatcher.event.handler import HandlerObject
|
||||||
from aiogram.dispatcher.event.telegram import TelegramEventObserver
|
from aiogram.dispatcher.event.telegram import TelegramEventObserver
|
||||||
from aiogram.dispatcher.router import Router
|
from aiogram.dispatcher.router import Router
|
||||||
from aiogram.exceptions import FiltersResolveError
|
from aiogram.exceptions import FiltersResolveError
|
||||||
from aiogram.filters import BaseFilter
|
from aiogram.filters import BaseFilter, Command
|
||||||
from aiogram.types import Chat, Message, User
|
from aiogram.types import Chat, Message, User
|
||||||
|
from tests.deprecated import check_deprecated
|
||||||
|
|
||||||
pytestmark = pytest.mark.asyncio
|
pytestmark = pytest.mark.asyncio
|
||||||
|
|
||||||
|
|
@ -368,3 +369,13 @@ class TestTelegramEventObserver:
|
||||||
r2.message.register(handler)
|
r2.message.register(handler)
|
||||||
|
|
||||||
assert await r1.message.trigger(None) is REJECTED
|
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"]})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue