mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Use enums, removed Helper
This commit is contained in:
parent
024f1e028b
commit
0fd984e016
23 changed files with 53 additions and 533 deletions
|
|
@ -15,4 +15,3 @@ class BotCommandScopeType(str, Enum):
|
|||
CHAT = "chat"
|
||||
CHAT_ADMINISTRATORS = "chat_administrators"
|
||||
CHAT_MEMBER = "chat_member"
|
||||
ALL = "all"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ from .bot_command_scope_default import BotCommandScopeDefault
|
|||
from .callback_game import CallbackGame
|
||||
from .callback_query import CallbackQuery
|
||||
from .chat import Chat
|
||||
from .chat_action import ChatAction
|
||||
from .chat_administrator_rights import ChatAdministratorRights
|
||||
from .chat_invite_link import ChatInviteLink
|
||||
from .chat_join_request import ChatJoinRequest
|
||||
|
|
@ -286,7 +285,6 @@ __all__ = (
|
|||
"Game",
|
||||
"CallbackGame",
|
||||
"GameHighScore",
|
||||
"ChatAction",
|
||||
)
|
||||
|
||||
# Load typing forward refs for every TelegramObject
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import BotCommandScopeType
|
||||
from .bot_command_scope import BotCommandScope
|
||||
|
||||
|
||||
|
|
@ -12,5 +13,5 @@ class BotCommandScopeAllChatAdministrators(BotCommandScope):
|
|||
Source: https://core.telegram.org/bots/api#botcommandscopeallchatadministrators
|
||||
"""
|
||||
|
||||
type: str = Field("all_chat_administrators", const=True)
|
||||
type: str = Field(BotCommandScopeType.ALL_CHAT_ADMINISTRATORS, const=True)
|
||||
"""Scope type, must be *all_chat_administrators*"""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import BotCommandScopeType
|
||||
from .bot_command_scope import BotCommandScope
|
||||
|
||||
|
||||
|
|
@ -12,5 +13,5 @@ class BotCommandScopeAllGroupChats(BotCommandScope):
|
|||
Source: https://core.telegram.org/bots/api#botcommandscopeallgroupchats
|
||||
"""
|
||||
|
||||
type: str = Field("all_group_chats", const=True)
|
||||
type: str = Field(BotCommandScopeType.ALL_GROUP_CHATS, const=True)
|
||||
"""Scope type, must be *all_group_chats*"""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import BotCommandScopeType
|
||||
from .bot_command_scope import BotCommandScope
|
||||
|
||||
|
||||
|
|
@ -12,5 +13,5 @@ class BotCommandScopeAllPrivateChats(BotCommandScope):
|
|||
Source: https://core.telegram.org/bots/api#botcommandscopeallprivatechats
|
||||
"""
|
||||
|
||||
type: str = Field("all_private_chats", const=True)
|
||||
type: str = Field(BotCommandScopeType.ALL_PRIVATE_CHATS, const=True)
|
||||
"""Scope type, must be *all_private_chats*"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import Union
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import BotCommandScopeType
|
||||
from .bot_command_scope import BotCommandScope
|
||||
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ class BotCommandScopeChat(BotCommandScope):
|
|||
Source: https://core.telegram.org/bots/api#botcommandscopechat
|
||||
"""
|
||||
|
||||
type: str = Field("chat", const=True)
|
||||
type: str = Field(BotCommandScopeType.CHAT, const=True)
|
||||
"""Scope type, must be *chat*"""
|
||||
chat_id: Union[int, str]
|
||||
"""Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import Union
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import BotCommandScopeType
|
||||
from .bot_command_scope import BotCommandScope
|
||||
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ class BotCommandScopeChatAdministrators(BotCommandScope):
|
|||
Source: https://core.telegram.org/bots/api#botcommandscopechatadministrators
|
||||
"""
|
||||
|
||||
type: str = Field("chat_administrators", const=True)
|
||||
type: str = Field(BotCommandScopeType.CHAT_ADMINISTRATORS, const=True)
|
||||
"""Scope type, must be *chat_administrators*"""
|
||||
chat_id: Union[int, str]
|
||||
"""Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import Union
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import BotCommandScopeType
|
||||
from .bot_command_scope import BotCommandScope
|
||||
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ class BotCommandScopeChatMember(BotCommandScope):
|
|||
Source: https://core.telegram.org/bots/api#botcommandscopechatmember
|
||||
"""
|
||||
|
||||
type: str = Field("chat_member", const=True)
|
||||
type: str = Field(BotCommandScopeType.CHAT_MEMBER, const=True)
|
||||
"""Scope type, must be *chat_member*"""
|
||||
chat_id: Union[int, str]
|
||||
"""Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)"""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import BotCommandScopeType
|
||||
from .bot_command_scope import BotCommandScope
|
||||
|
||||
|
||||
|
|
@ -12,5 +13,5 @@ class BotCommandScopeDefault(BotCommandScope):
|
|||
Source: https://core.telegram.org/bots/api#botcommandscopedefault
|
||||
"""
|
||||
|
||||
type: str = Field("default", const=True)
|
||||
type: str = Field(BotCommandScopeType.DEFAULT, const=True)
|
||||
"""Scope type, must be *default*"""
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
import enum
|
||||
|
||||
from ..utils.enum import AutoName
|
||||
|
||||
|
||||
class ChatAction(AutoName):
|
||||
"""
|
||||
This object represents bot actions.
|
||||
|
||||
Choose one, depending on what the user is about to receive:
|
||||
• typing for text messages,
|
||||
• upload_photo for photos,
|
||||
• record_video or upload_video for videos,
|
||||
• record_voice or upload_voice for voice notes,
|
||||
• upload_document for general files,
|
||||
• choose_sticker for stickers,
|
||||
• find_location for location data,
|
||||
• record_video_note or upload_video_note for video notes.
|
||||
|
||||
Source: https://core.telegram.org/bots/api#sendchataction
|
||||
"""
|
||||
|
||||
TYPING = enum.auto()
|
||||
UPLOAD_PHOTO = enum.auto()
|
||||
RECORD_VIDEO = enum.auto()
|
||||
UPLOAD_VIDEO = enum.auto()
|
||||
RECORD_VOICE = enum.auto()
|
||||
UPLOAD_VOICE = enum.auto()
|
||||
UPLOAD_DOCUMENT = enum.auto()
|
||||
CHOOSE_STICKER = enum.auto()
|
||||
FIND_LOCATION = enum.auto()
|
||||
RECORD_VIDEO_NOTE = enum.auto()
|
||||
UPLOAD_VIDEO_NOTE = enum.auto()
|
||||
|
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import ChatMemberStatus
|
||||
from .chat_member import ChatMember
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -17,7 +18,7 @@ class ChatMemberAdministrator(ChatMember):
|
|||
Source: https://core.telegram.org/bots/api#chatmemberadministrator
|
||||
"""
|
||||
|
||||
status: str = Field("administrator", const=True)
|
||||
status: str = Field(ChatMemberStatus.ADMINISTRATOR, const=True)
|
||||
"""The member's status in the chat, always 'administrator'"""
|
||||
user: User
|
||||
"""Information about the user"""
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import ChatMemberStatus
|
||||
from .chat_member import ChatMember
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -18,7 +19,7 @@ class ChatMemberBanned(ChatMember):
|
|||
Source: https://core.telegram.org/bots/api#chatmemberbanned
|
||||
"""
|
||||
|
||||
status: str = Field("kicked", const=True)
|
||||
status: str = Field(ChatMemberStatus.KICKED, const=True)
|
||||
"""The member's status in the chat, always 'kicked'"""
|
||||
user: User
|
||||
"""Information about the user"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import ChatMemberStatus
|
||||
from .chat_member import ChatMember
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -17,7 +18,7 @@ class ChatMemberLeft(ChatMember):
|
|||
Source: https://core.telegram.org/bots/api#chatmemberleft
|
||||
"""
|
||||
|
||||
status: str = Field("left", const=True)
|
||||
status: str = Field(ChatMemberStatus.LEFT, const=True)
|
||||
"""The member's status in the chat, always 'left'"""
|
||||
user: User
|
||||
"""Information about the user"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import ChatMemberStatus
|
||||
from .chat_member import ChatMember
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -17,7 +18,7 @@ class ChatMemberMember(ChatMember):
|
|||
Source: https://core.telegram.org/bots/api#chatmembermember
|
||||
"""
|
||||
|
||||
status: str = Field("member", const=True)
|
||||
status: str = Field(ChatMemberStatus.MEMBER, const=True)
|
||||
"""The member's status in the chat, always 'member'"""
|
||||
user: User
|
||||
"""Information about the user"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import ChatMemberStatus
|
||||
from .chat_member import ChatMember
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -17,7 +18,7 @@ class ChatMemberOwner(ChatMember):
|
|||
Source: https://core.telegram.org/bots/api#chatmemberowner
|
||||
"""
|
||||
|
||||
status: str = Field("creator", const=True)
|
||||
status: str = Field(ChatMemberStatus.CREATOR, const=True)
|
||||
"""The member's status in the chat, always 'creator'"""
|
||||
user: User
|
||||
"""Information about the user"""
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import ChatMemberStatus
|
||||
from .chat_member import ChatMember
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -18,7 +19,7 @@ class ChatMemberRestricted(ChatMember):
|
|||
Source: https://core.telegram.org/bots/api#chatmemberrestricted
|
||||
"""
|
||||
|
||||
status: str = Field("restricted", const=True)
|
||||
status: str = Field(ChatMemberStatus.RESTRICTED, const=True)
|
||||
"""The member's status in the chat, always 'restricted'"""
|
||||
user: User
|
||||
"""Information about the user"""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import MenuButtonType
|
||||
from .menu_button import MenuButton
|
||||
|
||||
|
||||
|
|
@ -12,5 +13,5 @@ class MenuButtonCommands(MenuButton):
|
|||
Source: https://core.telegram.org/bots/api#menubuttoncommands
|
||||
"""
|
||||
|
||||
type: str = Field("commands", const=True)
|
||||
type: str = Field(MenuButtonType.COMMANDS, const=True)
|
||||
"""Type of the button, must be *commands*"""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import MenuButtonType
|
||||
from .menu_button import MenuButton
|
||||
|
||||
|
||||
|
|
@ -12,5 +13,5 @@ class MenuButtonDefault(MenuButton):
|
|||
Source: https://core.telegram.org/bots/api#menubuttondefault
|
||||
"""
|
||||
|
||||
type: str = Field("default", const=True)
|
||||
type: str = Field(MenuButtonType.DEFAULT, const=True)
|
||||
"""Type of the button, must be *default*"""
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING
|
|||
|
||||
from pydantic import Field
|
||||
|
||||
from ..enums import MenuButtonType
|
||||
from .menu_button import MenuButton
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -17,7 +18,7 @@ class MenuButtonWebApp(MenuButton):
|
|||
Source: https://core.telegram.org/bots/api#menubuttonwebapp
|
||||
"""
|
||||
|
||||
type: str = Field("web_app", const=True)
|
||||
type: str = Field(MenuButtonType.WEB_APP, const=True)
|
||||
"""Type of the button, must be *web_app*"""
|
||||
text: str
|
||||
"""Text on the button"""
|
||||
|
|
|
|||
|
|
@ -1,293 +0,0 @@
|
|||
"""
|
||||
Example:
|
||||
>>> from aiogram.utils.helper import Helper, ListItem, HelperMode, Item
|
||||
>>> class MyHelper(Helper):
|
||||
... mode = HelperMode.lowerCamelCase
|
||||
... FOO_ITEM = ListItem()
|
||||
... BAR_ITEM = ListItem()
|
||||
... BAZ_ITEM = ListItem()
|
||||
... LOREM = Item()
|
||||
...
|
||||
>>> print(MyHelper.FOO_ITEM & MyHelper.BAR_ITEM)
|
||||
<<< ['fooItem', 'barItem']
|
||||
>>> print(MyHelper.all())
|
||||
<<< ['barItem', 'bazItem', 'fooItem', 'lorem']
|
||||
"""
|
||||
import inspect
|
||||
from typing import Any, Callable, Generic, Iterable, List, Optional, TypeVar, Union, cast
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
PROPS_KEYS_ATTR_NAME = "_props_keys"
|
||||
|
||||
|
||||
class Helper:
|
||||
mode = ""
|
||||
|
||||
@classmethod
|
||||
def all(cls) -> List[Any]:
|
||||
"""
|
||||
Get all consts
|
||||
:return: list
|
||||
"""
|
||||
result: List[Any] = []
|
||||
for name in dir(cls):
|
||||
if not name.isupper():
|
||||
continue
|
||||
value = getattr(cls, name)
|
||||
if isinstance(value, ItemsList):
|
||||
result.append(value[0])
|
||||
else:
|
||||
result.append(value)
|
||||
return result
|
||||
|
||||
|
||||
class HelperMode(Helper):
|
||||
mode = "original"
|
||||
|
||||
SCREAMING_SNAKE_CASE = "SCREAMING_SNAKE_CASE"
|
||||
lowerCamelCase = "lowerCamelCase"
|
||||
CamelCase = "CamelCase"
|
||||
snake_case = "snake_case"
|
||||
lowercase = "lowercase"
|
||||
|
||||
@classmethod
|
||||
def all(cls) -> List[str]:
|
||||
return [
|
||||
cls.SCREAMING_SNAKE_CASE,
|
||||
cls.lowerCamelCase,
|
||||
cls.CamelCase,
|
||||
cls.snake_case,
|
||||
cls.lowercase,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _screaming_snake_case(cls, text: str) -> str:
|
||||
"""
|
||||
Transform text to SCREAMING_SNAKE_CASE
|
||||
|
||||
:param text:
|
||||
:return:
|
||||
"""
|
||||
if text.isupper():
|
||||
return text
|
||||
result = ""
|
||||
for pos, symbol in enumerate(text):
|
||||
if symbol.isupper() and pos > 0:
|
||||
result += "_" + symbol
|
||||
else:
|
||||
result += symbol.upper()
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def _snake_case(cls, text: str) -> str:
|
||||
"""
|
||||
Transform text to snake case (Based on SCREAMING_SNAKE_CASE)
|
||||
|
||||
:param text:
|
||||
:return:
|
||||
"""
|
||||
if text.islower():
|
||||
return text
|
||||
return cls._screaming_snake_case(text).lower()
|
||||
|
||||
@classmethod
|
||||
def _camel_case(cls, text: str, first_upper: bool = False) -> str:
|
||||
"""
|
||||
Transform text to camelCase or CamelCase
|
||||
|
||||
:param text:
|
||||
:param first_upper: first symbol must be upper?
|
||||
:return:
|
||||
"""
|
||||
result = ""
|
||||
need_upper = False
|
||||
for pos, symbol in enumerate(text):
|
||||
if symbol == "_" and pos > 0:
|
||||
need_upper = True
|
||||
else:
|
||||
if need_upper:
|
||||
result += symbol.upper()
|
||||
else:
|
||||
result += symbol.lower()
|
||||
need_upper = False
|
||||
if first_upper:
|
||||
result = result[0].upper() + result[1:]
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def apply(cls, text: str, mode: Union[str, Callable[[str], str]]) -> str:
|
||||
"""
|
||||
Apply mode for text
|
||||
|
||||
:param text:
|
||||
:param mode:
|
||||
:return:
|
||||
"""
|
||||
if mode == cls.SCREAMING_SNAKE_CASE:
|
||||
return cls._screaming_snake_case(text)
|
||||
if mode == cls.snake_case:
|
||||
return cls._snake_case(text)
|
||||
if mode == cls.lowercase:
|
||||
return cls._snake_case(text).replace("_", "")
|
||||
if mode == cls.lowerCamelCase:
|
||||
return cls._camel_case(text)
|
||||
if mode == cls.CamelCase:
|
||||
return cls._camel_case(text, True)
|
||||
if callable(mode):
|
||||
return mode(text)
|
||||
return text
|
||||
|
||||
|
||||
class _BaseItem:
|
||||
def __init__(self, value: Optional[str] = None):
|
||||
self._value = cast(str, value)
|
||||
|
||||
def __set_name__(self, owner: Any, name: str) -> None:
|
||||
if not name.isupper():
|
||||
raise NameError("Name for helper item must be in uppercase!")
|
||||
if not self._value:
|
||||
if not inspect.isclass(owner) or not issubclass(owner, Helper):
|
||||
raise RuntimeError("Instances of Item can be used only as Helper attributes")
|
||||
self._value = HelperMode.apply(name, owner.mode)
|
||||
|
||||
|
||||
class Item(_BaseItem):
|
||||
"""
|
||||
Helper item
|
||||
|
||||
If a value is not provided,
|
||||
it will be automatically generated based on a variable's name
|
||||
"""
|
||||
|
||||
def __get__(self, instance: Any, owner: Any) -> str:
|
||||
return self._value
|
||||
|
||||
|
||||
class ListItem(_BaseItem):
|
||||
"""
|
||||
This item is always a list
|
||||
|
||||
You can use &, | and + operators for that.
|
||||
"""
|
||||
|
||||
def add(self, other: "ListItem") -> "ListItem": # pragma: no cover
|
||||
return self + other
|
||||
|
||||
def __get__(self, instance: Any, owner: Any) -> "ItemsList":
|
||||
return ItemsList(self._value)
|
||||
|
||||
def __getitem__(self, item: Any) -> Any: # pragma: no cover
|
||||
# Only for IDE. This method is never be called.
|
||||
return self._value
|
||||
|
||||
# Need only for IDE
|
||||
__iadd__ = __add__ = __rand__ = __and__ = __ror__ = __or__ = add
|
||||
|
||||
|
||||
class ItemsList(List[str]):
|
||||
"""
|
||||
Patch for default list
|
||||
|
||||
This class provides +, &, |, +=, &=, |= operators for extending the list
|
||||
"""
|
||||
|
||||
def __init__(self, *seq: Any):
|
||||
super(ItemsList, self).__init__(map(str, seq))
|
||||
|
||||
def add(self, other: Iterable[str]) -> "ItemsList":
|
||||
self.extend(other)
|
||||
return self
|
||||
|
||||
__iadd__ = __add__ = __rand__ = __and__ = __ror__ = __or__ = add # type: ignore
|
||||
|
||||
|
||||
class OrderedHelperMeta(type):
|
||||
def __new__(mcs, name: Any, bases: Any, namespace: Any, **kwargs: Any) -> "OrderedHelperMeta":
|
||||
cls = super().__new__(mcs, name, bases, namespace)
|
||||
|
||||
props_keys = []
|
||||
|
||||
for prop_name in (
|
||||
name for name, prop in namespace.items() if isinstance(prop, (Item, ListItem))
|
||||
):
|
||||
props_keys.append(prop_name)
|
||||
|
||||
setattr(cls, PROPS_KEYS_ATTR_NAME, props_keys)
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
class OrderedHelper(Helper, metaclass=OrderedHelperMeta):
|
||||
mode = ""
|
||||
|
||||
@classmethod
|
||||
def all(cls) -> List[str]:
|
||||
"""
|
||||
Get all Items values
|
||||
"""
|
||||
result = []
|
||||
for name in getattr(cls, PROPS_KEYS_ATTR_NAME, []):
|
||||
value = getattr(cls, name)
|
||||
if isinstance(value, ItemsList):
|
||||
result.append(value[0])
|
||||
else:
|
||||
result.append(value)
|
||||
return result
|
||||
|
||||
|
||||
class Default(Generic[T]):
|
||||
"""
|
||||
Descriptor that holds default value getter
|
||||
|
||||
Example:
|
||||
>>> class MyClass:
|
||||
... att = Default("dflt")
|
||||
...
|
||||
>>> my_instance = MyClass()
|
||||
>>> my_instance.att = "not dflt"
|
||||
>>> my_instance.att
|
||||
'not dflt'
|
||||
>>> MyClass.att
|
||||
'dflt'
|
||||
>>> del my_instance.att
|
||||
>>> my_instance.att
|
||||
'dflt'
|
||||
>>>
|
||||
|
||||
Intended to be used as a class attribute and only internally.
|
||||
"""
|
||||
|
||||
__slots__ = "fget", "_descriptor_instances"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
default: Optional[T] = None,
|
||||
*,
|
||||
fget: Optional[Callable[[Any], T]] = None,
|
||||
) -> None:
|
||||
self.fget = fget or (lambda _: cast(T, default))
|
||||
self._descriptor_instances = WeakKeyDictionary() # type: ignore
|
||||
|
||||
def __get__(self, instance: Any, owner: Any) -> T:
|
||||
if instance is None:
|
||||
return self.fget(instance)
|
||||
|
||||
return self._descriptor_instances.get(instance, self.fget(instance))
|
||||
|
||||
def __set__(self, instance: Any, value: T) -> None:
|
||||
if instance is None or isinstance(instance, type):
|
||||
raise AttributeError(
|
||||
"Instance cannot be class or None. Setter must be called from a class."
|
||||
)
|
||||
|
||||
self._descriptor_instances[instance] = value
|
||||
|
||||
def __delete__(self, instance: Any) -> None:
|
||||
if instance is None or isinstance(instance, type):
|
||||
raise AttributeError(
|
||||
"Instance cannot be class or None. Deleter must be called from a class."
|
||||
)
|
||||
|
||||
self._descriptor_instances.pop(instance, None)
|
||||
|
|
@ -5,6 +5,8 @@ import re
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Generator, List, Optional, Pattern, cast
|
||||
|
||||
from aiogram.enums import MessageEntityType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from aiogram.types import MessageEntity
|
||||
|
||||
|
|
@ -36,25 +38,37 @@ class TextDecoration(ABC):
|
|||
:param text:
|
||||
:return:
|
||||
"""
|
||||
if entity.type in {"bot_command", "url", "mention", "phone_number"}:
|
||||
if entity.type in {
|
||||
MessageEntityType.BOT_COMMAND,
|
||||
MessageEntityType.URL,
|
||||
MessageEntityType.MENTION,
|
||||
MessageEntityType.PHONE_NUMBER,
|
||||
}:
|
||||
# This entities should not be changed
|
||||
return text
|
||||
if entity.type in {"bold", "italic", "code", "underline", "strikethrough", "spoiler"}:
|
||||
if entity.type in {
|
||||
MessageEntityType.BOLD,
|
||||
MessageEntityType.ITALIC,
|
||||
MessageEntityType.CODE,
|
||||
MessageEntityType.UNDERLINE,
|
||||
MessageEntityType.STRIKETHROUGH,
|
||||
MessageEntityType.SPOILER,
|
||||
}:
|
||||
return cast(str, getattr(self, entity.type)(value=text))
|
||||
if entity.type == "pre":
|
||||
if entity.type == MessageEntityType.PRE:
|
||||
return (
|
||||
self.pre_language(value=text, language=entity.language)
|
||||
if entity.language
|
||||
else self.pre(value=text)
|
||||
)
|
||||
if entity.type == "text_mention":
|
||||
if entity.type == MessageEntityType.TEXT_MENTION:
|
||||
from aiogram.types import User
|
||||
|
||||
user = cast(User, entity.user)
|
||||
return self.link(value=text, link=f"tg://user?id={user.id}")
|
||||
if entity.type == "text_link":
|
||||
if entity.type == MessageEntityType.TEXT_LINK:
|
||||
return self.link(value=text, link=cast(str, entity.url))
|
||||
if entity.type == "custom_emoji":
|
||||
if entity.type == MessageEntityType.CUSTOM_EMOJI:
|
||||
return self.custom_emoji(value=text, custom_emoji_id=cast(str, entity.custom_emoji_id))
|
||||
|
||||
return self.quote(text)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.enums import ChatAction
|
||||
from aiogram.methods import Request, SendChatAction
|
||||
from aiogram.types import ChatAction
|
||||
from tests.mocked_bot import MockedBot
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -1,181 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from aiogram.utils.helper import Default, Helper, HelperMode, Item, ListItem, OrderedHelper
|
||||
|
||||
|
||||
class TestHelper:
|
||||
def test_items_all(self):
|
||||
class MyHelper(Helper):
|
||||
A = Item()
|
||||
B = Item()
|
||||
C = Item()
|
||||
D = Item()
|
||||
|
||||
assert set(MyHelper.all()) == {"A", "B", "C", "D"}
|
||||
|
||||
def test_listed_items_all(self):
|
||||
class MyHelper(Helper):
|
||||
A = ListItem()
|
||||
B = ListItem()
|
||||
C = ListItem()
|
||||
D = ListItem()
|
||||
|
||||
assert set(MyHelper.all()) == {"A", "B", "C", "D"}
|
||||
|
||||
def test_listed_items_combinations(self):
|
||||
class MyHelper(Helper):
|
||||
A = ListItem()
|
||||
B = ListItem()
|
||||
C = ListItem()
|
||||
D = ListItem()
|
||||
|
||||
assert (MyHelper.A | MyHelper.B) == ["A", "B"]
|
||||
assert (MyHelper.C & MyHelper.D) == ["C", "D"]
|
||||
assert MyHelper.A.add(MyHelper.D) == ["A", "D"]
|
||||
assert MyHelper.B + MyHelper.D == ["B", "D"]
|
||||
|
||||
def test_wrong_name(self):
|
||||
with pytest.raises(RuntimeError):
|
||||
|
||||
class MyHelper(Helper):
|
||||
kaboom = Item()
|
||||
|
||||
def test_not_a_helper_subclass(self):
|
||||
with pytest.raises(RuntimeError):
|
||||
|
||||
class NotAHelperSubclass:
|
||||
A = Item()
|
||||
|
||||
|
||||
class TestHelperMode:
|
||||
def test_helper_mode_all(self):
|
||||
assert set(HelperMode.all()) == {
|
||||
"SCREAMING_SNAKE_CASE",
|
||||
"lowerCamelCase",
|
||||
"CamelCase",
|
||||
"snake_case",
|
||||
"lowercase",
|
||||
}
|
||||
|
||||
def test_screaming_snake_case(self):
|
||||
class MyHelper(Helper):
|
||||
mode = HelperMode.SCREAMING_SNAKE_CASE
|
||||
|
||||
FOO = Item()
|
||||
BAR_BAZ = Item()
|
||||
|
||||
assert MyHelper.FOO == "FOO"
|
||||
assert MyHelper.BAR_BAZ == "BAR_BAZ"
|
||||
|
||||
def test_lower_camel_case(self):
|
||||
class MyHelper(Helper):
|
||||
mode = HelperMode.lowerCamelCase
|
||||
|
||||
FOO = Item()
|
||||
BAR_BAZ = Item()
|
||||
|
||||
assert MyHelper.FOO == "foo"
|
||||
assert MyHelper.BAR_BAZ == "barBaz"
|
||||
|
||||
def test_camel_case(self):
|
||||
class MyHelper(Helper):
|
||||
mode = HelperMode.CamelCase
|
||||
|
||||
FOO = Item()
|
||||
BAR_BAZ = Item()
|
||||
|
||||
assert MyHelper.FOO == "Foo"
|
||||
assert MyHelper.BAR_BAZ == "BarBaz"
|
||||
|
||||
def test_snake_case(self):
|
||||
class MyHelper(Helper):
|
||||
mode = HelperMode.snake_case
|
||||
|
||||
FOO = Item()
|
||||
BAR_BAZ = Item()
|
||||
|
||||
assert MyHelper.FOO == "foo"
|
||||
assert MyHelper.BAR_BAZ == "bar_baz"
|
||||
|
||||
def test_lowercase(self):
|
||||
class MyHelper(Helper):
|
||||
mode = HelperMode.lowercase
|
||||
|
||||
FOO = Item()
|
||||
BAR_BAZ = Item()
|
||||
|
||||
assert MyHelper.FOO == "foo"
|
||||
assert MyHelper.BAR_BAZ == "barbaz"
|
||||
|
||||
def test_extended_converters(self):
|
||||
assert HelperMode.apply("test_text", mode=HelperMode.SCREAMING_SNAKE_CASE) == "TEST_TEXT"
|
||||
assert HelperMode.apply("TestText", mode=HelperMode.SCREAMING_SNAKE_CASE) == "TEST_TEXT"
|
||||
assert HelperMode.apply("test_text", mode=HelperMode.snake_case) == "test_text"
|
||||
assert HelperMode.apply("foo", mode=lambda m: m.upper()) == "FOO"
|
||||
|
||||
|
||||
class TestOrderedHelper:
|
||||
def test_items_are_ordered(self):
|
||||
class MyOrderedHelper(OrderedHelper):
|
||||
A = Item()
|
||||
D = Item()
|
||||
C = Item()
|
||||
B = Item()
|
||||
|
||||
assert MyOrderedHelper.all() == ["A", "D", "C", "B"]
|
||||
|
||||
def test_list_items_are_ordered(self):
|
||||
class MyOrderedHelper(OrderedHelper):
|
||||
A = ListItem()
|
||||
D = ListItem()
|
||||
C = ListItem()
|
||||
B = ListItem()
|
||||
|
||||
assert MyOrderedHelper.all() == ["A", "D", "C", "B"]
|
||||
|
||||
|
||||
class TestDefaultDescriptor:
|
||||
def test_descriptor_fs(self):
|
||||
obj = type("ClassA", (), {})()
|
||||
default_x_val = "some_x"
|
||||
x = Default(default_x_val)
|
||||
|
||||
# we can omit owner, usually it's just obj.__class__
|
||||
assert x.__get__(instance=obj, owner=None) == default_x_val
|
||||
assert x.__get__(instance=obj, owner=obj.__class__) == default_x_val
|
||||
|
||||
new_x_val = "new_x"
|
||||
assert x.__set__(instance=obj, value=new_x_val) is None
|
||||
|
||||
with pytest.raises(AttributeError) as exc:
|
||||
x.__set__(instance=obj.__class__, value="will never be set")
|
||||
assert "Instance cannot be class or None" in str(exc.value)
|
||||
|
||||
assert x.__get__(instance=obj, owner=obj.__class__) == new_x_val
|
||||
|
||||
with pytest.raises(AttributeError) as exc:
|
||||
x.__delete__(instance=obj.__class__)
|
||||
assert "Instance cannot be class or None" in str(exc.value)
|
||||
|
||||
x.__delete__(instance=obj)
|
||||
assert x.__get__(instance=obj, owner=obj.__class__) == default_x_val
|
||||
|
||||
def test_init(self):
|
||||
class A:
|
||||
x = Default(fget=lambda a_inst: "nothing")
|
||||
|
||||
assert isinstance(A.__dict__["x"], Default)
|
||||
|
||||
a = A()
|
||||
assert a.x == "nothing"
|
||||
|
||||
x = Default("x")
|
||||
assert x.__get__(None, None) == "x"
|
||||
assert x.fget(None) == x.__get__(None, None)
|
||||
|
||||
def test_nullability(self):
|
||||
class A:
|
||||
x = Default(default=None, fget=None)
|
||||
|
||||
assert A.x is None
|
||||
assert A().x is None
|
||||
Loading…
Add table
Add a link
Reference in a new issue