Global filters for router (#644)

* Bump version

* Added more comments

* Cover registering global filters

* Reformat code

* Add more tests

* Rework event propagation to routers mechanism. Fixed compatibility with Python 3.10 syntax (match keyword)

* Fixed tests

* Fixed coverage

Co-authored-by: evgfilim1 <evgfilim1@yandex.ru>
This commit is contained in:
Alex Root Junior 2021-07-31 23:34:09 +03:00 committed by GitHub
parent a70ecb767f
commit 4f2cc75951
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 176 additions and 31 deletions

View file

@ -232,20 +232,11 @@ class Dispatcher(Router):
"installed not latest version of aiogram framework", "installed not latest version of aiogram framework",
RuntimeWarning, RuntimeWarning,
) )
raise SkipHandler raise SkipHandler()
kwargs.update(event_update=update) kwargs.update(event_update=update)
for router in self.chain: return await self.propagate_event(update_type=update_type, event=event, **kwargs)
kwargs.update(event_router=router)
observer = router.observers[update_type]
response = await observer.trigger(event, update=update, **kwargs)
if response is not UNHANDLED:
break
else:
response = UNHANDLED
return response
@classmethod @classmethod
async def _silent_call_request(cls, bot: Bot, result: TelegramMethod[Any]) -> None: async def _silent_call_request(cls, bot: Bot, result: TelegramMethod[Any]) -> None:

View file

@ -12,6 +12,7 @@ MiddlewareType = Union[
] ]
UNHANDLED = sentinel.UNHANDLED UNHANDLED = sentinel.UNHANDLED
REJECTED = sentinel.REJECTED
class SkipHandler(Exception): class SkipHandler(Exception):

View file

@ -8,7 +8,7 @@ from pydantic import ValidationError
from ...types import TelegramObject from ...types import TelegramObject
from ..filters.base import BaseFilter from ..filters.base import BaseFilter
from .bases import UNHANDLED, MiddlewareType, NextMiddlewareType, SkipHandler from .bases import REJECTED, UNHANDLED, MiddlewareType, NextMiddlewareType, SkipHandler
from .handler import CallbackType, FilterObject, FilterType, HandlerObject, HandlerType from .handler import CallbackType, FilterObject, FilterType, HandlerObject, HandlerType
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
@ -32,6 +32,24 @@ class TelegramEventObserver:
self.outer_middlewares: List[MiddlewareType] = [] self.outer_middlewares: List[MiddlewareType] = []
self.middlewares: List[MiddlewareType] = [] self.middlewares: List[MiddlewareType] = []
# Re-used filters check method from already implemented handler object
# with dummy callback which never will be used
self._handler = HandlerObject(callback=lambda: True, filters=[])
def filter(self, *filters: FilterType, **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(bound_filters)
if self._handler.filters is None:
self._handler.filters = []
self._handler.filters.extend(
[FilterObject(filter_) for filter_ in chain(resolved_filters, filters)]
)
def bind_filter(self, bound_filter: Type[BaseFilter]) -> None: def bind_filter(self, bound_filter: Type[BaseFilter]) -> None:
""" """
Register filter class in factory Register filter class in factory
@ -139,6 +157,12 @@ class TelegramEventObserver:
return await wrapped_outer(event, kwargs) return await wrapped_outer(event, kwargs)
async def _trigger(self, event: TelegramObject, **kwargs: Any) -> Any: async def _trigger(self, event: TelegramObject, **kwargs: Any) -> Any:
# Check globally defined filters before any other handler will be checked
result, data = await self._handler.check(event, **kwargs)
if not result:
return REJECTED
kwargs.update(data)
for handler in self.handlers: for handler in self.handlers:
result, data = await handler.check(event, **kwargs) result, data = await handler.check(event, **kwargs)
if result: if result:

View file

@ -89,7 +89,7 @@ class Command(BaseFilter):
if isinstance(allowed_command, Pattern): # Regexp if isinstance(allowed_command, Pattern): # Regexp
result = allowed_command.match(command.command) result = allowed_command.match(command.command)
if result: if result:
return replace(command, match=result) return replace(command, regexp_match=result)
elif command.command == allowed_command: # String elif command.command == allowed_command: # String
return command return command
raise CommandException("Command did not match pattern") raise CommandException("Command did not match pattern")
@ -134,7 +134,7 @@ class CommandObject:
"""Mention (if available)""" """Mention (if available)"""
args: Optional[str] = field(repr=False, default=None) args: Optional[str] = field(repr=False, default=None)
"""Command argument""" """Command argument"""
match: Optional[Match[str]] = field(repr=False, default=None) regexp_match: Optional[Match[str]] = field(repr=False, default=None)
"""Will be presented match result if the command is presented as regexp in filter""" """Will be presented match result if the command is presented as regexp in filter"""
@property @property

View file

@ -26,20 +26,20 @@ class ExceptionMessageFilter(BaseFilter):
Allow to match exception by message Allow to match exception by message
""" """
match: Union[str, Pattern[str]] pattern: Union[str, Pattern[str]]
"""Regexp pattern""" """Regexp pattern"""
class Config: class Config:
arbitrary_types_allowed = True arbitrary_types_allowed = True
@validator("match") @validator("pattern")
def _validate_match(cls, value: Union[str, Pattern[str]]) -> Union[str, Pattern[str]]: def _validate_match(cls, value: Union[str, Pattern[str]]) -> Union[str, Pattern[str]]:
if isinstance(value, str): if isinstance(value, str):
return re.compile(value) return re.compile(value)
return value return value
async def __call__(self, exception: Exception) -> Union[bool, Dict[str, Any]]: async def __call__(self, exception: Exception) -> Union[bool, Dict[str, Any]]:
pattern = cast(Pattern[str], self.match) pattern = cast(Pattern[str], self.pattern)
result = pattern.match(str(exception)) result = pattern.match(str(exception))
if not result: if not result:
return False return False

View file

@ -45,7 +45,7 @@ class RedisStorage(BaseStorage):
return cls(redis=redis, **kwargs) return cls(redis=redis, **kwargs)
async def close(self) -> None: async def close(self) -> None:
await self.redis.close() await self.redis.close() # type: ignore
def generate_key(self, bot: Bot, *parts: Any) -> str: def generate_key(self, bot: Bot, *parts: Any) -> str:
prefix_parts = [self.prefix] prefix_parts = [self.prefix]
@ -73,7 +73,7 @@ class RedisStorage(BaseStorage):
await self.redis.delete(key) await self.redis.delete(key)
else: else:
await self.redis.set( await self.redis.set(
key, state.state if isinstance(state, State) else state, ex=self.state_ttl key, state.state if isinstance(state, State) else state, ex=self.state_ttl # type: ignore[arg-type]
) )
async def get_state(self, bot: Bot, chat_id: int, user_id: int) -> Optional[str]: async def get_state(self, bot: Bot, chat_id: int, user_id: int) -> Optional[str]:
@ -89,7 +89,7 @@ class RedisStorage(BaseStorage):
await self.redis.delete(key) await self.redis.delete(key)
return return
json_data = bot.session.json_dumps(data) json_data = bot.session.json_dumps(data)
await self.redis.set(key, json_data, ex=self.data_ttl) await self.redis.set(key, json_data, ex=self.data_ttl) # type: ignore[arg-type]
async def get_data(self, bot: Bot, chat_id: int, user_id: int) -> Dict[str, Any]: async def get_data(self, bot: Bot, chat_id: int, user_id: int) -> Dict[str, Any]:
key = self.generate_key(bot, chat_id, user_id, STATE_DATA_KEY) key = self.generate_key(bot, chat_id, user_id, STATE_DATA_KEY)

View file

@ -3,8 +3,10 @@ from __future__ import annotations
import warnings import warnings
from typing import Any, Dict, Generator, List, Optional, Union from typing import Any, Dict, Generator, List, Optional, Union
from ..types import TelegramObject
from ..utils.imports import import_module from ..utils.imports import import_module
from ..utils.warnings import CodeHasNoEffect from ..utils.warnings import CodeHasNoEffect
from .event.bases import REJECTED, UNHANDLED
from .event.event import EventObserver from .event.event import EventObserver
from .event.telegram import TelegramEventObserver from .event.telegram import TelegramEventObserver
from .filters import BUILTIN_FILTERS from .filters import BUILTIN_FILTERS
@ -82,6 +84,22 @@ class Router:
for builtin_filter in BUILTIN_FILTERS.get(name, ()): for builtin_filter in BUILTIN_FILTERS.get(name, ()):
observer.bind_filter(builtin_filter) observer.bind_filter(builtin_filter)
async def propagate_event(self, update_type: str, event: TelegramObject, **kwargs: Any) -> Any:
kwargs.update(event_router=self)
observer = self.observers[update_type]
response = await observer.trigger(event, **kwargs)
if response is REJECTED:
return UNHANDLED
if response is not UNHANDLED:
return response
for router in self.sub_routers:
response = await router.propagate_event(update_type=update_type, event=event, **kwargs)
if response is not UNHANDLED:
break
return response
@property @property
def chain_head(self) -> Generator[Router, None, None]: def chain_head(self) -> Generator[Router, None, None]:
router: Optional[Router] = self router: Optional[Router] = self

59
poetry.lock generated
View file

@ -40,7 +40,7 @@ python-socks = {version = ">=1.0.1", extras = ["asyncio"]}
[[package]] [[package]]
name = "aioredis" name = "aioredis"
version = "2.0.0a1" version = "2.0.0"
description = "asyncio (PEP 3156) Redis support" description = "asyncio (PEP 3156) Redis support"
category = "main" category = "main"
optional = false optional = false
@ -296,6 +296,14 @@ importlib-metadata = "*"
jinja2 = ">=2.9.0" jinja2 = ">=2.9.0"
pygments = ">=2.2.0" pygments = ">=2.2.0"
[[package]]
name = "frozenlist"
version = "1.1.1"
description = "A list-like structure which implements collections.abc.MutableSequence"
category = "main"
optional = false
python-versions = ">=3.6"
[[package]] [[package]]
name = "furo" name = "furo"
version = "2021.6.18b36" version = "2021.6.18b36"
@ -1201,7 +1209,7 @@ redis = ["aioredis"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.8" python-versions = "^3.8"
content-hash = "ef3571030ff35c2a05e01dca86e9347239e98ad0f45bed6f5d9a73121013f376" content-hash = "c51e22cdb0e17fb996fda81c5484d34f3dff0e57511380b1103a1d53c9416440"
[metadata.files] [metadata.files]
aiofiles = [ aiofiles = [
@ -1252,8 +1260,8 @@ aiohttp-socks = [
{file = "aiohttp_socks-0.5.5.tar.gz", hash = "sha256:2eb2059756bde34c55bb429541cbf2eba3fd53e36ac80875b461221e2858b04a"}, {file = "aiohttp_socks-0.5.5.tar.gz", hash = "sha256:2eb2059756bde34c55bb429541cbf2eba3fd53e36ac80875b461221e2858b04a"},
] ]
aioredis = [ aioredis = [
{file = "aioredis-2.0.0a1-py3-none-any.whl", hash = "sha256:32d7910724282a475c91b8b34403867069a4f07bf0c5ad5fe66cd797322f9a0d"}, {file = "aioredis-2.0.0-py3-none-any.whl", hash = "sha256:9921d68a3df5c5cdb0d5b49ad4fc88a4cfdd60c108325df4f0066e8410c55ffb"},
{file = "aioredis-2.0.0a1.tar.gz", hash = "sha256:5884f384b8ecb143bb73320a96e7c464fd38e117950a7d48340a35db8e35e7d2"}, {file = "aioredis-2.0.0.tar.gz", hash = "sha256:3a2de4b614e6a5f8e104238924294dc4e811aefbe17ddf52c04a93cbf06e67db"},
] ]
alabaster = [ alabaster = [
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
@ -1401,6 +1409,49 @@ flake8-html = [
{file = "flake8-html-0.4.1.tar.gz", hash = "sha256:2fb436cbfe1e109275bc8fb7fdd0cb00e67b3b48cfeb397309b6b2c61eeb4cb4"}, {file = "flake8-html-0.4.1.tar.gz", hash = "sha256:2fb436cbfe1e109275bc8fb7fdd0cb00e67b3b48cfeb397309b6b2c61eeb4cb4"},
{file = "flake8_html-0.4.1-py2.py3-none-any.whl", hash = "sha256:17324eb947e7006807e4184ee26953e67baf421b3cf9e646a38bfec34eec5a94"}, {file = "flake8_html-0.4.1-py2.py3-none-any.whl", hash = "sha256:17324eb947e7006807e4184ee26953e67baf421b3cf9e646a38bfec34eec5a94"},
] ]
frozenlist = [
{file = "frozenlist-1.1.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:968b520ef969541b2c8f47d9a13c78e080806dc97862434d29163d44c2c1d709"},
{file = "frozenlist-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:451b445120ea95d86af3817bbd4d67ab77269fe7f055dc67b8c70bf4633f4efe"},
{file = "frozenlist-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:313384e54a7285a6f20ca6530b207a0a9cf6ebcda6c7b074ee802e4a82a0a6dc"},
{file = "frozenlist-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:3c4f7399e7338a5788d32802017f94aaab3267afa8b1a663272b81eee7193e66"},
{file = "frozenlist-1.1.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1e7b18fdf6682028f512d3e6142b79ca95b9b66f30c1bec2be237160d9eb6518"},
{file = "frozenlist-1.1.1-cp36-cp36m-manylinux2014_ppc64le.whl", hash = "sha256:7622f5c4c3dfaa09b9c6a62fb1af94da124626bb30f3ad9095f9cec6328074c1"},
{file = "frozenlist-1.1.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:657341b9bc166d3f7418d37e1decf6d95485501e0d0e7da1a26a881e624216c6"},
{file = "frozenlist-1.1.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:fc6d994de78b11e1f465f2224c56858eb52cb51c8f9faf0c33e5799184d414a7"},
{file = "frozenlist-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:08428f9d0178b6fa0da95a42ab87a5b20ed2a707bacc97e3689e96ae6cab13fa"},
{file = "frozenlist-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5c4c42bdbf5754010e0cc5cc0f91019437839bc6b7e585262bcc126557a244bc"},
{file = "frozenlist-1.1.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:32491ac26e72e5f35913887bc3ab7bcfe562b4fb65b0e58350fce6efa22fec75"},
{file = "frozenlist-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:d160a73e4a034a857a98384b5e05204c375489d2bbb6ecf1ee8fc124735028fa"},
{file = "frozenlist-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b3cf6737afc4347092a0c8392b4c0e77acc5594e73f4aef355705117a945743a"},
{file = "frozenlist-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:efb805e383836250bef3c99f1857c432a8941c802d0ed7767751315617a54794"},
{file = "frozenlist-1.1.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:2f1f56a36962e28c304872797e226cd646395381de97517870fb819ff7b4f496"},
{file = "frozenlist-1.1.1-cp37-cp37m-manylinux2014_ppc64le.whl", hash = "sha256:7443d815fd9ff2de75b810e192cfa92854bada43aed47ec1598766c7bc9d4a40"},
{file = "frozenlist-1.1.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:fe463e7b3cd089d221f33bd9c22cfad2726622b2a96c3af56a8eb5a71c0943bd"},
{file = "frozenlist-1.1.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:c228886891dc0170d21acbfb62fea801856c3fa207619c973e17d96455ab83e3"},
{file = "frozenlist-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:de170ea97e7b5051a13989ea457300b8159c00455d2207d22afb6b129a433152"},
{file = "frozenlist-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:158c6258ab4ee8a01470d86e75a7514091391b27bb400ba28a7f6a30466cc8e0"},
{file = "frozenlist-1.1.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:572a5a0977b1bd2f15183a352df907726b20da5f91cd1242343b0d72ac677be6"},
{file = "frozenlist-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:748150da8bbd9cbe1b29f0965a675b5732337ae874eed47ccb48dfa75815d0d7"},
{file = "frozenlist-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:caba5ae97c40020771502866dee5024b0031187293185ca5c7714ea52a824a92"},
{file = "frozenlist-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:aea1b84bbebec7c46cd59da13aff90e23bece13bba91974a305bd555f66a72f3"},
{file = "frozenlist-1.1.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:d07f08268b8d37c357f4d34272f1f7588a0618d3fa509a87ad614b5e1cf7109f"},
{file = "frozenlist-1.1.1-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:66f7ba888fe51685502be51ad548b226eb4214fcab0ef48672a2a91a4de08417"},
{file = "frozenlist-1.1.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:4282d897ea190b5e38a18fc3b70295e20e00af7734892250876e1e1b452a7dc8"},
{file = "frozenlist-1.1.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:84ef9f6f6f8e2dd9cf828367c61715202a781ef6d32caa9a016d9055a7daef8b"},
{file = "frozenlist-1.1.1-cp38-cp38-win32.whl", hash = "sha256:d5cba2a537bcf8b4abffc9e01b037eb4ca5c9d1cb29d575fd433f82919a04c68"},
{file = "frozenlist-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:6c1cefdc3666507f7241b120b828e223c1dfb18e599f960ebbd0558de5010efe"},
{file = "frozenlist-1.1.1-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:a6d2d80222eefe6e08b8167005e5a0c1a05ce784ce97de4d6d693be7e2a99862"},
{file = "frozenlist-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:8fbdc86968f71d1d1e216f1f3467da96571b092378ad55b7eb6fc9f3ff877902"},
{file = "frozenlist-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1032a7eb76ca47cb94dcfd05a289dfb2f31b5e155c9cd845f97a56526eca9800"},
{file = "frozenlist-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:4fdfb300d205f3d007462d66c9e8ffa89d7b1b3699e538ae7344845223291ff0"},
{file = "frozenlist-1.1.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:e2d35804cf42b58e42e9b2cca6a2a5bb7155bb545808ff652503a8bacab2be5d"},
{file = "frozenlist-1.1.1-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:a2ebc6dd4f73f39212073add6b3a629a4274ed0a5e43c2fa87bd91957f511450"},
{file = "frozenlist-1.1.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:9ee0bca9801eea5431680bdf22817b1b07310474ac284a3aa7a3902d0dba2382"},
{file = "frozenlist-1.1.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:08a4f1bd182659416c8ae518ef8a63c37953eb2d4bd77cf8b45941a90e87d27c"},
{file = "frozenlist-1.1.1-cp39-cp39-win32.whl", hash = "sha256:803bc0fdb904a762b0a49572fe2f1cb2a03ade5514b265971da5c3e7a8b14798"},
{file = "frozenlist-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:301220a5752fc2585ef97794b1dc88f87d77f5c1951782488896fcb6a732f883"},
{file = "frozenlist-1.1.1.tar.gz", hash = "sha256:32fa8c86eee5c6f11b44863c0d3e945b2b1a03df3bf218617e832f1f04ba3146"},
]
furo = [ furo = [
{file = "furo-2021.6.18b36-py3-none-any.whl", hash = "sha256:a4c00634afeb5896a34d141a5dffb62f20c5eca7831b78269823a8cd8b09a5e4"}, {file = "furo-2021.6.18b36-py3-none-any.whl", hash = "sha256:a4c00634afeb5896a34d141a5dffb62f20c5eca7831b78269823a8cd8b09a5e4"},
{file = "furo-2021.6.18b36.tar.gz", hash = "sha256:46a30bc597a9067088d39d730e7d9bf6c1a1d71967e4af062f796769f66b3bdb"}, {file = "furo-2021.6.18b36.tar.gz", hash = "sha256:46a30bc597a9067088d39d730e7d9bf6c1a1d71967e4af062f796769f66b3bdb"},

View file

@ -37,8 +37,9 @@ pydantic = "^1.8.1"
Babel = "^2.9.1" Babel = "^2.9.1"
aiofiles = "^0.6.0" aiofiles = "^0.6.0"
async_lru = "^1.0.2" async_lru = "^1.0.2"
frozenlist = "^1.1.1"
aiohttp-socks = { version = "^0.5.5", optional = true } aiohttp-socks = { version = "^0.5.5", optional = true }
aioredis = { version = "^2.0.0a1", allow-prereleases = true, optional = true } aioredis = { version = "^2.0.0", allow-prereleases = true, optional = true }
magic-filter = { version = "1.0.0a1", allow-prereleases = true } magic-filter = { version = "1.0.0a1", allow-prereleases = true }
sphinx = { version = "^3.1.0", optional = true } sphinx = { version = "^3.1.0", optional = true }
sphinx-intl = { version = "^2.0.1", optional = true } sphinx-intl = { version = "^2.0.1", optional = true }

View file

@ -1,6 +1,6 @@
import pytest import pytest
from aiogram.methods import BanChatMember, DeleteMyCommands, Request from aiogram.methods import DeleteMyCommands, Request
from tests.mocked_bot import MockedBot from tests.mocked_bot import MockedBot

View file

@ -4,7 +4,7 @@ from typing import Any, Awaitable, Callable, Dict, NoReturn, Union
import pytest import pytest
from aiogram.dispatcher.event.bases import SkipHandler from aiogram.dispatcher.event.bases import REJECTED, SkipHandler
from aiogram.dispatcher.event.handler import HandlerObject from aiogram.dispatcher.event.handler import HandlerObject
from aiogram.dispatcher.event.telegram import TelegramEventObserver from aiogram.dispatcher.event.telegram import TelegramEventObserver
from aiogram.dispatcher.filters.base import BaseFilter from aiogram.dispatcher.filters.base import BaseFilter
@ -233,3 +233,48 @@ class TestTelegramEventObserver:
assert my_middleware3 in middlewares assert my_middleware3 in middlewares
assert middlewares == [my_middleware1, my_middleware2, my_middleware3] assert middlewares == [my_middleware1, my_middleware2, my_middleware3]
def test_register_global_filters(self):
router = Router(use_builtin_filters=False)
assert isinstance(router.message._handler.filters, list)
assert not router.message._handler.filters
my_filter = MyFilter1(test="pass")
router.message.filter(my_filter)
assert len(router.message._handler.filters) == 1
assert router.message._handler.filters[0].callback is my_filter
router.message._handler.filters = None
router.message.filter(my_filter)
assert len(router.message._handler.filters) == 1
assert router.message._handler.filters[0].callback is my_filter
@pytest.mark.asyncio
async def test_global_filter(self):
r1 = Router()
r2 = Router()
async def handler(evt):
return evt
r1.message.filter(lambda evt: False)
r1.message.register(handler)
r2.message.register(handler)
assert await r1.message.trigger(None) is REJECTED
assert await r2.message.trigger(None) is None
@pytest.mark.asyncio
async def test_global_filter_in_nested_router(self):
r1 = Router()
r2 = Router()
async def handler(evt):
return evt
r1.include_router(r2)
r1.message.filter(lambda evt: False)
r2.message.register(handler)
assert await r1.message.trigger(None) is REJECTED

View file

@ -8,12 +8,12 @@ from aiogram.dispatcher.filters import ExceptionMessageFilter, ExceptionTypeFilt
class TestExceptionMessageFilter: class TestExceptionMessageFilter:
@pytest.mark.parametrize("value", ["value", re.compile("value")]) @pytest.mark.parametrize("value", ["value", re.compile("value")])
def test_converter(self, value): def test_converter(self, value):
obj = ExceptionMessageFilter(match=value) obj = ExceptionMessageFilter(pattern=value)
assert isinstance(obj.match, re.Pattern) assert isinstance(obj.pattern, re.Pattern)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_match(self): async def test_match(self):
obj = ExceptionMessageFilter(match="KABOOM") obj = ExceptionMessageFilter(pattern="KABOOM")
result = await obj(Exception()) result = await obj(Exception())
assert not result assert not result

View file

@ -1,6 +1,6 @@
import pytest import pytest
from aiogram.dispatcher.event.bases import SkipHandler, skip from aiogram.dispatcher.event.bases import SkipHandler, skip, UNHANDLED
from aiogram.dispatcher.router import Router from aiogram.dispatcher.router import Router
from aiogram.utils.warnings import CodeHasNoEffect from aiogram.utils.warnings import CodeHasNoEffect
@ -122,3 +122,17 @@ class TestRouter:
skip() skip()
with pytest.raises(SkipHandler, match="KABOOM"): with pytest.raises(SkipHandler, match="KABOOM"):
skip("KABOOM") skip("KABOOM")
@pytest.mark.asyncio
async def test_global_filter_in_nested_router(self):
r1 = Router()
r2 = Router()
async def handler(evt):
return evt
r1.include_router(r2)
r1.message.filter(lambda evt: False)
r2.message.register(handler)
assert await r1.propagate_event(update_type="message", event=None) is UNHANDLED