mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-12 10:11:52 +00:00
Remove filters factory, introduce docs translation (#978)
* Rewrite filters * Update README.rst * Fixed tests * Small optimization of the Text filter (TY to @bomzheg) * Remove dataclass slots argument in due to the only Python 3.10 has an slots argument * Fixed mypy * Update tests * Disable Python 3.11 * Fixed #1013: Empty mention should be None instead of empty string. * Added #990 to the changelog * Added #942 to the changelog * Fixed coverage * Update poetry and dependencies * Fixed mypy * Remove deprecated code * Added more tests, update pyproject.toml * Partial update docs * Added initial Docs translation files * Added more changes * Added log message when connection is established in polling process * Fixed action * Disable lint for PyPy * Added changelog for docs translation
This commit is contained in:
parent
94030903ec
commit
f4251382e8
610 changed files with 61738 additions and 1687 deletions
|
|
@ -2,12 +2,14 @@ import functools
|
|||
from typing import Any, Dict, Union
|
||||
|
||||
import pytest
|
||||
from magic_filter import F as A
|
||||
|
||||
from aiogram import F
|
||||
from aiogram.dispatcher.event.handler import CallableMixin, FilterObject, HandlerObject
|
||||
from aiogram.filters import BaseFilter
|
||||
from aiogram.filters import Filter
|
||||
from aiogram.handlers import BaseHandler
|
||||
from aiogram.types import Update
|
||||
from aiogram.utils.warnings import Recommendation
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ async def callback4(foo: int, *, bar: int, baz: int):
|
|||
return locals()
|
||||
|
||||
|
||||
class Filter(BaseFilter):
|
||||
class TestFilter(Filter):
|
||||
async def __call__(self, foo: int, bar: int, baz: int) -> Union[bool, Dict[str, Any]]:
|
||||
return locals()
|
||||
|
||||
|
|
@ -39,7 +41,7 @@ class SyncCallable:
|
|||
|
||||
|
||||
class TestCallableMixin:
|
||||
@pytest.mark.parametrize("callback", [callback2, Filter()])
|
||||
@pytest.mark.parametrize("callback", [callback2, TestFilter()])
|
||||
def test_init_awaitable(self, callback):
|
||||
obj = CallableMixin(callback)
|
||||
assert obj.awaitable
|
||||
|
|
@ -57,7 +59,7 @@ class TestCallableMixin:
|
|||
pytest.param(callback1, {"foo", "bar", "baz"}),
|
||||
pytest.param(callback2, {"foo", "bar", "baz"}),
|
||||
pytest.param(callback3, {"foo"}),
|
||||
pytest.param(Filter(), {"self", "foo", "bar", "baz"}),
|
||||
pytest.param(TestFilter(), {"self", "foo", "bar", "baz"}),
|
||||
pytest.param(SyncCallable(), {"self", "foo", "bar", "baz"}),
|
||||
],
|
||||
)
|
||||
|
|
@ -117,7 +119,7 @@ class TestCallableMixin:
|
|||
{"foo": 42, "baz": "fuz", "bar": "test"},
|
||||
),
|
||||
pytest.param(
|
||||
Filter(), {"foo": 42, "spam": True, "baz": "fuz"}, {"foo": 42, "baz": "fuz"}
|
||||
TestFilter(), {"foo": 42, "spam": True, "baz": "fuz"}, {"foo": 42, "baz": "fuz"}
|
||||
),
|
||||
pytest.param(
|
||||
SyncCallable(), {"foo": 42, "spam": True, "baz": "fuz"}, {"foo": 42, "baz": "fuz"}
|
||||
|
|
@ -209,3 +211,7 @@ class TestHandlerObject:
|
|||
assert len(handler.filters) == 1
|
||||
result = await handler.call(Update(update_id=42))
|
||||
assert result == 42
|
||||
|
||||
def test_warn_another_magic(self):
|
||||
with pytest.warns(Recommendation):
|
||||
FilterObject(callback=A.test.is_(True))
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import datetime
|
||||
import functools
|
||||
from typing import Any, Awaitable, Callable, Dict, NoReturn, Optional, Union
|
||||
from typing import Any, Dict, NoReturn, Optional, Union
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
from aiogram.dispatcher.event.bases import REJECTED, SkipHandler
|
||||
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, Command
|
||||
from aiogram.filters import Filter
|
||||
from aiogram.types import Chat, Message, User
|
||||
from tests.deprecated import check_deprecated
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
|
@ -31,7 +30,7 @@ async def pipe_handler(*args, **kwargs):
|
|||
return args, kwargs
|
||||
|
||||
|
||||
class MyFilter1(BaseFilter):
|
||||
class MyFilter1(Filter, BaseModel):
|
||||
test: str
|
||||
|
||||
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||
|
|
@ -46,14 +45,14 @@ class MyFilter3(MyFilter1):
|
|||
pass
|
||||
|
||||
|
||||
class OptionalFilter(BaseFilter):
|
||||
class OptionalFilter(Filter, BaseModel):
|
||||
optional: Optional[str]
|
||||
|
||||
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||
return True
|
||||
|
||||
|
||||
class DefaultFilter(BaseFilter):
|
||||
class DefaultFilter(Filter, BaseModel):
|
||||
default: str = "Default"
|
||||
|
||||
async def __call__(self, *args: Any, **kwargs: Any) -> Union[bool, Dict[str, Any]]:
|
||||
|
|
@ -61,144 +60,9 @@ class DefaultFilter(BaseFilter):
|
|||
|
||||
|
||||
class TestTelegramEventObserver:
|
||||
def test_bind_filter(self):
|
||||
event_observer = TelegramEventObserver(Router(), "test")
|
||||
with pytest.raises(TypeError):
|
||||
event_observer.bind_filter(object) # type: ignore
|
||||
|
||||
class MyFilter(BaseFilter):
|
||||
async def __call__(
|
||||
self, *args: Any, **kwargs: Any
|
||||
) -> Callable[[Any], Awaitable[Union[bool, Dict[str, Any]]]]:
|
||||
pass
|
||||
|
||||
event_observer.bind_filter(MyFilter)
|
||||
assert event_observer.filters
|
||||
assert MyFilter in event_observer.filters
|
||||
|
||||
def test_resolve_filters_chain(self):
|
||||
router1 = Router(use_builtin_filters=False)
|
||||
router2 = Router(use_builtin_filters=False)
|
||||
router3 = Router(use_builtin_filters=False)
|
||||
router1.include_router(router2)
|
||||
router2.include_router(router3)
|
||||
|
||||
router1.message.bind_filter(MyFilter1)
|
||||
router1.message.bind_filter(MyFilter2)
|
||||
router2.message.bind_filter(MyFilter2)
|
||||
router3.message.bind_filter(MyFilter3)
|
||||
|
||||
filters_chain1 = list(router1.message._resolve_filters_chain())
|
||||
filters_chain2 = list(router2.message._resolve_filters_chain())
|
||||
filters_chain3 = list(router3.message._resolve_filters_chain())
|
||||
|
||||
assert MyFilter1 in filters_chain1
|
||||
assert MyFilter1 in filters_chain2
|
||||
assert MyFilter1 in filters_chain3
|
||||
assert MyFilter2 in filters_chain1
|
||||
assert MyFilter2 in filters_chain2
|
||||
assert MyFilter2 in filters_chain3
|
||||
assert MyFilter3 in filters_chain3
|
||||
|
||||
assert MyFilter3 not in filters_chain1
|
||||
|
||||
async def test_resolve_filters_data_from_parent_router(self):
|
||||
class FilterSet(BaseFilter):
|
||||
set_filter: bool
|
||||
|
||||
async def __call__(self, message: Message) -> dict:
|
||||
return {"test": "hello world"}
|
||||
|
||||
class FilterGet(BaseFilter):
|
||||
get_filter: bool
|
||||
|
||||
async def __call__(self, message: Message, **data) -> bool:
|
||||
assert "test" in data
|
||||
return True
|
||||
|
||||
router1 = Router(use_builtin_filters=False)
|
||||
router2 = Router(use_builtin_filters=False)
|
||||
router1.include_router(router2)
|
||||
|
||||
router1.message.bind_filter(FilterSet)
|
||||
router2.message.bind_filter(FilterGet)
|
||||
|
||||
@router2.message(set_filter=True, get_filter=True)
|
||||
def handler_test(msg: Message, test: str):
|
||||
assert test == "hello world"
|
||||
|
||||
await router1.propagate_event(
|
||||
"message",
|
||||
Message(message_id=1, date=datetime.datetime.now(), chat=Chat(id=1, type="private")),
|
||||
)
|
||||
|
||||
def test_resolve_filters(self):
|
||||
router = Router(use_builtin_filters=False)
|
||||
observer = router.message
|
||||
observer.bind_filter(MyFilter1)
|
||||
|
||||
resolved = observer.resolve_filters((), {"test": "PASS"})
|
||||
assert isinstance(resolved, list)
|
||||
assert any(isinstance(item, MyFilter1) for item in resolved)
|
||||
|
||||
# Unknown filter
|
||||
with pytest.raises(FiltersResolveError, match="Unknown keyword filters: {'@bad'}"):
|
||||
assert observer.resolve_filters((), {"@bad": "very"})
|
||||
|
||||
# Unknown filter
|
||||
with pytest.raises(FiltersResolveError, match="Unknown keyword filters: {'@bad'}"):
|
||||
assert observer.resolve_filters((), {"test": "ok", "@bad": "very"})
|
||||
|
||||
# Bad argument type
|
||||
with pytest.raises(FiltersResolveError, match="Unknown keyword filters: {'test'}"):
|
||||
assert observer.resolve_filters((), {"test": ...})
|
||||
|
||||
# Disallow same filter using
|
||||
with pytest.raises(FiltersResolveError, match="Unknown keyword filters: {'test'}"):
|
||||
observer.resolve_filters((MyFilter1(test="test"),), {"test": ...})
|
||||
|
||||
def test_dont_autoresolve_optional_filters_for_router(self):
|
||||
router = Router(use_builtin_filters=False)
|
||||
observer = router.message
|
||||
observer.bind_filter(MyFilter1)
|
||||
observer.bind_filter(OptionalFilter)
|
||||
observer.bind_filter(DefaultFilter)
|
||||
|
||||
observer.filter(test="test")
|
||||
assert len(observer._handler.filters) == 1
|
||||
|
||||
def test_register_autoresolve_optional_filters(self):
|
||||
router = Router(use_builtin_filters=False)
|
||||
observer = router.message
|
||||
observer.bind_filter(MyFilter1)
|
||||
observer.bind_filter(OptionalFilter)
|
||||
observer.bind_filter(DefaultFilter)
|
||||
|
||||
assert observer.register(my_handler) == my_handler
|
||||
assert isinstance(observer.handlers[0], HandlerObject)
|
||||
assert isinstance(observer.handlers[0].filters[0].callback, OptionalFilter)
|
||||
assert len(observer.handlers[0].filters) == 2
|
||||
assert isinstance(observer.handlers[0].filters[0].callback, OptionalFilter)
|
||||
assert isinstance(observer.handlers[0].filters[1].callback, DefaultFilter)
|
||||
|
||||
observer.register(my_handler, test="ok")
|
||||
assert isinstance(observer.handlers[1], HandlerObject)
|
||||
assert len(observer.handlers[1].filters) == 3
|
||||
assert isinstance(observer.handlers[1].filters[0].callback, MyFilter1)
|
||||
assert isinstance(observer.handlers[1].filters[1].callback, OptionalFilter)
|
||||
assert isinstance(observer.handlers[1].filters[2].callback, DefaultFilter)
|
||||
|
||||
observer.register(my_handler, test="ok", optional="ok")
|
||||
assert isinstance(observer.handlers[2], HandlerObject)
|
||||
assert len(observer.handlers[2].filters) == 3
|
||||
assert isinstance(observer.handlers[2].filters[0].callback, MyFilter1)
|
||||
assert isinstance(observer.handlers[2].filters[1].callback, OptionalFilter)
|
||||
assert isinstance(observer.handlers[2].filters[2].callback, DefaultFilter)
|
||||
|
||||
def test_register(self):
|
||||
router = Router(use_builtin_filters=False)
|
||||
router = Router()
|
||||
observer = router.message
|
||||
observer.bind_filter(MyFilter1)
|
||||
|
||||
assert observer.register(my_handler) == my_handler
|
||||
assert isinstance(observer.handlers[0], HandlerObject)
|
||||
|
|
@ -210,19 +74,19 @@ class TestTelegramEventObserver:
|
|||
assert len(observer.handlers[1].filters) == 1
|
||||
assert observer.handlers[1].filters[0].callback == f
|
||||
|
||||
observer.register(my_handler, test="PASS")
|
||||
observer.register(my_handler, MyFilter1(test="PASS"))
|
||||
assert isinstance(observer.handlers[2], HandlerObject)
|
||||
assert any(isinstance(item.callback, MyFilter1) for item in observer.handlers[2].filters)
|
||||
|
||||
f2 = MyFilter2(test="ok")
|
||||
observer.register(my_handler, f2, test="PASS")
|
||||
observer.register(my_handler, f2, MyFilter1(test="PASS"))
|
||||
assert isinstance(observer.handlers[3], HandlerObject)
|
||||
callbacks = [filter_.callback for filter_ in observer.handlers[3].filters]
|
||||
assert f2 in callbacks
|
||||
assert MyFilter1(test="PASS") in callbacks
|
||||
|
||||
def test_register_decorator(self):
|
||||
router = Router(use_builtin_filters=False)
|
||||
router = Router()
|
||||
observer = router.message
|
||||
|
||||
@observer()
|
||||
|
|
@ -233,10 +97,9 @@ class TestTelegramEventObserver:
|
|||
assert observer.handlers[0].callback == my_handler
|
||||
|
||||
async def test_trigger(self):
|
||||
router = Router(use_builtin_filters=False)
|
||||
router = Router()
|
||||
observer = router.message
|
||||
observer.bind_filter(MyFilter1)
|
||||
observer.register(my_handler, test="ok")
|
||||
observer.register(my_handler, MyFilter1(test="ok"))
|
||||
|
||||
message = Message(
|
||||
message_id=42,
|
||||
|
|
@ -258,7 +121,7 @@ class TestTelegramEventObserver:
|
|||
),
|
||||
)
|
||||
def test_register_filters_via_decorator(self, count, handler, filters):
|
||||
router = Router(use_builtin_filters=False)
|
||||
router = Router()
|
||||
observer = router.message
|
||||
|
||||
for index in range(count):
|
||||
|
|
@ -272,7 +135,7 @@ class TestTelegramEventObserver:
|
|||
assert len(registered_handler.filters) == len(filters)
|
||||
|
||||
async def test_trigger_right_context_in_handlers(self):
|
||||
router = Router(use_builtin_filters=False)
|
||||
router = Router()
|
||||
observer = router.message
|
||||
|
||||
async def mix_unnecessary_data(event):
|
||||
|
|
@ -328,7 +191,7 @@ class TestTelegramEventObserver:
|
|||
assert list(middlewares) == [my_middleware1, my_middleware2, my_middleware3]
|
||||
|
||||
def test_register_global_filters(self):
|
||||
router = Router(use_builtin_filters=False)
|
||||
router = Router()
|
||||
assert isinstance(router.message._handler.filters, list)
|
||||
assert not router.message._handler.filters
|
||||
|
||||
|
|
@ -369,13 +232,3 @@ 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"]})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue