mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge branch 'aiogram:dev-2.x' into patch-1
This commit is contained in:
commit
d180b47869
15 changed files with 147 additions and 78 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
2
setup.py
2
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(),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue