mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-12 10:11:52 +00:00
Added lost files
This commit is contained in:
parent
782102561e
commit
7dd80d281f
7 changed files with 429 additions and 0 deletions
113
docs2/dispatcher/middlewares.rst
Normal file
113
docs2/dispatcher/middlewares.rst
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
===========
|
||||||
|
Middlewares
|
||||||
|
===========
|
||||||
|
|
||||||
|
**aiogram** provides powerful mechanism for customizing event handlers via middlewares.
|
||||||
|
|
||||||
|
Middlewares in bot framework seems like Middlewares mechanism in web-frameworks
|
||||||
|
like `aiohttp <https://docs.aiohttp.org/en/stable/web_advanced.html#aiohttp-web-middlewares>`_,
|
||||||
|
`fastapi <https://fastapi.tiangolo.com/tutorial/middleware/>`_,
|
||||||
|
`Django <https://docs.djangoproject.com/en/3.0/topics/http/middleware/>`_ or etc.)
|
||||||
|
with small difference - here is implemented two layers of middlewares (before and after filters).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Middleware is function that triggered on every event received from
|
||||||
|
Telegram Bot API in many points on processing pipeline.
|
||||||
|
|
||||||
|
Base theory
|
||||||
|
===========
|
||||||
|
|
||||||
|
As many books and other literature in internet says:
|
||||||
|
|
||||||
|
Middleware is reusable software that leverages patterns and frameworks to bridge
|
||||||
|
the gap between the functional requirements of applications and the underlying operating systems,
|
||||||
|
network protocol stacks, and databases.
|
||||||
|
|
||||||
|
Middleware can modify, extend or reject processing event in many places of pipeline.
|
||||||
|
|
||||||
|
Basics
|
||||||
|
======
|
||||||
|
|
||||||
|
Middleware instance can be applied for every type of Telegram Event (Update, Message, etc.) in two places
|
||||||
|
|
||||||
|
1. Outer scope - before processing filters (:code:`<router>.<event>.outer_middleware(...)`)
|
||||||
|
2. Inner scope - after processing filters but before handler (:code:`<router>.<event>.middleware(...)`)
|
||||||
|
|
||||||
|
.. image:: ../_static/basics_middleware.png
|
||||||
|
:alt: Middleware basics
|
||||||
|
|
||||||
|
.. attention::
|
||||||
|
|
||||||
|
Middleware should be subclass of :code:`BaseMiddleware` (:code:`from aiogram import BaseMiddleware`) or any async callable
|
||||||
|
|
||||||
|
Arguments specification
|
||||||
|
=======================
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.dispatcher.middlewares.base.BaseMiddleware
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
:member-order: bysource
|
||||||
|
:special-members: __init__, __call__
|
||||||
|
:undoc-members: True
|
||||||
|
|
||||||
|
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
.. danger::
|
||||||
|
|
||||||
|
Middleware should always call :code:`await handler(event, data)` to propagate event for next middleware/handler
|
||||||
|
|
||||||
|
|
||||||
|
Class-based
|
||||||
|
-----------
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from aiogram import BaseMiddleware
|
||||||
|
from aiogram.api.types import Message
|
||||||
|
|
||||||
|
|
||||||
|
class CounterMiddleware(BaseMiddleware[Message]):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.counter = 0
|
||||||
|
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
handler: Callable[[Message, Dict[str, Any]], Awaitable[Any]],
|
||||||
|
event: Message,
|
||||||
|
data: Dict[str, Any]
|
||||||
|
) -> Any:
|
||||||
|
self.counter += 1
|
||||||
|
data['counter'] = self.counter
|
||||||
|
return await handler(event, data)
|
||||||
|
|
||||||
|
and then
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
router.message.middleware(CounterMiddleware())
|
||||||
|
|
||||||
|
|
||||||
|
Function-based
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. code-block:: python3
|
||||||
|
|
||||||
|
@dispatcher.update.outer_middleware()
|
||||||
|
async def database_transaction_middleware(
|
||||||
|
handler: Callable[[Update, Dict[str, Any]], Awaitable[Any]],
|
||||||
|
event: Update,
|
||||||
|
data: Dict[str, Any]
|
||||||
|
) -> Any:
|
||||||
|
async with database.transaction():
|
||||||
|
return await handler(event, data)
|
||||||
|
|
||||||
|
|
||||||
|
Facts
|
||||||
|
=====
|
||||||
|
|
||||||
|
1. Middlewares from outer scope will be called on every incoming event
|
||||||
|
2. Middlewares from inner scope will be called only when filters pass
|
||||||
|
3. Inner middlewares is always calls for :class:`aiogram.types.update.Update` event type in due to all incoming updates going to specific event type handler through built in update handler
|
||||||
49
tests/test_api/test_methods/test_create_chat_invite_link.py
Normal file
49
tests/test_api/test_methods/test_create_chat_invite_link.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiogram.methods import CreateChatInviteLink, Request
|
||||||
|
from aiogram.types import ChatInviteLink, User
|
||||||
|
from tests.mocked_bot import MockedBot
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateChatInviteLink:
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_method(self, bot: MockedBot):
|
||||||
|
prepare_result = bot.add_result_for(
|
||||||
|
CreateChatInviteLink,
|
||||||
|
ok=True,
|
||||||
|
result=ChatInviteLink(
|
||||||
|
invite_link="https://t.me/username",
|
||||||
|
creator=User(id=42, is_bot=False, first_name="User"),
|
||||||
|
is_primary=False,
|
||||||
|
is_revoked=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response: ChatInviteLink = await CreateChatInviteLink(
|
||||||
|
chat_id=-42,
|
||||||
|
)
|
||||||
|
request: Request = bot.get_request()
|
||||||
|
assert request.method == "createChatInviteLink"
|
||||||
|
# assert request.data == {"chat_id": -42}
|
||||||
|
assert response == prepare_result.result
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_bot_method(self, bot: MockedBot):
|
||||||
|
prepare_result = bot.add_result_for(
|
||||||
|
CreateChatInviteLink,
|
||||||
|
ok=True,
|
||||||
|
result=ChatInviteLink(
|
||||||
|
invite_link="https://t.me/username",
|
||||||
|
creator=User(id=42, is_bot=False, first_name="User"),
|
||||||
|
is_primary=False,
|
||||||
|
is_revoked=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response: ChatInviteLink = await bot.create_chat_invite_link(
|
||||||
|
chat_id=-42,
|
||||||
|
)
|
||||||
|
request: Request = bot.get_request()
|
||||||
|
assert request.method == "createChatInviteLink"
|
||||||
|
# assert request.data == {"chat_id": -42}
|
||||||
|
assert response == prepare_result.result
|
||||||
49
tests/test_api/test_methods/test_edit_chat_invite_link.py
Normal file
49
tests/test_api/test_methods/test_edit_chat_invite_link.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiogram.methods import EditChatInviteLink, Request
|
||||||
|
from aiogram.types import ChatInviteLink, User
|
||||||
|
from tests.mocked_bot import MockedBot
|
||||||
|
|
||||||
|
|
||||||
|
class TestEditChatInviteLink:
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_method(self, bot: MockedBot):
|
||||||
|
prepare_result = bot.add_result_for(
|
||||||
|
EditChatInviteLink,
|
||||||
|
ok=True,
|
||||||
|
result=ChatInviteLink(
|
||||||
|
invite_link="https://t.me/username2",
|
||||||
|
creator=User(id=42, is_bot=False, first_name="User"),
|
||||||
|
is_primary=False,
|
||||||
|
is_revoked=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response: ChatInviteLink = await EditChatInviteLink(
|
||||||
|
chat_id=-42, invite_link="https://t.me/username", member_limit=1
|
||||||
|
)
|
||||||
|
request: Request = bot.get_request()
|
||||||
|
assert request.method == "editChatInviteLink"
|
||||||
|
# assert request.data == {}
|
||||||
|
assert response == prepare_result.result
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_bot_method(self, bot: MockedBot):
|
||||||
|
prepare_result = bot.add_result_for(
|
||||||
|
EditChatInviteLink,
|
||||||
|
ok=True,
|
||||||
|
result=ChatInviteLink(
|
||||||
|
invite_link="https://t.me/username2",
|
||||||
|
creator=User(id=42, is_bot=False, first_name="User"),
|
||||||
|
is_primary=False,
|
||||||
|
is_revoked=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response: ChatInviteLink = await bot.edit_chat_invite_link(
|
||||||
|
chat_id=-42, invite_link="https://t.me/username", member_limit=1
|
||||||
|
)
|
||||||
|
request: Request = bot.get_request()
|
||||||
|
assert request.method == "editChatInviteLink"
|
||||||
|
# assert request.data == {}
|
||||||
|
assert response == prepare_result.result
|
||||||
51
tests/test_api/test_methods/test_revoke_chat_invite_link.py
Normal file
51
tests/test_api/test_methods/test_revoke_chat_invite_link.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiogram.methods import Request, RevokeChatInviteLink
|
||||||
|
from aiogram.types import ChatInviteLink, User
|
||||||
|
from tests.mocked_bot import MockedBot
|
||||||
|
|
||||||
|
|
||||||
|
class TestRevokeChatInviteLink:
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_method(self, bot: MockedBot):
|
||||||
|
prepare_result = bot.add_result_for(
|
||||||
|
RevokeChatInviteLink,
|
||||||
|
ok=True,
|
||||||
|
result=ChatInviteLink(
|
||||||
|
invite_link="https://t.me/username",
|
||||||
|
creator=User(id=42, is_bot=False, first_name="User"),
|
||||||
|
is_primary=False,
|
||||||
|
is_revoked=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response: ChatInviteLink = await RevokeChatInviteLink(
|
||||||
|
chat_id=-42,
|
||||||
|
invite_link="https://t.me/username",
|
||||||
|
)
|
||||||
|
request: Request = bot.get_request()
|
||||||
|
assert request.method == "revokeChatInviteLink"
|
||||||
|
# assert request.data == {}
|
||||||
|
assert response == prepare_result.result
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_bot_method(self, bot: MockedBot):
|
||||||
|
prepare_result = bot.add_result_for(
|
||||||
|
RevokeChatInviteLink,
|
||||||
|
ok=True,
|
||||||
|
result=ChatInviteLink(
|
||||||
|
invite_link="https://t.me/username",
|
||||||
|
creator=User(id=42, is_bot=False, first_name="User"),
|
||||||
|
is_primary=False,
|
||||||
|
is_revoked=True,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
response: ChatInviteLink = await bot.revoke_chat_invite_link(
|
||||||
|
chat_id=-42,
|
||||||
|
invite_link="https://t.me/username",
|
||||||
|
)
|
||||||
|
request: Request = bot.get_request()
|
||||||
|
assert request.method == "revokeChatInviteLink"
|
||||||
|
# assert request.data == {}
|
||||||
|
assert response == prepare_result.result
|
||||||
15
tests/test_api/test_types/test_message_entity.py
Normal file
15
tests/test_api/test_types/test_message_entity.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiogram.types import MessageEntity
|
||||||
|
from tests.deprecated import check_deprecated
|
||||||
|
|
||||||
|
|
||||||
|
class TestMessageEntity:
|
||||||
|
def test_extract(self):
|
||||||
|
entity = MessageEntity(type="hashtag", length=4, offset=5)
|
||||||
|
assert entity.extract("#foo #bar #baz") == "#bar"
|
||||||
|
|
||||||
|
def test_get_text(self):
|
||||||
|
entity = MessageEntity(type="hashtag", length=4, offset=5)
|
||||||
|
with check_deprecated("3.2", exception=AttributeError):
|
||||||
|
assert entity.get_text("#foo #bar #baz") == "#bar"
|
||||||
0
tests/test_dispatcher/fsm/__init__.py
Normal file
0
tests/test_dispatcher/fsm/__init__.py
Normal file
152
tests/test_dispatcher/fsm/test_state.py
Normal file
152
tests/test_dispatcher/fsm/test_state.py
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiogram.dispatcher.fsm.state import State, StatesGroup, any_state
|
||||||
|
|
||||||
|
|
||||||
|
class TestState:
|
||||||
|
def test_empty(self):
|
||||||
|
state = State()
|
||||||
|
assert state._state is None
|
||||||
|
assert state._group_name is None
|
||||||
|
assert state._group is None
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
assert state.group
|
||||||
|
|
||||||
|
assert state.state is None
|
||||||
|
assert str(state) == "<State ''>"
|
||||||
|
|
||||||
|
def test_star(self):
|
||||||
|
state = State(state="*")
|
||||||
|
assert state._state == "*"
|
||||||
|
assert state._group_name is None
|
||||||
|
assert state._group is None
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
assert state.group
|
||||||
|
assert state.state == "*"
|
||||||
|
assert str(state) == "<State '*'>"
|
||||||
|
|
||||||
|
def test_star_filter(self):
|
||||||
|
assert any_state(None, "foo")
|
||||||
|
assert any_state(None, "bar")
|
||||||
|
assert any_state(None, "baz")
|
||||||
|
|
||||||
|
def test_alone(self):
|
||||||
|
state = State("test")
|
||||||
|
assert state._state == "test"
|
||||||
|
assert state._group_name is None
|
||||||
|
assert state._group is None
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
assert state.group
|
||||||
|
|
||||||
|
assert state.state == "@:test"
|
||||||
|
assert str(state) == "<State '@:test'>"
|
||||||
|
|
||||||
|
def test_alone_with_group(self):
|
||||||
|
state = State("test", group_name="Test")
|
||||||
|
assert state._state == "test"
|
||||||
|
assert state._group_name == "Test"
|
||||||
|
assert state._group is None
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
assert state.group == "Test"
|
||||||
|
|
||||||
|
assert state.state == "Test:test"
|
||||||
|
assert str(state) == "<State 'Test:test'>"
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"state,check,result",
|
||||||
|
[
|
||||||
|
[State("test"), "test", False],
|
||||||
|
[State("test"), "@:test", True],
|
||||||
|
[State("test"), "test1", False],
|
||||||
|
[State("test", group_name="test"), "test:test", True],
|
||||||
|
[State("test", group_name="test"), "test:test2", False],
|
||||||
|
[State("test", group_name="test"), "test2:test", False],
|
||||||
|
[State("test", group_name="test"), "test2:test2", False],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_filter(self, state, check, result):
|
||||||
|
assert state(None, check) is result
|
||||||
|
|
||||||
|
def test_state_in_unknown_class(self):
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
state1 = State()
|
||||||
|
|
||||||
|
|
||||||
|
class TestStatesGroup:
|
||||||
|
def test_empty(self):
|
||||||
|
class MyGroup(StatesGroup):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert MyGroup.__states__ == ()
|
||||||
|
assert MyGroup.__state_names__ == ()
|
||||||
|
assert MyGroup.__all_childs__ == ()
|
||||||
|
assert MyGroup.__all_states__ == ()
|
||||||
|
assert MyGroup.__all_states_names__ == ()
|
||||||
|
assert MyGroup.__parent__ is None
|
||||||
|
assert MyGroup.__full_group_name__ == "MyGroup"
|
||||||
|
|
||||||
|
assert str(MyGroup) == "<StatesGroup 'MyGroup'>"
|
||||||
|
|
||||||
|
def test_with_state(self):
|
||||||
|
class MyGroup(StatesGroup):
|
||||||
|
state1 = State()
|
||||||
|
|
||||||
|
assert MyGroup.__states__ == (MyGroup.state1,)
|
||||||
|
assert MyGroup.__state_names__ == ("MyGroup:state1",)
|
||||||
|
assert MyGroup.__all_childs__ == ()
|
||||||
|
assert MyGroup.__all_states__ == (MyGroup.state1,)
|
||||||
|
assert MyGroup.__parent__ is None
|
||||||
|
assert MyGroup.__full_group_name__ == "MyGroup"
|
||||||
|
|
||||||
|
assert str(MyGroup) == "<StatesGroup 'MyGroup'>"
|
||||||
|
|
||||||
|
assert MyGroup.state1.state == "MyGroup:state1"
|
||||||
|
assert MyGroup.state1.group == MyGroup
|
||||||
|
|
||||||
|
def test_nested_group(self):
|
||||||
|
class MyGroup(StatesGroup):
|
||||||
|
state1 = State()
|
||||||
|
|
||||||
|
class MyNestedGroup(StatesGroup):
|
||||||
|
state1 = State()
|
||||||
|
|
||||||
|
assert MyGroup.__states__ == (MyGroup.state1,)
|
||||||
|
assert MyGroup.__state_names__ == ("MyGroup:state1",)
|
||||||
|
assert MyGroup.__all_childs__ == (MyGroup.MyNestedGroup,)
|
||||||
|
assert MyGroup.__all_states__ == (MyGroup.state1, MyGroup.MyNestedGroup.state1)
|
||||||
|
assert MyGroup.__parent__ is None
|
||||||
|
assert MyGroup.MyNestedGroup.__parent__ is MyGroup
|
||||||
|
assert MyGroup.__full_group_name__ == "MyGroup"
|
||||||
|
assert MyGroup.MyNestedGroup.__full_group_name__ == "MyGroup.MyNestedGroup"
|
||||||
|
|
||||||
|
assert str(MyGroup) == "<StatesGroup 'MyGroup'>"
|
||||||
|
assert str(MyGroup.MyNestedGroup) == "<StatesGroup 'MyGroup.MyNestedGroup'>"
|
||||||
|
|
||||||
|
assert MyGroup.state1.state == "MyGroup:state1"
|
||||||
|
assert MyGroup.state1.group == MyGroup
|
||||||
|
|
||||||
|
assert MyGroup.MyNestedGroup.state1.state == "MyGroup.MyNestedGroup:state1"
|
||||||
|
assert MyGroup.MyNestedGroup.state1.group == MyGroup.MyNestedGroup
|
||||||
|
|
||||||
|
assert MyGroup.MyNestedGroup.state1 in MyGroup.MyNestedGroup
|
||||||
|
assert MyGroup.MyNestedGroup.state1 in MyGroup
|
||||||
|
assert MyGroup.state1 not in MyGroup.MyNestedGroup
|
||||||
|
assert MyGroup.state1 in MyGroup
|
||||||
|
|
||||||
|
# Not working as well
|
||||||
|
# assert MyGroup.MyNestedGroup in MyGroup
|
||||||
|
|
||||||
|
assert "MyGroup.MyNestedGroup:state1" in MyGroup
|
||||||
|
assert "MyGroup.MyNestedGroup:state1" in MyGroup.MyNestedGroup
|
||||||
|
|
||||||
|
assert MyGroup.state1 not in MyGroup.MyNestedGroup
|
||||||
|
assert "test" not in MyGroup
|
||||||
|
assert 42 not in MyGroup
|
||||||
|
|
||||||
|
assert MyGroup.MyNestedGroup.get_root() is MyGroup
|
||||||
Loading…
Add table
Add a link
Reference in a new issue