mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge branch 'aiogram:dev-2.x' into dev-2.x
This commit is contained in:
commit
56f4f2749e
33 changed files with 618 additions and 348 deletions
|
|
@ -6,7 +6,7 @@
|
|||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://pypi.python.org/pypi/aiogram)
|
||||
[](https://core.telegram.org/bots/api)
|
||||
[](https://core.telegram.org/bots/api)
|
||||
[](http://docs.aiogram.dev/en/latest/?badge=latest)
|
||||
[](https://github.com/aiogram/aiogram/issues)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ aiogram
|
|||
:target: https://pypi.python.org/pypi/aiogram
|
||||
:alt: Supported python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.3-blue.svg?style=flat-square&logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.4-blue.svg?style=flat-square&logo=telegram
|
||||
:target: https://core.telegram.org/bots/api
|
||||
:alt: Telegram Bot API
|
||||
|
||||
|
|
|
|||
|
|
@ -43,5 +43,5 @@ __all__ = (
|
|||
'utils',
|
||||
)
|
||||
|
||||
__version__ = '2.23.1'
|
||||
__api_version__ = '6.3'
|
||||
__version__ = '2.25.1'
|
||||
__api_version__ = '6.5'
|
||||
|
|
|
|||
|
|
@ -259,6 +259,11 @@ class Methods(Helper):
|
|||
REOPEN_FORUM_TOPIC = Item() # reopenForumTopic
|
||||
DELETE_FORUM_TOPIC = Item() # deleteForumTopic
|
||||
UNPIN_ALL_FORUM_TOPIC_MESSAGES = Item() # unpinAllForumTopicMessages
|
||||
EDIT_GENERAL_FORUM_TOPIC = Item() # editGeneralForumTopic
|
||||
CLOSE_GENERAL_FORUM_TOPIC = Item() # closeGeneralForumTopic
|
||||
REOPEN_GENERAL_FORUM_TOPIC = Item() # reopenGeneralForumTopic
|
||||
HIDE_GENERAL_FORUM_TOPIC = Item() # hideGeneralForumTopic
|
||||
UNHIDE_GENERAL_FORUM_TOPIC = Item() # unhideGeneralForumTopic
|
||||
ANSWER_CALLBACK_QUERY = Item() # answerCallbackQuery
|
||||
SET_MY_COMMANDS = Item() # setMyCommands
|
||||
DELETE_MY_COMMANDS = Item() # deleteMyCommands
|
||||
|
|
|
|||
|
|
@ -498,6 +498,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
types.ReplyKeyboardMarkup,
|
||||
types.ReplyKeyboardRemove,
|
||||
types.ForceReply, None] = None,
|
||||
has_spoiler: typing.Optional[base.Boolean] = None,
|
||||
) -> types.Message:
|
||||
"""
|
||||
Use this method to send photos.
|
||||
|
|
@ -544,6 +545,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||
|
||||
:param has_spoiler: Pass True if the photo needs to be covered with a spoiler animation
|
||||
:type has_spoiler: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:return: On success, the sent Message is returned
|
||||
:rtype: :obj:`types.Message`
|
||||
"""
|
||||
|
|
@ -776,6 +780,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
types.ReplyKeyboardMarkup,
|
||||
types.ReplyKeyboardRemove,
|
||||
types.ForceReply, None] = None,
|
||||
has_spoiler: typing.Optional[base.Boolean] = None,
|
||||
) -> types.Message:
|
||||
"""
|
||||
Use this method to send video files, Telegram clients support mp4 videos
|
||||
|
|
@ -838,6 +843,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
|
||||
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
|
||||
|
||||
:param has_spoiler: Pass True if the video needs to be covered with a spoiler animation
|
||||
:type has_spoiler: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:return: On success, the sent Message is returned
|
||||
:rtype: :obj:`types.Message`
|
||||
"""
|
||||
|
|
@ -875,6 +883,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
types.ReplyKeyboardMarkup,
|
||||
types.ReplyKeyboardRemove,
|
||||
types.ForceReply], None] = None,
|
||||
has_spoiler: typing.Optional[base.Boolean] = None,
|
||||
) -> types.Message:
|
||||
"""
|
||||
Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
|
||||
|
|
@ -940,6 +949,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
:type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup,
|
||||
types.ReplyKeyboardRemove, types.ForceReply], None]`
|
||||
|
||||
:param has_spoiler: Pass True if the animation needs to be covered with a spoiler animation
|
||||
:type has_spoiler: :obj:`typing.Optional[base.Boolean]`
|
||||
|
||||
:return: On success, the sent Message is returned
|
||||
:rtype: :obj:`types.Message`
|
||||
"""
|
||||
|
|
@ -1711,7 +1723,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
return types.Message(**result)
|
||||
|
||||
async def send_chat_action(self, chat_id: typing.Union[base.Integer, base.String],
|
||||
action: base.String) -> base.Boolean:
|
||||
action: base.String, message_thread_id: typing.Optional[base.Integer] = None) -> base.Boolean:
|
||||
"""
|
||||
Use this method when you need to tell the user that something is
|
||||
happening on the bot's side. The status is set for 5 seconds or
|
||||
|
|
@ -1743,6 +1755,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
`upload_video_note` for video notes.
|
||||
:type action: :obj:`base.String`
|
||||
|
||||
:param message_thread_id: Unique identifier for the target message thread; supergroups only
|
||||
:type message_thread_id: :obj:`typing.Optional[base.Integer]`
|
||||
|
||||
:return: Returns True on success
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
|
|
@ -1885,16 +1900,20 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
|
||||
return await self.request(api.Methods.UNBAN_CHAT_MEMBER, payload)
|
||||
|
||||
async def restrict_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
|
||||
user_id: base.Integer,
|
||||
permissions: typing.Optional[types.ChatPermissions] = None,
|
||||
# permissions argument need to be required after removing other `can_*` arguments
|
||||
until_date: typing.Union[
|
||||
base.Integer, datetime.datetime, datetime.timedelta, None] = None,
|
||||
can_send_messages: typing.Optional[base.Boolean] = None,
|
||||
can_send_media_messages: typing.Optional[base.Boolean] = None,
|
||||
can_send_other_messages: typing.Optional[base.Boolean] = None,
|
||||
can_add_web_page_previews: typing.Optional[base.Boolean] = None) -> base.Boolean:
|
||||
async def restrict_chat_member(
|
||||
self,
|
||||
chat_id: typing.Union[base.Integer, base.String],
|
||||
user_id: base.Integer,
|
||||
permissions: typing.Optional[types.ChatPermissions],
|
||||
use_independent_chat_permissions: typing.Optional[base.Boolean] = None,
|
||||
# permissions argument need to be required after removing other `can_*` arguments
|
||||
until_date: typing.Union[
|
||||
base.Integer, datetime.datetime, datetime.timedelta, None] = None,
|
||||
can_send_messages: typing.Optional[base.Boolean] = None,
|
||||
can_send_media_messages: typing.Optional[base.Boolean] = None,
|
||||
can_send_other_messages: typing.Optional[base.Boolean] = None,
|
||||
can_add_web_page_previews: typing.Optional[base.Boolean] = None,
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to restrict a user in a supergroup.
|
||||
The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights.
|
||||
|
|
@ -1908,6 +1927,15 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
:type user_id: :obj:`base.Integer`
|
||||
:param permissions: New user permissions
|
||||
:type permissions: :obj:`ChatPermissions`
|
||||
:param use_independent_chat_permissions: Pass True if chat
|
||||
permissions are set independently. Otherwise,
|
||||
the can_send_other_messages and can_add_web_page_previews
|
||||
permissions will imply the can_send_messages,
|
||||
can_send_audios, can_send_documents, can_send_photos,
|
||||
can_send_videos, can_send_video_notes, and
|
||||
can_send_voice_notes permissions; the can_send_polls
|
||||
permission will imply the can_send_messages permission.
|
||||
:type use_independent_chat_permissions: :obj:`typing.Optional[base.Boolean]`
|
||||
:param until_date: Date when restrictions will be lifted for the user, unix time
|
||||
:type until_date: :obj:`typing.Optional[base.Integer]`
|
||||
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
|
||||
|
|
@ -2091,8 +2119,12 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
|
||||
return await self.request(api.Methods.UNBAN_CHAT_SENDER_CHAT, payload)
|
||||
|
||||
async def set_chat_permissions(self, chat_id: typing.Union[base.Integer, base.String],
|
||||
permissions: types.ChatPermissions) -> base.Boolean:
|
||||
async def set_chat_permissions(
|
||||
self,
|
||||
chat_id: typing.Union[base.Integer, base.String],
|
||||
permissions: types.ChatPermissions,
|
||||
use_independent_chat_permissions: base.Boolean = None,
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to set default chat permissions for all members.
|
||||
The bot must be an administrator in the group or a supergroup for this to work and must have the
|
||||
|
|
@ -2102,6 +2134,15 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup
|
||||
:param permissions: New default chat permissions
|
||||
:param use_independent_chat_permissions: Pass True if chat
|
||||
permissions are set independently. Otherwise,
|
||||
the can_send_other_messages and can_add_web_page_previews
|
||||
permissions will imply the can_send_messages,
|
||||
can_send_audios, can_send_documents, can_send_photos,
|
||||
can_send_videos, can_send_video_notes, and
|
||||
can_send_voice_notes permissions; the can_send_polls
|
||||
permission will imply the can_send_messages permission.
|
||||
:type use_independent_chat_permissions: :obj:`typing.Optional[base.Boolean]`
|
||||
:return: True on success.
|
||||
"""
|
||||
permissions = prepare_arg(permissions)
|
||||
|
|
@ -2458,6 +2499,97 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
|
||||
return await self.request(api.Methods.UNPIN_ALL_CHAT_MESSAGES, payload)
|
||||
|
||||
async def close_general_forum_topic(
|
||||
self,
|
||||
chat_id: typing.Union[base.Integer, base.String],
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to close an open 'General' topic in a forum supergroup chat.
|
||||
The bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator rights.
|
||||
|
||||
Returns :code:`True` on success.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)
|
||||
:return: Returns :code:`True` on success.
|
||||
"""
|
||||
|
||||
payload = generate_payload(**locals())
|
||||
|
||||
return await self.request(api.Methods.CLOSE_GENERAL_FORUM_TOPIC, payload)
|
||||
|
||||
async def edit_general_forum_topic(
|
||||
self,
|
||||
chat_id: typing.Union[base.Integer, base.String],
|
||||
name: base.String,
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to edit the name of the 'General' topic in a forum supergroup chat.
|
||||
The bot must be an administrator in the chat for this to work and must have *can_manage_topics* administrator rights.
|
||||
|
||||
Returns :code:`True` on success.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)
|
||||
:param name: New topic name, 1-128 characters
|
||||
:return: Returns :code:`True` on success.
|
||||
"""
|
||||
|
||||
payload = generate_payload(**locals())
|
||||
|
||||
return await self.request(api.Methods.EDIT_GENERAL_FORUM_TOPIC, payload)
|
||||
|
||||
async def hide_general_forum_topic(
|
||||
self,
|
||||
chat_id: typing.Union[base.Integer, base.String],
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to hide the 'General' topic in a forum supergroup chat.
|
||||
The bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator rights.
|
||||
|
||||
The topic will be automatically closed if it was open. Returns :code:`True` on success.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)
|
||||
:return: Returns :code:`True` on success.
|
||||
"""
|
||||
|
||||
payload = generate_payload(**locals())
|
||||
|
||||
return await self.request(api.Methods.HIDE_GENERAL_FORUM_TOPIC, payload)
|
||||
|
||||
async def reopen_general_forum_topic(
|
||||
self,
|
||||
chat_id: typing.Union[base.Integer, base.String],
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to reopen a closed 'General' topic in a forum supergroup chat.
|
||||
The bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator rights.
|
||||
The topic will be automatically unhidden if it was hidden. Returns :code:`True` on success.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)
|
||||
:return: Returns :code:`True` on success.
|
||||
"""
|
||||
|
||||
payload = generate_payload(**locals())
|
||||
|
||||
return await self.request(api.Methods.REOPEN_GENERAL_FORUM_TOPIC, payload)
|
||||
|
||||
async def unhide_general_forum_topic(
|
||||
self,
|
||||
chat_id: typing.Union[base.Integer, base.String],
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method to unhide the 'General' topic in a forum supergroup chat.
|
||||
The bot must be an administrator in the chat for this to work and must have the *can_manage_topics* administrator rights.
|
||||
|
||||
Returns :code:`True` on success.
|
||||
|
||||
:param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)
|
||||
:return: Returns :code:`True` on success.
|
||||
"""
|
||||
|
||||
payload = generate_payload(**locals())
|
||||
|
||||
return await self.request(api.Methods.UNPIN_ALL_CHAT_MESSAGES, payload)
|
||||
|
||||
async def leave_chat(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean:
|
||||
"""
|
||||
Use this method for your bot to leave a group, supergroup or channel.
|
||||
|
|
@ -2631,7 +2763,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
|
|||
return types.ForumTopic(**result)
|
||||
|
||||
async def edit_forum_topic(self, chat_id: typing.Union[int, str],
|
||||
name: base.String,
|
||||
name: typing.Optional[base.String] = None,
|
||||
message_thread_id: typing.Optional[base.Integer] = None,
|
||||
icon_custom_emoji_id: typing.Optional[base.String] = None) -> base.Boolean:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
"""
|
||||
This module has redis storage for finite-state machine based on `aioredis <https://github.com/aio-libs/aioredis>`_ driver
|
||||
This module has redis storage for finite-state machine based on `redis <https://pypi.org/project/redis/>`_ driver.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import typing
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import aioredis
|
||||
|
||||
from ...dispatcher.storage import BaseStorage
|
||||
from ...utils import json
|
||||
from ...utils.deprecated import deprecated
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import aioredis
|
||||
|
||||
STATE_KEY = 'state'
|
||||
STATE_DATA_KEY = 'data'
|
||||
STATE_BUCKET_KEY = 'bucket'
|
||||
|
|
@ -67,6 +67,8 @@ class RedisStorage(BaseStorage):
|
|||
Get Redis connection
|
||||
"""
|
||||
# Use thread-safe asyncio Lock because this method without that is not safe
|
||||
import aioredis
|
||||
|
||||
async with self._connection_lock:
|
||||
if self._redis is None or self._redis.closed:
|
||||
self._redis = await aioredis.create_connection((self._host, self._port),
|
||||
|
|
@ -207,138 +209,6 @@ class RedisStorage(BaseStorage):
|
|||
await self.set_record(chat=chat, user=user, state=record['state'], data=record_bucket, bucket=bucket)
|
||||
|
||||
|
||||
class AioRedisAdapterBase(ABC):
|
||||
"""Base aioredis adapter class."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
host: str = "localhost",
|
||||
port: int = 6379,
|
||||
db: typing.Optional[int] = None,
|
||||
password: typing.Optional[str] = None,
|
||||
ssl: typing.Optional[bool] = None,
|
||||
pool_size: int = 10,
|
||||
loop: typing.Optional[asyncio.AbstractEventLoop] = None,
|
||||
prefix: str = "fsm",
|
||||
state_ttl: typing.Optional[int] = None,
|
||||
data_ttl: typing.Optional[int] = None,
|
||||
bucket_ttl: typing.Optional[int] = None,
|
||||
**kwargs,
|
||||
):
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._db = db
|
||||
self._password = password
|
||||
self._ssl = ssl
|
||||
self._pool_size = pool_size
|
||||
self._kwargs = kwargs
|
||||
self._prefix = (prefix,)
|
||||
|
||||
self._state_ttl = state_ttl
|
||||
self._data_ttl = data_ttl
|
||||
self._bucket_ttl = bucket_ttl
|
||||
|
||||
self._redis: typing.Optional["aioredis.Redis"] = None
|
||||
self._connection_lock = asyncio.Lock()
|
||||
|
||||
@abstractmethod
|
||||
async def get_redis(self) -> aioredis.Redis:
|
||||
"""Get Redis connection."""
|
||||
pass
|
||||
|
||||
async def close(self):
|
||||
"""Grace shutdown."""
|
||||
pass
|
||||
|
||||
async def wait_closed(self):
|
||||
"""Wait for grace shutdown finishes."""
|
||||
pass
|
||||
|
||||
async def set(self, name, value, ex=None, **kwargs):
|
||||
"""Set the value at key ``name`` to ``value``."""
|
||||
if ex == 0:
|
||||
ex = None
|
||||
return await self._redis.set(name, value, ex=ex, **kwargs)
|
||||
|
||||
async def get(self, name, **kwargs):
|
||||
"""Return the value at key ``name`` or None."""
|
||||
return await self._redis.get(name, **kwargs)
|
||||
|
||||
async def delete(self, *names):
|
||||
"""Delete one or more keys specified by ``names``"""
|
||||
return await self._redis.delete(*names)
|
||||
|
||||
async def keys(self, pattern, **kwargs):
|
||||
"""Returns a list of keys matching ``pattern``."""
|
||||
return await self._redis.keys(pattern, **kwargs)
|
||||
|
||||
async def flushdb(self):
|
||||
"""Delete all keys in the current database."""
|
||||
return await self._redis.flushdb()
|
||||
|
||||
|
||||
class AioRedisAdapterV1(AioRedisAdapterBase):
|
||||
"""Redis adapter for aioredis v1."""
|
||||
|
||||
async def get_redis(self) -> aioredis.Redis:
|
||||
"""Get Redis connection."""
|
||||
async with self._connection_lock: # to prevent race
|
||||
if self._redis is None or self._redis.closed:
|
||||
self._redis = await aioredis.create_redis_pool(
|
||||
(self._host, self._port),
|
||||
db=self._db,
|
||||
password=self._password,
|
||||
ssl=self._ssl,
|
||||
minsize=1,
|
||||
maxsize=self._pool_size,
|
||||
**self._kwargs,
|
||||
)
|
||||
return self._redis
|
||||
|
||||
async def close(self):
|
||||
async with self._connection_lock:
|
||||
if self._redis and not self._redis.closed:
|
||||
self._redis.close()
|
||||
|
||||
async def wait_closed(self):
|
||||
async with self._connection_lock:
|
||||
if self._redis:
|
||||
return await self._redis.wait_closed()
|
||||
return True
|
||||
|
||||
async def get(self, name, **kwargs):
|
||||
return await self._redis.get(name, encoding="utf8", **kwargs)
|
||||
|
||||
async def set(self, name, value, ex=None, **kwargs):
|
||||
if ex == 0:
|
||||
ex = None
|
||||
return await self._redis.set(name, value, expire=ex, **kwargs)
|
||||
|
||||
async def keys(self, pattern, **kwargs):
|
||||
"""Returns a list of keys matching ``pattern``."""
|
||||
return await self._redis.keys(pattern, encoding="utf8", **kwargs)
|
||||
|
||||
|
||||
class AioRedisAdapterV2(AioRedisAdapterBase):
|
||||
"""Redis adapter for aioredis v2."""
|
||||
|
||||
async def get_redis(self) -> aioredis.Redis:
|
||||
"""Get Redis connection."""
|
||||
async with self._connection_lock: # to prevent race
|
||||
if self._redis is None:
|
||||
self._redis = aioredis.Redis(
|
||||
host=self._host,
|
||||
port=self._port,
|
||||
db=self._db,
|
||||
password=self._password,
|
||||
ssl=self._ssl,
|
||||
max_connections=self._pool_size,
|
||||
decode_responses=True,
|
||||
**self._kwargs,
|
||||
)
|
||||
return self._redis
|
||||
|
||||
|
||||
class RedisStorage2(BaseStorage):
|
||||
"""
|
||||
Busted Redis-base storage for FSM.
|
||||
|
|
@ -356,7 +226,6 @@ class RedisStorage2(BaseStorage):
|
|||
.. code-block:: python3
|
||||
|
||||
await dp.storage.close()
|
||||
await dp.storage.wait_closed()
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -375,75 +244,49 @@ class RedisStorage2(BaseStorage):
|
|||
bucket_ttl: typing.Optional[int] = None,
|
||||
**kwargs,
|
||||
):
|
||||
self._host = host
|
||||
self._port = port
|
||||
self._db = db
|
||||
self._password = password
|
||||
self._ssl = ssl
|
||||
self._pool_size = pool_size
|
||||
self._kwargs = kwargs
|
||||
self._prefix = (prefix,)
|
||||
from redis.asyncio import Redis
|
||||
|
||||
self._redis: typing.Optional[Redis] = Redis(
|
||||
host=host,
|
||||
port=port,
|
||||
db=db,
|
||||
password=password,
|
||||
ssl=ssl,
|
||||
max_connections=pool_size,
|
||||
decode_responses=True,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self._prefix = (prefix,)
|
||||
self._state_ttl = state_ttl
|
||||
self._data_ttl = data_ttl
|
||||
self._bucket_ttl = bucket_ttl
|
||||
|
||||
self._redis: typing.Optional[AioRedisAdapterBase] = None
|
||||
self._connection_lock = asyncio.Lock()
|
||||
|
||||
@deprecated("This method will be removed in aiogram v3.0. "
|
||||
"You should use your own instance of Redis.", stacklevel=3)
|
||||
async def redis(self) -> aioredis.Redis:
|
||||
adapter = await self._get_adapter()
|
||||
return await adapter.get_redis()
|
||||
|
||||
async def _get_adapter(self) -> AioRedisAdapterBase:
|
||||
"""Get adapter based on aioredis version."""
|
||||
if self._redis is None:
|
||||
redis_version = int(aioredis.__version__.split(".")[0])
|
||||
connection_data = dict(
|
||||
host=self._host,
|
||||
port=self._port,
|
||||
db=self._db,
|
||||
password=self._password,
|
||||
ssl=self._ssl,
|
||||
pool_size=self._pool_size,
|
||||
**self._kwargs,
|
||||
)
|
||||
if redis_version == 1:
|
||||
self._redis = AioRedisAdapterV1(**connection_data)
|
||||
elif redis_version == 2:
|
||||
self._redis = AioRedisAdapterV2(**connection_data)
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported aioredis version: {redis_version}")
|
||||
await self._redis.get_redis()
|
||||
async def redis(self) -> "aioredis.Redis":
|
||||
return self._redis
|
||||
|
||||
def generate_key(self, *parts):
|
||||
return ':'.join(self._prefix + tuple(map(str, parts)))
|
||||
|
||||
async def close(self):
|
||||
if self._redis:
|
||||
return await self._redis.close()
|
||||
await self._redis.close()
|
||||
|
||||
async def wait_closed(self):
|
||||
if self._redis:
|
||||
await self._redis.wait_closed()
|
||||
self._redis = None
|
||||
pass
|
||||
|
||||
async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
default: typing.Optional[str] = None) -> typing.Optional[str]:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
key = self.generate_key(chat, user, STATE_KEY)
|
||||
redis = await self._get_adapter()
|
||||
return await redis.get(key) or self.resolve_state(default)
|
||||
return await self._redis.get(key) or self.resolve_state(default)
|
||||
|
||||
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
default: typing.Optional[dict] = None) -> typing.Dict:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
key = self.generate_key(chat, user, STATE_DATA_KEY)
|
||||
redis = await self._get_adapter()
|
||||
raw_result = await redis.get(key)
|
||||
raw_result = await self._redis.get(key)
|
||||
if raw_result:
|
||||
return json.loads(raw_result)
|
||||
return default or {}
|
||||
|
|
@ -452,21 +295,19 @@ class RedisStorage2(BaseStorage):
|
|||
state: typing.Optional[typing.AnyStr] = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
key = self.generate_key(chat, user, STATE_KEY)
|
||||
redis = await self._get_adapter()
|
||||
if state is None:
|
||||
await redis.delete(key)
|
||||
await self._redis.delete(key)
|
||||
else:
|
||||
await redis.set(key, self.resolve_state(state), ex=self._state_ttl)
|
||||
await self._redis.set(key, self.resolve_state(state), ex=self._state_ttl)
|
||||
|
||||
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
data: typing.Dict = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
key = self.generate_key(chat, user, STATE_DATA_KEY)
|
||||
redis = await self._get_adapter()
|
||||
if data:
|
||||
await redis.set(key, json.dumps(data), ex=self._data_ttl)
|
||||
await self._redis.set(key, json.dumps(data), ex=self._data_ttl)
|
||||
else:
|
||||
await redis.delete(key)
|
||||
await self._redis.delete(key)
|
||||
|
||||
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
|
||||
data: typing.Dict = None, **kwargs):
|
||||
|
|
@ -483,8 +324,7 @@ class RedisStorage2(BaseStorage):
|
|||
default: typing.Optional[dict] = None) -> typing.Dict:
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
|
||||
redis = await self._get_adapter()
|
||||
raw_result = await redis.get(key)
|
||||
raw_result = await self._redis.get(key)
|
||||
if raw_result:
|
||||
return json.loads(raw_result)
|
||||
return default or {}
|
||||
|
|
@ -493,11 +333,10 @@ class RedisStorage2(BaseStorage):
|
|||
bucket: typing.Dict = None):
|
||||
chat, user = self.check_address(chat=chat, user=user)
|
||||
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
|
||||
redis = await self._get_adapter()
|
||||
if bucket:
|
||||
await redis.set(key, json.dumps(bucket), ex=self._bucket_ttl)
|
||||
await self._redis.set(key, json.dumps(bucket), ex=self._bucket_ttl)
|
||||
else:
|
||||
await redis.delete(key)
|
||||
await self._redis.delete(key)
|
||||
|
||||
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
|
||||
user: typing.Union[str, int, None] = None,
|
||||
|
|
@ -515,13 +354,11 @@ class RedisStorage2(BaseStorage):
|
|||
:param full: clean DB or clean only states
|
||||
:return:
|
||||
"""
|
||||
redis = await self._get_adapter()
|
||||
|
||||
if full:
|
||||
await redis.flushdb()
|
||||
await self._redis.flushdb()
|
||||
else:
|
||||
keys = await redis.keys(self.generate_key('*'))
|
||||
await redis.delete(*keys)
|
||||
keys = await self._redis.keys(self.generate_key('*'))
|
||||
await self._redis.delete(*keys)
|
||||
|
||||
async def get_states_list(self) -> typing.List[typing.Tuple[str, str]]:
|
||||
"""
|
||||
|
|
@ -529,10 +366,9 @@ class RedisStorage2(BaseStorage):
|
|||
|
||||
:return: list of tuples where first element is chat id and second is user id
|
||||
"""
|
||||
redis = await self._get_adapter()
|
||||
result = []
|
||||
|
||||
keys = await redis.keys(self.generate_key('*', '*', STATE_KEY))
|
||||
keys = await self._redis.keys(self.generate_key('*', '*', STATE_KEY))
|
||||
for item in keys:
|
||||
*_, chat, user, _ = item.split(':')
|
||||
result.append((chat, user))
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from .chat_member import ChatMember, ChatMemberAdministrator, ChatMemberBanned,
|
|||
from .chat_member_updated import ChatMemberUpdated
|
||||
from .chat_permissions import ChatPermissions
|
||||
from .chat_photo import ChatPhoto
|
||||
from .chat_shared import ChatShared
|
||||
from .chosen_inline_result import ChosenInlineResult
|
||||
from .contact import Contact
|
||||
from .dice import Dice, DiceEmoji
|
||||
|
|
@ -32,9 +33,12 @@ from .force_reply import ForceReply
|
|||
from .forum_topic import ForumTopic
|
||||
from .forum_topic_closed import ForumTopicClosed
|
||||
from .forum_topic_created import ForumTopicCreated
|
||||
from .forum_topic_edited import ForumTopicEdited
|
||||
from .forum_topic_reopened import ForumTopicReopened
|
||||
from .game import Game
|
||||
from .game_high_score import GameHighScore
|
||||
from .general_forum_topic_hidden import GeneralForumTopicHidden
|
||||
from .general_forum_topic_unhidden import GeneralForumTopicUnhidden
|
||||
from .inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from .inline_query import InlineQuery
|
||||
from .inline_query_result import InlineQueryResult, InlineQueryResultArticle, InlineQueryResultAudio, \
|
||||
|
|
@ -68,7 +72,8 @@ from .photo_size import PhotoSize
|
|||
from .poll import PollOption, Poll, PollAnswer, PollType
|
||||
from .pre_checkout_query import PreCheckoutQuery
|
||||
from .proximity_alert_triggered import ProximityAlertTriggered
|
||||
from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, KeyboardButtonPollType
|
||||
from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, KeyboardButtonPollType, \
|
||||
KeyboardButtonRequestChat, KeyboardButtonRequestUser
|
||||
from .response_parameters import ResponseParameters
|
||||
from .sent_web_app_message import SentWebAppMessage
|
||||
from .shipping_address import ShippingAddress
|
||||
|
|
@ -80,6 +85,7 @@ from .successful_payment import SuccessfulPayment
|
|||
from .update import AllowedUpdates, Update
|
||||
from .user import User
|
||||
from .user_profile_photos import UserProfilePhotos
|
||||
from .user_shared import UserShared
|
||||
from .venue import Venue
|
||||
from .video import Video
|
||||
from .video_chat_ended import VideoChatEnded
|
||||
|
|
@ -95,6 +101,7 @@ from .voice_chat_started import VoiceChatStarted
|
|||
from .web_app_data import WebAppData
|
||||
from .web_app_info import WebAppInfo
|
||||
from .webhook_info import WebhookInfo
|
||||
from .write_access_allowed import WriteAccessAllowed
|
||||
|
||||
__all__ = (
|
||||
'AllowedUpdates',
|
||||
|
|
@ -185,6 +192,8 @@ __all__ = (
|
|||
'Invoice',
|
||||
'KeyboardButton',
|
||||
'KeyboardButtonPollType',
|
||||
'KeyboardButtonRequestChat',
|
||||
'KeyboardButtonRequestUser',
|
||||
'LabeledPrice',
|
||||
'Location',
|
||||
'LoginUrl',
|
||||
|
|
@ -248,6 +257,12 @@ __all__ = (
|
|||
'ForumTopicCreated',
|
||||
'ForumTopicClosed',
|
||||
'ForumTopicReopened',
|
||||
'ForumTopicEdited',
|
||||
'GeneralForumTopicHidden',
|
||||
'GeneralForumTopicUnhidden',
|
||||
'WriteAccessAllowed',
|
||||
"ChatShared",
|
||||
"UserShared",
|
||||
'base',
|
||||
'fields',
|
||||
)
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ class Chat(base.TelegramObject):
|
|||
can_set_sticker_set: base.Boolean = fields.Field()
|
||||
linked_chat_id: base.Integer = fields.Field()
|
||||
location: ChatLocation = fields.Field()
|
||||
has_hidden_members: base.Boolean = fields.Field()
|
||||
has_aggressive_anti_spam_enabled: base.Boolean = fields.Field()
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
|
@ -312,14 +314,18 @@ class Chat(base.TelegramObject):
|
|||
async def promote(self,
|
||||
user_id: base.Integer,
|
||||
is_anonymous: typing.Optional[base.Boolean] = None,
|
||||
can_manage_chat: typing.Optional[base.Boolean] = None,
|
||||
can_change_info: typing.Optional[base.Boolean] = None,
|
||||
can_post_messages: typing.Optional[base.Boolean] = None,
|
||||
can_edit_messages: typing.Optional[base.Boolean] = None,
|
||||
can_delete_messages: typing.Optional[base.Boolean] = None,
|
||||
can_manage_voice_chats: typing.Optional[base.Boolean] = None,
|
||||
can_invite_users: typing.Optional[base.Boolean] = None,
|
||||
can_restrict_members: typing.Optional[base.Boolean] = None,
|
||||
can_pin_messages: typing.Optional[base.Boolean] = None,
|
||||
can_promote_members: typing.Optional[base.Boolean] = None) -> base.Boolean:
|
||||
can_promote_members: typing.Optional[base.Boolean] = None,
|
||||
can_manage_video_chats: typing.Optional[base.Boolean] = None,
|
||||
can_manage_topics: typing.Optional[base.Boolean] = None,) -> base.Boolean:
|
||||
"""
|
||||
Use this method to promote or demote a user in a supergroup or a channel.
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
|
|
@ -362,6 +368,7 @@ class Chat(base.TelegramObject):
|
|||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
|
||||
return await self.bot.promote_chat_member(self.id,
|
||||
user_id=user_id,
|
||||
is_anonymous=is_anonymous,
|
||||
|
|
@ -372,7 +379,12 @@ class Chat(base.TelegramObject):
|
|||
can_invite_users=can_invite_users,
|
||||
can_restrict_members=can_restrict_members,
|
||||
can_pin_messages=can_pin_messages,
|
||||
can_promote_members=can_promote_members)
|
||||
can_promote_members=can_promote_members,
|
||||
can_manage_chat=can_manage_chat,
|
||||
can_manage_voice_chats=can_manage_voice_chats,
|
||||
can_manage_video_chats=can_manage_video_chats,
|
||||
can_manage_topics=can_manage_topics
|
||||
)
|
||||
|
||||
async def set_permissions(self, permissions: ChatPermissions) -> base.Boolean:
|
||||
"""
|
||||
|
|
@ -552,7 +564,7 @@ class Chat(base.TelegramObject):
|
|||
"""
|
||||
return await self.bot.delete_chat_sticker_set(self.id)
|
||||
|
||||
async def do(self, action: base.String) -> base.Boolean:
|
||||
async def do(self, action: base.String, message_thread_id: typing.Optional[base.Integer] = None) -> base.Boolean:
|
||||
"""
|
||||
Use this method when you need to tell the user that something is happening on the bot's side.
|
||||
The status is set for 5 seconds or less
|
||||
|
|
@ -565,6 +577,8 @@ class Chat(base.TelegramObject):
|
|||
|
||||
:param action: Type of action to broadcast.
|
||||
:type action: :obj:`base.String`
|
||||
:param message_thread_id: Unique identifier for the target message thread; supergroups only
|
||||
:type message_thread_id: :obj:`typing.Optional[base.Integer]`
|
||||
:return: Returns True on success.
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class ChatJoinRequest(base.TelegramObject):
|
|||
|
||||
chat: Chat = fields.Field(base=Chat)
|
||||
from_user: User = fields.Field(alias="from", base=User)
|
||||
user_chat_id: base.Integer = fields.Field()
|
||||
date: datetime = fields.DateTimeField()
|
||||
bio: base.String = fields.Field()
|
||||
invite_link: ChatInviteLink = fields.Field(base=ChatInviteLink)
|
||||
|
|
|
|||
|
|
@ -189,7 +189,16 @@ class ChatMemberRestricted(ChatMember):
|
|||
can_pin_messages: base.Boolean = fields.Field()
|
||||
can_manage_topics: base.Boolean = fields.Field()
|
||||
can_send_messages: base.Boolean = fields.Field()
|
||||
can_send_audios: base.Boolean = fields.Field()
|
||||
can_send_documents: base.Boolean = fields.Field()
|
||||
can_send_photos: base.Boolean = fields.Field()
|
||||
can_send_videos: base.Boolean = fields.Field()
|
||||
can_send_video_notes: base.Boolean = fields.Field()
|
||||
can_send_voice_notes: base.Boolean = fields.Field()
|
||||
|
||||
# warning! field was replaced: https://core.telegram.org/bots/api#february-3-2023
|
||||
can_send_media_messages: base.Boolean = fields.Field()
|
||||
|
||||
can_send_polls: base.Boolean = fields.Field()
|
||||
can_send_other_messages: base.Boolean = fields.Field()
|
||||
can_add_web_page_previews: base.Boolean = fields.Field()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ class ChatPermissions(base.TelegramObject):
|
|||
https://core.telegram.org/bots/api#chatpermissions
|
||||
"""
|
||||
can_send_messages: base.Boolean = fields.Field()
|
||||
can_send_media_messages: base.Boolean = fields.Field()
|
||||
can_send_media_messages: base.Boolean = fields.Field() # Deprecated since Bot API 6.5
|
||||
can_send_audios: base.Boolean = fields.Field()
|
||||
can_send_documents: base.Boolean = fields.Field()
|
||||
can_send_photos: base.Boolean = fields.Field()
|
||||
can_send_videos: base.Boolean = fields.Field()
|
||||
can_send_video_notes: base.Boolean = fields.Field()
|
||||
can_send_voice_notes: base.Boolean = fields.Field()
|
||||
can_send_polls: base.Boolean = fields.Field()
|
||||
can_send_other_messages: base.Boolean = fields.Field()
|
||||
can_add_web_page_previews: base.Boolean = fields.Field()
|
||||
|
|
@ -21,6 +27,12 @@ class ChatPermissions(base.TelegramObject):
|
|||
def __init__(self,
|
||||
can_send_messages: base.Boolean = None,
|
||||
can_send_media_messages: base.Boolean = None,
|
||||
can_send_audios: base.Boolean = None,
|
||||
can_send_documents: base.Boolean = None,
|
||||
can_send_photos: base.Boolean = None,
|
||||
can_send_videos: base.Boolean = None,
|
||||
can_send_video_notes: base.Boolean = None,
|
||||
can_send_voice_notes: base.Boolean = None,
|
||||
can_send_polls: base.Boolean = None,
|
||||
can_send_other_messages: base.Boolean = None,
|
||||
can_add_web_page_previews: base.Boolean = None,
|
||||
|
|
@ -32,6 +44,12 @@ class ChatPermissions(base.TelegramObject):
|
|||
super(ChatPermissions, self).__init__(
|
||||
can_send_messages=can_send_messages,
|
||||
can_send_media_messages=can_send_media_messages,
|
||||
can_send_audios=can_send_audios,
|
||||
can_send_documents=can_send_documents,
|
||||
can_send_photos=can_send_photos,
|
||||
can_send_videos=can_send_videos,
|
||||
can_send_video_notes=can_send_video_notes,
|
||||
can_send_voice_notes=can_send_voice_notes,
|
||||
can_send_polls=can_send_polls,
|
||||
can_send_other_messages=can_send_other_messages,
|
||||
can_add_web_page_previews=can_add_web_page_previews,
|
||||
|
|
@ -39,4 +57,5 @@ class ChatPermissions(base.TelegramObject):
|
|||
can_invite_users=can_invite_users,
|
||||
can_pin_messages=can_pin_messages,
|
||||
can_manage_topics=can_manage_topics,
|
||||
**kwargs
|
||||
)
|
||||
|
|
|
|||
12
aiogram/types/chat_shared.py
Normal file
12
aiogram/types/chat_shared.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from . import base, fields
|
||||
|
||||
|
||||
class ChatShared(base.TelegramObject):
|
||||
"""
|
||||
This object contains information about the chat whose identifier was
|
||||
shared with the bot using a KeyboardButtonRequestChat button.
|
||||
|
||||
https://core.telegram.org/bots/api#chatshared
|
||||
"""
|
||||
request_id: base.Integer = fields.Field()
|
||||
user_id: base.Integer = fields.Field()
|
||||
14
aiogram/types/forum_topic_edited.py
Normal file
14
aiogram/types/forum_topic_edited.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import typing
|
||||
|
||||
from . import base
|
||||
from . import fields
|
||||
|
||||
|
||||
class ForumTopicEdited(base.TelegramObject):
|
||||
"""
|
||||
This object represents a service message about an edited forum topic.
|
||||
|
||||
https://core.telegram.org/bots/api#forumtopicedited
|
||||
"""
|
||||
name: typing.Optional[base.String] = fields.Field()
|
||||
icon_custom_emoji_id: typing.Optional[base.String] = fields.Field()
|
||||
9
aiogram/types/general_forum_topic_hidden.py
Normal file
9
aiogram/types/general_forum_topic_hidden.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from . import base
|
||||
|
||||
|
||||
class GeneralForumTopicHidden(base.TelegramObject):
|
||||
"""
|
||||
This object represents a service message about General forum topic hidden in the chat. Currently holds no information.
|
||||
|
||||
https://core.telegram.org/bots/api#generalforumtopichidden
|
||||
"""
|
||||
9
aiogram/types/general_forum_topic_unhidden.py
Normal file
9
aiogram/types/general_forum_topic_unhidden.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from . import base
|
||||
|
||||
|
||||
class GeneralForumTopicUnhidden(base.TelegramObject):
|
||||
"""
|
||||
This object represents a service message about General forum topic unhidden in the chat. Currently holds no information.
|
||||
|
||||
https://core.telegram.org/bots/api#generalforumtopicunhidden
|
||||
"""
|
||||
|
|
@ -107,6 +107,7 @@ class InputMediaAnimation(InputMedia):
|
|||
width: base.Integer = fields.Field()
|
||||
height: base.Integer = fields.Field()
|
||||
duration: base.Integer = fields.Field()
|
||||
has_spoiler: typing.Optional[base.Boolean] = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -118,12 +119,13 @@ class InputMediaAnimation(InputMedia):
|
|||
duration: base.Integer = None,
|
||||
parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
has_spoiler: typing.Optional[base.Boolean] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
type='animation', media=media, thumb=thumb, caption=caption, width=width,
|
||||
height=height, duration=duration, parse_mode=parse_mode,
|
||||
caption_entities=caption_entities, conf=kwargs,
|
||||
caption_entities=caption_entities, has_spoiler=has_spoiler, conf=kwargs,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -188,6 +190,7 @@ class InputMediaPhoto(InputMedia):
|
|||
|
||||
https://core.telegram.org/bots/api#inputmediaphoto
|
||||
"""
|
||||
has_spoiler: typing.Optional[base.Boolean] = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -195,11 +198,12 @@ class InputMediaPhoto(InputMedia):
|
|||
caption: base.String = None,
|
||||
parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
has_spoiler: typing.Optional[base.Boolean] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
type='photo', media=media, caption=caption, parse_mode=parse_mode,
|
||||
caption_entities=caption_entities, conf=kwargs,
|
||||
caption_entities=caption_entities, has_spoiler=has_spoiler, conf=kwargs,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -213,6 +217,7 @@ class InputMediaVideo(InputMedia):
|
|||
height: base.Integer = fields.Field()
|
||||
duration: base.Integer = fields.Field()
|
||||
supports_streaming: base.Boolean = fields.Field()
|
||||
has_spoiler: typing.Optional[base.Boolean] = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -225,13 +230,14 @@ class InputMediaVideo(InputMedia):
|
|||
parse_mode: base.String = None,
|
||||
caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
|
||||
supports_streaming: base.Boolean = None,
|
||||
has_spoiler: typing.Optional[base.Boolean] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
type='video', media=media, thumb=thumb, caption=caption,
|
||||
width=width, height=height, duration=duration,
|
||||
parse_mode=parse_mode, caption_entities=caption_entities,
|
||||
supports_streaming=supports_streaming, conf=kwargs
|
||||
supports_streaming=supports_streaming, has_spoiler=has_spoiler, conf=kwargs
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,18 @@ from . import base, fields
|
|||
from .animation import Animation
|
||||
from .audio import Audio
|
||||
from .chat import Chat, ChatType
|
||||
from .chat_shared import ChatShared
|
||||
from .contact import Contact
|
||||
from .dice import Dice
|
||||
from .document import Document
|
||||
from .force_reply import ForceReply
|
||||
from .forum_topic_closed import ForumTopicClosed
|
||||
from .forum_topic_created import ForumTopicCreated
|
||||
from .forum_topic_edited import ForumTopicEdited
|
||||
from .forum_topic_reopened import ForumTopicReopened
|
||||
from .game import Game
|
||||
from .general_forum_topic_hidden import GeneralForumTopicHidden
|
||||
from .general_forum_topic_unhidden import GeneralForumTopicUnhidden
|
||||
from .inline_keyboard import InlineKeyboardMarkup
|
||||
from .input_media import InputMedia, MediaGroup
|
||||
from .invoice import Invoice
|
||||
|
|
@ -28,6 +35,7 @@ from .reply_keyboard import ReplyKeyboardMarkup, ReplyKeyboardRemove
|
|||
from .sticker import Sticker
|
||||
from .successful_payment import SuccessfulPayment
|
||||
from .user import User
|
||||
from .user_shared import UserShared
|
||||
from .venue import Venue
|
||||
from .video import Video
|
||||
from .video_chat_ended import VideoChatEnded
|
||||
|
|
@ -41,9 +49,7 @@ from .voice_chat_participants_invited import VoiceChatParticipantsInvited
|
|||
from .voice_chat_scheduled import VoiceChatScheduled
|
||||
from .voice_chat_started import VoiceChatStarted
|
||||
from .web_app_data import WebAppData
|
||||
from .forum_topic_created import ForumTopicCreated
|
||||
from .forum_topic_closed import ForumTopicClosed
|
||||
from .forum_topic_reopened import ForumTopicReopened
|
||||
from .write_access_allowed import WriteAccessAllowed
|
||||
from ..utils import helper
|
||||
from ..utils import markdown as md
|
||||
from ..utils.text_decorations import html_decoration, markdown_decoration
|
||||
|
|
@ -108,6 +114,8 @@ class Message(base.TelegramObject):
|
|||
pinned_message: Message = fields.Field(base="Message")
|
||||
invoice: Invoice = fields.Field(base=Invoice)
|
||||
successful_payment: SuccessfulPayment = fields.Field(base=SuccessfulPayment)
|
||||
user_shared: UserShared = fields.Field(base=UserShared)
|
||||
chat_shared: ChatShared = fields.Field(base=ChatShared)
|
||||
connected_website: base.String = fields.Field()
|
||||
passport_data: PassportData = fields.Field(base=PassportData)
|
||||
proximity_alert_triggered: ProximityAlertTriggered = fields.Field(base=ProximityAlertTriggered)
|
||||
|
|
@ -124,6 +132,11 @@ class Message(base.TelegramObject):
|
|||
video_chat_started: VideoChatStarted = fields.Field(base=VideoChatStarted)
|
||||
video_chat_ended: VideoChatEnded = fields.Field(base=VideoChatEnded)
|
||||
video_chat_participants_invited: VideoChatParticipantsInvited = fields.Field(base=VideoChatParticipantsInvited)
|
||||
forum_topic_edited: ForumTopicEdited = fields.Field(base=ForumTopicEdited)
|
||||
general_forum_topic_hidden: GeneralForumTopicHidden = fields.Field(base=GeneralForumTopicHidden)
|
||||
general_forum_topic_unhidden: GeneralForumTopicUnhidden = fields.Field(base=GeneralForumTopicUnhidden)
|
||||
write_access_allowed: WriteAccessAllowed = fields.Field(base=WriteAccessAllowed)
|
||||
has_media_spoiler: base.Boolean = fields.Field()
|
||||
|
||||
@property
|
||||
@functools.lru_cache()
|
||||
|
|
@ -212,6 +225,18 @@ class Message(base.TelegramObject):
|
|||
return ContentType.VIDEO_CHAT_ENDED
|
||||
if self.video_chat_participants_invited:
|
||||
return ContentType.VIDEO_CHAT_PARTICIPANTS_INVITED
|
||||
if self.forum_topic_edited:
|
||||
return ContentType.FORUM_TOPIC_EDITED
|
||||
if self.general_forum_topic_hidden:
|
||||
return ContentType.GENERAL_FORUM_TOPIC_HIDDEN
|
||||
if self.general_forum_topic_unhidden:
|
||||
return ContentType.GENERAL_FORUM_TOPIC_UNHIDDEN
|
||||
if self.write_access_allowed:
|
||||
return ContentType.WRITE_ACCESS_ALLOWED
|
||||
if self.chat_shared:
|
||||
return ContentType.CHAT_SHARED
|
||||
if self.user_shared:
|
||||
return ContentType.USER_SHARED
|
||||
|
||||
return ContentType.UNKNOWN
|
||||
|
||||
|
|
@ -1578,7 +1603,7 @@ class Message(base.TelegramObject):
|
|||
|
||||
async def answer_chat_action(
|
||||
self,
|
||||
action: base.String,
|
||||
action: base.String, message_thread_id: typing.Optional[base.Integer] = None
|
||||
) -> base.Boolean:
|
||||
"""
|
||||
Use this method when you need to tell the user that something is happening on the bot's side.
|
||||
|
|
@ -1592,6 +1617,8 @@ class Message(base.TelegramObject):
|
|||
|
||||
:param action: Type of action to broadcast
|
||||
:type action: :obj:`base.String`
|
||||
:param message_thread_id: Unique identifier for the target message thread; supergroups only
|
||||
:type message_thread_id: :obj:`typing.Optional[base.Integer]`
|
||||
:return: Returns True on success
|
||||
:rtype: :obj:`base.Boolean`
|
||||
"""
|
||||
|
|
@ -3245,9 +3272,9 @@ class Message(base.TelegramObject):
|
|||
reply_to_message_id: typing.Optional[base.Integer] = None,
|
||||
allow_sending_without_reply: typing.Optional[base.Boolean] = None,
|
||||
reply_markup: typing.Union[InlineKeyboardMarkup,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
ForceReply, None] = None,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
ForceReply, None] = None,
|
||||
) -> MessageId:
|
||||
return await self.bot.copy_message(
|
||||
chat_id=chat_id,
|
||||
|
|
@ -3343,6 +3370,12 @@ class ContentType(helper.Helper):
|
|||
VIDEO_CHAT_STARTED = helper.Item() # video_chat_started
|
||||
VIDEO_CHAT_ENDED = helper.Item() # video_chat_ended
|
||||
VIDEO_CHAT_PARTICIPANTS_INVITED = helper.Item() # video_chat_participants_invited
|
||||
FORUM_TOPIC_EDITED = helper.Item() # forum_topic_edited
|
||||
GENERAL_FORUM_TOPIC_HIDDEN = helper.Item() # general_forum_topic_hidden
|
||||
GENERAL_FORUM_TOPIC_UNHIDDEN = helper.Item() # general_forum_topic_unhidden
|
||||
WRITE_ACCESS_ALLOWED = helper.Item() # write_access_allowed
|
||||
CHAT_SHARED = helper.Item() # chat_shared
|
||||
USER_SHARED = helper.Item() # user_shared
|
||||
|
||||
UNKNOWN = helper.Item() # unknown
|
||||
ANY = helper.Item() # any
|
||||
|
|
@ -3409,14 +3442,20 @@ class ContentTypes(helper.Helper):
|
|||
DELETE_CHAT_PHOTO = helper.ListItem() # delete_chat_photo
|
||||
GROUP_CHAT_CREATED = helper.ListItem() # group_chat_created
|
||||
PASSPORT_DATA = helper.ListItem() # passport_data
|
||||
WEB_APP_DATA = helper.Item() # web_app_data
|
||||
WEB_APP_DATA = helper.ListItem() # web_app_data
|
||||
FORUM_TOPIC_CREATED = helper.ListItem() # forum_topic_created
|
||||
FORUM_TOPIC_CLOSED = helper.ListItem() # forum_topic_closed
|
||||
FORUM_TOPIC_REOPENED = helper.ListItem() # forum_topic_reopened
|
||||
VIDEO_CHAT_SCHEDULED = helper.Item() # video_chat_scheduled
|
||||
VIDEO_CHAT_STARTED = helper.Item() # video_chat_started
|
||||
VIDEO_CHAT_ENDED = helper.Item() # video_chat_ended
|
||||
VIDEO_CHAT_PARTICIPANTS_INVITED = helper.Item() # video_chat_participants_invited
|
||||
VIDEO_CHAT_SCHEDULED = helper.ListItem() # video_chat_scheduled
|
||||
VIDEO_CHAT_STARTED = helper.ListItem() # video_chat_started
|
||||
VIDEO_CHAT_ENDED = helper.ListItem() # video_chat_ended
|
||||
VIDEO_CHAT_PARTICIPANTS_INVITED = helper.ListItem() # video_chat_participants_invited
|
||||
FORUM_TOPIC_EDITED = helper.ListItem() # forum_topic_edited
|
||||
GENERAL_FORUM_TOPIC_HIDDEN = helper.ListItem() # general_forum_topic_hidden
|
||||
GENERAL_FORUM_TOPIC_UNHIDDEN = helper.ListItem() # general_forum_topic_unhidden
|
||||
WRITE_ACCESS_ALLOWED = helper.ListItem() # write_access_allowed
|
||||
CHAT_SHARED = helper.ListItem() # chat_shared
|
||||
USER_SHARED = helper.ListItem() # user_shared
|
||||
|
||||
UNKNOWN = helper.ListItem() # unknown
|
||||
ANY = helper.ListItem() # any
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import typing
|
|||
|
||||
from . import base
|
||||
from . import fields
|
||||
from .chat_administrator_rights import ChatAdministratorRights
|
||||
from .web_app_info import WebAppInfo
|
||||
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
|||
one_time_keyboard: base.Boolean = fields.Field()
|
||||
input_field_placeholder: base.String = fields.Field()
|
||||
selective: base.Boolean = fields.Field()
|
||||
is_persistent: base.Boolean = fields.Field()
|
||||
|
||||
def __init__(self, keyboard: 'typing.List[typing.List[KeyboardButton]]' = None,
|
||||
resize_keyboard: base.Boolean = None,
|
||||
|
|
@ -37,6 +39,7 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
|||
input_field_placeholder: base.String = None,
|
||||
selective: base.Boolean = None,
|
||||
row_width: base.Integer = 3,
|
||||
is_persistent: base.Boolean = None,
|
||||
conf=None):
|
||||
if conf is None:
|
||||
conf = {}
|
||||
|
|
@ -46,6 +49,7 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
|||
one_time_keyboard=one_time_keyboard,
|
||||
input_field_placeholder=input_field_placeholder,
|
||||
selective=selective,
|
||||
is_persistent=is_persistent,
|
||||
conf={'row_width': row_width, **conf},
|
||||
)
|
||||
|
||||
|
|
@ -102,6 +106,76 @@ class ReplyKeyboardMarkup(base.TelegramObject):
|
|||
return self
|
||||
|
||||
|
||||
|
||||
class KeyboardButtonRequestUser(base.TelegramObject):
|
||||
"""
|
||||
This object defines the criteria used to request a suitable user.
|
||||
The identifier of the selected user will be shared with the bot when
|
||||
the corresponding button is pressed.
|
||||
|
||||
https://core.telegram.org/bots/api#keyboardbuttonrequestuser
|
||||
"""
|
||||
request_id: base.Integer = fields.Field()
|
||||
user_is_bot: base.Boolean = fields.Field()
|
||||
user_is_premium: base.Boolean = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request_id: base.Integer,
|
||||
user_is_bot: typing.Optional[base.Boolean] = None,
|
||||
user_is_premium: typing.Optional[base.Boolean] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
request_id=request_id,
|
||||
user_is_bot=user_is_bot,
|
||||
user_is_premium=user_is_premium,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class KeyboardButtonRequestChat(base.TelegramObject):
|
||||
"""
|
||||
This object defines the criteria used to request a suitable chat.
|
||||
The identifier of the selected chat will be shared with the bot when
|
||||
the corresponding button is pressed.
|
||||
|
||||
https://core.telegram.org/bots/api#keyboardbuttonrequestchat
|
||||
"""
|
||||
request_id: base.Integer = fields.Field()
|
||||
chat_is_channel: base.Boolean = fields.Field()
|
||||
chat_is_forum: base.Boolean = fields.Field()
|
||||
chat_has_username: base.Boolean = fields.Field()
|
||||
chat_is_created: base.Boolean = fields.Field()
|
||||
user_administrator_rights: ChatAdministratorRights = fields.Field()
|
||||
bot_administrator_rights: ChatAdministratorRights = fields.Field()
|
||||
bot_is_member: base.Boolean = fields.Field()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request_id: base.Integer,
|
||||
chat_is_channel: base.Boolean,
|
||||
chat_is_forum: typing.Optional[base.Boolean] = None,
|
||||
chat_has_username: typing.Optional[base.Boolean] = None,
|
||||
chat_is_created: typing.Optional[base.Boolean] = None,
|
||||
user_administrator_rights: typing.Optional[ChatAdministratorRights] = None,
|
||||
bot_administrator_rights: typing.Optional[ChatAdministratorRights] = None,
|
||||
bot_is_member: typing.Optional[base.Boolean] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
request_id=request_id,
|
||||
chat_is_channel=chat_is_channel,
|
||||
chat_is_forum=chat_is_forum,
|
||||
chat_has_username=chat_has_username,
|
||||
chat_is_created=chat_is_created,
|
||||
user_administrator_rights=user_administrator_rights,
|
||||
bot_administrator_rights=bot_administrator_rights,
|
||||
bot_is_member=bot_is_member,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class KeyboardButton(base.TelegramObject):
|
||||
"""
|
||||
This object represents one button of the reply keyboard.
|
||||
|
|
@ -115,18 +189,24 @@ class KeyboardButton(base.TelegramObject):
|
|||
https://core.telegram.org/bots/api#keyboardbutton
|
||||
"""
|
||||
text: base.String = fields.Field()
|
||||
request_user: KeyboardButtonRequestUser = fields.Field()
|
||||
request_chat: KeyboardButtonRequestChat = fields.Field()
|
||||
request_contact: base.Boolean = fields.Field()
|
||||
request_location: base.Boolean = fields.Field()
|
||||
request_poll: KeyboardButtonPollType = fields.Field()
|
||||
web_app: WebAppInfo = fields.Field(base=WebAppInfo)
|
||||
|
||||
def __init__(self, text: base.String,
|
||||
request_user: typing.Optional[KeyboardButtonRequestUser] = None,
|
||||
request_chat: typing.Optional[KeyboardButtonRequestChat] = None,
|
||||
request_contact: base.Boolean = None,
|
||||
request_location: base.Boolean = None,
|
||||
request_poll: KeyboardButtonPollType = None,
|
||||
web_app: WebAppInfo = None,
|
||||
**kwargs):
|
||||
super(KeyboardButton, self).__init__(text=text,
|
||||
request_user=request_user,
|
||||
request_chat=request_chat,
|
||||
request_contact=request_contact,
|
||||
request_location=request_location,
|
||||
request_poll=request_poll,
|
||||
|
|
@ -134,6 +214,7 @@ class KeyboardButton(base.TelegramObject):
|
|||
**kwargs)
|
||||
|
||||
|
||||
|
||||
class ReplyKeyboardRemove(base.TelegramObject):
|
||||
"""
|
||||
Upon receiving a message with this object, Telegram clients will remove the current custom keyboard
|
||||
|
|
|
|||
12
aiogram/types/user_shared.py
Normal file
12
aiogram/types/user_shared.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from . import base, fields
|
||||
|
||||
|
||||
class UserShared(base.TelegramObject):
|
||||
"""
|
||||
This object contains information about the user whose identifier was
|
||||
shared with the bot using a KeyboardButtonRequestUser button.
|
||||
|
||||
https://core.telegram.org/bots/api#usershared
|
||||
"""
|
||||
request_id: base.Integer = fields.Field()
|
||||
user_id: base.Integer = fields.Field()
|
||||
9
aiogram/types/write_access_allowed.py
Normal file
9
aiogram/types/write_access_allowed.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from . import base
|
||||
|
||||
|
||||
class WriteAccessAllowed(base.TelegramObject):
|
||||
"""
|
||||
This object represents a service message about a user allowing a bot added to the attachment menu to write messages. Currently holds no information.
|
||||
|
||||
https://core.telegram.org/bots/api#writeaccessallowed
|
||||
"""
|
||||
|
|
@ -22,7 +22,7 @@ Welcome to aiogram's documentation!
|
|||
:target: https://pypi.python.org/pypi/aiogram
|
||||
:alt: Supported python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.3-blue.svg?style=flat-square&logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-6.4-blue.svg?style=flat-square&logo=telegram
|
||||
:target: https://core.telegram.org/bots/api
|
||||
:alt: Telegram Bot API
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ def pytest_addoption(parser):
|
|||
default=None,
|
||||
help="run tests which require redis connection",
|
||||
)
|
||||
parser.addini("asyncio_mode", "", default='auto')
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
import aioredis
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from pytest_lazyfixture import lazy_fixture
|
||||
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from aiogram.contrib.fsm_storage.redis import RedisStorage, RedisStorage2
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
@pytest.mark.redis
|
||||
async def redis_store(redis_options):
|
||||
if int(aioredis.__version__.split(".")[0]) == 2:
|
||||
pytest.skip('aioredis v2 is not supported.')
|
||||
return
|
||||
s = RedisStorage(**redis_options)
|
||||
try:
|
||||
yield s
|
||||
finally:
|
||||
conn = await s.redis()
|
||||
await conn.execute('FLUSHDB')
|
||||
await s.close()
|
||||
await s.wait_closed()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
@pytest.mark.redis
|
||||
async def redis_store2(redis_options):
|
||||
s = RedisStorage2(**redis_options)
|
||||
try:
|
||||
yield s
|
||||
finally:
|
||||
conn = await s.redis()
|
||||
await conn.flushdb()
|
||||
await s.close()
|
||||
await s.wait_closed()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def memory_store():
|
||||
yield MemoryStorage()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"store", [
|
||||
lazy_fixture('redis_store'),
|
||||
lazy_fixture('redis_store2'),
|
||||
lazy_fixture('memory_store'),
|
||||
]
|
||||
)
|
||||
class TestStorage:
|
||||
@pytest.mark.asyncio
|
||||
async def test_set_get(self, store):
|
||||
assert await store.get_data(chat='1234') == {}
|
||||
await store.set_data(chat='1234', data={'foo': 'bar'})
|
||||
assert await store.get_data(chat='1234') == {'foo': 'bar'}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reset(self, store):
|
||||
await store.set_data(chat='1234', data={'foo': 'bar'})
|
||||
await store.reset_data(chat='1234')
|
||||
assert await store.get_data(chat='1234') == {}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reset_empty(self, store):
|
||||
await store.reset_data(chat='1234')
|
||||
assert await store.get_data(chat='1234') == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"store", [
|
||||
lazy_fixture('redis_store'),
|
||||
lazy_fixture('redis_store2'),
|
||||
]
|
||||
)
|
||||
class TestRedisStorage2:
|
||||
@pytest.mark.asyncio
|
||||
async def test_close_and_open_connection(self, store):
|
||||
await store.set_data(chat='1234', data={'foo': 'bar'})
|
||||
assert await store.get_data(chat='1234') == {'foo': 'bar'}
|
||||
pool_id = id(store._redis)
|
||||
await store.close()
|
||||
await store.wait_closed()
|
||||
|
||||
# new pool will be open at this point
|
||||
assert await store.get_data(chat='1234') == {
|
||||
'foo': 'bar',
|
||||
}
|
||||
assert id(store._redis) != pool_id
|
||||
|
|
@ -3,8 +3,6 @@ import pytest
|
|||
from aiogram import Bot, types
|
||||
from . import BOT_ID, FakeTelegram
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_get_me(bot: Bot):
|
||||
""" getMe method test """
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ from aiogram.utils.json import json
|
|||
from tests import TOKEN
|
||||
from tests.types.dataset import FILE
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(name='bot')
|
||||
async def bot_fixture():
|
||||
|
|
|
|||
157
tests/test_contrib/test_fsm_storage/test_storage.py
Normal file
157
tests/test_contrib/test_fsm_storage/test_storage.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import aioredis
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from pytest_lazyfixture import lazy_fixture
|
||||
from redis.asyncio.connection import Connection, ConnectionPool
|
||||
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from aiogram.contrib.fsm_storage.redis import RedisStorage, RedisStorage2
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
@pytest.mark.redis
|
||||
async def redis_store(redis_options):
|
||||
if int(aioredis.__version__.split(".")[0]) == 2:
|
||||
pytest.skip('aioredis v2 is not supported.')
|
||||
return
|
||||
s = RedisStorage(**redis_options)
|
||||
try:
|
||||
yield s
|
||||
finally:
|
||||
conn = await s.redis()
|
||||
await conn.execute('FLUSHDB')
|
||||
await s.close()
|
||||
await s.wait_closed()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
@pytest.mark.redis
|
||||
async def redis_store2(redis_options):
|
||||
s = RedisStorage2(**redis_options)
|
||||
try:
|
||||
yield s
|
||||
finally:
|
||||
conn = await s.redis()
|
||||
await conn.flushdb()
|
||||
await s.close()
|
||||
await s.wait_closed()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture()
|
||||
async def memory_store():
|
||||
yield MemoryStorage()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"store", [
|
||||
lazy_fixture('redis_store'),
|
||||
lazy_fixture('redis_store2'),
|
||||
lazy_fixture('memory_store'),
|
||||
]
|
||||
)
|
||||
class TestStorage:
|
||||
async def test_set_get(self, store):
|
||||
assert await store.get_data(chat='1234') == {}
|
||||
await store.set_data(chat='1234', data={'foo': 'bar'})
|
||||
assert await store.get_data(chat='1234') == {'foo': 'bar'}
|
||||
|
||||
async def test_reset(self, store):
|
||||
await store.set_data(chat='1234', data={'foo': 'bar'})
|
||||
await store.reset_data(chat='1234')
|
||||
assert await store.get_data(chat='1234') == {}
|
||||
|
||||
async def test_reset_empty(self, store):
|
||||
await store.reset_data(chat='1234')
|
||||
assert await store.get_data(chat='1234') == {}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"store", [
|
||||
lazy_fixture('redis_store'),
|
||||
lazy_fixture('redis_store2'),
|
||||
]
|
||||
)
|
||||
class TestRedisStorage2:
|
||||
async def test_close_and_open_connection(self, store: RedisStorage2):
|
||||
await store.set_data(chat='1234', data={'foo': 'bar'})
|
||||
assert await store.get_data(chat='1234') == {'foo': 'bar'}
|
||||
await store.close()
|
||||
await store.wait_closed()
|
||||
|
||||
pool: ConnectionPool = store._redis.connection_pool
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
assert not pool._in_use_connections
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
if pool._available_connections:
|
||||
# noinspection PyUnresolvedReferences
|
||||
connection: Connection = pool._available_connections[0]
|
||||
assert connection.is_connected is False
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"chat_id,user_id,state",
|
||||
[
|
||||
[12345, 54321, "foo"],
|
||||
[12345, 54321, None],
|
||||
[12345, None, "foo"],
|
||||
[None, 54321, "foo"],
|
||||
],
|
||||
)
|
||||
async def test_set_get_state(self, chat_id, user_id, state, store):
|
||||
await store.reset_state(chat=chat_id, user=user_id, with_data=False)
|
||||
|
||||
await store.set_state(chat=chat_id, user=user_id, state=state)
|
||||
s = await store.get_state(chat=chat_id, user=user_id)
|
||||
assert s == state
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"chat_id,user_id,data,new_data",
|
||||
[
|
||||
[12345, 54321, {"foo": "bar"}, {"bar": "foo"}],
|
||||
[12345, 54321, None, None],
|
||||
[12345, 54321, {"foo": "bar"}, None],
|
||||
[12345, 54321, None, {"bar": "foo"}],
|
||||
[12345, None, {"foo": "bar"}, {"bar": "foo"}],
|
||||
[None, 54321, {"foo": "bar"}, {"bar": "foo"}],
|
||||
],
|
||||
)
|
||||
async def test_set_get_update_data(self, chat_id, user_id, data, new_data, store):
|
||||
await store.reset_state(chat=chat_id, user=user_id, with_data=True)
|
||||
|
||||
await store.set_data(chat=chat_id, user=user_id, data=data)
|
||||
d = await store.get_data(chat=chat_id, user=user_id)
|
||||
assert d == (data or {})
|
||||
|
||||
await store.update_data(chat=chat_id, user=user_id, data=new_data)
|
||||
d = await store.get_data(chat=chat_id, user=user_id)
|
||||
updated_data = (data or {})
|
||||
updated_data.update(new_data or {})
|
||||
assert d == updated_data
|
||||
|
||||
async def test_has_bucket(self, store):
|
||||
assert store.has_bucket()
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"chat_id,user_id,data,new_data",
|
||||
[
|
||||
[12345, 54321, {"foo": "bar"}, {"bar": "foo"}],
|
||||
[12345, 54321, None, None],
|
||||
[12345, 54321, {"foo": "bar"}, None],
|
||||
[12345, 54321, None, {"bar": "foo"}],
|
||||
[12345, None, {"foo": "bar"}, {"bar": "foo"}],
|
||||
[None, 54321, {"foo": "bar"}, {"bar": "foo"}],
|
||||
],
|
||||
)
|
||||
async def test_set_get_update_bucket(self, chat_id, user_id, data, new_data, store):
|
||||
await store.reset_state(chat=chat_id, user=user_id, with_data=True)
|
||||
|
||||
await store.set_bucket(chat=chat_id, user=user_id, bucket=data)
|
||||
d = await store.get_bucket(chat=chat_id, user=user_id)
|
||||
assert d == (data or {})
|
||||
|
||||
await store.update_bucket(chat=chat_id, user=user_id, bucket=new_data)
|
||||
d = await store.get_bucket(chat=chat_id, user=user_id)
|
||||
updated_bucket = (data or {})
|
||||
updated_bucket.update(new_data or {})
|
||||
assert d == updated_bucket
|
||||
|
|
@ -2,8 +2,6 @@ import pytest
|
|||
|
||||
from aiogram import Dispatcher, Bot
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
class TestDispatcherInit:
|
||||
async def test_successful_init(self, bot):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from aiogram.dispatcher.middlewares import LifetimeControllerMiddleware
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
async def test_no_skip():
|
||||
class Middleware(LifetimeControllerMiddleware):
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@ import pytest
|
|||
from aiogram.dispatcher.filters import Text, CommandStart
|
||||
from aiogram.types import Message, CallbackQuery, InlineQuery, Poll
|
||||
|
||||
# enable asyncio mode
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
def data_sample_1():
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import pytest_asyncio
|
|||
from aiogram import Bot, types
|
||||
from . import FakeTelegram
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(name="message")
|
||||
async def message_fixture(bot: Bot):
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ from aiogram.utils.deep_linking import (
|
|||
)
|
||||
from tests.types import dataset
|
||||
|
||||
# enable asyncio mode
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
PAYLOADS = [
|
||||
'foo',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from aiogram import Bot, types
|
||||
from .dataset import CHAT, FULL_CHAT
|
||||
from .. import FakeTelegram
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
chat = types.Chat(**CHAT)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ from aiogram.utils.json import json
|
|||
from tests import TOKEN
|
||||
from tests.types.dataset import FILE
|
||||
|
||||
pytestmark = pytest.mark.asyncio
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(name='bot')
|
||||
async def bot_fixture():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue