Migrate to hatchling instead of poetry, ruff instead of flake8

This commit is contained in:
Alex Root Junior 2023-01-08 21:33:20 +02:00
parent 04ccb390d5
commit c62a450253
No known key found for this signature in database
GPG key ID: 074C1D455EBEA4AC
48 changed files with 452 additions and 2772 deletions

View file

@ -1,6 +0,0 @@
[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
@abstractmethod
@overload

12
.flake8
View file

@ -1,12 +0,0 @@
[flake8]
max-line-length = 99
select = C,E,F,W,B,B950
ignore = E501,W503,E203
exclude =
.git
build
dist
venv
docs
*.egg-info
experiment.py

2
.gitignore vendored
View file

@ -13,7 +13,7 @@ dist/
site/
*.egg-info/
*.egg
aiogram/_meta.py
.ruff_cache
.mypy_cache
.pytest_cache

View file

@ -19,34 +19,8 @@ repos:
- id: black
files: &files '^(aiogram|tests|examples)'
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.10.1
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.215'
hooks:
- id: isort
additional_dependencies: [ toml ]
files: *files
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
args: [ '--config=.flake8' ]
files: *files
- repo: https://github.com/floatingpurr/sync_with_poetry
rev: 0.2.0
hooks:
- id: sync_with_poetry
- repo: https://github.com/python-poetry/poetry
rev: '1.2.1'
hooks:
- id: poetry-check
- id: poetry-lock
args: [ "--no-update" ]
- id: poetry-export
args: [ "-f", "requirements.txt", "--without-hashes", "-o", "requirements/base.txt" ]
- id: poetry-export
args: [ "-f", "requirements.txt", "--without-hashes", "-o", "requirements/docs.txt",
"-E", "fast", "-E", "redis", "-E", "proxy", "-E", "i18n",
"--with", "docs" ]
- id: ruff
args: [ "--force-exclude" ]

View file

@ -68,16 +68,15 @@ clean:
.PHONY: lint
lint:
$(py) isort --check-only $(code_dir)
$(py) black --check --diff $(code_dir)
$(py) flake8 $(code_dir)
$(py) mypy $(package_dir)
# TODO: wemake-python-styleguide
isort --check-only $(code_dir)
black --check --diff $(code_dir)
ruff $(code_dir)
mypy $(package_dir)
.PHONY: reformat
reformat:
$(py) black $(code_dir)
$(py) isort $(code_dir)
black $(code_dir)
isort $(code_dir)
# =================================================================================================
# Tests

View file

@ -1,3 +1,5 @@
from contextlib import suppress
from aiogram.dispatcher.flags import FlagGenerator
from .client import session
@ -9,12 +11,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()

View file

@ -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()

View file

@ -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):

View file

@ -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

View file

@ -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"""

View file

@ -113,7 +113,7 @@ class Dispatcher(Router):
:return:
"""
return None
pass
@parent_router.setter
def parent_router(self, value: Router) -> None:

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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}"
)

View file

@ -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
"""

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -2,7 +2,7 @@ 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
from pydantic import BaseConfig, BaseModel, Extra, root_validator
from pydantic.generics import GenericModel

View file

@ -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

View file

@ -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}"

View file

@ -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()

View file

@ -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})"
)

View file

@ -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.
"""

View file

@ -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:

View file

@ -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):

View file

@ -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,

View file

@ -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 users profile photo. The photo can be in .jpeg or .svg formats. Only returned for Web Apps launched from the attachment menu."""
"""URL of the users 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

View file

@ -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)

View file

@ -8,7 +8,12 @@ from aiogram import Bot, Dispatcher, F, Router, html
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import KeyboardButton, Message, ReplyKeyboardMarkup, ReplyKeyboardRemove
from aiogram.types import (
KeyboardButton,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
form_router = Router()

View file

@ -2,7 +2,6 @@ from os import getenv
from typing import Any, Dict, Union
from aiohttp import web
from finite_state_machine import form_router
from aiogram import Bot, Dispatcher, F, Router
from aiogram.client.session.aiohttp import AiohttpSession
@ -16,6 +15,7 @@ from aiogram.webhook.aiohttp_server import (
TokenBasedRequestHandler,
setup_application,
)
from finite_state_machine import form_router
main_router = Router()
@ -48,7 +48,7 @@ async def command_add_bot(message: Message, command: CommandObject, bot: Bot) ->
return message.answer("Invalid token")
await new_bot.delete_webhook(drop_pending_updates=True)
await new_bot.set_webhook(OTHER_BOTS_URL.format(bot_token=command.args))
await message.answer(f"Bot @{bot_user.username} successful added")
return await message.answer(f"Bot @{bot_user.username} successful added")
async def on_startup(dispatcher: Dispatcher, bot: Bot):

View file

@ -36,7 +36,6 @@ async def send_message_handler(request: Request):
except ValueError:
return json_response({"ok": False, "err": "Unauthorized"}, status=401)
print(data)
reply_markup = None
if data["with_webview"] == "1":
reply_markup = InlineKeyboardMarkup(

View file

@ -1,37 +0,0 @@
[mypy]
plugins = pydantic.mypy
python_version = 3.8
show_error_codes = True
show_error_context = True
pretty = True
ignore_missing_imports = False
warn_unused_configs = True
disallow_subclassing_any = True
disallow_any_generics = True
disallow_untyped_calls = True
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True
no_implicit_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_return_any = True
follow_imports_for_stubs = True
namespace_packages = True
show_absolute_path = True
[mypy-aiofiles]
ignore_missing_imports = True
[mypy-async_lru]
ignore_missing_imports = True
[mypy-uvloop]
ignore_missing_imports = True
[mypy-redis.*]
ignore_missing_imports = True
[mypy-babel.*]
ignore_missing_imports = True

2486
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,19 @@
[tool.poetry]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "aiogram"
version = "3.0.0-beta.7"
description = "Modern and fully asynchronous framework for Telegram Bot API"
description = 'Modern and fully asynchronous framework for Telegram Bot API'
readme = "README.rst"
requires-python = ">=3.8"
license = "MIT"
authors = [
"Alex Root Junior <jroot.junior@gmail.com>",
{ name = "Alex Root Junior", email = "jroot.junior@gmail.com" },
]
maintainers = [
"Alex Root Junior <jroot.junior@gmail.com>",
{ name = "Alex Root Junior", email = "jroot.junior@gmail.com" },
]
license = "MIT"
readme = "README.rst"
homepage = "https://aiogram.dev/"
documentation = "https://docs.aiogram.dev/"
repository = "https://github.com/aiogram/aiogram/"
keywords = [
"telegram",
"bot",
@ -38,77 +39,230 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Communications :: Chat",
]
packages = [
{ include = "aiogram" }
dependencies = [
"magic-filter~=1.0.9",
"aiohttp~=3.8.3",
"pydantic~=1.10.4",
"aiofiles~=22.1.0",
"certifi>=2022.9.24",
]
dynamic = ["version"]
[tool.hatch.version]
path = "aiogram/__init__.py"
[project.optional-dependencies]
fast = [
"uvloop>=0.17.0; sys_platform == 'darwin' or sys_platform == 'linux'",
]
redis = [
"redis~=4.3.4",
]
proxy = [
"aiohttp-socks~=0.7.1",
]
i18n = [
"Babel~=2.9.1",
]
test = [
"pytest~=7.1.3",
"pytest-html~=3.1.1",
"pytest-asyncio~=0.19.0",
"pytest-lazy-fixture~=0.6.3",
"pytest-mock~=3.9.0",
"pytest-mypy~=0.10.0",
"pytest-cov~=4.0.0",
"pytest-aiohttp~=1.0.4",
"aresponses~=2.1.6",
]
docs = [
"Sphinx~=5.2.3",
"sphinx-intl~=2.0.1",
"sphinx-autobuild~=2021.3.14",
"sphinx-copybutton~=0.5.0",
"furo~=2022.9.29",
"sphinx-prompt~=1.5.0",
"Sphinx-Substitution-Extensions~=2022.2.16",
"towncrier~=22.8.0",
"pygments~=2.4",
"pymdown-extensions~=9.6",
"markdown-include~=0.7.0",
"Pygments~=2.13.0",
"sphinxcontrib-towncrier~=0.3.1a3",
]
[project.urls]
Homepage = "https://aiogram.dev/"
Documentation = "https://docs.aiogram.dev/"
Repository = "https://github.com/aiogram/aiogram/"
[tool.poetry.dependencies]
python = "^3.8"
magic-filter = "^1.0.9"
aiohttp = "^3.8.3"
pydantic = "^1.10.2"
aiofiles = "^22.1.0"
# Fast
uvloop = { version = "^0.17.0", markers = "sys_platform == 'darwin' or sys_platform == 'linux'", optional = true }
# i18n
Babel = { version = "^2.9.1", optional = true }
# Proxy
aiohttp-socks = { version = "^0.7.1", optional = true }
# Redis
redis = { version = "^4.3.4", optional = true }
certifi = "^2022.9.24"
[tool.hatch.envs.default]
features = [
"fast",
"redis",
"proxy",
"i18n",
]
dependencies = [
"black~=22.8",
"isort~=5.11",
"ruff~=0.0.215",
"mypy~=0.981",
"toml~=0.10.2",
"pre-commit~=2.20.0",
"packaging~=21.3",
"typing-extensions~=4.3.0",
]
post-install-commands = [
"pre-commit install",
]
[tool.hatch.envs.default.scripts]
reformat = [
"black aiogram tests",
"isort aiogram tests",
]
lint = "ruff aiogram"
[tool.hatch.envs.docs]
features = [
"fast",
"redis",
"proxy",
"i18n",
"docs",
]
[tool.hatch.envs.docs.scripts]
serve = "sphinx-autobuild --watch aiogram/ --watch CHANGELOG.rst --watch README.rst docs/ docs/_build/ {args}"
[tool.hatch.envs.dev]
python = "3.10"
features = [
"fast",
"redis",
"proxy",
"i18n",
"test",
]
extra-dependencies = [
"butcher @ git+https://github.com/aiogram/butcher.git@v0.1.9"
]
[tool.hatch.envs.test]
features = [
"fast",
"redis",
"proxy",
"i18n",
"test",
]
[tool.hatch.envs.test.scripts]
cov = [
"pytest --cov-config pyproject.toml --cov=aiogram --html=reports/py{matrix:python}/tests/index.html {args}",
"coverage html -d reports/py{matrix:python}/coverage",
]
cov-redis = [
"pytest --cov-config pyproject.toml --cov=aiogram --html=reports/py{matrix:python}/tests/index.html --redis {env:REDIS_DNS:'redis://localhost:6379'} {args}",
"coverage html -d reports/py{matrix:python}/coverage",
]
view-cov = "google-chrome-stable reports/py{matrix:python}/coverage/index.html"
[tool.poetry.group.docs.dependencies]
Sphinx = "^5.2.3"
sphinx-intl = "^2.0.1"
sphinx-autobuild = "^2021.3.14"
sphinx-copybutton = "^0.5.0"
furo = "^2022.9.29"
sphinx-prompt = "^1.5.0"
Sphinx-Substitution-Extensions = "^2022.2.16"
towncrier = "^22.8.0"
pygments = "^2.4"
pymdown-extensions = "^9.6"
markdown-include = "^0.7.0"
Pygments = "^2.13.0"
sphinxcontrib-towncrier = "^0.3.1a3"
[[tool.hatch.envs.test.matrix]]
python = ["38", "39", "310", "311"]
[tool.ruff]
line-length = 99
select = [
# "C", # TODO: mccabe - code complecity
"C4",
"E",
"F",
"T10",
"T20",
"Q",
"RET",
"I"
]
src = ["aiogram", "tests"]
exclude = [
".git",
"build",
"dist",
"venv",
".venv",
"docs",
"tests",
"dev",
"scripts",
"*.egg-info",
]
target-version = "py310"
[tool.ruff.isort]
known-first-party = [
"aiogram",
"finite_state_machine",
"handlers",
"routes",
]
[tool.ruff.per-file-ignores]
"aiogram/client/bot.py" = ["E501"]
"aiogram/types/*" = ["E501"]
"aiogram/methods/*" = ["E501"]
"aiogram/enums/*" = ["E501"]
[tool.poetry.group.test.dependencies]
pytest = "^7.1.3"
pytest-html = "^3.1.1"
pytest-asyncio = "^0.19.0"
pytest-lazy-fixture = "^0.6.3"
pytest-mock = "^3.9.0"
pytest-mypy = "^0.10.0"
pytest-cov = "^4.0.0"
pytest-aiohttp = "^1.0.4"
aresponses = "^2.1.6"
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = [
"tests",
]
[tool.coverage.run]
branch = true
parallel = true
omit = [
"aiogram/__about__.py",
]
[tool.poetry.group.dev.dependencies]
black = "^22.8.0"
isort = "^5.10.1"
flake8 = "^5.0.4"
mypy = "^0.981"
toml = "^0.10.2"
pre-commit = "^2.20.0"
packaging = "^21.3"
typing-extensions = "^4.3.0"
butcher = { git = "https://github.com/aiogram/butcher.git", rev = "v0.1.8", python = "3.10" }
[tool.coverage.report]
exclude_lines = [
"if __name__ == .__main__.:",
"pragma: no cover",
"if TYPE_CHECKING:",
"@abstractmethod",
"@overload",
]
[tool.mypy]
strict = true
[tool.poetry.extras]
fast = ["uvloop"]
redis = ["redis"]
proxy = ["aiohttp-socks"]
i18n = ["Babel"]
[[tool.mypy.overrides]]
module = "mypy-aiofiles"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "mypy-async_lru"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "mypy-uvloop"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "mypy-redis.*"
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "mypy-babel.*"
ignore_missing_imports = true
[tool.black]
line-length = 99
target-version = ['py38', 'py39', 'py310']
target-version = ['py38', 'py39', 'py310', 'py311']
exclude = '''
(
\.eggs
@ -122,21 +276,7 @@ exclude = '''
'''
[tool.isort]
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
line_length = 99
known_third_party = [
"aiofiles",
"aiohttp",
"aiohttp_socks",
"aresponses",
"packaging",
"pkg_resources",
"pydantic",
"pytest"
]
profile = "black"
[tool.towncrier]
package = "aiogram"
@ -172,7 +312,3 @@ showcontent = true
directory = "misc"
name = "Misc"
showcontent = true
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View file

@ -1,4 +0,0 @@
[pytest]
asyncio_mode = auto
testpaths =
tests

View file

@ -5,7 +5,11 @@ from _pytest.config import UsageError
from redis.asyncio.connection import parse_url as parse_redis_url
from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import DisabledEventIsolation, MemoryStorage, SimpleEventIsolation
from aiogram.fsm.storage.memory import (
DisabledEventIsolation,
MemoryStorage,
SimpleEventIsolation,
)
from aiogram.fsm.storage.redis import RedisEventIsolation, RedisStorage
from tests.mocked_bot import MockedBot

View file

@ -1,6 +1,10 @@
from aiogram import Bot
from aiogram.methods import AnswerInlineQuery, Request
from aiogram.types import InlineQueryResult, InlineQueryResultPhoto, InputTextMessageContent
from aiogram.types import (
InlineQueryResult,
InlineQueryResultPhoto,
InputTextMessageContent,
)
from tests.mocked_bot import MockedBot

View file

@ -1,5 +1,11 @@
from aiogram.methods import AnswerShippingQuery
from aiogram.types import LabeledPrice, ShippingAddress, ShippingOption, ShippingQuery, User
from aiogram.types import (
LabeledPrice,
ShippingAddress,
ShippingOption,
ShippingQuery,
User,
)
class TestInlineQuery:

View file

@ -5,7 +5,15 @@ from typing import Sequence, Type
import pytest
from aiogram.filters import Text
from aiogram.types import CallbackQuery, Chat, InlineQuery, Message, Poll, PollOption, User
from aiogram.types import (
CallbackQuery,
Chat,
InlineQuery,
Message,
Poll,
PollOption,
User,
)
class TestText:

View file

@ -1,7 +1,11 @@
import pytest
from aiogram.fsm.storage.base import DEFAULT_DESTINY, StorageKey
from aiogram.fsm.storage.redis import DefaultKeyBuilder, RedisEventIsolation, RedisStorage
from aiogram.fsm.storage.redis import (
DefaultKeyBuilder,
RedisEventIsolation,
RedisStorage,
)
PREFIX = "test"
BOT_ID = 42

View file

@ -7,7 +7,12 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.storage.base import StorageKey
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import Update, User
from aiogram.utils.i18n import ConstI18nMiddleware, FSMI18nMiddleware, I18n, SimpleI18nMiddleware
from aiogram.utils.i18n import (
ConstI18nMiddleware,
FSMI18nMiddleware,
I18n,
SimpleI18nMiddleware,
)
from aiogram.utils.i18n.context import get_i18n, gettext, lazy_gettext
from tests.conftest import DATA_DIR
from tests.mocked_bot import MockedBot

View file

@ -7,7 +7,11 @@ from aiogram.types import (
KeyboardButton,
ReplyKeyboardMarkup,
)
from aiogram.utils.keyboard import InlineKeyboardBuilder, KeyboardBuilder, ReplyKeyboardBuilder
from aiogram.utils.keyboard import (
InlineKeyboardBuilder,
KeyboardBuilder,
ReplyKeyboardBuilder,
)
class MyCallback(CallbackData, prefix="test"):

View file

@ -3,7 +3,11 @@ from typing import List, Optional
import pytest
from aiogram.types import MessageEntity, User
from aiogram.utils.text_decorations import TextDecoration, html_decoration, markdown_decoration
from aiogram.utils.text_decorations import (
TextDecoration,
html_decoration,
markdown_decoration,
)
class TestTextDecoration: