Remove Deprecated Functionality (#2644, #2740, #2745)

This commit is contained in:
Bibo-Joshi 2021-08-30 16:31:19 +02:00 committed by Hinrich Mahler
parent 5c500fb6fd
commit 8f0031e9c8
28 changed files with 68 additions and 1097 deletions

View file

@ -1,9 +0,0 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/messagequeue.py
telegram.ext.DelayQueue
=======================
.. autoclass:: telegram.ext.DelayQueue
:members:
:show-inheritance:
:special-members:

View file

@ -1,9 +0,0 @@
:github_url: https://github.com/python-telegram-bot/python-telegram-bot/blob/master/telegram/ext/messagequeue.py
telegram.ext.MessageQueue
=========================
.. autoclass:: telegram.ext.MessageQueue
:members:
:show-inheritance:
:special-members:

View file

@ -10,8 +10,6 @@ telegram.ext package
telegram.ext.callbackcontext
telegram.ext.job
telegram.ext.jobqueue
telegram.ext.messagequeue
telegram.ext.delayqueue
telegram.ext.contexttypes
telegram.ext.defaults

View file

@ -148,6 +148,11 @@ class Bot(TelegramObject):
incorporated into PTB. However, this is not guaranteed to work, i.e. it will fail for
passing files.
.. versionchanged:: 14.0
* Removed the deprecated methods ``kick_chat_member``, ``kickChatMember``,
``get_chat_members_count`` and ``getChatMembersCount``.
* Removed the deprecated property ``commands``.
Args:
token (:obj:`str`): Bot's unique authentication.
base_url (:obj:`str`, optional): Telegram Bot API service URL.
@ -173,7 +178,6 @@ class Bot(TelegramObject):
'private_key',
'defaults',
'_bot',
'_commands',
'_request',
'logger',
)
@ -209,7 +213,6 @@ class Bot(TelegramObject):
self.base_url = str(base_url) + str(self.token)
self.base_file_url = str(base_file_url) + str(self.token)
self._bot: Optional[User] = None
self._commands: Optional[List[BotCommand]] = None
self._request = request or Request()
self.private_key = None
self.logger = logging.getLogger(__name__)
@ -395,26 +398,6 @@ class Bot(TelegramObject):
""":obj:`bool`: Bot's :attr:`telegram.User.supports_inline_queries` attribute."""
return self.bot.supports_inline_queries # type: ignore
@property
def commands(self) -> List[BotCommand]:
"""
List[:class:`BotCommand`]: Bot's commands as available in the default scope.
.. deprecated:: 13.7
This property has been deprecated since there can be different commands available for
different scopes.
"""
warnings.warn(
"Bot.commands has been deprecated since there can be different command "
"lists for different scopes.",
TelegramDeprecationWarning,
stacklevel=2,
)
if self._commands is None:
self._commands = self.get_my_commands()
return self._commands
@property
def name(self) -> str:
""":obj:`str`: Bot's @username."""
@ -2424,36 +2407,6 @@ class Bot(TelegramObject):
return File.de_json(result, self) # type: ignore[return-value, arg-type]
@log
def kick_chat_member(
self,
chat_id: Union[str, int],
user_id: Union[str, int],
timeout: ODVInput[float] = DEFAULT_NONE,
until_date: Union[int, datetime] = None,
api_kwargs: JSONDict = None,
revoke_messages: bool = None,
) -> bool:
"""
Deprecated, use :func:`~telegram.Bot.ban_chat_member` instead.
.. deprecated:: 13.7
"""
warnings.warn(
'`bot.kick_chat_member` is deprecated. Use `bot.ban_chat_member` instead.',
TelegramDeprecationWarning,
stacklevel=2,
)
return self.ban_chat_member(
chat_id=chat_id,
user_id=user_id,
timeout=timeout,
until_date=until_date,
api_kwargs=api_kwargs,
revoke_messages=revoke_messages,
)
@log
def ban_chat_member(
self,
@ -3284,26 +3237,6 @@ class Bot(TelegramObject):
return ChatMember.de_list(result, self) # type: ignore
@log
def get_chat_members_count(
self,
chat_id: Union[str, int],
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> int:
"""
Deprecated, use :func:`~telegram.Bot.get_chat_member_count` instead.
.. deprecated:: 13.7
"""
warnings.warn(
'`bot.get_chat_members_count` is deprecated. '
'Use `bot.get_chat_member_count` instead.',
TelegramDeprecationWarning,
stacklevel=2,
)
return self.get_chat_member_count(chat_id=chat_id, timeout=timeout, api_kwargs=api_kwargs)
@log
def get_chat_member_count(
self,
@ -5420,10 +5353,6 @@ class Bot(TelegramObject):
result = self._post('getMyCommands', data, timeout=timeout, api_kwargs=api_kwargs)
if (scope is None or scope.type == scope.DEFAULT) and language_code is None:
self._commands = BotCommand.de_list(result, self) # type: ignore[assignment,arg-type]
return self._commands # type: ignore[return-value]
return BotCommand.de_list(result, self) # type: ignore[return-value,arg-type]
@log
@ -5480,11 +5409,6 @@ class Bot(TelegramObject):
result = self._post('setMyCommands', data, timeout=timeout, api_kwargs=api_kwargs)
# Set commands only for default scope. No need to check for outcome.
# If request failed, we won't come this far
if (scope is None or scope.type == scope.DEFAULT) and language_code is None:
self._commands = cmds
return result # type: ignore[return-value]
@log
@ -5532,9 +5456,6 @@ class Bot(TelegramObject):
result = self._post('deleteMyCommands', data, timeout=timeout, api_kwargs=api_kwargs)
if (scope is None or scope.type == scope.DEFAULT) and language_code is None:
self._commands = []
return result # type: ignore[return-value]
@log
@ -5736,8 +5657,6 @@ class Bot(TelegramObject):
"""Alias for :meth:`ban_chat_member`"""
banChatSenderChat = ban_chat_sender_chat
"""Alias for :meth:`ban_chat_sender_chat`"""
kickChatMember = kick_chat_member
"""Alias for :meth:`kick_chat_member`"""
unbanChatMember = unban_chat_member
"""Alias for :meth:`unban_chat_member`"""
unbanChatSenderChat = unban_chat_sender_chat
@ -5772,8 +5691,6 @@ class Bot(TelegramObject):
"""Alias for :meth:`delete_chat_sticker_set`"""
getChatMemberCount = get_chat_member_count
"""Alias for :meth:`get_chat_member_count`"""
getChatMembersCount = get_chat_members_count
"""Alias for :meth:`get_chat_members_count`"""
getWebhookInfo = get_webhook_info
"""Alias for :meth:`get_webhook_info`"""
setGameScore = set_game_score

View file

@ -18,13 +18,11 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Chat."""
import warnings
from datetime import datetime
from typing import TYPE_CHECKING, List, Optional, ClassVar, Union, Tuple, Any
from telegram import ChatPhoto, TelegramObject, constants
from telegram.utils.types import JSONDict, FileInput, ODVInput, DVInput
from telegram.utils.deprecate import TelegramDeprecationWarning
from .chatpermissions import ChatPermissions
from .chatlocation import ChatLocation
@ -65,6 +63,9 @@ class Chat(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id` is equal.
.. versionchanged:: 14.0
Removed the deprecated methods ``kick_member`` and ``get_members_count``.
Args:
id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits
and some programming languages may have difficulty/silent defects in interpreting it.
@ -341,25 +342,6 @@ class Chat(TelegramObject):
api_kwargs=api_kwargs,
)
def get_members_count(
self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None
) -> int:
"""
Deprecated, use :func:`~telegram.Chat.get_member_count` instead.
.. deprecated:: 13.7
"""
warnings.warn(
'`Chat.get_members_count` is deprecated. Use `Chat.get_member_count` instead.',
TelegramDeprecationWarning,
stacklevel=2,
)
return self.get_member_count(
timeout=timeout,
api_kwargs=api_kwargs,
)
def get_member_count(
self, timeout: ODVInput[float] = DEFAULT_NONE, api_kwargs: JSONDict = None
) -> int:
@ -402,33 +384,6 @@ class Chat(TelegramObject):
api_kwargs=api_kwargs,
)
def kick_member(
self,
user_id: Union[str, int],
timeout: ODVInput[float] = DEFAULT_NONE,
until_date: Union[int, datetime] = None,
api_kwargs: JSONDict = None,
revoke_messages: bool = None,
) -> bool:
"""
Deprecated, use :func:`~telegram.Chat.ban_member` instead.
.. deprecated:: 13.7
"""
warnings.warn(
'`Chat.kick_member` is deprecated. Use `Chat.ban_member` instead.',
TelegramDeprecationWarning,
stacklevel=2,
)
return self.ban_member(
user_id=user_id,
timeout=timeout,
until_date=until_date,
api_kwargs=api_kwargs,
revoke_messages=revoke_messages,
)
def ban_member(
self,
user_id: Union[str, int],

View file

@ -23,17 +23,15 @@ from telegram import constants
class ChatAction:
"""Helper class to provide constants for different chat actions."""
"""Helper class to provide constants for different chat actions.
.. versionchanged:: 14.0
Removed the deprecated constants ``RECORD_AUDIO`` and ``UPLOAD_AUDIO``.
"""
__slots__ = ()
FIND_LOCATION: ClassVar[str] = constants.CHATACTION_FIND_LOCATION
""":const:`telegram.constants.CHATACTION_FIND_LOCATION`"""
RECORD_AUDIO: ClassVar[str] = constants.CHATACTION_RECORD_AUDIO
""":const:`telegram.constants.CHATACTION_RECORD_AUDIO`
.. deprecated:: 13.5
Deprecated by Telegram. Use :attr:`RECORD_VOICE` instead.
"""
RECORD_VOICE: ClassVar[str] = constants.CHATACTION_RECORD_VOICE
""":const:`telegram.constants.CHATACTION_RECORD_VOICE`
@ -45,12 +43,6 @@ class ChatAction:
""":const:`telegram.constants.CHATACTION_RECORD_VIDEO_NOTE`"""
TYPING: ClassVar[str] = constants.CHATACTION_TYPING
""":const:`telegram.constants.CHATACTION_TYPING`"""
UPLOAD_AUDIO: ClassVar[str] = constants.CHATACTION_UPLOAD_AUDIO
""":const:`telegram.constants.CHATACTION_UPLOAD_AUDIO`
.. deprecated:: 13.5
Deprecated by Telegram. Use :attr:`UPLOAD_VOICE` instead.
"""
UPLOAD_VOICE: ClassVar[str] = constants.CHATACTION_UPLOAD_VOICE
""":const:`telegram.constants.CHATACTION_UPLOAD_VOICE`

View file

@ -70,12 +70,11 @@ Attributes:
:class:`telegram.ChatAction`:
.. versionchanged:: 14.0
Removed the deprecated constants ``CHATACTION_RECORD_AUDIO`` and ``CHATACTION_UPLOAD_AUDIO``.
Attributes:
CHATACTION_FIND_LOCATION (:obj:`str`): ``'find_location'``
CHATACTION_RECORD_AUDIO (:obj:`str`): ``'record_audio'``
.. deprecated:: 13.5
Deprecated by Telegram. Use :const:`CHATACTION_RECORD_VOICE` instead.
CHATACTION_RECORD_VOICE (:obj:`str`): ``'record_voice'``
.. versionadded:: 13.5
@ -83,9 +82,6 @@ Attributes:
CHATACTION_RECORD_VIDEO_NOTE (:obj:`str`): ``'record_video_note'``
CHATACTION_TYPING (:obj:`str`): ``'typing'``
CHATACTION_UPLOAD_AUDIO (:obj:`str`): ``'upload_audio'``
.. deprecated:: 13.5
Deprecated by Telegram. Use :const:`CHATACTION_UPLOAD_VOICE` instead.
CHATACTION_UPLOAD_VOICE (:obj:`str`): ``'upload_voice'``
.. versionadded:: 13.5
@ -274,12 +270,10 @@ CHAT_SUPERGROUP: str = 'supergroup'
CHAT_CHANNEL: str = 'channel'
CHATACTION_FIND_LOCATION: str = 'find_location'
CHATACTION_RECORD_AUDIO: str = 'record_audio'
CHATACTION_RECORD_VOICE: str = 'record_voice'
CHATACTION_RECORD_VIDEO: str = 'record_video'
CHATACTION_RECORD_VIDEO_NOTE: str = 'record_video_note'
CHATACTION_TYPING: str = 'typing'
CHATACTION_UPLOAD_AUDIO: str = 'upload_audio'
CHATACTION_UPLOAD_VOICE: str = 'upload_voice'
CHATACTION_UPLOAD_DOCUMENT: str = 'upload_document'
CHATACTION_CHOOSE_STICKER: str = 'choose_sticker'

View file

@ -25,7 +25,7 @@ from .dictpersistence import DictPersistence
from .handler import Handler
from .callbackcontext import CallbackContext
from .contexttypes import ContextTypes
from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async
from .dispatcher import Dispatcher, DispatcherHandlerStop
from .jobqueue import JobQueue, Job
from .updater import Updater
@ -41,8 +41,6 @@ from .typehandler import TypeHandler
from .conversationhandler import ConversationHandler
from .precheckoutqueryhandler import PreCheckoutQueryHandler
from .shippingqueryhandler import ShippingQueryHandler
from .messagequeue import MessageQueue
from .messagequeue import DelayQueue
from .pollanswerhandler import PollAnswerHandler
from .pollhandler import PollHandler
from .chatmemberhandler import ChatMemberHandler
@ -63,7 +61,6 @@ __all__ = (
'ContextTypes',
'ConversationHandler',
'Defaults',
'DelayQueue',
'DictPersistence',
'Dispatcher',
'DispatcherHandlerStop',
@ -76,7 +73,6 @@ __all__ = (
'JobQueue',
'MessageFilter',
'MessageHandler',
'MessageQueue',
'PersistenceInput',
'PicklePersistence',
'PollAnswerHandler',
@ -89,5 +85,4 @@ __all__ = (
'TypeHandler',
'UpdateFilter',
'Updater',
'run_async',
)

View file

@ -22,7 +22,6 @@ import logging
import warnings
import weakref
from collections import defaultdict
from functools import wraps
from queue import Empty, Queue
from threading import BoundedSemaphore, Event, Lock, Thread, current_thread
from time import sleep
@ -44,11 +43,9 @@ from uuid import uuid4
from telegram import TelegramError, Update
from telegram.ext import BasePersistence, ContextTypes
from telegram.ext.callbackcontext import CallbackContext
from telegram.ext.handler import Handler
import telegram.ext.extbot
from telegram.ext.callbackdatacache import CallbackDataCache
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.ext.utils.promise import Promise
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
from telegram.ext.utils.types import CCT, UD, CD, BD
@ -56,46 +53,13 @@ from telegram.ext.utils.types import CCT, UD, CD, BD
if TYPE_CHECKING:
from telegram import Bot
from telegram.ext import JobQueue
from telegram.ext.callbackcontext import CallbackContext
DEFAULT_GROUP: int = 0
UT = TypeVar('UT')
def run_async(
func: Callable[[Update, CallbackContext], object]
) -> Callable[[Update, CallbackContext], object]:
"""
Function decorator that will run the function in a new thread.
Will run :attr:`telegram.ext.Dispatcher.run_async`.
Using this decorator is only possible when only a single Dispatcher exist in the system.
Note:
DEPRECATED. Use :attr:`telegram.ext.Dispatcher.run_async` directly instead or the
:attr:`Handler.run_async` parameter.
Warning:
If you're using ``@run_async`` you cannot rely on adding custom attributes to
:class:`telegram.ext.CallbackContext`. See its docs for more info.
"""
@wraps(func)
def async_func(*args: object, **kwargs: object) -> object:
warnings.warn(
'The @run_async decorator is deprecated. Use the `run_async` parameter of '
'your Handler or `Dispatcher.run_async` instead.',
TelegramDeprecationWarning,
stacklevel=2,
)
return Dispatcher.get_instance()._run_async( # pylint: disable=W0212
func, *args, update=None, error_handling=False, **kwargs
)
return async_func
class DispatcherHandlerStop(Exception):
"""
Raise this in handler to prevent execution of any other handler (even in different group).
@ -180,7 +144,6 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
'__async_queue',
'__async_threads',
'bot',
'__dict__',
'__weakref__',
'context_types',
)
@ -359,13 +322,6 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
self.logger.error('An uncaught error was raised while handling the error.')
continue
# Don't perform error handling for a `Promise` with deactivated error handling. This
# should happen only via the deprecated `@run_async` decorator or `Promises` created
# within error handlers
if not promise.error_handling:
self.logger.error('A promise with deactivated error handling raised an error.')
continue
# If we arrive here, an exception happened in the promise and was neither
# DispatcherHandlerStop nor raised by an error handler. So we can and must handle it
try:
@ -399,18 +355,7 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
Promise
"""
return self._run_async(func, *args, update=update, error_handling=True, **kwargs)
def _run_async(
self,
func: Callable[..., object],
*args: object,
update: object = None,
error_handling: bool = True,
**kwargs: object,
) -> Promise:
# TODO: Remove error_handling parameter once we drop the @run_async decorator
promise = Promise(func, args, kwargs, update=update, error_handling=error_handling)
promise = Promise(func, args, kwargs, update=update)
self.__async_queue.put(promise)
return promise

View file

@ -20,7 +20,6 @@
"""This module contains the Filters for use with the MessageHandler class."""
import re
import warnings
from abc import ABC, abstractmethod
from threading import Lock
@ -50,7 +49,6 @@ __all__ = [
'XORFilter',
]
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.utils.types import SLT
DataDict = Dict[str, list]
@ -1307,48 +1305,6 @@ officedocument.wordprocessingml.document")``.
"""""" # remove method from docs
return any(entity.type == self.entity_type for entity in message.caption_entities)
class _Private(MessageFilter):
__slots__ = ()
name = 'Filters.private'
def filter(self, message: Message) -> bool:
warnings.warn(
'Filters.private is deprecated. Use Filters.chat_type.private instead.',
TelegramDeprecationWarning,
stacklevel=2,
)
return message.chat.type == Chat.PRIVATE
private = _Private()
"""
Messages sent in a private chat.
Note:
DEPRECATED. Use
:attr:`telegram.ext.Filters.chat_type.private` instead.
"""
class _Group(MessageFilter):
__slots__ = ()
name = 'Filters.group'
def filter(self, message: Message) -> bool:
warnings.warn(
'Filters.group is deprecated. Use Filters.chat_type.groups instead.',
TelegramDeprecationWarning,
stacklevel=2,
)
return message.chat.type in [Chat.GROUP, Chat.SUPERGROUP]
group = _Group()
"""
Messages sent in a group or a supergroup chat.
Note:
DEPRECATED. Use
:attr:`telegram.ext.Filters.chat_type.groups` instead.
"""
class _ChatType(MessageFilter):
__slots__ = ()
name = 'Filters.chat_type'

View file

@ -176,7 +176,7 @@ class JobQueue:
job_kwargs = {}
name = name or callback.__name__
job = Job(callback, context, name, self)
job = Job(callback, context, name)
date_time = self._parse_time_input(when, shift_day=True)
j = self.scheduler.add_job(
@ -260,7 +260,7 @@ class JobQueue:
job_kwargs = {}
name = name or callback.__name__
job = Job(callback, context, name, self)
job = Job(callback, context, name)
dt_first = self._parse_time_input(first)
dt_last = self._parse_time_input(last)
@ -325,7 +325,7 @@ class JobQueue:
job_kwargs = {}
name = name or callback.__name__
job = Job(callback, context, name, self)
job = Job(callback, context, name)
j = self.scheduler.add_job(
callback,
@ -382,7 +382,7 @@ class JobQueue:
job_kwargs = {}
name = name or callback.__name__
job = Job(callback, context, name, self)
job = Job(callback, context, name)
j = self.scheduler.add_job(
callback,
@ -425,7 +425,7 @@ class JobQueue:
"""
name = name or callback.__name__
job = Job(callback, context, name, self)
job = Job(callback, context, name)
j = self.scheduler.add_job(callback, args=self._build_args(job), name=name, **job_kwargs)
@ -445,7 +445,7 @@ class JobQueue:
def jobs(self) -> Tuple['Job', ...]:
"""Returns a tuple of all *scheduled* jobs that are currently in the ``JobQueue``."""
return tuple(
Job._from_aps_job(job, self) # pylint: disable=W0212
Job._from_aps_job(job) # pylint: disable=protected-access
for job in self.scheduler.get_jobs()
)
@ -472,21 +472,21 @@ class Job:
* If :attr:`job` isn't passed on initialization, it must be set manually afterwards for
this :class:`telegram.ext.Job` to be useful.
.. versionchanged:: 14.0
Removed argument and attribute :attr:`job_queue`.
Args:
callback (:obj:`callable`): The callback function that should be executed by the new job.
Callback signature: ``def callback(update: Update, context: CallbackContext)``
context (:obj:`object`, optional): Additional data needed for the callback function. Can be
accessed through ``job.context`` in the callback. Defaults to :obj:`None`.
name (:obj:`str`, optional): The name of the new job. Defaults to ``callback.__name__``.
job_queue (:class:`telegram.ext.JobQueue`, optional): The ``JobQueue`` this job belongs to.
Only optional for backward compatibility with ``JobQueue.put()``.
job (:class:`apscheduler.job.Job`, optional): The APS Job this job is a wrapper for.
Attributes:
callback (:obj:`callable`): The callback function that should be executed by the new job.
context (:obj:`object`): Optional. Additional data needed for the callback function.
name (:obj:`str`): Optional. The name of the new job.
job_queue (:class:`telegram.ext.JobQueue`): Optional. The ``JobQueue`` this job belongs to.
job (:class:`apscheduler.job.Job`): Optional. The APS Job this job is a wrapper for.
"""
@ -494,7 +494,6 @@ class Job:
'callback',
'context',
'name',
'job_queue',
'_removed',
'_enabled',
'job',
@ -505,14 +504,12 @@ class Job:
callback: Callable[['CallbackContext'], None],
context: object = None,
name: str = None,
job_queue: JobQueue = None,
job: APSJob = None,
):
self.callback = callback
self.context = context
self.name = name or callback.__name__
self.job_queue = job_queue
self._removed = False
self._enabled = False
@ -570,13 +567,13 @@ class Job:
return self.job.next_run_time
@classmethod
def _from_aps_job(cls, job: APSJob, job_queue: JobQueue) -> 'Job':
def _from_aps_job(cls, job: APSJob) -> 'Job':
# context based callbacks
if len(job.args) == 1:
context = job.args[0].job.context
else:
context = job.args[1].context
return cls(job.func, context=context, name=job.name, job_queue=job_queue, job=job)
return cls(job.func, context=context, name=job.name, job=job)
def __getattr__(self, item: str) -> object:
return getattr(self.job, item)

View file

@ -1,334 +0,0 @@
#!/usr/bin/env python
#
# Module author:
# Tymofii A. Khodniev (thodnev) <thodnev@mail.ru>
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]
"""A throughput-limiting message processor for Telegram bots."""
import functools
import queue as q
import threading
import time
import warnings
from typing import TYPE_CHECKING, Callable, List, NoReturn
from telegram.ext.utils.promise import Promise
from telegram.utils.deprecate import TelegramDeprecationWarning
if TYPE_CHECKING:
from telegram import Bot
# We need to count < 1s intervals, so the most accurate timer is needed
curtime = time.perf_counter
class DelayQueueError(RuntimeError):
"""Indicates processing errors."""
__slots__ = ()
class DelayQueue(threading.Thread):
"""
Processes callbacks from queue with specified throughput limits. Creates a separate thread to
process callbacks with delays.
.. deprecated:: 13.3
:class:`telegram.ext.DelayQueue` in its current form is deprecated and will be reinvented
in a future release. See `this thread <https://git.io/JtDbF>`_ for a list of known bugs.
Args:
queue (:obj:`Queue`, optional): Used to pass callbacks to thread. Creates ``Queue``
implicitly if not provided.
burst_limit (:obj:`int`, optional): Number of maximum callbacks to process per time-window
defined by :attr:`time_limit_ms`. Defaults to 30.
time_limit_ms (:obj:`int`, optional): Defines width of time-window used when each
processing limit is calculated. Defaults to 1000.
exc_route (:obj:`callable`, optional): A callable, accepting 1 positional argument; used to
route exceptions from processor thread to main thread; is called on `Exception`
subclass exceptions. If not provided, exceptions are routed through dummy handler,
which re-raises them.
autostart (:obj:`bool`, optional): If :obj:`True`, processor is started immediately after
object's creation; if :obj:`False`, should be started manually by `start` method.
Defaults to :obj:`True`.
name (:obj:`str`, optional): Thread's name. Defaults to ``'DelayQueue-N'``, where N is
sequential number of object created.
Attributes:
burst_limit (:obj:`int`): Number of maximum callbacks to process per time-window.
time_limit (:obj:`int`): Defines width of time-window used when each processing limit is
calculated.
exc_route (:obj:`callable`): A callable, accepting 1 positional argument; used to route
exceptions from processor thread to main thread;
name (:obj:`str`): Thread's name.
"""
_instcnt = 0 # instance counter
def __init__(
self,
queue: q.Queue = None,
burst_limit: int = 30,
time_limit_ms: int = 1000,
exc_route: Callable[[Exception], None] = None,
autostart: bool = True,
name: str = None,
):
warnings.warn(
'DelayQueue in its current form is deprecated and will be reinvented in a future '
'release. See https://git.io/JtDbF for a list of known bugs.',
category=TelegramDeprecationWarning,
)
self._queue = queue if queue is not None else q.Queue()
self.burst_limit = burst_limit
self.time_limit = time_limit_ms / 1000
self.exc_route = exc_route if exc_route is not None else self._default_exception_handler
self.__exit_req = False # flag to gently exit thread
self.__class__._instcnt += 1
if name is None:
name = f'{self.__class__.__name__}-{self.__class__._instcnt}'
super().__init__(name=name)
self.daemon = False
if autostart: # immediately start processing
super().start()
def run(self) -> None:
"""
Do not use the method except for unthreaded testing purposes, the method normally is
automatically called by autostart argument.
"""
times: List[float] = [] # used to store each callable processing time
while True:
item = self._queue.get()
if self.__exit_req:
return # shutdown thread
# delay routine
now = time.perf_counter()
t_delta = now - self.time_limit # calculate early to improve perf.
if times and t_delta > times[-1]:
# if last call was before the limit time-window
# used to impr. perf. in long-interval calls case
times = [now]
else:
# collect last in current limit time-window
times = [t for t in times if t >= t_delta]
times.append(now)
if len(times) >= self.burst_limit: # if throughput limit was hit
time.sleep(times[1] - t_delta)
# finally process one
try:
func, args, kwargs = item
func(*args, **kwargs)
except Exception as exc: # re-route any exceptions
self.exc_route(exc) # to prevent thread exit
def stop(self, timeout: float = None) -> None:
"""Used to gently stop processor and shutdown its thread.
Args:
timeout (:obj:`float`): Indicates maximum time to wait for processor to stop and its
thread to exit. If timeout exceeds and processor has not stopped, method silently
returns. :attr:`is_alive` could be used afterwards to check the actual status.
``timeout`` set to :obj:`None`, blocks until processor is shut down.
Defaults to :obj:`None`.
"""
self.__exit_req = True # gently request
self._queue.put(None) # put something to unfreeze if frozen
super().join(timeout=timeout)
@staticmethod
def _default_exception_handler(exc: Exception) -> NoReturn:
"""
Dummy exception handler which re-raises exception in thread. Could be possibly overwritten
by subclasses.
"""
raise exc
def __call__(self, func: Callable, *args: object, **kwargs: object) -> None:
"""Used to process callbacks in throughput-limiting thread through queue.
Args:
func (:obj:`callable`): The actual function (or any callable) that is processed through
queue.
*args (:obj:`list`): Variable-length `func` arguments.
**kwargs (:obj:`dict`): Arbitrary keyword-arguments to `func`.
"""
if not self.is_alive() or self.__exit_req:
raise DelayQueueError('Could not process callback in stopped thread')
self._queue.put((func, args, kwargs))
# The most straightforward way to implement this is to use 2 sequential delay
# queues, like on classic delay chain schematics in electronics.
# So, message path is:
# msg --> group delay if group msg, else no delay --> normal msg delay --> out
# This way OS threading scheduler cares of timings accuracy.
# (see time.time, time.clock, time.perf_counter, time.sleep @ docs.python.org)
class MessageQueue:
"""
Implements callback processing with proper delays to avoid hitting Telegram's message limits.
Contains two ``DelayQueue``, for group and for all messages, interconnected in delay chain.
Callables are processed through *group* ``DelayQueue``, then through *all* ``DelayQueue`` for
group-type messages. For non-group messages, only the *all* ``DelayQueue`` is used.
.. deprecated:: 13.3
:class:`telegram.ext.MessageQueue` in its current form is deprecated and will be reinvented
in a future release. See `this thread <https://git.io/JtDbF>`_ for a list of known bugs.
Args:
all_burst_limit (:obj:`int`, optional): Number of maximum *all-type* callbacks to process
per time-window defined by :attr:`all_time_limit_ms`. Defaults to 30.
all_time_limit_ms (:obj:`int`, optional): Defines width of *all-type* time-window used when
each processing limit is calculated. Defaults to 1000 ms.
group_burst_limit (:obj:`int`, optional): Number of maximum *group-type* callbacks to
process per time-window defined by :attr:`group_time_limit_ms`. Defaults to 20.
group_time_limit_ms (:obj:`int`, optional): Defines width of *group-type* time-window used
when each processing limit is calculated. Defaults to 60000 ms.
exc_route (:obj:`callable`, optional): A callable, accepting one positional argument; used
to route exceptions from processor threads to main thread; is called on ``Exception``
subclass exceptions. If not provided, exceptions are routed through dummy handler,
which re-raises them.
autostart (:obj:`bool`, optional): If :obj:`True`, processors are started immediately after
object's creation; if :obj:`False`, should be started manually by :attr:`start` method.
Defaults to :obj:`True`.
"""
def __init__(
self,
all_burst_limit: int = 30,
all_time_limit_ms: int = 1000,
group_burst_limit: int = 20,
group_time_limit_ms: int = 60000,
exc_route: Callable[[Exception], None] = None,
autostart: bool = True,
):
warnings.warn(
'MessageQueue in its current form is deprecated and will be reinvented in a future '
'release. See https://git.io/JtDbF for a list of known bugs.',
category=TelegramDeprecationWarning,
)
# create according delay queues, use composition
self._all_delayq = DelayQueue(
burst_limit=all_burst_limit,
time_limit_ms=all_time_limit_ms,
exc_route=exc_route,
autostart=autostart,
)
self._group_delayq = DelayQueue(
burst_limit=group_burst_limit,
time_limit_ms=group_time_limit_ms,
exc_route=exc_route,
autostart=autostart,
)
def start(self) -> None:
"""Method is used to manually start the ``MessageQueue`` processing."""
self._all_delayq.start()
self._group_delayq.start()
def stop(self, timeout: float = None) -> None:
"""Stops the ``MessageQueue``."""
self._group_delayq.stop(timeout=timeout)
self._all_delayq.stop(timeout=timeout)
stop.__doc__ = DelayQueue.stop.__doc__ or '' # reuse docstring if any
def __call__(self, promise: Callable, is_group_msg: bool = False) -> Callable:
"""
Processes callables in throughput-limiting queues to avoid hitting limits (specified with
:attr:`burst_limit` and :attr:`time_limit`.
Args:
promise (:obj:`callable`): Mainly the ``telegram.utils.promise.Promise`` (see Notes for
other callables), that is processed in delay queues.
is_group_msg (:obj:`bool`, optional): Defines whether ``promise`` would be processed in
group*+*all* ``DelayQueue``s (if set to :obj:`True`), or only through *all*
``DelayQueue`` (if set to :obj:`False`), resulting in needed delays to avoid
hitting specified limits. Defaults to :obj:`False`.
Note:
Method is designed to accept ``telegram.utils.promise.Promise`` as ``promise``
argument, but other callables could be used too. For example, lambdas or simple
functions could be used to wrap original func to be called with needed args. In that
case, be sure that either wrapper func does not raise outside exceptions or the proper
:attr:`exc_route` handler is provided.
Returns:
:obj:`callable`: Used as ``promise`` argument.
"""
if not is_group_msg: # ignore middle group delay
self._all_delayq(promise)
else: # use middle group delay
self._group_delayq(self._all_delayq, promise)
return promise
def queuedmessage(method: Callable) -> Callable:
"""A decorator to be used with :attr:`telegram.Bot` send* methods.
Note:
As it probably wouldn't be a good idea to make this decorator a property, it has been coded
as decorator function, so it implies that first positional argument to wrapped MUST be
self.
The next object attributes are used by decorator:
Attributes:
self._is_messages_queued_default (:obj:`bool`): Value to provide class-defaults to
``queued`` kwarg if not provided during wrapped method call.
self._msg_queue (:class:`telegram.ext.messagequeue.MessageQueue`): The actual
``MessageQueue`` used to delay outbound messages according to specified time-limits.
Wrapped method starts accepting the next kwargs:
Args:
queued (:obj:`bool`, optional): If set to :obj:`True`, the ``MessageQueue`` is used to
process output messages. Defaults to `self._is_queued_out`.
isgroup (:obj:`bool`, optional): If set to :obj:`True`, the message is meant to be
group-type(as there's no obvious way to determine its type in other way at the moment).
Group-type messages could have additional processing delay according to limits set
in `self._out_queue`. Defaults to :obj:`False`.
Returns:
``telegram.utils.promise.Promise``: In case call is queued or original method's return
value if it's not.
"""
@functools.wraps(method)
def wrapped(self: 'Bot', *args: object, **kwargs: object) -> object:
# pylint: disable=W0212
queued = kwargs.pop(
'queued', self._is_messages_queued_default # type: ignore[attr-defined]
)
isgroup = kwargs.pop('isgroup', False)
if queued:
prom = Promise(method, (self,) + args, kwargs)
return self._msg_queue(prom, isgroup) # type: ignore[attr-defined]
return method(self, *args, **kwargs)
return wrapped

View file

@ -342,7 +342,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
self,
poll_interval: float = 0.0,
timeout: float = 10,
clean: bool = None,
bootstrap_retries: int = -1,
read_latency: float = 2.0,
allowed_updates: List[str] = None,
@ -350,6 +349,9 @@ class Updater(Generic[CCT, UD, CD, BD]):
) -> Optional[Queue]:
"""Starts polling updates from Telegram.
.. versionchanged:: 14.0
Removed the ``clean`` argument in favor of ``drop_pending_updates``.
Args:
poll_interval (:obj:`float`, optional): Time to wait between polling updates from
Telegram in seconds. Default is ``0.0``.
@ -358,10 +360,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
Telegram servers before actually starting to poll. Default is :obj:`False`.
.. versionadded :: 13.4
clean (:obj:`bool`, optional): Alias for ``drop_pending_updates``.
.. deprecated:: 13.4
Use ``drop_pending_updates`` instead.
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
@ -379,19 +377,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
:obj:`Queue`: The update queue that can be filled from the main thread.
"""
if (clean is not None) and (drop_pending_updates is not None):
raise TypeError('`clean` and `drop_pending_updates` are mutually exclusive.')
if clean is not None:
warnings.warn(
'The argument `clean` of `start_polling` is deprecated. Please use '
'`drop_pending_updates` instead.',
category=TelegramDeprecationWarning,
stacklevel=2,
)
drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean
with self.__lock:
if not self.running:
self.running = True
@ -428,11 +413,9 @@ class Updater(Generic[CCT, UD, CD, BD]):
url_path: str = '',
cert: str = None,
key: str = None,
clean: bool = None,
bootstrap_retries: int = 0,
webhook_url: str = None,
allowed_updates: List[str] = None,
force_event_loop: bool = None,
drop_pending_updates: bool = None,
ip_address: str = None,
max_connections: int = 40,
@ -448,6 +431,10 @@ class Updater(Generic[CCT, UD, CD, BD]):
:meth:`start_webhook` now *always* calls :meth:`telegram.Bot.set_webhook`, so pass
``webhook_url`` instead of calling ``updater.bot.set_webhook(webhook_url)`` manually.
.. versionchanged:: 14.0
Removed the ``clean`` argument in favor of ``drop_pending_updates`` and removed the
deprecated argument ``force_event_loop``.
Args:
listen (:obj:`str`, optional): IP-Address to listen on. Default ``127.0.0.1``.
port (:obj:`int`, optional): Port the bot should be listening on. Default ``80``.
@ -458,10 +445,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
Telegram servers before actually starting to poll. Default is :obj:`False`.
.. versionadded :: 13.4
clean (:obj:`bool`, optional): Alias for ``drop_pending_updates``.
.. deprecated:: 13.4
Use ``drop_pending_updates`` instead.
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
@ -477,13 +460,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
.. versionadded :: 13.4
allowed_updates (List[:obj:`str`], optional): Passed to
:meth:`telegram.Bot.set_webhook`.
force_event_loop (:obj:`bool`, optional): Legacy parameter formerly used for a
workaround on Windows + Python 3.8+. No longer has any effect.
.. deprecated:: 13.6
Since version 13.6, ``tornade>=6.1`` is required, which resolves the former
issue.
max_connections (:obj:`int`, optional): Passed to
:meth:`telegram.Bot.set_webhook`.
@ -493,27 +469,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
:obj:`Queue`: The update queue that can be filled from the main thread.
"""
if (clean is not None) and (drop_pending_updates is not None):
raise TypeError('`clean` and `drop_pending_updates` are mutually exclusive.')
if clean is not None:
warnings.warn(
'The argument `clean` of `start_webhook` is deprecated. Please use '
'`drop_pending_updates` instead.',
category=TelegramDeprecationWarning,
stacklevel=2,
)
if force_event_loop is not None:
warnings.warn(
'The argument `force_event_loop` of `start_webhook` is deprecated and no longer '
'has any effect.',
category=TelegramDeprecationWarning,
stacklevel=2,
)
drop_pending_updates = drop_pending_updates if drop_pending_updates is not None else clean
with self.__lock:
if not self.running:
self.running = True

View file

@ -33,14 +33,15 @@ logger = logging.getLogger(__name__)
class Promise:
"""A simple Promise implementation for use with the run_async decorator, DelayQueue etc.
.. versionchanged:: 14.0
Removed the argument and attribute ``error_handler``.
Args:
pooled_function (:obj:`callable`): The callable that will be called concurrently.
args (:obj:`list` | :obj:`tuple`): Positional arguments for :attr:`pooled_function`.
kwargs (:obj:`dict`): Keyword arguments for :attr:`pooled_function`.
update (:class:`telegram.Update` | :obj:`object`, optional): The update this promise is
associated with.
error_handling (:obj:`bool`, optional): Whether exceptions raised by :attr:`func`
may be handled by error handlers. Defaults to :obj:`True`.
Attributes:
pooled_function (:obj:`callable`): The callable that will be called concurrently.
@ -49,8 +50,6 @@ class Promise:
done (:obj:`threading.Event`): Is set when the result is available.
update (:class:`telegram.Update` | :obj:`object`): Optional. The update this promise is
associated with.
error_handling (:obj:`bool`): Optional. Whether exceptions raised by :attr:`func`
may be handled by error handlers. Defaults to :obj:`True`.
"""
@ -59,27 +58,23 @@ class Promise:
'args',
'kwargs',
'update',
'error_handling',
'done',
'_done_callback',
'_result',
'_exception',
)
# TODO: Remove error_handling parameter once we drop the @run_async decorator
def __init__(
self,
pooled_function: Callable[..., RT],
args: Union[List, Tuple],
kwargs: JSONDict,
update: object = None,
error_handling: bool = True,
):
self.pooled_function = pooled_function
self.args = args
self.kwargs = kwargs
self.update = update
self.error_handling = error_handling
self.done = Event()
self._done_callback: Optional[Callable] = None
self._result: Optional[RT] = None

View file

@ -1,38 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the :class:`telegram.ext.utils.promise.Promise` class for backwards
compatibility.
"""
import warnings
import telegram.ext.utils.promise as promise
from telegram.utils.deprecate import TelegramDeprecationWarning
warnings.warn(
'telegram.utils.promise is deprecated. Please use telegram.ext.utils.promise instead.',
TelegramDeprecationWarning,
)
Promise = promise.Promise
"""
:class:`telegram.ext.utils.promise.Promise`
.. deprecated:: v13.2
Use :class:`telegram.ext.utils.promise.Promise` instead.
"""

View file

@ -1,35 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the :class:`telegram.ext.utils.webhookhandler.WebhookHandler` class for
backwards compatibility.
"""
import warnings
import telegram.ext.utils.webhookhandler as webhook_handler
from telegram.utils.deprecate import TelegramDeprecationWarning
warnings.warn(
'telegram.utils.webhookhandler is deprecated. Please use telegram.ext.utils.webhookhandler '
'instead.',
TelegramDeprecationWarning,
)
WebhookHandler = webhook_handler.WebhookHandler
WebhookServer = webhook_handler.WebhookServer
WebhookAppClass = webhook_handler.WebhookAppClass

View file

@ -104,6 +104,10 @@ class DictBot(Bot):
pass
class DictDispatcher(Dispatcher):
pass
@pytest.fixture(scope='session')
def bot(bot_info):
return DictExtBot(bot_info['token'], private_key=PRIVATE_KEY, request=DictRequest())
@ -159,7 +163,7 @@ def provider_token(bot_info):
def create_dp(bot):
# Dispatcher is heavy to init (due to many threads and such) so we have a single session
# scoped one here, but before each test, reset it (dp fixture below)
dispatcher = Dispatcher(bot, Queue(), job_queue=JobQueue(), workers=2)
dispatcher = DictDispatcher(bot, Queue(), job_queue=JobQueue(), workers=2)
dispatcher.job_queue.set_dispatcher(dispatcher)
thr = Thread(target=dispatcher.start)
thr.start()
@ -188,17 +192,12 @@ def dp(_dp):
_dp.handlers = {}
_dp.groups = []
_dp.error_handlers = {}
# For some reason if we setattr with the name mangled, then some tests(like async) run forever,
# due to threads not acquiring, (blocking). This adds these attributes to the __dict__.
object.__setattr__(_dp, '__stop_event', Event())
object.__setattr__(_dp, '__exception_event', Event())
object.__setattr__(_dp, '__async_queue', Queue())
object.__setattr__(_dp, '__async_threads', set())
_dp.__exception_event = Event()
_dp.__stop_event = Event()
_dp.__async_queue = Queue()
_dp.__async_threads = set()
_dp.persistence = None
if _dp._Dispatcher__singleton_semaphore.acquire(blocking=0):
Dispatcher._set_singleton(_dp)
yield _dp
Dispatcher._Dispatcher__singleton_semaphore.release()
@pytest.fixture(scope='function')

View file

@ -193,7 +193,6 @@ class TestBot:
@flaky(3, 1)
def test_get_me_and_properties(self, bot):
get_me_bot = bot.get_me()
commands = bot.get_my_commands()
assert isinstance(get_me_bot, User)
assert get_me_bot.id == bot.id
@ -205,9 +204,6 @@ class TestBot:
assert get_me_bot.can_read_all_group_messages == bot.can_read_all_group_messages
assert get_me_bot.supports_inline_queries == bot.supports_inline_queries
assert f'https://t.me/{get_me_bot.username}' == bot.link
assert commands == bot.commands
bot._commands = None
assert commands == bot.commands
def test_equality(self):
a = Bot(FALLBACKS[0]["token"])
@ -723,12 +719,10 @@ class TestBot:
'chat_action',
[
ChatAction.FIND_LOCATION,
ChatAction.RECORD_AUDIO,
ChatAction.RECORD_VIDEO,
ChatAction.RECORD_VIDEO_NOTE,
ChatAction.RECORD_VOICE,
ChatAction.TYPING,
ChatAction.UPLOAD_AUDIO,
ChatAction.UPLOAD_DOCUMENT,
ChatAction.UPLOAD_PHOTO,
ChatAction.UPLOAD_VIDEO,
@ -1039,18 +1033,6 @@ class TestBot:
assert bot.ban_chat_sender_chat(2, 32)
monkeypatch.delattr(bot.request, 'post')
def test_kick_chat_member_warning(self, monkeypatch, bot, recwarn):
def test(url, data, *args, **kwargs):
chat_id = data['chat_id'] == 2
user_id = data['user_id'] == 32
return chat_id and user_id
monkeypatch.setattr(bot.request, 'post', test)
bot.kick_chat_member(2, 32)
assert len(recwarn) == 1
assert '`bot.kick_chat_member` is deprecated' in str(recwarn[0].message)
monkeypatch.delattr(bot.request, 'post')
# TODO: Needs improvement.
@pytest.mark.parametrize('only_if_banned', [True, False, None])
def test_unban_chat_member(self, monkeypatch, bot, only_if_banned):
@ -1401,16 +1383,6 @@ class TestBot:
assert isinstance(count, int)
assert count > 3
def test_get_chat_members_count_warning(self, bot, channel_id, recwarn):
bot.get_chat_members_count(channel_id)
assert len(recwarn) == 1
assert '`bot.get_chat_members_count` is deprecated' in str(recwarn[0].message)
def test_bot_command_property_warning(self, bot, recwarn):
_ = bot.commands
assert len(recwarn) == 1
assert 'Bot.commands has been deprecated since there can' in str(recwarn[0].message)
@flaky(3, 1)
def test_get_chat_member(self, bot, channel_id, chat_id):
chat_member = bot.get_chat_member(channel_id, chat_id)
@ -2059,39 +2031,14 @@ class TestBot:
@flaky(3, 1)
def test_set_and_get_my_commands(self, bot):
commands = [
BotCommand('cmd1', 'descr1'),
BotCommand('cmd2', 'descr2'),
]
commands = [BotCommand('cmd1', 'descr1'), ['cmd2', 'descr2']]
bot.set_my_commands([])
assert bot.get_my_commands() == []
assert bot.commands == []
assert bot.set_my_commands(commands)
for bc in [bot.get_my_commands(), bot.commands]:
assert len(bc) == 2
assert bc[0].command == 'cmd1'
assert bc[0].description == 'descr1'
assert bc[1].command == 'cmd2'
assert bc[1].description == 'descr2'
@flaky(3, 1)
def test_set_and_get_my_commands_strings(self, bot):
commands = [
['cmd1', 'descr1'],
['cmd2', 'descr2'],
]
bot.set_my_commands([])
assert bot.get_my_commands() == []
assert bot.commands == []
assert bot.set_my_commands(commands)
for bc in [bot.get_my_commands(), bot.commands]:
assert len(bc) == 2
assert bc[0].command == 'cmd1'
assert bc[0].description == 'descr1'
assert bc[1].command == 'cmd2'
assert bc[1].description == 'descr2'
for i, bc in enumerate(bot.get_my_commands()):
assert bc.command == f'cmd{i+1}'
assert bc.description == f'descr{i+1}'
@flaky(3, 1)
def test_get_set_delete_my_commands_with_scope(self, bot, super_group_id, chat_id):
@ -2114,9 +2061,6 @@ class TestBot:
assert len(gotten_private_cmd) == len(private_cmds)
assert gotten_private_cmd[0].command == private_cmds[0].command
assert len(bot.commands) == 2 # set from previous test. Makes sure this hasn't changed.
assert bot.commands[0].command == 'cmd1'
# Delete command list from that supergroup and private chat-
bot.delete_my_commands(private_scope)
bot.delete_my_commands(group_scope, 'en')
@ -2129,7 +2073,7 @@ class TestBot:
assert len(deleted_priv_cmds) == 0 == len(private_cmds) - 1
bot.delete_my_commands() # Delete commands from default scope
assert not bot.commands # Check if this has been updated to reflect the deletion.
assert len(bot.get_my_commands()) == 0
def test_log_out(self, monkeypatch, bot):
# We don't actually make a request as to not break the test setup

View file

@ -196,15 +196,6 @@ class TestChat:
monkeypatch.setattr(chat.bot, 'get_chat_member_count', make_assertion)
assert chat.get_member_count()
def test_get_members_count_warning(self, chat, monkeypatch, recwarn):
def make_assertion(*_, **kwargs):
return kwargs['chat_id'] == chat.id
monkeypatch.setattr(chat.bot, 'get_chat_member_count', make_assertion)
assert chat.get_members_count()
assert len(recwarn) == 1
assert '`Chat.get_members_count` is deprecated' in str(recwarn[0].message)
def test_get_member(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
chat_id = kwargs['chat_id'] == chat.id
@ -262,18 +253,6 @@ class TestChat:
monkeypatch.setattr(chat.bot, 'ban_chat_sender_chat', make_assertion)
assert chat.ban_chat(42)
def test_kick_member_warning(self, chat, monkeypatch, recwarn):
def make_assertion(*_, **kwargs):
chat_id = kwargs['chat_id'] == chat.id
user_id = kwargs['user_id'] == 42
until = kwargs['until_date'] == 43
return chat_id and user_id and until
monkeypatch.setattr(chat.bot, 'ban_chat_member', make_assertion)
assert chat.kick_member(user_id=42, until_date=43)
assert len(recwarn) == 1
assert '`Chat.kick_member` is deprecated' in str(recwarn[0].message)
@pytest.mark.parametrize('only_if_banned', [True, False, None])
def test_unban_member(self, monkeypatch, chat, only_if_banned):
def make_assertion(*_, **kwargs):

View file

@ -197,7 +197,7 @@ class TestCommandHandler(BaseTest):
def test_with_filter(self, command):
"""Test that a CH with a (generic) filter responds if its filters match"""
handler = self.make_default_handler(filters=Filters.group)
handler = self.make_default_handler(filters=Filters.chat_type.group)
assert is_match(handler, make_command_update(command, chat=Chat(-23, Chat.GROUP)))
assert not is_match(handler, make_command_update(command, chat=Chat(23, Chat.PRIVATE)))
@ -321,7 +321,7 @@ class TestPrefixHandler(BaseTest):
self._test_edited(prefix_message, handler_edited, handler_no_edited)
def test_with_filter(self, prefix_message_text):
handler = self.make_default_handler(filters=Filters.group)
handler = self.make_default_handler(filters=Filters.chat_type.group)
text = prefix_message_text
assert is_match(handler, make_message_update(text, chat=Chat(-23, Chat.GROUP)))
assert not is_match(handler, make_message_update(text, chat=Chat(23, Chat.PRIVATE)))

View file

@ -35,8 +35,7 @@ from telegram.ext import (
ContextTypes,
)
from telegram.ext import PersistenceInput
from telegram.ext.dispatcher import run_async, Dispatcher, DispatcherHandlerStop
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.ext.dispatcher import Dispatcher, DispatcherHandlerStop
from telegram.utils.helpers import DEFAULT_FALSE
from tests.conftest import create_dp
from collections import defaultdict
@ -58,12 +57,6 @@ class TestDispatcher:
received = None
count = 0
def test_slot_behaviour(self, dp2, mro_slots):
for at in dp2.__slots__:
at = f"_Dispatcher{at}" if at.startswith('__') and not at.endswith('__') else at
assert getattr(dp2, at, 'err') != 'err', f"got extra slot '{at}'"
assert len(mro_slots(dp2)) == len(set(mro_slots(dp2))), "duplicate slot"
@pytest.fixture(autouse=True, name='reset')
def reset_fixture(self):
self.reset()
@ -103,6 +96,13 @@ class TestDispatcher:
):
self.received = context.error.message
def test_slot_behaviour(self, bot, mro_slots):
dp = Dispatcher(bot=bot, update_queue=None)
for at in dp.__slots__:
at = f"_Dispatcher{at}" if at.startswith('__') and not at.endswith('__') else at
assert getattr(dp, at, 'err') != 'err', f"got extra slot '{at}'"
assert len(mro_slots(dp)) == len(set(mro_slots(dp))), "duplicate slot"
def test_less_than_one_worker_warning(self, dp, recwarn):
Dispatcher(dp.bot, dp.update_queue, job_queue=dp.job_queue, workers=0)
assert len(recwarn) == 1
@ -243,37 +243,11 @@ class TestDispatcher:
assert name1 != name2
def test_multiple_run_async_decorator(self, dp, dp2):
# Make sure we got two dispatchers and that they are not the same
assert isinstance(dp, Dispatcher)
assert isinstance(dp2, Dispatcher)
assert dp is not dp2
@run_async
def must_raise_runtime_error():
pass
with pytest.raises(RuntimeError):
must_raise_runtime_error()
def test_multiple_run_async_deprecation(self, dp):
assert isinstance(dp, Dispatcher)
@run_async
def callback(update, context):
pass
dp.add_handler(MessageHandler(Filters.all, callback))
with pytest.warns(TelegramDeprecationWarning, match='@run_async decorator'):
dp.process_update(self.message_update)
def test_async_raises_dispatcher_handler_stop(self, dp, caplog):
@run_async
def callback(update, context):
raise DispatcherHandlerStop()
dp.add_handler(MessageHandler(Filters.all, callback))
dp.add_handler(MessageHandler(Filters.all, callback, run_async=True))
with caplog.at_level(logging.WARNING):
dp.update_queue.put(self.message_update)
@ -282,24 +256,7 @@ class TestDispatcher:
assert (
caplog.records[-1]
.getMessage()
.startswith('DispatcherHandlerStop is not supported ' 'with async functions')
)
def test_async_raises_exception(self, dp, caplog):
@run_async
def callback(update, context):
raise RuntimeError('async raising exception')
dp.add_handler(MessageHandler(Filters.all, callback))
with caplog.at_level(logging.WARNING):
dp.update_queue.put(self.message_update)
sleep(0.1)
assert len(caplog.records) == 1
assert (
caplog.records[-1]
.getMessage()
.startswith('A promise with deactivated error handling')
.startswith('DispatcherHandlerStop is not supported with async functions')
)
def test_add_async_handler(self, dp):

View file

@ -26,8 +26,6 @@ from telegram.ext import Filters, BaseFilter, MessageFilter, UpdateFilter
import inspect
import re
from telegram.utils.deprecate import TelegramDeprecationWarning
@pytest.fixture(scope='function')
def update():
@ -971,26 +969,6 @@ class TestFilters:
assert Filters.caption_entity(message_entity.type)(update)
assert not Filters.entity(message_entity.type)(update)
def test_private_filter(self, update):
assert Filters.private(update)
update.message.chat.type = 'group'
assert not Filters.private(update)
def test_private_filter_deprecation(self, update):
with pytest.warns(TelegramDeprecationWarning):
Filters.private(update)
def test_group_filter(self, update):
assert not Filters.group(update)
update.message.chat.type = 'group'
assert Filters.group(update)
update.message.chat.type = 'supergroup'
assert Filters.group(update)
def test_group_filter_deprecation(self, update):
with pytest.warns(TelegramDeprecationWarning):
Filters.group(update)
@pytest.mark.parametrize(
('chat_type, results'),
[
@ -1832,7 +1810,7 @@ class TestFilters:
update.message.text = 'test'
update.message.forward_date = datetime.datetime.utcnow()
assert (Filters.text & Filters.forwarded & Filters.private)(update)
assert (Filters.text & Filters.forwarded & Filters.chat_type.private)(update)
def test_or_filters(self, update):
update.message.text = 'test'

View file

@ -91,7 +91,6 @@ class TestJobQueue:
and context.chat_data is None
and context.user_data is None
and isinstance(context.bot_data, dict)
and context.job_queue is not context.job.job_queue
):
self.result += 1

View file

@ -120,7 +120,7 @@ class TestMessageHandler:
self.test_flag = types and num
def test_with_filter(self, message):
handler = MessageHandler(Filters.group, self.callback_context)
handler = MessageHandler(Filters.chat_type.group, self.callback_context)
message.chat.type = 'group'
assert handler.check_update(Update(0, message))

View file

@ -1,69 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import os
from time import sleep, perf_counter
import pytest
import telegram.ext.messagequeue as mq
@pytest.mark.skipif(
os.getenv('GITHUB_ACTIONS', False) and os.name == 'nt',
reason="On windows precise timings are not accurate.",
)
class TestDelayQueue:
N = 128
burst_limit = 30
time_limit_ms = 1000
margin_ms = 0
testtimes = []
def call(self):
self.testtimes.append(perf_counter())
def test_delayqueue_limits(self):
dsp = mq.DelayQueue(
burst_limit=self.burst_limit, time_limit_ms=self.time_limit_ms, autostart=True
)
assert dsp.is_alive() is True
for _ in range(self.N):
dsp(self.call)
starttime = perf_counter()
# wait up to 20 sec more than needed
app_endtime = (self.N * self.burst_limit / (1000 * self.time_limit_ms)) + starttime + 20
while not dsp._queue.empty() and perf_counter() < app_endtime:
sleep(1)
assert dsp._queue.empty() is True # check loop exit condition
dsp.stop()
assert dsp.is_alive() is False
assert self.testtimes or self.N == 0
passes, fails = [], []
delta = (self.time_limit_ms - self.margin_ms) / 1000
for start, stop in enumerate(range(self.burst_limit + 1, len(self.testtimes))):
part = self.testtimes[start:stop]
if (part[-1] - part[0]) >= delta:
passes.append(part)
else:
fails.append(part)
assert not fails

View file

@ -27,7 +27,6 @@ import inspect
included = { # These modules/classes intentionally have __dict__.
'CallbackContext',
'BasePersistence',
'Dispatcher',
}

View file

@ -301,7 +301,6 @@ class TestUpdater:
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
# prevent api calls from @info decorator when updater.bot.id is used in thread names
monkeypatch.setattr(updater.bot, '_bot', User(id=123, first_name='bot', is_bot=True))
monkeypatch.setattr(updater.bot, '_commands', [])
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port
@ -480,57 +479,6 @@ class TestUpdater:
)
assert self.test_flag is True
def test_deprecation_warnings_start_webhook(self, recwarn, updater, monkeypatch):
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
# prevent api calls from @info decorator when updater.bot.id is used in thread names
monkeypatch.setattr(updater.bot, '_bot', User(id=123, first_name='bot', is_bot=True))
monkeypatch.setattr(updater.bot, '_commands', [])
ip = '127.0.0.1'
port = randrange(1024, 49152) # Select random port
updater.start_webhook(ip, port, clean=True, force_event_loop=False)
updater.stop()
for warning in recwarn:
print(warning)
try: # This is for flaky tests (there's an unclosed socket sometimes)
recwarn.pop(ResourceWarning) # internally iterates through recwarn.list and deletes it
except AssertionError:
pass
assert len(recwarn) == 2
assert str(recwarn[0].message).startswith('The argument `clean` of')
assert str(recwarn[1].message).startswith('The argument `force_event_loop` of')
def test_clean_deprecation_warning_polling(self, recwarn, updater, monkeypatch):
monkeypatch.setattr(updater.bot, 'set_webhook', lambda *args, **kwargs: True)
monkeypatch.setattr(updater.bot, 'delete_webhook', lambda *args, **kwargs: True)
# prevent api calls from @info decorator when updater.bot.id is used in thread names
monkeypatch.setattr(updater.bot, '_bot', User(id=123, first_name='bot', is_bot=True))
monkeypatch.setattr(updater.bot, '_commands', [])
updater.start_polling(clean=True)
updater.stop()
for msg in recwarn:
print(msg)
try: # This is for flaky tests (there's an unclosed socket sometimes)
recwarn.pop(ResourceWarning) # internally iterates through recwarn.list and deletes it
except AssertionError:
pass
assert len(recwarn) == 1
assert str(recwarn[0].message).startswith('The argument `clean` of')
def test_clean_drop_pending_mutually_exclusive(self, updater):
with pytest.raises(TypeError, match='`clean` and `drop_pending_updates` are mutually'):
updater.start_polling(clean=True, drop_pending_updates=False)
with pytest.raises(TypeError, match='`clean` and `drop_pending_updates` are mutually'):
updater.start_webhook(clean=True, drop_pending_updates=False)
@flaky(3, 1)
def test_webhook_invalid_posts(self, updater):
ip = '127.0.0.1'

View file

@ -1,37 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
class TestUtils:
def test_promise_deprecation(self, recwarn):
import telegram.utils.promise # noqa: F401
assert len(recwarn) == 1
assert str(recwarn[0].message) == (
'telegram.utils.promise is deprecated. Please use telegram.ext.utils.promise instead.'
)
def test_webhookhandler_deprecation(self, recwarn):
import telegram.utils.webhookhandler # noqa: F401
assert len(recwarn) == 1
assert str(recwarn[0].message) == (
'telegram.utils.webhookhandler is deprecated. Please use '
'telegram.ext.utils.webhookhandler instead.'
)