mirror of
https://github.com/aiogram/aiogram.git
synced 2025-12-12 02:03:04 +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