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

@ -136,7 +136,7 @@ from ..methods import (
UploadStickerFile,
)
from ..types import (
UNSET,
UNSET_PARSE_MODE,
BotCommand,
BotCommandScope,
BotDescription,
@ -186,6 +186,7 @@ from ..types import (
UserProfilePhotos,
WebhookInfo,
)
from ..types.base import UNSET_DISABLE_WEB_PAGE_PREVIEW, UNSET_PROTECT_CONTENT
from .session.aiohttp import AiohttpSession
from .session.base import BaseSession
@ -198,6 +199,8 @@ class Bot(ContextInstanceMixin["Bot"]):
token: str,
session: Optional[BaseSession] = None,
parse_mode: Optional[str] = None,
disable_web_page_preview: Optional[bool] = None,
protect_content: Optional[bool] = None,
) -> None:
"""
Bot class
@ -207,6 +210,10 @@ class Bot(ContextInstanceMixin["Bot"]):
If not specified it will be automatically created.
:param parse_mode: Default parse mode.
If specified it will be propagated into the API methods at runtime.
:param disable_web_page_preview: Default disable_web_page_preview mode.
If specified it will be propagated into the API methods at runtime.
:param protect_content: Default protect_content mode.
If specified it will be propagated into the API methods at runtime.
:raise TokenValidationError: When token has invalid format this exception will be raised
"""
@ -217,6 +224,8 @@ class Bot(ContextInstanceMixin["Bot"]):
self.session = session
self.parse_mode = parse_mode
self.disable_web_page_preview = disable_web_page_preview
self.protect_content = protect_content
self.__token = token
self._me: Optional[User] = None
@ -704,10 +713,10 @@ class Bot(ContextInstanceMixin["Bot"]):
message_id: int,
message_thread_id: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
caption_entities: Optional[List[MessageEntity]] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -1188,7 +1197,7 @@ class Bot(ContextInstanceMixin["Bot"]):
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
caption: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
caption_entities: Optional[List[MessageEntity]] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
request_timeout: Optional[int] = None,
@ -1331,9 +1340,9 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id: Optional[Union[int, str]] = None,
message_id: Optional[int] = None,
inline_message_id: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
entities: Optional[List[MessageEntity]] = None,
disable_web_page_preview: Optional[bool] = None,
disable_web_page_preview: Optional[bool] = UNSET_DISABLE_WEB_PAGE_PREVIEW,
reply_markup: Optional[InlineKeyboardMarkup] = None,
request_timeout: Optional[int] = None,
) -> Union[Message, bool]:
@ -1395,7 +1404,7 @@ class Bot(ContextInstanceMixin["Bot"]):
message_id: int,
message_thread_id: Optional[int] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
request_timeout: Optional[int] = None,
) -> Message:
"""
@ -1995,11 +2004,11 @@ class Bot(ContextInstanceMixin["Bot"]):
height: Optional[int] = None,
thumbnail: Optional[Union[InputFile, str]] = None,
caption: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
caption_entities: Optional[List[MessageEntity]] = None,
has_spoiler: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2058,14 +2067,14 @@ class Bot(ContextInstanceMixin["Bot"]):
audio: Union[InputFile, str],
message_thread_id: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
caption_entities: Optional[List[MessageEntity]] = None,
duration: Optional[int] = None,
performer: Optional[str] = None,
title: Optional[str] = None,
thumbnail: Optional[Union[InputFile, str]] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2156,7 +2165,7 @@ class Bot(ContextInstanceMixin["Bot"]):
last_name: Optional[str] = None,
vcard: Optional[str] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2205,7 +2214,7 @@ class Bot(ContextInstanceMixin["Bot"]):
message_thread_id: Optional[int] = None,
emoji: Optional[str] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2249,11 +2258,11 @@ class Bot(ContextInstanceMixin["Bot"]):
message_thread_id: Optional[int] = None,
thumbnail: Optional[Union[InputFile, str]] = None,
caption: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
caption_entities: Optional[List[MessageEntity]] = None,
disable_content_type_detection: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2306,7 +2315,7 @@ class Bot(ContextInstanceMixin["Bot"]):
game_short_name: str,
message_thread_id: Optional[int] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
@ -2367,7 +2376,7 @@ class Bot(ContextInstanceMixin["Bot"]):
send_email_to_provider: Optional[bool] = None,
is_flexible: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[InlineKeyboardMarkup] = None,
@ -2453,7 +2462,7 @@ class Bot(ContextInstanceMixin["Bot"]):
heading: Optional[int] = None,
proximity_alert_radius: Optional[int] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2506,7 +2515,7 @@ class Bot(ContextInstanceMixin["Bot"]):
media: List[Union[InputMediaAudio, InputMediaDocument, InputMediaPhoto, InputMediaVideo]],
message_thread_id: Optional[int] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
request_timeout: Optional[int] = None,
@ -2543,11 +2552,11 @@ class Bot(ContextInstanceMixin["Bot"]):
chat_id: Union[int, str],
text: str,
message_thread_id: Optional[int] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
entities: Optional[List[MessageEntity]] = None,
disable_web_page_preview: Optional[bool] = None,
disable_web_page_preview: Optional[bool] = UNSET_DISABLE_WEB_PAGE_PREVIEW,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2596,11 +2605,11 @@ class Bot(ContextInstanceMixin["Bot"]):
photo: Union[InputFile, str],
message_thread_id: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
caption_entities: Optional[List[MessageEntity]] = None,
has_spoiler: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2656,13 +2665,13 @@ class Bot(ContextInstanceMixin["Bot"]):
allows_multiple_answers: Optional[bool] = None,
correct_option_id: Optional[int] = None,
explanation: Optional[str] = None,
explanation_parse_mode: Optional[str] = UNSET,
explanation_parse_mode: Optional[str] = UNSET_PARSE_MODE,
explanation_entities: Optional[List[MessageEntity]] = None,
open_period: Optional[int] = None,
close_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None,
is_closed: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2728,7 +2737,7 @@ class Bot(ContextInstanceMixin["Bot"]):
message_thread_id: Optional[int] = None,
emoji: Optional[str] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2780,7 +2789,7 @@ class Bot(ContextInstanceMixin["Bot"]):
google_place_id: Optional[str] = None,
google_place_type: Optional[str] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2841,12 +2850,12 @@ class Bot(ContextInstanceMixin["Bot"]):
height: Optional[int] = None,
thumbnail: Optional[Union[InputFile, str]] = None,
caption: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
caption_entities: Optional[List[MessageEntity]] = None,
has_spoiler: Optional[bool] = None,
supports_streaming: Optional[bool] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2910,7 +2919,7 @@ class Bot(ContextInstanceMixin["Bot"]):
length: Optional[int] = None,
thumbnail: Optional[Union[InputFile, str]] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[
@ -2959,11 +2968,11 @@ class Bot(ContextInstanceMixin["Bot"]):
voice: Union[InputFile, str],
message_thread_id: Optional[int] = None,
caption: Optional[str] = None,
parse_mode: Optional[str] = UNSET,
parse_mode: Optional[str] = UNSET_PARSE_MODE,
caption_entities: Optional[List[MessageEntity]] = None,
duration: Optional[int] = None,
disable_notification: Optional[bool] = None,
protect_content: Optional[bool] = None,
protect_content: Optional[bool] = UNSET_PROTECT_CONTENT,
reply_to_message_id: Optional[int] = None,
allow_sending_without_reply: Optional[bool] = None,
reply_markup: Optional[

View file

@ -19,12 +19,12 @@ from typing import (
import certifi
from aiohttp import BasicAuth, ClientError, ClientSession, FormData, TCPConnector
from aiogram.methods import Request, TelegramMethod
from aiogram.methods import TelegramMethod
from ...exceptions import TelegramNetworkError
from ...methods.base import TelegramType
from ...types import InputFile
from .base import UNSET, BaseSession
from .base import BaseSession
if TYPE_CHECKING:
from ..bot import Bot
@ -130,15 +130,16 @@ class AiohttpSession(BaseSession):
if self._session is not None and not self._session.closed:
await self._session.close()
def build_form_data(self, request: Request) -> FormData:
def build_form_data(self, bot: Bot, method: TelegramMethod[TelegramType]) -> FormData:
form = FormData(quote_fields=False)
for key, value in request.data.items():
if value is None or value is UNSET:
files: Dict[str, InputFile] = {}
for key, value in method.dict().items():
value = self.prepare_value(value, bot=bot, files=files)
if not value:
continue
form.add_field(key, self.prepare_value(value))
if request.files:
for key, value in request.files.items():
form.add_field(key, value, filename=value.filename or key)
form.add_field(key, value)
for key, value in files.items():
form.add_field(key, value, filename=value.filename or key)
return form
async def make_request(
@ -146,9 +147,8 @@ class AiohttpSession(BaseSession):
) -> TelegramType:
session = await self.create_session()
request = method.build_request(bot)
url = self.api.api_url(token=bot.token, method=request.method)
form = self.build_form_data(request)
url = self.api.api_url(token=bot.token, method=method.__api_method__)
form = self.build_form_data(bot=bot, method=method)
try:
async with session.post(

View file

@ -3,6 +3,7 @@ from __future__ import annotations
import abc
import datetime
import json
import secrets
from enum import Enum
from http import HTTPStatus
from types import TracebackType
@ -11,10 +12,10 @@ from typing import (
Any,
AsyncGenerator,
Callable,
Dict,
Final,
Optional,
Type,
Union,
cast,
)
@ -37,7 +38,8 @@ from aiogram.exceptions import (
from ...methods import Response, TelegramMethod
from ...methods.base import TelegramType
from ...types import UNSET
from ...types import UNSET_PARSE_MODE, InputFile
from ...types.base import UNSET_DISABLE_WEB_PAGE_PREVIEW, UNSET_PROTECT_CONTENT
from ..telegram import PRODUCTION, TelegramAPIServer
from .middlewares.manager import RequestMiddlewareManager
@ -138,7 +140,10 @@ class BaseSession(abc.ABC):
@abc.abstractmethod
async def make_request(
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
self,
bot: Bot,
method: TelegramMethod[TelegramType],
timeout: Optional[int] = None,
) -> TelegramType: # pragma: no cover
"""
Make request to Telegram Bot API
@ -160,35 +165,81 @@ class BaseSession(abc.ABC):
"""
yield b""
def prepare_value(self, value: Any) -> Union[str, int, bool]:
def prepare_value(
self,
value: Any,
bot: Bot,
files: Dict[str, Any],
_dumps_json: bool = True,
) -> Any:
"""
Prepare value before send
"""
if value is None:
return None
if isinstance(value, str):
return value
if isinstance(value, (list, dict)):
return self.json_dumps(self.clean_json(value))
if value is UNSET_PARSE_MODE:
return self.prepare_value(
bot.parse_mode, bot=bot, files=files, _dumps_json=_dumps_json
)
if value is UNSET_DISABLE_WEB_PAGE_PREVIEW:
return self.prepare_value(
bot.disable_web_page_preview, bot=bot, files=files, _dumps_json=_dumps_json
)
if value is UNSET_PROTECT_CONTENT:
return self.prepare_value(
bot.protect_content, bot=bot, files=files, _dumps_json=_dumps_json
)
if isinstance(value, InputFile):
key = secrets.token_urlsafe(10)
files[key] = value
return f"attach://{key}"
if isinstance(value, dict):
value = {
key: prepared_item
for key, item in value.items()
if (
prepared_item := self.prepare_value(
item, bot=bot, files=files, _dumps_json=False
)
)
is not None
}
if _dumps_json:
return self.json_dumps(value)
return value
if isinstance(value, list):
value = [
prepared_item
for item in value
if (
prepared_item := self.prepare_value(
item, bot=bot, files=files, _dumps_json=False
)
)
is not None
]
if _dumps_json:
return self.json_dumps(value)
return value
if isinstance(value, datetime.timedelta):
now = datetime.datetime.now()
return str(round((now + value).timestamp()))
if isinstance(value, datetime.datetime):
return str(round(value.timestamp()))
if isinstance(value, Enum):
return self.prepare_value(value.value)
return str(value)
return self.prepare_value(value.value, bot=bot, files=files)
def clean_json(self, value: Any) -> Any:
"""
Clean data before send
"""
if isinstance(value, list):
return [self.clean_json(v) for v in value if v is not None]
if isinstance(value, dict):
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
if _dumps_json:
return self.json_dumps(value)
return value
async def __call__(
self, bot: Bot, method: TelegramMethod[TelegramType], timeout: Optional[int] = UNSET
self,
bot: Bot,
method: TelegramMethod[TelegramType],
timeout: Optional[int] = None,
) -> TelegramType:
middleware = self.middleware.wrap_middlewares(self.make_request, timeout=timeout)
return cast(TelegramType, await middleware(bot, method))