mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Migrate to hatchling (#1095)
* Migrate to hatchling instead of poetry, ruff instead of flake8 * Migrate to hatchling instead of poetry, ruff instead of flake8 * Update tests suite * venv? * -m venv? * Change dependencies * Remove venv * Change mypy config * Added changelog * Mark uvloop incompatible with pypy * Update release script * Use internal caching for dependencies * Re-disable cov branches * Added contributing guide
This commit is contained in:
parent
04ccb390d5
commit
f4ce4431f9
58 changed files with 799 additions and 3001 deletions
|
|
@ -1,5 +1,9 @@
|
|||
from aiogram.dispatcher.flags import FlagGenerator
|
||||
from contextlib import suppress
|
||||
|
||||
from aiogram.dispatcher.flags import FlagGenerator
|
||||
from . import methods
|
||||
from . import types
|
||||
from . import enums
|
||||
from .client import session
|
||||
from .client.bot import Bot
|
||||
from .dispatcher.dispatcher import Dispatcher
|
||||
|
|
@ -9,12 +13,10 @@ from .utils.magic_filter import MagicFilter
|
|||
from .utils.text_decorations import html_decoration as html
|
||||
from .utils.text_decorations import markdown_decoration as md
|
||||
|
||||
try:
|
||||
with suppress(ImportError):
|
||||
import uvloop as _uvloop
|
||||
|
||||
_uvloop.install()
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
F = MagicFilter()
|
||||
flags = FlagGenerator()
|
||||
|
|
@ -24,6 +26,7 @@ __all__ = (
|
|||
"__version__",
|
||||
"types",
|
||||
"methods",
|
||||
"enums",
|
||||
"Bot",
|
||||
"session",
|
||||
"Dispatcher",
|
||||
|
|
|
|||
|
|
@ -310,10 +310,9 @@ class Bot(ContextInstanceMixin["Bot"]):
|
|||
if isinstance(destination, (str, pathlib.Path)):
|
||||
await self.__download_file(destination=destination, stream=stream)
|
||||
return None
|
||||
else:
|
||||
return await self.__download_file_binary_io(
|
||||
destination=destination, seek=seek, stream=stream
|
||||
)
|
||||
return await self.__download_file_binary_io(
|
||||
destination=destination, seek=seek, stream=stream
|
||||
)
|
||||
finally:
|
||||
if close_stream:
|
||||
await stream.aclose()
|
||||
|
|
|
|||
|
|
@ -48,18 +48,22 @@ def _retrieve_basic(basic: _ProxyBasic) -> Dict[str, Any]:
|
|||
username = proxy_auth.login
|
||||
password = proxy_auth.password
|
||||
|
||||
return dict(
|
||||
proxy_type=proxy_type,
|
||||
host=host,
|
||||
port=port,
|
||||
username=username,
|
||||
password=password,
|
||||
rdns=True,
|
||||
)
|
||||
return {
|
||||
"proxy_type": proxy_type,
|
||||
"host": host,
|
||||
"port": port,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"rdns": True,
|
||||
}
|
||||
|
||||
|
||||
def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"], Dict[str, Any]]:
|
||||
from aiohttp_socks import ChainProxyConnector, ProxyConnector, ProxyInfo # type: ignore
|
||||
from aiohttp_socks import ( # type: ignore
|
||||
ChainProxyConnector,
|
||||
ProxyConnector,
|
||||
ProxyInfo,
|
||||
)
|
||||
|
||||
# since tuple is Iterable(compatible with _ProxyChain) object, we assume that
|
||||
# user wants chained proxies if tuple is a pair of string(url) and BasicAuth
|
||||
|
|
@ -74,7 +78,7 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"]
|
|||
for basic in chain_or_plain:
|
||||
infos.append(ProxyInfo(**_retrieve_basic(basic)))
|
||||
|
||||
return ChainProxyConnector, dict(proxy_infos=infos)
|
||||
return ChainProxyConnector, {"proxy_infos": infos}
|
||||
|
||||
|
||||
class AiohttpSession(BaseSession):
|
||||
|
|
|
|||
|
|
@ -6,7 +6,17 @@ import json
|
|||
from enum import Enum
|
||||
from http import HTTPStatus
|
||||
from types import TracebackType
|
||||
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Final, Optional, Type, Union, cast
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
AsyncGenerator,
|
||||
Callable,
|
||||
Final,
|
||||
Optional,
|
||||
Type,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
|
|
@ -165,8 +175,7 @@ class BaseSession(abc.ABC):
|
|||
return str(round(value.timestamp()))
|
||||
if isinstance(value, Enum):
|
||||
return self.prepare_value(value.value)
|
||||
else:
|
||||
return str(value)
|
||||
return str(value)
|
||||
|
||||
def clean_json(self, value: Any) -> Any:
|
||||
"""
|
||||
|
|
@ -174,7 +183,7 @@ class BaseSession(abc.ABC):
|
|||
"""
|
||||
if isinstance(value, list):
|
||||
return [self.clean_json(v) for v in value if v is not None]
|
||||
elif isinstance(value, dict):
|
||||
if isinstance(value, dict):
|
||||
return {k: self.clean_json(v) for k, v in value.items() if v is not None}
|
||||
return value
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ class TelegramAPIServer:
|
|||
file: str
|
||||
"""Files URL"""
|
||||
is_local: bool = False
|
||||
"""Mark this server is in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_."""
|
||||
"""Mark this server is
|
||||
in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_."""
|
||||
wrap_local_file: FilesPathWrapper = BareFilesPathWrapper()
|
||||
"""Callback to wrap files path in local mode"""
|
||||
|
||||
|
|
|
|||
|
|
@ -107,13 +107,13 @@ class Dispatcher(Router):
|
|||
return self.fsm.storage
|
||||
|
||||
@property
|
||||
def parent_router(self) -> None:
|
||||
def parent_router(self) -> Optional[Router]:
|
||||
"""
|
||||
Dispatcher has no parent router and can't be included to any other routers or dispatchers
|
||||
|
||||
:return:
|
||||
"""
|
||||
return None
|
||||
return None # noqa: RET501
|
||||
|
||||
@parent_router.setter
|
||||
def parent_router(self, value: Router) -> None:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ class EventObserver:
|
|||
"""
|
||||
Simple events observer
|
||||
|
||||
Is used for managing events is not related with Telegram (For example startup/shutdown processes)
|
||||
Is used for managing events is not related with Telegram
|
||||
(For example startup/shutdown processes)
|
||||
|
||||
Handlers can be registered via decorator or method
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ def extract_flags(handler: Union["HandlerObject", Dict[str, Any]]) -> Dict[str,
|
|||
handler = handler["handler"]
|
||||
if not hasattr(handler, "flags"):
|
||||
return {}
|
||||
return handler.flags # type: ignore
|
||||
return handler.flags
|
||||
|
||||
|
||||
def get_flag(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import functools
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence, Union, overload
|
||||
|
||||
from aiogram.dispatcher.event.bases import MiddlewareEventType, MiddlewareType, NextMiddlewareType
|
||||
from aiogram.dispatcher.event.bases import (
|
||||
MiddlewareEventType,
|
||||
MiddlewareType,
|
||||
NextMiddlewareType,
|
||||
)
|
||||
from aiogram.dispatcher.event.handler import CallbackType
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class Router:
|
|||
if observer.handlers and update_name not in skip_events:
|
||||
handlers_in_use.add(update_name)
|
||||
|
||||
return list(sorted(handlers_in_use))
|
||||
return list(sorted(handlers_in_use)) # NOQA: C413
|
||||
|
||||
async def propagate_event(self, update_type: str, event: TelegramObject, **kwargs: Any) -> Any:
|
||||
kwargs.update(event_router=self)
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ class ClientDecodeError(AiogramError):
|
|||
original_type = type(self.original)
|
||||
return (
|
||||
f"{self.message}\n"
|
||||
f"Caused from error: {original_type.__module__}.{original_type.__name__}: {self.original}\n"
|
||||
f"Caused from error: "
|
||||
f"{original_type.__module__}.{original_type.__name__}: {self.original}\n"
|
||||
f"Content: {self.data}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ class Filter(ABC):
|
|||
|
||||
def update_handler_flags(self, flags: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Also if you want to extend handler flags with using this filter you should implement this method
|
||||
Also if you want to extend handler flags with using this filter
|
||||
you should implement this method
|
||||
|
||||
:param flags: existing flags, can be updated directly
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -3,7 +3,17 @@ from __future__ import annotations
|
|||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from fractions import Fraction
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Literal, Optional, Type, TypeVar, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
ClassVar,
|
||||
Dict,
|
||||
Literal,
|
||||
Optional,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from magic_filter import MagicFilter
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class _MemberStatusMarker:
|
|||
result = self.name.upper()
|
||||
if self.is_member is not None:
|
||||
result = ("+" if self.is_member else "-") + result
|
||||
return result
|
||||
return result # noqa: RET504
|
||||
|
||||
def __pos__(self: MarkerT) -> MarkerT:
|
||||
return type(self)(name=self.name, is_member=True)
|
||||
|
|
@ -38,7 +38,8 @@ class _MemberStatusMarker:
|
|||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return other | self
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||
f"unsupported operand type(s) for |: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
__ror__ = __or__
|
||||
|
|
@ -52,7 +53,8 @@ class _MemberStatusMarker:
|
|||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return _MemberStatusTransition(old=old, new=other)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for >>: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||
f"unsupported operand type(s) for >>: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __lshift__(
|
||||
|
|
@ -64,7 +66,8 @@ class _MemberStatusMarker:
|
|||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return _MemberStatusTransition(old=other, new=new)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for <<: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||
f"unsupported operand type(s) for <<: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
|
|
@ -89,10 +92,11 @@ class _MemberStatusGroupMarker:
|
|||
) -> MarkerGroupT:
|
||||
if isinstance(other, _MemberStatusMarker):
|
||||
return type(self)(*self.statuses, other)
|
||||
elif isinstance(other, _MemberStatusGroupMarker):
|
||||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return type(self)(*self.statuses, *other.statuses)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for |: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||
f"unsupported operand type(s) for |: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __rshift__(
|
||||
|
|
@ -103,7 +107,8 @@ class _MemberStatusGroupMarker:
|
|||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return _MemberStatusTransition(old=self, new=other)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for >>: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||
f"unsupported operand type(s) for >>: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __lshift__(
|
||||
|
|
@ -114,7 +119,8 @@ class _MemberStatusGroupMarker:
|
|||
if isinstance(other, _MemberStatusGroupMarker):
|
||||
return _MemberStatusTransition(old=other, new=self)
|
||||
raise TypeError(
|
||||
f"unsupported operand type(s) for <<: {type(self).__name__!r} and {type(other).__name__!r}"
|
||||
f"unsupported operand type(s) for <<: "
|
||||
f"{type(self).__name__!r} and {type(other).__name__!r}"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
|
@ -124,10 +130,7 @@ class _MemberStatusGroupMarker:
|
|||
return result
|
||||
|
||||
def check(self, *, member: ChatMember) -> bool:
|
||||
for status in self.statuses:
|
||||
if status.check(member=member):
|
||||
return True
|
||||
return False
|
||||
return any(status.check(member=member) for status in self.statuses)
|
||||
|
||||
|
||||
class _MemberStatusTransition:
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ class Command(Filter):
|
|||
await self.validate_mention(bot=bot, command=command)
|
||||
command = self.validate_command(command)
|
||||
command = self.do_magic(command=command)
|
||||
return command
|
||||
return command # noqa: RET504
|
||||
|
||||
def do_magic(self, command: CommandObject) -> Any:
|
||||
if not self.magic:
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class Text(Filter):
|
|||
@classmethod
|
||||
def _validate_constraints(cls, **values: Any) -> None:
|
||||
# Validate that only one text filter type is presented
|
||||
used_args = set(key for key, value in values.items() if value is not None)
|
||||
used_args = {key for key, value in values.items() if value is not None}
|
||||
if len(used_args) < 1:
|
||||
raise ValueError(f"Filter should contain one of arguments: {set(values.keys())}")
|
||||
if len(used_args) > 1:
|
||||
|
|
@ -133,5 +133,4 @@ class Text(Filter):
|
|||
def prepare_text(self, text: str) -> str:
|
||||
if self.ignore_case:
|
||||
return str(text).lower()
|
||||
else:
|
||||
return str(text)
|
||||
return str(text)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,12 @@ from typing import Any, Awaitable, Callable, Dict, Optional, cast
|
|||
from aiogram import Bot
|
||||
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.storage.base import DEFAULT_DESTINY, BaseEventIsolation, BaseStorage, StorageKey
|
||||
from aiogram.fsm.storage.base import (
|
||||
DEFAULT_DESTINY,
|
||||
BaseEventIsolation,
|
||||
BaseStorage,
|
||||
StorageKey,
|
||||
)
|
||||
from aiogram.fsm.strategy import FSMStrategy, apply_strategy
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ from typing import Any, AsyncGenerator, DefaultDict, Dict, Hashable, Optional
|
|||
|
||||
from aiogram import Bot
|
||||
from aiogram.fsm.state import State
|
||||
from aiogram.fsm.storage.base import BaseEventIsolation, BaseStorage, StateType, StorageKey
|
||||
from aiogram.fsm.storage.base import (
|
||||
BaseEventIsolation,
|
||||
BaseStorage,
|
||||
StateType,
|
||||
StorageKey,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -2,7 +2,16 @@ from __future__ import annotations
|
|||
|
||||
import abc
|
||||
import secrets
|
||||
from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, Optional, TypeVar, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Generator,
|
||||
Generic,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from pydantic import BaseConfig, BaseModel, Extra, root_validator
|
||||
from pydantic.generics import GenericModel
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@ from .audio import Audio
|
|||
from .base import UNSET, TelegramObject
|
||||
from .bot_command import BotCommand
|
||||
from .bot_command_scope import BotCommandScope
|
||||
from .bot_command_scope_all_chat_administrators import BotCommandScopeAllChatAdministrators
|
||||
from .bot_command_scope_all_chat_administrators import (
|
||||
BotCommandScopeAllChatAdministrators,
|
||||
)
|
||||
from .bot_command_scope_all_group_chats import BotCommandScopeAllGroupChats
|
||||
from .bot_command_scope_all_private_chats import BotCommandScopeAllPrivateChats
|
||||
from .bot_command_scope_chat import BotCommandScopeChat
|
||||
|
|
@ -110,7 +112,9 @@ from .passport_element_error_front_side import PassportElementErrorFrontSide
|
|||
from .passport_element_error_reverse_side import PassportElementErrorReverseSide
|
||||
from .passport_element_error_selfie import PassportElementErrorSelfie
|
||||
from .passport_element_error_translation_file import PassportElementErrorTranslationFile
|
||||
from .passport_element_error_translation_files import PassportElementErrorTranslationFiles
|
||||
from .passport_element_error_translation_files import (
|
||||
PassportElementErrorTranslationFiles,
|
||||
)
|
||||
from .passport_element_error_unspecified import PassportElementErrorUnspecified
|
||||
from .passport_file import PassportFile
|
||||
from .photo_size import PhotoSize
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ from typing import TYPE_CHECKING, Any, List, Optional, Union
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from aiogram.utils.text_decorations import TextDecoration, html_decoration, markdown_decoration
|
||||
from aiogram.utils.text_decorations import (
|
||||
TextDecoration,
|
||||
html_decoration,
|
||||
markdown_decoration,
|
||||
)
|
||||
|
||||
from ..enums import ContentType
|
||||
from .base import UNSET, TelegramObject
|
||||
|
|
@ -2465,7 +2469,7 @@ class Message(TelegramObject):
|
|||
**kwargs,
|
||||
)
|
||||
|
||||
def send_copy(
|
||||
def send_copy( # noqa: C901
|
||||
self: Message,
|
||||
chat_id: Union[str, int],
|
||||
disable_notification: Optional[bool] = None,
|
||||
|
|
@ -2538,7 +2542,7 @@ class Message(TelegramObject):
|
|||
|
||||
if self.text:
|
||||
return SendMessage(text=text, entities=entities, **kwargs)
|
||||
elif self.audio:
|
||||
if self.audio:
|
||||
return SendAudio(
|
||||
audio=self.audio.file_id,
|
||||
caption=text,
|
||||
|
|
@ -2548,29 +2552,29 @@ class Message(TelegramObject):
|
|||
caption_entities=entities,
|
||||
**kwargs,
|
||||
)
|
||||
elif self.animation:
|
||||
if self.animation:
|
||||
return SendAnimation(
|
||||
animation=self.animation.file_id, caption=text, caption_entities=entities, **kwargs
|
||||
)
|
||||
elif self.document:
|
||||
if self.document:
|
||||
return SendDocument(
|
||||
document=self.document.file_id, caption=text, caption_entities=entities, **kwargs
|
||||
)
|
||||
elif self.photo:
|
||||
if self.photo:
|
||||
return SendPhoto(
|
||||
photo=self.photo[-1].file_id, caption=text, caption_entities=entities, **kwargs
|
||||
)
|
||||
elif self.sticker:
|
||||
if self.sticker:
|
||||
return SendSticker(sticker=self.sticker.file_id, **kwargs)
|
||||
elif self.video:
|
||||
if self.video:
|
||||
return SendVideo(
|
||||
video=self.video.file_id, caption=text, caption_entities=entities, **kwargs
|
||||
)
|
||||
elif self.video_note:
|
||||
if self.video_note:
|
||||
return SendVideoNote(video_note=self.video_note.file_id, **kwargs)
|
||||
elif self.voice:
|
||||
if self.voice:
|
||||
return SendVoice(voice=self.voice.file_id, **kwargs)
|
||||
elif self.contact:
|
||||
if self.contact:
|
||||
return SendContact(
|
||||
phone_number=self.contact.phone_number,
|
||||
first_name=self.contact.first_name,
|
||||
|
|
@ -2578,7 +2582,7 @@ class Message(TelegramObject):
|
|||
vcard=self.contact.vcard,
|
||||
**kwargs,
|
||||
)
|
||||
elif self.venue:
|
||||
if self.venue:
|
||||
return SendVenue(
|
||||
latitude=self.venue.location.latitude,
|
||||
longitude=self.venue.location.longitude,
|
||||
|
|
@ -2588,20 +2592,20 @@ class Message(TelegramObject):
|
|||
foursquare_type=self.venue.foursquare_type,
|
||||
**kwargs,
|
||||
)
|
||||
elif self.location:
|
||||
if self.location:
|
||||
return SendLocation(
|
||||
latitude=self.location.latitude, longitude=self.location.longitude, **kwargs
|
||||
)
|
||||
elif self.poll:
|
||||
if self.poll:
|
||||
return SendPoll(
|
||||
question=self.poll.question,
|
||||
options=[option.text for option in self.poll.options],
|
||||
**kwargs,
|
||||
)
|
||||
elif self.dice: # Dice value can't be controlled
|
||||
if self.dice: # Dice value can't be controlled
|
||||
return SendDice(**kwargs)
|
||||
else:
|
||||
raise TypeError("This type of message can't be copied.")
|
||||
|
||||
raise TypeError("This type of message can't be copied.")
|
||||
|
||||
def copy_to(
|
||||
self,
|
||||
|
|
@ -3066,9 +3070,10 @@ class Message(TelegramObject):
|
|||
if self.chat.type in ("private", "group"):
|
||||
return None
|
||||
|
||||
if not self.chat.username or force_private:
|
||||
chat_value = f"c/{self.chat.shifted_id}"
|
||||
else:
|
||||
chat_value = self.chat.username
|
||||
chat_value = (
|
||||
f"c/{self.chat.shifted_id}"
|
||||
if not self.chat.username or force_private
|
||||
else self.chat.username
|
||||
)
|
||||
|
||||
return f"https://t.me/{chat_value}/{self.message_id}"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ def check_signature(token: str, hash: str, **kwargs: Any) -> bool:
|
|||
:return:
|
||||
"""
|
||||
secret = hashlib.sha256(token.encode("utf-8"))
|
||||
check_string = "\n".join(map(lambda k: f"{k}={kwargs[k]}", sorted(kwargs)))
|
||||
check_string = "\n".join(f"{k}={kwargs[k]}" for k in sorted(kwargs))
|
||||
hmac_string = hmac.new(
|
||||
secret.digest(), check_string.encode("utf-8"), digestmod=hashlib.sha256
|
||||
).hexdigest()
|
||||
|
|
|
|||
|
|
@ -77,4 +77,7 @@ class Backoff:
|
|||
self._next_delay = self.min_delay
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Backoff(tryings={self._counter}, current_delay={self._current_delay}, next_delay={self._next_delay})"
|
||||
return (
|
||||
f"Backoff(tryings={self._counter}, current_delay={self._current_delay}, "
|
||||
f"next_delay={self._next_delay})"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ class ChatActionSender:
|
|||
Provides simply to use context manager.
|
||||
|
||||
Technically sender start background task with infinity loop which works
|
||||
until action will be finished and sends the `chat action <https://core.telegram.org/bots/api#sendchataction>`_
|
||||
until action will be finished and sends the
|
||||
`chat action <https://core.telegram.org/bots/api#sendchataction>`_
|
||||
every 5 seconds.
|
||||
"""
|
||||
|
||||
|
|
@ -110,7 +111,7 @@ class ChatActionSender:
|
|||
async with self._lock:
|
||||
if not self.running:
|
||||
return
|
||||
if not self._close_event.is_set():
|
||||
if not self._close_event.is_set(): # pragma: no branches
|
||||
self._close_event.set()
|
||||
await self._closed_event.wait()
|
||||
self._task = None
|
||||
|
|
|
|||
|
|
@ -96,7 +96,8 @@ class KeyboardBuilder(Generic[ButtonType]):
|
|||
"""
|
||||
if not isinstance(row, list):
|
||||
raise ValueError(
|
||||
f"Row {row!r} should be type 'List[{self._button_type.__name__}]' not type {type(row).__name__}"
|
||||
f"Row {row!r} should be type 'List[{self._button_type.__name__}]' "
|
||||
f"not type {type(row).__name__}"
|
||||
)
|
||||
if len(row) > MAX_WIDTH:
|
||||
raise ValueError(f"Row {row!r} is too long (MAX_WIDTH={MAX_WIDTH})")
|
||||
|
|
@ -114,7 +115,8 @@ class KeyboardBuilder(Generic[ButtonType]):
|
|||
count = 0
|
||||
if not isinstance(markup, list):
|
||||
raise ValueError(
|
||||
f"Markup should be type 'List[List[{self._button_type.__name__}]]' not type {type(markup).__name__!r}"
|
||||
f"Markup should be type 'List[List[{self._button_type.__name__}]]' "
|
||||
f"not type {type(markup).__name__!r}"
|
||||
)
|
||||
for row in markup:
|
||||
self._validate_row(row)
|
||||
|
|
@ -206,7 +208,8 @@ class KeyboardBuilder(Generic[ButtonType]):
|
|||
|
||||
By default, when the sum of passed sizes is lower than buttons count the last
|
||||
one size will be used for tail of the markup.
|
||||
If repeat=True is passed - all sizes will be cycled when available more buttons count than all sizes
|
||||
If repeat=True is passed - all sizes will be cycled when available more buttons
|
||||
count than all sizes
|
||||
|
||||
:param sizes:
|
||||
:param repeat:
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ class AsFilterResultOperation(BaseOperation):
|
|||
self.name = name
|
||||
|
||||
def resolve(self, value: Any, initial_value: Any) -> Any:
|
||||
if value:
|
||||
return {self.name: value}
|
||||
if not value:
|
||||
return None
|
||||
return {self.name: value}
|
||||
|
||||
|
||||
class MagicFilter(_MagicFilter):
|
||||
|
|
|
|||
|
|
@ -81,13 +81,12 @@ class TextDecoration(ABC):
|
|||
:param entities: Array of MessageEntities
|
||||
:return:
|
||||
"""
|
||||
result = "".join(
|
||||
return "".join(
|
||||
self._unparse_entities(
|
||||
add_surrogates(text),
|
||||
sorted(entities, key=lambda item: item.offset) if entities else [],
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
def _unparse_entities(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@ class WebAppUser(TelegramObject):
|
|||
"""
|
||||
|
||||
id: int
|
||||
"""A unique identifier for the user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. It has at most 52 significant bits, so a 64-bit integer or a double-precision float type is safe for storing this identifier."""
|
||||
"""A unique identifier for the user or bot. This number may have more than 32 significant bits
|
||||
and some programming languages may have difficulty/silent defects in interpreting it.
|
||||
It has at most 52 significant bits, so a 64-bit integer or a double-precision float type
|
||||
is safe for storing this identifier."""
|
||||
is_bot: Optional[bool] = None
|
||||
"""True, if this user is a bot. Returns in the receiver field only."""
|
||||
first_name: str
|
||||
|
|
@ -29,24 +32,32 @@ class WebAppUser(TelegramObject):
|
|||
language_code: Optional[str] = None
|
||||
"""IETF language tag of the user's language. Returns in user field only."""
|
||||
photo_url: Optional[str] = None
|
||||
"""URL of the user’s profile photo. The photo can be in .jpeg or .svg formats. Only returned for Web Apps launched from the attachment menu."""
|
||||
"""URL of the user’s profile photo. The photo can be in .jpeg or .svg formats.
|
||||
Only returned for Web Apps launched from the attachment menu."""
|
||||
|
||||
|
||||
class WebAppInitData(TelegramObject):
|
||||
"""
|
||||
This object contains data that is transferred to the Web App when it is opened. It is empty if the Web App was launched from a keyboard button.
|
||||
This object contains data that is transferred to the Web App when it is opened.
|
||||
It is empty if the Web App was launched from a keyboard button.
|
||||
|
||||
Source: https://core.telegram.org/bots/webapps#webappinitdata
|
||||
"""
|
||||
|
||||
query_id: Optional[str] = None
|
||||
"""A unique identifier for the Web App session, required for sending messages via the answerWebAppQuery method."""
|
||||
"""A unique identifier for the Web App session, required for sending messages
|
||||
via the answerWebAppQuery method."""
|
||||
user: Optional[WebAppUser] = None
|
||||
"""An object containing data about the current user."""
|
||||
receiver: Optional[WebAppUser] = None
|
||||
"""An object containing data about the chat partner of the current user in the chat where the bot was launched via the attachment menu. Returned only for Web Apps launched via the attachment menu."""
|
||||
"""An object containing data about the chat partner of the current user in the chat where
|
||||
the bot was launched via the attachment menu.
|
||||
Returned only for Web Apps launched via the attachment menu."""
|
||||
start_param: Optional[str] = None
|
||||
"""The value of the startattach parameter, passed via link. Only returned for Web Apps when launched from the attachment menu via link. The value of the start_param parameter will also be passed in the GET-parameter tgWebAppStartParam, so the Web App can load the correct interface right away."""
|
||||
"""The value of the startattach parameter, passed via link.
|
||||
Only returned for Web Apps when launched from the attachment menu via link.
|
||||
The value of the start_param parameter will also be passed in the GET-parameter
|
||||
tgWebAppStartParam, so the Web App can load the correct interface right away."""
|
||||
auth_date: datetime
|
||||
"""Unix time when the form was opened."""
|
||||
hash: str
|
||||
|
|
|
|||
|
|
@ -88,7 +88,8 @@ class BaseRequestHandler(ABC):
|
|||
) -> None:
|
||||
"""
|
||||
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
||||
:param handle_in_background: immediately respond to the Telegram instead of waiting end of handler process
|
||||
:param handle_in_background: immediately respond to the Telegram instead of
|
||||
waiting end of handler process
|
||||
"""
|
||||
self.dispatcher = dispatcher
|
||||
self.handle_in_background = handle_in_background
|
||||
|
|
@ -166,7 +167,8 @@ class SimpleRequestHandler(BaseRequestHandler):
|
|||
) -> None:
|
||||
"""
|
||||
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
||||
:param handle_in_background: immediately respond to the Telegram instead of waiting end of handler process
|
||||
:param handle_in_background: immediately respond to the Telegram instead of
|
||||
waiting end of handler process
|
||||
:param bot: instance of :class:`aiogram.client.bot.Bot`
|
||||
"""
|
||||
super().__init__(dispatcher=dispatcher, handle_in_background=handle_in_background, **data)
|
||||
|
|
@ -184,7 +186,8 @@ class SimpleRequestHandler(BaseRequestHandler):
|
|||
|
||||
class TokenBasedRequestHandler(BaseRequestHandler):
|
||||
"""
|
||||
Handler that supports multiple bots, the context will be resolved from path variable 'bot_token'
|
||||
Handler that supports multiple bots, the context will be resolved
|
||||
from path variable 'bot_token'
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
@ -196,7 +199,8 @@ class TokenBasedRequestHandler(BaseRequestHandler):
|
|||
) -> None:
|
||||
"""
|
||||
:param dispatcher: instance of :class:`aiogram.dispatcher.dispatcher.Dispatcher`
|
||||
:param handle_in_background: immediately respond to the Telegram instead of waiting end of handler process
|
||||
:param handle_in_background: immediately respond to the Telegram instead of
|
||||
waiting end of handler process
|
||||
:param bot_settings: kwargs that will be passed to new Bot instance
|
||||
"""
|
||||
super().__init__(dispatcher=dispatcher, handle_in_background=handle_in_background, **data)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue