From 3731a628856daf15d6c7ff21c41a905409203eee Mon Sep 17 00:00:00 2001 From: He <123play123.loggin@gmail.com> Date: Tue, 8 Feb 2022 07:25:15 +0700 Subject: [PATCH 1/7] Deleting deprecated CHOSEN_INLINE_QUERY (#805) * Deleting deprecated CHOSEN_INLINE_QUERY * Del from comments CHOSEN_INLINE_QUERY --- aiogram/dispatcher/dispatcher.py | 6 +++--- aiogram/types/update.py | 8 +------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 60aeed49..5b3cafd3 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -768,7 +768,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin): .. code-block:: python3 - dp.register_chosen_inline_handler(some_chosen_inline_handler, lambda chosen_inline_query: True) + dp.register_chosen_inline_handler(some_chosen_inline_handler, lambda chosen_inline_result: True) :param callback: :param state: @@ -793,8 +793,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin): .. code-block:: python3 - @dp.chosen_inline_handler(lambda chosen_inline_query: True) - async def some_chosen_inline_handler(chosen_inline_query: types.ChosenInlineResult) + @dp.chosen_inline_handler(lambda chosen_inline_result: True) + async def some_chosen_inline_handler(chosen_inline_result: types.ChosenInlineResult) :param state: :param custom_filters: diff --git a/aiogram/types/update.py b/aiogram/types/update.py index 4d5a74d5..957fdfa6 100644 --- a/aiogram/types/update.py +++ b/aiogram/types/update.py @@ -11,7 +11,7 @@ from .poll import Poll, PollAnswer from .pre_checkout_query import PreCheckoutQuery from .shipping_query import ShippingQuery from .chat_join_request import ChatJoinRequest -from ..utils import helper, deprecated +from ..utils import helper class Update(base.TelegramObject): @@ -70,12 +70,6 @@ class AllowedUpdates(helper.Helper): CHAT_MEMBER = helper.ListItem() # chat_member CHAT_JOIN_REQUEST = helper.ListItem() # chat_join_request - CHOSEN_INLINE_QUERY = deprecated.DeprecatedReadOnlyClassVar( - "`CHOSEN_INLINE_QUERY` is a deprecated value for allowed update. " - "Use `CHOSEN_INLINE_RESULT`", - new_value_getter=lambda cls: cls.CHOSEN_INLINE_RESULT, - ) - @classmethod def default(cls): return [] From b39672f9b6641dea538d9176546e60f97dc55f74 Mon Sep 17 00:00:00 2001 From: Oleg A Date: Tue, 8 Feb 2022 03:29:53 +0300 Subject: [PATCH 2/7] [2.x] Don't save error as a file (#813) * fix: don't save error as file Raise an aiohttp.ClientResponseError if the response status is 400 or higher #799 * fix tests Co-authored-by: darksidecat <58224121+darksidecat@users.noreply.github.com> --- aiogram/bot/base.py | 8 +++- tests/test_bot/test_bot_download_file.py | 35 +++++++++++------ tests/types/test_mixins.py | 48 +++++++++++++++--------- 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index f885e6dc..8fd20949 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -277,7 +277,13 @@ class BaseBot: dest = destination if isinstance(destination, io.IOBase) else open(destination, 'wb') session = await self.get_session() - async with session.get(url, timeout=timeout, proxy=self.proxy, proxy_auth=self.proxy_auth) as response: + async with session.get( + url, + timeout=timeout, + proxy=self.proxy, + proxy_auth=self.proxy_auth, + raise_for_status=True, + ) as response: while True: chunk = await response.content.read(chunk_size) if not chunk: diff --git a/tests/test_bot/test_bot_download_file.py b/tests/test_bot/test_bot_download_file.py index 195a06f7..11b16934 100644 --- a/tests/test_bot/test_bot_download_file.py +++ b/tests/test_bot/test_bot_download_file.py @@ -1,11 +1,14 @@ import os from io import BytesIO from pathlib import Path +from unittest.mock import AsyncMock import pytest +from aiohttp import ClientResponseError from aiogram import Bot from aiogram.types import File +from aiogram.utils.json import json from tests import TOKEN from tests.types.dataset import FILE @@ -14,12 +17,9 @@ pytestmark = pytest.mark.asyncio @pytest.fixture(name='bot') async def bot_fixture(): - async def get_file(): - return File(**FILE) - """ Bot fixture """ _bot = Bot(TOKEN) - _bot.get_file = get_file + _bot.get_file = AsyncMock(return_value=File(**FILE)) yield _bot session = await _bot.get_session() await session.close() @@ -37,43 +37,54 @@ def tmppath(tmpdir, request): os.chdir(request.config.invocation_dir) +@pytest.fixture() +def get_file_response(aresponses): + aresponses.add(response=aresponses.Response(body=json.dumps(FILE))) + + class TestBotDownload: - async def test_download_file(self, tmppath, bot, file): + async def test_download_file(self, tmppath, bot, file, get_file_response): f = await bot.download_file(file_path=file.file_path) assert len(f.read()) != 0 - async def test_download_file_destination(self, tmppath, bot, file): + async def test_download_file_destination(self, tmppath, bot, file, get_file_response): await bot.download_file(file_path=file.file_path, destination="test.file") assert os.path.isfile(tmppath.joinpath('test.file')) - async def test_download_file_destination_with_dir(self, tmppath, bot, file): + async def test_download_file_destination_with_dir(self, tmppath, bot, file, get_file_response): await bot.download_file(file_path=file.file_path, destination=os.path.join('dir_name', 'file_name')) assert os.path.isfile(tmppath.joinpath('dir_name', 'file_name')) - async def test_download_file_destination_raise_file_not_found(self, tmppath, bot, file): + async def test_download_file_destination_raise_file_not_found(self, tmppath, bot, file, get_file_response): with pytest.raises(FileNotFoundError): await bot.download_file(file_path=file.file_path, destination=os.path.join('dir_name', 'file_name'), make_dirs=False) - async def test_download_file_destination_io_bytes(self, tmppath, bot, file): + async def test_download_file_destination_io_bytes(self, tmppath, bot, file, get_file_response): f = BytesIO() await bot.download_file(file_path=file.file_path, destination=f) assert len(f.read()) != 0 - async def test_download_file_raise_value_error(self, tmppath, bot, file): + async def test_download_file_raise_value_error(self, tmppath, bot, file, get_file_response): with pytest.raises(ValueError): await bot.download_file(file_path=file.file_path, destination="a", destination_dir="b") - async def test_download_file_destination_dir(self, tmppath, bot, file): + async def test_download_file_destination_dir(self, tmppath, bot, file, get_file_response): await bot.download_file(file_path=file.file_path, destination_dir='test_dir') assert os.path.isfile(tmppath.joinpath('test_dir', file.file_path)) - async def test_download_file_destination_dir_raise_file_not_found(self, tmppath, bot, file): + async def test_download_file_destination_dir_raise_file_not_found(self, tmppath, bot, file, get_file_response): with pytest.raises(FileNotFoundError): await bot.download_file(file_path=file.file_path, destination_dir='test_dir', make_dirs=False) assert os.path.isfile(tmppath.joinpath('test_dir', file.file_path)) + + async def test_download_file_404(self, tmppath, bot, file): + with pytest.raises(ClientResponseError) as exc_info: + await bot.download_file(file_path=file.file_path) + + assert exc_info.value.status == 404 diff --git a/tests/types/test_mixins.py b/tests/types/test_mixins.py index 4ee4381a..c3f816c8 100644 --- a/tests/types/test_mixins.py +++ b/tests/types/test_mixins.py @@ -1,12 +1,15 @@ import os from io import BytesIO from pathlib import Path +from unittest.mock import AsyncMock import pytest +from aiohttp import ClientResponseError from aiogram import Bot from aiogram.types import File from aiogram.types.mixins import Downloadable +from aiogram.utils.json import json from tests import TOKEN from tests.types.dataset import FILE @@ -18,7 +21,8 @@ async def bot_fixture(): """ Bot fixture """ _bot = Bot(TOKEN) yield _bot - await (await _bot.get_session()).close() + session = await _bot.get_session() + await session.close() @pytest.fixture @@ -30,73 +34,81 @@ def tmppath(tmpdir, request): @pytest.fixture def downloadable(bot): - async def get_file(): - return File(**FILE) - downloadable = Downloadable() - downloadable.get_file = get_file + downloadable.get_file = AsyncMock(return_value=File(**FILE)) downloadable.bot = bot return downloadable +@pytest.fixture() +def get_file_response(aresponses): + aresponses.add(response=aresponses.Response(body=json.dumps(FILE))) + + class TestDownloadable: - async def test_download_make_dirs_false_nodir(self, tmppath, downloadable): + async def test_download_make_dirs_false_nodir(self, tmppath, downloadable, get_file_response): with pytest.raises(FileNotFoundError): await downloadable.download(make_dirs=False) - async def test_download_make_dirs_false_mkdir(self, tmppath, downloadable): + async def test_download_make_dirs_false_mkdir(self, tmppath, downloadable, get_file_response): os.mkdir('voice') await downloadable.download(make_dirs=False) assert os.path.isfile(tmppath.joinpath(FILE["file_path"])) - async def test_download_make_dirs_true(self, tmppath, downloadable): + async def test_download_make_dirs_true(self, tmppath, downloadable, get_file_response): await downloadable.download(make_dirs=True) assert os.path.isfile(tmppath.joinpath(FILE["file_path"])) - async def test_download_deprecation_warning(self, tmppath, downloadable): + async def test_download_deprecation_warning(self, tmppath, downloadable, get_file_response): with pytest.deprecated_call(): await downloadable.download("test.file") - async def test_download_destination(self, tmppath, downloadable): + async def test_download_destination(self, tmppath, downloadable, get_file_response): with pytest.deprecated_call(): await downloadable.download("test.file") assert os.path.isfile(tmppath.joinpath('test.file')) - async def test_download_destination_dir_exist(self, tmppath, downloadable): + async def test_download_destination_dir_exist(self, tmppath, downloadable, get_file_response): os.mkdir("test_folder") with pytest.deprecated_call(): await downloadable.download("test_folder") assert os.path.isfile(tmppath.joinpath('test_folder', FILE["file_path"])) - async def test_download_destination_with_dir(self, tmppath, downloadable): + async def test_download_destination_with_dir(self, tmppath, downloadable, get_file_response): with pytest.deprecated_call(): await downloadable.download(os.path.join('dir_name', 'file_name')) assert os.path.isfile(tmppath.joinpath('dir_name', 'file_name')) - async def test_download_destination_io_bytes(self, tmppath, downloadable): + async def test_download_destination_io_bytes(self, tmppath, downloadable, get_file_response): file = BytesIO() with pytest.deprecated_call(): await downloadable.download(file) assert len(file.read()) != 0 - async def test_download_raise_value_error(self, tmppath, downloadable): + async def test_download_raise_value_error(self, tmppath, downloadable, get_file_response): with pytest.raises(ValueError): await downloadable.download(destination_dir="a", destination_file="b") - async def test_download_destination_dir(self, tmppath, downloadable): + async def test_download_destination_dir(self, tmppath, downloadable, get_file_response): await downloadable.download(destination_dir='test_dir') assert os.path.isfile(tmppath.joinpath('test_dir', FILE["file_path"])) - async def test_download_destination_file(self, tmppath, downloadable): + async def test_download_destination_file(self, tmppath, downloadable, get_file_response): await downloadable.download(destination_file='file_name') assert os.path.isfile(tmppath.joinpath('file_name')) - async def test_download_destination_file_with_dir(self, tmppath, downloadable): + async def test_download_destination_file_with_dir(self, tmppath, downloadable, get_file_response): await downloadable.download(destination_file=os.path.join('dir_name', 'file_name')) assert os.path.isfile(tmppath.joinpath('dir_name', 'file_name')) - async def test_download_io_bytes(self, tmppath, downloadable): + async def test_download_io_bytes(self, tmppath, downloadable, get_file_response): file = BytesIO() await downloadable.download(destination_file=file) assert len(file.read()) != 0 + + async def test_download_404(self, tmppath, downloadable): + with pytest.raises(ClientResponseError) as exc_info: + await downloadable.download(destination_file='file_name') + + assert exc_info.value.status == 404 From 24e933bdde4f1eb1532aabcf860111ea4a9b79d8 Mon Sep 17 00:00:00 2001 From: Oleg A Date: Tue, 8 Feb 2022 03:30:41 +0300 Subject: [PATCH 3/7] Fix: default parent for `__setitem__` (#806) * fix: self is default parent * chore: mv bot fixture * chore: add update_chat test * fix: string CHAT_PHOTO data --- aiogram/types/base.py | 2 +- tests/conftest.py | 16 +++++++++++++++ tests/test_bot.py | 8 -------- tests/test_dispatcher.py | 8 -------- tests/test_message.py | 10 +--------- tests/types/dataset.py | 42 ++++++++++++++++++++++++++++++++++++++++ tests/types/test_chat.py | 16 +++++++++++++-- 7 files changed, 74 insertions(+), 28 deletions(-) diff --git a/aiogram/types/base.py b/aiogram/types/base.py index 5bb29472..cca3d5f1 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -240,7 +240,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject): :return: """ if key in self.props: - return self.props[key].set_value(self, value, self.conf.get('parent', None)) + return self.props[key].set_value(self, value, self.conf.get('parent', self)) self.values[key] = value # Log warning when Telegram silently adds new Fields diff --git a/tests/conftest.py b/tests/conftest.py index b56c7b77..455f8b0d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,12 @@ +import asyncio + import aioredis import pytest from _pytest.config import UsageError +from aiogram import Bot +from . import TOKEN + try: import aioredis.util except ImportError: @@ -72,3 +77,14 @@ def redis_options(request): raise UsageError(f"Invalid redis URI {redis_uri!r}: {e}") raise UsageError("Unsupported aioredis version") + + +@pytest.fixture(name='bot') +async def bot_fixture(): + """Bot fixture.""" + bot = Bot(TOKEN) + yield bot + session = await bot.get_session() + if session and not session.closed: + await session.close() + await asyncio.sleep(0.2) diff --git a/tests/test_bot.py b/tests/test_bot.py index b2da7952..eea20b44 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -6,14 +6,6 @@ from . import FakeTelegram, TOKEN, BOT_ID pytestmark = pytest.mark.asyncio -@pytest.fixture(name='bot') -async def bot_fixture(): - """ Bot fixture """ - _bot = Bot(TOKEN, parse_mode=types.ParseMode.MARKDOWN_V2) - yield _bot - await _bot.close() - - async def test_get_me(bot: Bot): """ getMe method test """ from .types.dataset import USER diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index 81ae565c..e38d7c4a 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -5,14 +5,6 @@ from aiogram import Dispatcher, Bot pytestmark = pytest.mark.asyncio -@pytest.fixture(name='bot') -async def bot_fixture(): - """ Bot fixture """ - _bot = Bot(token='123456789:AABBCCDDEEFFaabbccddeeff-1234567890') - yield _bot - await _bot.close() - - class TestDispatcherInit: async def test_successful_init(self, bot): """ diff --git a/tests/test_message.py b/tests/test_message.py index 6fca789f..bc53dfbc 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -8,16 +8,8 @@ from . import FakeTelegram, TOKEN pytestmark = pytest.mark.asyncio -@pytest.fixture(name='bot') -async def bot_fixture(): - """ Bot fixture """ - _bot = Bot(TOKEN, parse_mode=types.ParseMode.HTML) - yield _bot - await _bot.close() - - @pytest.fixture() -async def message(bot): +async def message(bot: Bot): """ Message fixture :param bot: Telegram bot fixture diff --git a/tests/types/dataset.py b/tests/types/dataset.py index be23da9e..bd73f377 100644 --- a/tests/types/dataset.py +++ b/tests/types/dataset.py @@ -19,6 +19,14 @@ CHAT = { "type": "private", } +CHAT_PHOTO = { + "small_file_id": "small_file_id", + "small_file_unique_id": "small_file_unique_id", + "big_file_id": "big_file_id", + "big_file_unique_id": "big_file_unique_id", +} + + PHOTO = { "file_id": "AgADBAADFak0G88YZAf8OAug7bHyS9x2ZxkABHVfpJywcloRAAGAAQABAg", "file_size": 1101, @@ -485,3 +493,37 @@ REPLY_KEYBOARD_MARKUP = { "keyboard": [[{"text": "something here"}]], "resize_keyboard": True, } + +CHAT_PERMISSIONS = { + "can_send_messages": True, + "can_send_media_messages": True, + "can_send_polls": True, + "can_send_other_messages": True, + "can_add_web_page_previews": True, + "can_change_info": True, + "can_invite_users": True, + "can_pin_messages": True, +} + +CHAT_LOCATION = { + "location": LOCATION, + "address": "address", +} + +FULL_CHAT = { + **CHAT, + "photo": CHAT_PHOTO, + "bio": "bio", + "has_private_forwards": False, + "description": "description", + "invite_link": "invite_link", + "pinned_message": MESSAGE, + "permissions": CHAT_PERMISSIONS, + "slow_mode_delay": 10, + "message_auto_delete_time": 60, + "has_protected_content": True, + "sticker_set_name": "sticker_set_name", + "can_set_sticker_set": True, + "linked_chat_id": -1234567890, + "location": CHAT_LOCATION, +} diff --git a/tests/types/test_chat.py b/tests/types/test_chat.py index 1caa228d..c8e20146 100644 --- a/tests/types/test_chat.py +++ b/tests/types/test_chat.py @@ -1,5 +1,10 @@ -from aiogram import types -from .dataset import CHAT +import pytest + +from aiogram import Bot, types +from .dataset import CHAT, FULL_CHAT +from .. import FakeTelegram + +pytestmark = pytest.mark.asyncio chat = types.Chat(**CHAT) @@ -59,3 +64,10 @@ def test_chat_actions(): assert types.ChatActions.FIND_LOCATION == 'find_location' assert types.ChatActions.RECORD_VIDEO_NOTE == 'record_video_note' assert types.ChatActions.UPLOAD_VIDEO_NOTE == 'upload_video_note' + + +async def test_update_chat(bot: Bot): + Bot.set_current(bot) + async with FakeTelegram(message_data=FULL_CHAT): + await chat.update_chat() + assert chat.to_python() == types.Chat(**FULL_CHAT).to_python() From bb1c774bccd084248bf47338255d1d375949e40b Mon Sep 17 00:00:00 2001 From: Grigory Statsenko Date: Sun, 13 Feb 2022 15:56:15 +0300 Subject: [PATCH 4/7] Add support for custom kwargs for AsyncIOMotorClient in MongoStorage (#831) --- aiogram/contrib/fsm_storage/mongo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiogram/contrib/fsm_storage/mongo.py b/aiogram/contrib/fsm_storage/mongo.py index 0055743a..7a128f1c 100644 --- a/aiogram/contrib/fsm_storage/mongo.py +++ b/aiogram/contrib/fsm_storage/mongo.py @@ -50,7 +50,7 @@ class MongoStorage(BaseStorage): self._uri = uri self._username = username self._password = password - self._kwargs = kwargs + self._kwargs = kwargs # custom client options like SSL configuration, etc. self._mongo: Optional[AsyncIOMotorClient] = None self._db: Optional[AsyncIOMotorDatabase] = None @@ -63,7 +63,7 @@ class MongoStorage(BaseStorage): if self._uri: try: - self._mongo = AsyncIOMotorClient(self._uri) + self._mongo = AsyncIOMotorClient(self._uri, **self._kwargs) except pymongo.errors.ConfigurationError as e: if "query() got an unexpected keyword argument 'lifetime'" in e.args[0]: import logging From 4a4eb51bd407c2591d12ee265e9e4c1bd356ac1d Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sun, 20 Feb 2022 17:11:04 +0200 Subject: [PATCH 5/7] Changed skip updates method (#842) --- aiogram/dispatcher/dispatcher.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py index 5b3cafd3..7abe1d8d 100644 --- a/aiogram/dispatcher/dispatcher.py +++ b/aiogram/dispatcher/dispatcher.py @@ -216,11 +216,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin): async def skip_updates(self): """ You can skip old incoming updates from queue. - This method is not recommended to use if you use payments or you bot has high-load. + This method is not recommended for using in production. - :return: None + Note that the webhook will be deleted! """ - await self.bot.get_updates(offset=-1, timeout=1) + await self.bot.delete_webhook(drop_pending_updates=True) async def process_updates(self, updates, fast: bool = True): """ From fe48a4a0141c5e4a264004e0a069dbad65f1dec7 Mon Sep 17 00:00:00 2001 From: viuipan <37146584+viuipan@users.noreply.github.com> Date: Wed, 2 Mar 2022 02:04:47 +0300 Subject: [PATCH 6/7] fix issue 812 (#843) --- aiogram/types/input_message_content.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aiogram/types/input_message_content.py b/aiogram/types/input_message_content.py index 8406bd05..6b8bad22 100644 --- a/aiogram/types/input_message_content.py +++ b/aiogram/types/input_message_content.py @@ -145,7 +145,7 @@ class InputTextMessageContent(InputMessageContent): """ message_text: base.String = fields.Field() parse_mode: typing.Optional[base.String] = fields.Field() - caption_entities: typing.Optional[typing.List[MessageEntity]] = fields.Field() + entities: typing.Optional[typing.List[MessageEntity]] = fields.Field() disable_web_page_preview: base.Boolean = fields.Field() def safe_get_parse_mode(self): @@ -164,7 +164,7 @@ class InputTextMessageContent(InputMessageContent): self, message_text: base.String, parse_mode: typing.Optional[base.String] = None, - caption_entities: typing.Optional[typing.List[MessageEntity]] = None, + entities: typing.Optional[typing.List[MessageEntity]] = None, disable_web_page_preview: typing.Optional[base.Boolean] = None, ): if parse_mode is None: @@ -175,7 +175,7 @@ class InputTextMessageContent(InputMessageContent): super().__init__( message_text=message_text, parse_mode=parse_mode, - caption_entities=caption_entities, + entities=entities, disable_web_page_preview=disable_web_page_preview, ) From 613cfb8df087bca5e9bcc5a2f3df8bbda1233094 Mon Sep 17 00:00:00 2001 From: Vishwa Kumaresh Date: Sat, 5 Mar 2022 06:04:53 +0530 Subject: [PATCH 7/7] fix: setup.py (#856) Changed requires_python to python_requires --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a381507a..afa3f673 100755 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ setup( url='https://github.com/aiogram/aiogram', license='MIT', author='Alex Root Junior', - requires_python='>=3.7', + python_requires='>=3.7', author_email='jroot.junior@gmail.com', description='Is a pretty simple and fully asynchronous framework for Telegram Bot API', long_description=get_description(),