diff --git a/aiogram/api/client/bot.py b/aiogram/api/client/bot.py index 7e133640..06d1af05 100644 --- a/aiogram/api/client/bot.py +++ b/aiogram/api/client/bot.py @@ -1,6 +1,8 @@ import datetime from typing import List, Optional, Union +from async_lru import alru_cache + from ..methods import ( AddStickerToSet, AnswerCallbackQuery, @@ -103,10 +105,9 @@ class Bot(BaseBot): Class where located all API methods """ + @alru_cache() async def me(self) -> User: - if self not in self: - self[self] = await self.get_me() - return self[self] + return await self.get_me() # ============================================================================================= # Group: Getting updates diff --git a/poetry.lock b/poetry.lock index ddde5df0..13935e33 100644 --- a/poetry.lock +++ b/poetry.lock @@ -50,6 +50,14 @@ version = "1.1.1" aiohttp = ">=3.1.0,<4.0.0" pytest-asyncio = "*" +[[package]] +category = "main" +description = "Simple lru_cache for asyncio" +name = "async-lru" +optional = false +python-versions = "*" +version = "1.0.2" + [[package]] category = "main" description = "Timeout context manager for asyncio programs" @@ -778,7 +786,7 @@ more-itertools = "*" fast = ["uvloop"] [metadata] -content-hash = "17ddf5163aca6e27a1a83c6f23cfde2f807a3e97ca366fe081666503f2a7b992" +content-hash = "827efd3cbbd07f5795145b39a5f25e92619a62c48121f4fa142dafb93eaa89db" python-versions = "^3.7" [metadata.hashes] @@ -787,6 +795,7 @@ aiohttp = ["1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e", " appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"] appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"] aresponses = ["d1d6ef52b9a97142d106688cf9b112602ef3dc66f6368de8f91f47241d8cfc9c", "f62bcdd739612b6254dd552467b5897a81dcf785e4bb48463bf71e40df398580"] +async-lru = ["baa898027619f5cc31b7966f96f00e4fc0df43ba206a8940a5d1af5336a477cb"] async-timeout = ["0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", "4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"] asynctest = ["5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676", "c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"] atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"] diff --git a/pyproject.toml b/pyproject.toml index a5e3bbe2..7d03c69b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ pydantic = "^1.1" Babel = "^2.7" aiofiles = "^0.4.0" uvloop = {version = "^0.14.0", optional = true} +async_lru = "^1.0" [tool.poetry.dev-dependencies] uvloop = "^0.14.0" diff --git a/tests/conftest.py b/tests/conftest.py index beeb19a9..60d9d0fe 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,3 +10,4 @@ def bot(): token = Bot.set_current(bot) yield bot Bot.reset_current(token) + bot.me.invalidate(bot) diff --git a/tests/test_api/test_methods/test_get_me.py b/tests/test_api/test_methods/test_get_me.py index 70856a03..bd1bb860 100644 --- a/tests/test_api/test_methods/test_get_me.py +++ b/tests/test_api/test_methods/test_get_me.py @@ -28,3 +28,28 @@ class TestGetMe: assert request.method == "getMe" assert request.data == {} assert response == prepare_result.result + + @pytest.mark.asyncio + async def test_me_property(self, bot: MockedBot): + prepare_result = bot.add_result_for( + GetMe, ok=True, result=User(id=42, is_bot=False, first_name="User") + ) + + response: User = await bot.me() + request: Request = bot.get_request() + + assert isinstance(response, User) + assert request.method == "getMe" + assert request.data == {} + assert response == prepare_result.result + + response2: User = await bot.me() + assert response2 == response + + response3: User = await bot.me() + assert response3 == response + assert response2 == response3 + + cache_info = bot.me.cache_info() + assert cache_info.hits == 2 + assert cache_info.misses == 1 diff --git a/tests/test_dispatcher/test_filters/test_command.py b/tests/test_dispatcher/test_filters/test_command.py index 5649be86..9ccabc4c 100644 --- a/tests/test_dispatcher/test_filters/test_command.py +++ b/tests/test_dispatcher/test_filters/test_command.py @@ -1,6 +1,109 @@ -from aiogram.dispatcher.filters import Command +import datetime +import re +from typing import Match + +import pytest + +from aiogram.api.methods import GetMe +from aiogram.api.types import User, Message, Chat +from aiogram.dispatcher.filters import CommandObject, Command +from tests.mocked_bot import MockedBot class TestCommandFilter: - def test_validator(self): - pass + @pytest.mark.asyncio + async def test_parse_command(self, bot: MockedBot): + # TODO: parametrize + # TODO: test ignore case + # TODO: test ignore mention + + bot.add_result_for( + GetMe, ok=True, result=User(id=42, is_bot=True, first_name="The bot", username="tbot") + ) + command = Command(commands=["test", re.compile(r"test(\d+)")], commands_prefix="/") + + assert not await command.parse_command("!test", bot) + assert not await command.parse_command("/test@mention", bot) + assert await command.parse_command("/test@tbot", bot) + assert not await command.parse_command("/tests", bot) + + result = await command.parse_command("/test@tbot some args", bot) + assert isinstance(result, dict) + assert "command" in result + assert isinstance(result["command"], CommandObject) + assert result["command"].command == "test" + assert result["command"].mention == "tbot" + assert result["command"].args == "some args" + + result = await command.parse_command("/test42@tbot some args", bot) + assert isinstance(result, dict) + assert "command" in result + assert isinstance(result["command"], CommandObject) + assert result["command"].command == "test42" + assert result["command"].mention == "tbot" + assert result["command"].args == "some args" + assert isinstance(result["command"].match, Match) + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "message,result", + [ + [ + Message( + message_id=42, + date=datetime.datetime.now(), + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + False, + ], + [ + Message( + message_id=42, + date=datetime.datetime.now(), + text="/test", + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ), + True, + ], + ], + ) + async def test_call(self, message: Message, result: bool, bot: MockedBot): + command = Command(commands=["test"]) + assert bool(await command(message=message, bot=bot)) is result + + +class TestCommandObject: + @pytest.mark.parametrize( + "obj,result", + [ + [CommandObject(prefix="/", command="command", mention="mention", args="args"), True], + [CommandObject(prefix="/", command="command", args="args"), False], + ], + ) + def test_mentioned(self, obj: CommandObject, result: bool): + assert isinstance(obj.mentioned, bool) + assert obj.mentioned is result + + @pytest.mark.parametrize( + "obj,result", + [ + [ + CommandObject(prefix="/", command="command", mention="mention", args="args"), + "/command@mention args", + ], + [ + CommandObject(prefix="/", command="command", mention="mention", args=None), + "/command@mention", + ], + [ + CommandObject(prefix="/", command="command", mention=None, args="args"), + "/command args", + ], + [CommandObject(prefix="/", command="command", mention=None, args=None), "/command"], + [CommandObject(prefix="!", command="command", mention=None, args=None), "!command"], + ], + ) + def test_text(self, obj: CommandObject, result: str): + assert obj.text == result diff --git a/tests/test_dispatcher/test_handler/test_message.py b/tests/test_dispatcher/test_handler/test_message.py index 4b9741cb..d4cc4818 100644 --- a/tests/test_dispatcher/test_handler/test_message.py +++ b/tests/test_dispatcher/test_handler/test_message.py @@ -40,7 +40,7 @@ class TestBaseMessageHandlerCommandMixin: Message( message_id=42, date=datetime.datetime.now(), - text="test", + text="/test args", chat=Chat(id=42, type="private"), from_user=User(id=42, is_bot=False, first_name="Test"), ), @@ -49,3 +49,16 @@ class TestBaseMessageHandlerCommandMixin: assert isinstance(handler.command, CommandObject) assert handler.command.command == "command" + + def test_command_not_presented(self): + handler = HandlerWithCommand( + Message( + message_id=42, + date=datetime.datetime.now(), + text="test", + chat=Chat(id=42, type="private"), + from_user=User(id=42, is_bot=False, first_name="Test"), + ) + ) + + assert handler.command is None