Reworked request builder (#1142)

* Reworked request builder

* Added more default values

* Update tests

* Fixed timestamp

* Fixed Py3.8 support

* Describe changes
This commit is contained in:
Alex Root Junior 2023-03-11 20:46:36 +02:00 committed by GitHub
parent 924a83966d
commit fea1b7b0a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
300 changed files with 1003 additions and 3448 deletions

View file

@ -1,5 +1,5 @@
import asyncio
from typing import AsyncContextManager, AsyncGenerator
from typing import Any, AsyncContextManager, AsyncGenerator, Dict, List
from unittest.mock import AsyncMock, patch
import aiohttp_socks
@ -11,8 +11,8 @@ from aiogram import Bot
from aiogram.client.session import aiohttp
from aiogram.client.session.aiohttp import AiohttpSession
from aiogram.exceptions import TelegramNetworkError
from aiogram.methods import Request, TelegramMethod
from aiogram.types import UNSET, InputFile
from aiogram.methods import TelegramMethod
from aiogram.types import UNSET_PARSE_MODE, InputFile
from tests.mocked_bot import MockedBot
@ -103,44 +103,60 @@ class TestAiohttpSession:
await session.close()
mocked_close.assert_called_once()
def test_build_form_data_with_data_only(self):
request = Request(
method="method",
data={
"str": "value",
"int": 42,
"bool": True,
"unset": UNSET,
"null": None,
"list": ["foo"],
"dict": {"bar": "baz"},
},
)
def test_build_form_data_with_data_only(self, bot: MockedBot):
class TestMethod(TelegramMethod[bool]):
__api_method__ = "test"
__returning__ = bool
str_: str
int_: int
bool_: bool
unset_: str = UNSET_PARSE_MODE
null_: None
list_: List[str]
dict_: Dict[str, Any]
session = AiohttpSession()
form = session.build_form_data(request)
form = session.build_form_data(
bot,
TestMethod(
str_="value",
int_=42,
bool_=True,
unset_=UNSET_PARSE_MODE,
null_=None,
list_=["foo"],
dict_={"bar": "baz"},
),
)
fields = form._fields
assert len(fields) == 5
assert all(isinstance(field[2], str) for field in fields)
assert "null" not in [item[0]["name"] for item in fields]
assert "null_" not in [item[0]["name"] for item in fields]
def test_build_form_data_with_files(self):
request = Request(
method="method",
data={"key": "value"},
files={"document": BareInputFile(filename="file.txt")},
)
def test_build_form_data_with_files(self, bot: Bot):
class TestMethod(TelegramMethod[bool]):
__api_method__ = "test"
__returning__ = bool
key: str
document: InputFile
session = AiohttpSession()
form = session.build_form_data(request)
form = session.build_form_data(
bot,
TestMethod(key="value", document=BareInputFile(filename="file.txt")),
)
fields = form._fields
assert len(fields) == 2
assert len(fields) == 3
assert fields[1][0]["name"] == "document"
assert fields[1][0]["filename"] == "file.txt"
assert isinstance(fields[1][2], BareInputFile)
assert fields[1][2].startswith("attach://")
assert fields[2][0]["name"] == fields[1][2][9:]
assert fields[2][0]["filename"] == "file.txt"
assert isinstance(fields[2][2], BareInputFile)
async def test_make_request(self, bot: MockedBot, aresponses: ResponsesMockServer):
aresponses.add(
@ -158,9 +174,7 @@ class TestAiohttpSession:
class TestMethod(TelegramMethod[int]):
__returning__ = int
def build_request(self, bot: Bot) -> Request:
return Request(method="method", data={})
__api_method__ = "method"
call = TestMethod()

View file

@ -1,14 +1,15 @@
import datetime
import json
from typing import AsyncContextManager, AsyncGenerator, Optional
from typing import Any, AsyncContextManager, AsyncGenerator, Optional
from unittest.mock import AsyncMock, patch
import pytest
from pytz import utc
from aiogram import Bot
from aiogram.client.session.base import BaseSession, TelegramType
from aiogram.client.telegram import PRODUCTION, TelegramAPIServer
from aiogram.enums import ChatType, TopicIconColor
from aiogram.enums import ChatType, ParseMode, TopicIconColor
from aiogram.exceptions import (
ClientDecodeError,
RestartingTelegram,
@ -24,7 +25,8 @@ from aiogram.exceptions import (
TelegramUnauthorizedError,
)
from aiogram.methods import DeleteMessage, GetMe, TelegramMethod
from aiogram.types import UNSET, User
from aiogram.types import UNSET_PARSE_MODE, User
from aiogram.types.base import UNSET_DISABLE_WEB_PAGE_PREVIEW, UNSET_PROTECT_CONTENT
from tests.mocked_bot import MockedBot
@ -33,17 +35,21 @@ class CustomSession(BaseSession):
pass
async def make_request(
self, token: str, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
self,
token: str,
method: TelegramMethod[TelegramType],
timeout: Optional[int] = UNSET_PARSE_MODE,
) -> None: # type: ignore
assert isinstance(token, str)
assert isinstance(method, TelegramMethod)
async def stream_content(
self, url: str, timeout: int, chunk_size: int
self, url: str, timeout: int, chunk_size: int, raise_for_status: bool
) -> AsyncGenerator[bytes, None]: # pragma: no cover
assert isinstance(url, str)
assert isinstance(timeout, int)
assert isinstance(chunk_size, int)
assert isinstance(raise_for_status, bool)
yield b"\f" * 10
@ -79,58 +85,56 @@ class TestBaseSession:
assert session.api == api
assert "example.com" in session.api.base
def test_prepare_value(self):
@pytest.mark.parametrize(
"value,result",
[
[None, None],
["text", "text"],
[ChatType.PRIVATE, "private"],
[TopicIconColor.RED, "16478047"],
[42, "42"],
[True, "true"],
[["test"], '["test"]'],
[["test", ["test"]], '["test", ["test"]]'],
[[{"test": "pass", "spam": None}], '[{"test": "pass"}]'],
[{"test": "pass", "number": 42, "spam": None}, '{"test": "pass", "number": 42}'],
[{"foo": {"test": "pass", "spam": None}}, '{"foo": {"test": "pass"}}'],
[
datetime.datetime(
year=2017, month=5, day=17, hour=4, minute=11, second=42, tzinfo=utc
),
"1494994302",
],
],
)
def test_prepare_value(self, value: Any, result: str, bot: MockedBot):
session = CustomSession()
now = datetime.datetime.now()
assert session.prepare_value(value, bot=bot, files={}) == result
assert session.prepare_value("text") == "text"
assert session.prepare_value(["test"]) == '["test"]'
assert session.prepare_value({"test": "ok"}) == '{"test": "ok"}'
assert session.prepare_value(now) == str(round(now.timestamp()))
assert isinstance(session.prepare_value(datetime.timedelta(minutes=2)), str)
assert session.prepare_value(42) == "42"
assert session.prepare_value(ChatType.PRIVATE) == "private"
assert session.prepare_value(TopicIconColor.RED) == "16478047"
def test_clean_json(self):
def test_prepare_value_timedelta(self, bot: MockedBot):
session = CustomSession()
cleaned_dict = session.clean_json({"key": "value", "null": None})
assert "key" in cleaned_dict
assert "null" not in cleaned_dict
value = session.prepare_value(datetime.timedelta(minutes=2), bot=bot, files={})
assert isinstance(value, str)
cleaned_list = session.clean_json(["kaboom", 42, None])
assert len(cleaned_list) == 2
assert 42 in cleaned_list
assert None not in cleaned_list
assert cleaned_list[0] == "kaboom"
def test_clean_json_with_nested_json(self):
session = CustomSession()
cleaned = session.clean_json(
{
"key": "value",
"null": None,
"nested_list": ["kaboom", 42, None],
"nested_dict": {"key": "value", "null": None},
}
def test_prepare_value_defaults_replace(self):
bot = MockedBot(
parse_mode=ParseMode.HTML,
protect_content=True,
disable_web_page_preview=True,
)
assert bot.session.prepare_value(UNSET_PARSE_MODE, bot=bot, files={}) == "HTML"
assert (
bot.session.prepare_value(UNSET_DISABLE_WEB_PAGE_PREVIEW, bot=bot, files={}) == "true"
)
assert bot.session.prepare_value(UNSET_PROTECT_CONTENT, bot=bot, files={}) == "true"
assert len(cleaned) == 3
assert "null" not in cleaned
assert isinstance(cleaned["nested_list"], list)
assert cleaned["nested_list"] == ["kaboom", 42]
assert isinstance(cleaned["nested_dict"], dict)
assert cleaned["nested_dict"] == {"key": "value"}
def test_clean_json_not_json(self):
session = CustomSession()
assert session.clean_json(42) == 42
def test_prepare_value_defaults_unset(self):
bot = MockedBot()
assert bot.session.prepare_value(UNSET_PARSE_MODE, bot=bot, files={}) is None
assert bot.session.prepare_value(UNSET_DISABLE_WEB_PAGE_PREVIEW, bot=bot, files={}) is None
assert bot.session.prepare_value(UNSET_PROTECT_CONTENT, bot=bot, files={}) is None
@pytest.mark.parametrize(
"status_code,content,error",
@ -210,7 +214,10 @@ class TestBaseSession:
async def test_stream_content(self):
session = CustomSession()
stream = session.stream_content(
"https://www.python.org/static/img/python-logo.png", timeout=5, chunk_size=65536
"https://www.python.org/static/img/python-logo.png",
timeout=5,
chunk_size=65536,
raise_for_status=True,
)
assert isinstance(stream, AsyncGenerator)