Remove __dict__ from __slots__ and drop Python 3.6 (#2619, #2636)

This commit is contained in:
Harshil 2021-08-20 01:31:10 +05:30 committed by Hinrich Mahler
parent be441d56f9
commit b4ea5557ac
222 changed files with 277 additions and 931 deletions

View file

@ -13,7 +13,7 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.7, 3.8, 3.9]
os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: False
steps:

View file

@ -56,4 +56,4 @@ repos:
- id: pyupgrade
files: ^(telegram|examples|tests)/.*\.py$
args:
- --py36-plus
- --py37-plus

View file

@ -93,7 +93,7 @@ Introduction
This library provides a pure Python interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It's compatible with Python versions 3.6.8+. PTB might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
It's compatible with Python versions **3.7+**. PTB might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
In addition to the pure API implementation, this library features a number of high-level classes to
make the development of bots easy and straightforward. These classes are contained in the

View file

@ -91,7 +91,7 @@ Introduction
This library provides a pure Python, lightweight interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_.
It's compatible with Python versions 3.6.8+. PTB-Raw might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
It's compatible with Python versions **3.7+**. PTB-Raw might also work on `PyPy <http://pypy.org/>`_, though there have been a lot of issues before. Hence, PyPy is not officially supported.
``python-telegram-bot-raw`` is part of the `python-telegram-bot <https://python-telegram-bot.org>`_ ecosystem and provides the pure API functionality extracted from PTB. It therefore does *not* have independent release schedules, changelogs or documentation. Please consult the PTB resources.

View file

@ -1,6 +1,6 @@
[tool.black]
line-length = 99
target-version = ['py36']
target-version = ['py37']
skip-string-normalization = true
# We need to force-exclude the negated include pattern

View file

@ -98,12 +98,11 @@ def get_setup_kwargs(raw=False):
'Topic :: Internet',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
python_requires='>=3.6'
python_requires='>=3.7'
)
return kwargs

View file

@ -23,10 +23,9 @@ except ImportError:
import json # type: ignore[no-redef]
import warnings
from typing import TYPE_CHECKING, List, Optional, Tuple, Type, TypeVar
from typing import TYPE_CHECKING, List, Optional, Type, TypeVar, Tuple
from telegram.utils.types import JSONDict
from telegram.utils.deprecate import set_new_attribute_deprecated
if TYPE_CHECKING:
from telegram import Bot
@ -37,12 +36,21 @@ TO = TypeVar('TO', bound='TelegramObject', covariant=True)
class TelegramObject:
"""Base class for most Telegram objects."""
_id_attrs: Tuple[object, ...] = ()
# type hints in __new__ are not read by mypy (https://github.com/python/mypy/issues/1021). As a
# workaround we can type hint instance variables in __new__ using a syntax defined in PEP 526 -
# https://www.python.org/dev/peps/pep-0526/#class-and-instance-variable-annotations
if TYPE_CHECKING:
_id_attrs: Tuple[object, ...]
# Adding slots reduces memory usage & allows for faster attribute access.
# Only instance variables should be added to __slots__.
# We add __dict__ here for backward compatibility & also to avoid repetition for subclasses.
__slots__ = ('__dict__',)
__slots__ = ('_id_attrs',)
def __new__(cls, *args: object, **kwargs: object) -> 'TelegramObject': # pylint: disable=W0613
# We add _id_attrs in __new__ instead of __init__ since we want to add this to the slots
# w/o calling __init__ in all of the subclasses. This is what we also do in BaseFilter.
instance = super().__new__(cls)
instance._id_attrs = ()
return instance
def __str__(self) -> str:
return str(self.to_dict())
@ -50,9 +58,6 @@ class TelegramObject:
def __getitem__(self, item: str) -> object:
return getattr(self, item, None)
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
@staticmethod
def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
return None if data is None else data.copy()
@ -76,7 +81,7 @@ class TelegramObject:
if cls == TelegramObject:
return cls()
return cls(bot=bot, **data) # type: ignore[call-arg]
return cls(bot=bot, **data)
@classmethod
def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]:
@ -132,6 +137,7 @@ class TelegramObject:
return data
def __eq__(self, other: object) -> bool:
# pylint: disable=no-member
if isinstance(other, self.__class__):
if self._id_attrs == ():
warnings.warn(
@ -144,9 +150,10 @@ class TelegramObject:
" for equivalence."
)
return self._id_attrs == other._id_attrs
return super().__eq__(other) # pylint: disable=no-member
return super().__eq__(other)
def __hash__(self) -> int:
# pylint: disable=no-member
if self._id_attrs:
return hash((self.__class__, self._id_attrs)) # pylint: disable=no-member
return hash((self.__class__, self._id_attrs))
return super().__hash__()

View file

@ -224,14 +224,6 @@ class Bot(TelegramObject):
private_key, password=private_key_password, backend=default_backend()
)
# The ext_bot argument is a little hack to get warnings handled correctly.
# It's not very clean, but the warnings will be dropped at some point anyway.
def __setattr__(self, key: str, value: object, ext_bot: bool = False) -> None:
if issubclass(self.__class__, Bot) and self.__class__ is not Bot and not ext_bot:
object.__setattr__(self, key, value)
return
super().__setattr__(key, value)
def _insert_defaults(
self, data: Dict[str, object], timeout: ODVInput[float]
) -> Optional[float]:

View file

@ -41,7 +41,7 @@ class BotCommand(TelegramObject):
"""
__slots__ = ('description', '_id_attrs', 'command')
__slots__ = ('description', 'command')
def __init__(self, command: str, description: str, **_kwargs: Any):
self.command = command

View file

@ -57,7 +57,7 @@ class BotCommandScope(TelegramObject):
type (:obj:`str`): Scope type.
"""
__slots__ = ('type', '_id_attrs')
__slots__ = ('type',)
DEFAULT = constants.BOT_COMMAND_SCOPE_DEFAULT
""":const:`telegram.constants.BOT_COMMAND_SCOPE_DEFAULT`"""

View file

@ -101,7 +101,6 @@ class CallbackQuery(TelegramObject):
'from_user',
'inline_message_id',
'data',
'_id_attrs',
)
def __init__(

View file

@ -186,7 +186,6 @@ class Chat(TelegramObject):
'message_auto_delete_time',
'has_protected_content',
'has_private_forwards',
'_id_attrs',
)
SENDER: ClassVar[str] = constants.CHAT_SENDER

View file

@ -20,13 +20,12 @@
"""This module contains an object that represents a Telegram ChatAction."""
from typing import ClassVar
from telegram import constants
from telegram.utils.deprecate import set_new_attribute_deprecated
class ChatAction:
"""Helper class to provide constants for different chat actions."""
__slots__ = ('__dict__',) # Adding __dict__ here since it doesn't subclass TGObject
__slots__ = ()
FIND_LOCATION: ClassVar[str] = constants.CHATACTION_FIND_LOCATION
""":const:`telegram.constants.CHATACTION_FIND_LOCATION`"""
RECORD_AUDIO: ClassVar[str] = constants.CHATACTION_RECORD_AUDIO
@ -69,6 +68,3 @@ class ChatAction:
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO`"""
UPLOAD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO_NOTE
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO_NOTE`"""
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)

View file

@ -92,7 +92,6 @@ class ChatInviteLink(TelegramObject):
'name',
'creates_join_request',
'pending_join_request_count',
'_id_attrs',
)
def __init__(

View file

@ -68,7 +68,6 @@ class ChatJoinRequest(TelegramObject):
'bio',
'invite_link',
'bot',
'_id_attrs',
)
def __init__(

View file

@ -47,7 +47,7 @@ class ChatLocation(TelegramObject):
"""
__slots__ = ('location', '_id_attrs', 'address')
__slots__ = ('location', 'address')
def __init__(
self,

View file

@ -287,7 +287,6 @@ class ChatMember(TelegramObject):
'can_manage_chat',
'can_manage_voice_chats',
'until_date',
'_id_attrs',
)
ADMINISTRATOR: ClassVar[str] = constants.CHATMEMBER_ADMINISTRATOR

View file

@ -69,7 +69,6 @@ class ChatMemberUpdated(TelegramObject):
'old_chat_member',
'new_chat_member',
'invite_link',
'_id_attrs',
)
def __init__(

View file

@ -82,7 +82,6 @@ class ChatPermissions(TelegramObject):
'can_send_other_messages',
'can_invite_users',
'can_send_polls',
'_id_attrs',
'can_send_messages',
'can_send_media_messages',
'can_change_info',

View file

@ -61,7 +61,7 @@ class ChosenInlineResult(TelegramObject):
"""
__slots__ = ('location', 'result_id', 'from_user', 'inline_message_id', '_id_attrs', 'query')
__slots__ = ('location', 'result_id', 'from_user', 'inline_message_id', 'query')
def __init__(
self,

View file

@ -64,7 +64,7 @@ class Dice(TelegramObject):
"""
__slots__ = ('emoji', 'value', '_id_attrs')
__slots__ = ('emoji', 'value')
def __init__(self, value: int, emoji: str, **_kwargs: Any):
self.value = value

View file

@ -41,7 +41,6 @@ def _lstrip_str(in_s: str, lstr: str) -> str:
class TelegramError(Exception):
"""Base class for Telegram errors."""
# Apparently the base class Exception already has __dict__ in it, so its not included here
__slots__ = ('message',)
def __init__(self, message: str):

View file

@ -16,7 +16,6 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=C0413
"""Extensions over the Telegram Bot API to facilitate bot making"""
from .extbot import ExtBot
@ -28,17 +27,6 @@ from .callbackcontext import CallbackContext
from .contexttypes import ContextTypes
from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async
# https://bugs.python.org/issue41451, fixed on 3.7+, doesn't actually remove slots
# try-except is just here in case the __init__ is called twice (like in the tests)
# this block is also the reason for the pylint-ignore at the top of the file
try:
del Dispatcher.__slots__
except AttributeError as exc:
if str(exc) == '__slots__':
pass
else:
raise exc
from .jobqueue import JobQueue, Job
from .updater import Updater
from .callbackqueryhandler import CallbackQueryHandler

View file

@ -18,13 +18,10 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the BasePersistence class."""
import warnings
from sys import version_info as py_ver
from abc import ABC, abstractmethod
from copy import copy
from typing import Dict, Optional, Tuple, cast, ClassVar, Generic, DefaultDict, NamedTuple
from telegram.utils.deprecate import set_new_attribute_deprecated
from telegram import Bot
import telegram.ext.extbot
@ -108,18 +105,11 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
persistence instance.
"""
# Apparently Py 3.7 and below have '__dict__' in ABC
if py_ver < (3, 7):
__slots__ = (
'store_data',
'bot',
)
else:
__slots__ = (
'store_data', # type: ignore[assignment]
'bot',
'__dict__',
)
__slots__ = (
'bot',
'store_data',
'__dict__', # __dict__ is included because we replace methods in the __new__
)
def __new__(
cls, *args: object, **kwargs: object # pylint: disable=W0613
@ -169,15 +159,15 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
obj_data, queue = data
return update_callback_data((instance.replace_bot(obj_data), queue))
# We want to ignore TGDeprecation warnings so we use obj.__setattr__. Adds to __dict__
object.__setattr__(instance, 'get_user_data', get_user_data_insert_bot)
object.__setattr__(instance, 'get_chat_data', get_chat_data_insert_bot)
object.__setattr__(instance, 'get_bot_data', get_bot_data_insert_bot)
object.__setattr__(instance, 'get_callback_data', get_callback_data_insert_bot)
object.__setattr__(instance, 'update_user_data', update_user_data_replace_bot)
object.__setattr__(instance, 'update_chat_data', update_chat_data_replace_bot)
object.__setattr__(instance, 'update_bot_data', update_bot_data_replace_bot)
object.__setattr__(instance, 'update_callback_data', update_callback_data_replace_bot)
# Adds to __dict__
setattr(instance, 'get_user_data', get_user_data_insert_bot)
setattr(instance, 'get_chat_data', get_chat_data_insert_bot)
setattr(instance, 'get_bot_data', get_bot_data_insert_bot)
setattr(instance, 'get_callback_data', get_callback_data_insert_bot)
setattr(instance, 'update_user_data', update_user_data_replace_bot)
setattr(instance, 'update_chat_data', update_chat_data_replace_bot)
setattr(instance, 'update_bot_data', update_bot_data_replace_bot)
setattr(instance, 'update_callback_data', update_callback_data_replace_bot)
return instance
def __init__(
@ -188,16 +178,6 @@ class BasePersistence(Generic[UD, CD, BD], ABC):
self.bot: Bot = None # type: ignore[assignment]
def __setattr__(self, key: str, value: object) -> None:
# Allow user defined subclasses to have custom attributes.
if issubclass(self.__class__, BasePersistence) and self.__class__.__name__ not in {
'DictPersistence',
'PicklePersistence',
}:
object.__setattr__(self, key, value)
return
set_new_attribute_deprecated(self, key, value)
def set_bot(self, bot: Bot) -> None:
"""Set the Bot to be used by this persistence instance.

View file

@ -46,7 +46,6 @@ CheckUpdateType = Optional[Tuple[Tuple[int, ...], Handler, object]]
class _ConversationTimeoutContext:
# '__dict__' is not included since this a private class
__slots__ = ('conversation_key', 'update', 'dispatcher', 'callback_context')
def __init__(

View file

@ -22,7 +22,6 @@ from typing import NoReturn, Optional, Dict, Any
import pytz
from telegram.utils.deprecate import set_new_attribute_deprecated
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.types import ODVInput
@ -67,7 +66,6 @@ class Defaults:
'_allow_sending_without_reply',
'_parse_mode',
'_api_defaults',
'__dict__',
)
def __init__(
@ -108,9 +106,6 @@ class Defaults:
if self._timeout != DEFAULT_NONE:
self._api_defaults['timeout'] = self._timeout
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
@property
def api_defaults(self) -> Dict[str, Any]: # skip-cq: PY-D0003
return self._api_defaults

View file

@ -48,7 +48,7 @@ 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, set_new_attribute_deprecated
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
@ -312,17 +312,6 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
else:
self._set_singleton(None)
def __setattr__(self, key: str, value: object) -> None:
# Mangled names don't automatically apply in __setattr__ (see
# https://docs.python.org/3/tutorial/classes.html#private-variables), so we have to make
# it mangled so they don't raise TelegramDeprecationWarning unnecessarily
if key.startswith('__'):
key = f"_{self.__class__.__name__}{key}"
if issubclass(self.__class__, Dispatcher) and self.__class__ is not Dispatcher:
object.__setattr__(self, key, value)
return
set_new_attribute_deprecated(self, key, value)
@property
def exception_event(self) -> Event: # skipcq: PY-D0003
return self.__exception_event

View file

@ -75,14 +75,6 @@ class ExtBot(telegram.bot.Bot):
__slots__ = ('arbitrary_callback_data', 'callback_data_cache')
# The ext_bot argument is a little hack to get warnings handled correctly.
# It's not very clean, but the warnings will be dropped at some point anyway.
def __setattr__(self, key: str, value: object, ext_bot: bool = True) -> None:
if issubclass(self.__class__, ExtBot) and self.__class__ is not ExtBot:
object.__setattr__(self, key, value)
return
super().__setattr__(key, value, ext_bot=ext_bot) # type: ignore[call-arg]
def __init__(
self,
token: str,
@ -265,7 +257,7 @@ class ExtBot(telegram.bot.Bot):
# different places
new_result = copy(result)
markup = self._replace_keyboard(result.reply_markup) # type: ignore[attr-defined]
new_result.reply_markup = markup
new_result.reply_markup = markup # type: ignore[attr-defined]
results.append(new_result)
return results, next_offset

View file

@ -23,7 +23,6 @@ import re
import warnings
from abc import ABC, abstractmethod
from sys import version_info as py_ver
from threading import Lock
from typing import (
Dict,
@ -51,7 +50,7 @@ __all__ = [
'XORFilter',
]
from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.utils.types import SLT
DataDict = Dict[str, list]
@ -113,12 +112,10 @@ class BaseFilter(ABC):
(depends on the handler).
"""
if py_ver < (3, 7):
__slots__ = ('_name', '_data_filter')
else:
__slots__ = ('_name', '_data_filter', '__dict__') # type: ignore[assignment]
__slots__ = ('_name', '_data_filter')
def __new__(cls, *args: object, **kwargs: object) -> 'BaseFilter': # pylint: disable=W0613
# We do this here instead of in a __init__ so filter don't have to call __init__ or super()
instance = super().__new__(cls)
instance._name = None
instance._data_filter = False
@ -141,18 +138,6 @@ class BaseFilter(ABC):
def __invert__(self) -> 'BaseFilter':
return InvertedFilter(self)
def __setattr__(self, key: str, value: object) -> None:
# Allow setting custom attributes w/o warning for user defined custom filters.
# To differentiate between a custom and a PTB filter, we use this hacky but
# simple way of checking the module name where the class is defined from.
if (
issubclass(self.__class__, (UpdateFilter, MessageFilter))
and self.__class__.__module__ != __name__
): # __name__ is telegram.ext.filters
object.__setattr__(self, key, value)
return
set_new_attribute_deprecated(self, key, value)
@property
def data_filter(self) -> bool:
return self._data_filter
@ -437,10 +422,7 @@ class Filters:
"""
__slots__ = ('__dict__',)
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
__slots__ = ()
class _All(MessageFilter):
__slots__ = ()

View file

@ -19,9 +19,6 @@
"""This module contains the base class for handlers as used by the Dispatcher."""
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, Union, Generic
from sys import version_info as py_ver
from telegram.utils.deprecate import set_new_attribute_deprecated
from telegram import Update
from telegram.ext.utils.promise import Promise
@ -93,26 +90,14 @@ class Handler(Generic[UT, CCT], ABC):
"""
# Apparently Py 3.7 and below have '__dict__' in ABC
if py_ver < (3, 7):
__slots__ = (
'callback',
'pass_update_queue',
'pass_job_queue',
'pass_user_data',
'pass_chat_data',
'run_async',
)
else:
__slots__ = (
'callback', # type: ignore[assignment]
'pass_update_queue',
'pass_job_queue',
'pass_user_data',
'pass_chat_data',
'run_async',
'__dict__',
)
__slots__ = (
'callback',
'pass_update_queue',
'pass_job_queue',
'pass_user_data',
'pass_chat_data',
'run_async',
)
def __init__(
self,
@ -130,17 +115,6 @@ class Handler(Generic[UT, CCT], ABC):
self.pass_chat_data = pass_chat_data
self.run_async = run_async
def __setattr__(self, key: str, value: object) -> None:
# See comment on BaseFilter to know why this was done.
if key.startswith('__'):
key = f"_{self.__class__.__name__}{key}"
if issubclass(self.__class__, Handler) and not self.__class__.__module__.startswith(
'telegram.ext.'
):
object.__setattr__(self, key, value)
return
set_new_attribute_deprecated(self, key, value)
@abstractmethod
def check_update(self, update: object) -> Optional[Union[bool, object]]:
"""

View file

@ -31,7 +31,6 @@ from apscheduler.job import Job as APSJob
from telegram.ext.callbackcontext import CallbackContext
from telegram.utils.types import JSONDict
from telegram.utils.deprecate import set_new_attribute_deprecated
if TYPE_CHECKING:
from telegram import Bot
@ -50,7 +49,7 @@ class JobQueue:
"""
__slots__ = ('_dispatcher', 'logger', 'scheduler', '__dict__')
__slots__ = ('_dispatcher', 'logger', 'scheduler')
def __init__(self) -> None:
self._dispatcher: 'Dispatcher' = None # type: ignore[assignment]
@ -67,9 +66,6 @@ class JobQueue:
logging.getLogger('apscheduler.executors.default').addFilter(aps_log_filter)
self.scheduler.add_listener(self._dispatch_error, EVENT_JOB_ERROR)
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
def _build_args(self, job: 'Job') -> List[Union[CallbackContext, 'Bot', 'Job']]:
if self._dispatcher.use_context:
return [self._dispatcher.context_types.context.from_job(job, self._dispatcher)]
@ -560,7 +556,6 @@ class Job:
'_removed',
'_enabled',
'job',
'__dict__',
)
def __init__(
@ -582,9 +577,6 @@ class Job:
self.job = cast(APSJob, job) # skipcq: PTC-W0052
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
def run(self, dispatcher: 'Dispatcher') -> None:
"""Executes the callback function independently of the jobs schedule."""
try:

View file

@ -42,7 +42,7 @@ from typing import (
from telegram import Bot, TelegramError
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized
from telegram.ext import Dispatcher, JobQueue, ContextTypes, ExtBot
from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated
from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.utils.helpers import get_signal_name, DEFAULT_FALSE, DefaultValue
from telegram.utils.request import Request
from telegram.ext.utils.types import CCT, UD, CD, BD
@ -149,7 +149,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
'httpd',
'__lock',
'__threads',
'__dict__',
)
@overload
@ -328,14 +327,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
self.__lock = Lock()
self.__threads: List[Thread] = []
def __setattr__(self, key: str, value: object) -> None:
if key.startswith('__'):
key = f"_{self.__class__.__name__}{key}"
if issubclass(self.__class__, Updater) and self.__class__ is not Updater:
object.__setattr__(self, key, value)
return
set_new_attribute_deprecated(self, key, value)
def _init_thread(self, target: Callable, name: str, *args: object, **kwargs: object) -> None:
thr = Thread(
target=self._thread_wrapper,

View file

@ -22,7 +22,6 @@ import logging
from threading import Event
from typing import Callable, List, Optional, Tuple, TypeVar, Union
from telegram.utils.deprecate import set_new_attribute_deprecated
from telegram.utils.types import JSONDict
RT = TypeVar('RT')
@ -65,7 +64,6 @@ class Promise:
'_done_callback',
'_result',
'_exception',
'__dict__',
)
# TODO: Remove error_handling parameter once we drop the @run_async decorator
@ -87,9 +85,6 @@ class Promise:
self._result: Optional[RT] = None
self._exception: Optional[Exception] = None
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
def run(self) -> None:
"""Calls the :attr:`pooled_function` callable."""
try:

View file

@ -31,7 +31,6 @@ from tornado.ioloop import IOLoop
from telegram import Update
from telegram.ext import ExtBot
from telegram.utils.deprecate import set_new_attribute_deprecated
from telegram.utils.types import JSONDict
if TYPE_CHECKING:
@ -53,7 +52,6 @@ class WebhookServer:
'is_running',
'server_lock',
'shutdown_lock',
'__dict__',
)
def __init__(
@ -68,9 +66,6 @@ class WebhookServer:
self.server_lock = Lock()
self.shutdown_lock = Lock()
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
def serve_forever(self, ready: Event = None) -> None:
with self.server_lock:
IOLoop().make_current()

View file

@ -76,7 +76,6 @@ class Animation(TelegramObject):
'mime_type',
'height',
'file_unique_id',
'_id_attrs',
)
def __init__(

View file

@ -80,7 +80,6 @@ class Audio(TelegramObject):
'performer',
'mime_type',
'file_unique_id',
'_id_attrs',
)
def __init__(

View file

@ -71,7 +71,6 @@ class ChatPhoto(TelegramObject):
'small_file_id',
'small_file_unique_id',
'big_file_id',
'_id_attrs',
)
def __init__(

View file

@ -46,7 +46,7 @@ class Contact(TelegramObject):
"""
__slots__ = ('vcard', 'user_id', 'first_name', 'last_name', 'phone_number', '_id_attrs')
__slots__ = ('vcard', 'user_id', 'first_name', 'last_name', 'phone_number')
def __init__(
self,

View file

@ -68,11 +68,8 @@ class Document(TelegramObject):
'thumb',
'mime_type',
'file_unique_id',
'_id_attrs',
)
_id_keys = ('file_id',)
def __init__(
self,
file_id: str,

View file

@ -74,7 +74,6 @@ class File(TelegramObject):
'file_unique_id',
'file_path',
'_credentials',
'_id_attrs',
)
def __init__(

View file

@ -26,8 +26,6 @@ import os
from typing import IO, Optional, Tuple, Union
from uuid import uuid4
from telegram.utils.deprecate import set_new_attribute_deprecated
DEFAULT_MIME_TYPE = 'application/octet-stream'
logger = logging.getLogger(__name__)
@ -52,7 +50,7 @@ class InputFile:
"""
__slots__ = ('filename', 'attach', 'input_file_content', 'mimetype', '__dict__')
__slots__ = ('filename', 'attach', 'input_file_content', 'mimetype')
def __init__(self, obj: Union[IO, bytes], filename: str = None, attach: bool = None):
self.filename = None
@ -78,9 +76,6 @@ class InputFile:
if not self.filename:
self.filename = self.mimetype.replace('/', '.')
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
@property
def field_tuple(self) -> Tuple[str, bytes, str]: # skipcq: PY-D0003
return self.filename, self.input_file_content, self.mimetype

View file

@ -63,7 +63,6 @@ class Location(TelegramObject):
'live_period',
'latitude',
'heading',
'_id_attrs',
)
def __init__(

View file

@ -58,7 +58,7 @@ class PhotoSize(TelegramObject):
"""
__slots__ = ('bot', 'width', 'file_id', 'file_size', 'height', 'file_unique_id', '_id_attrs')
__slots__ = ('bot', 'width', 'file_id', 'file_size', 'height', 'file_unique_id')
def __init__(
self,

View file

@ -97,7 +97,6 @@ class Sticker(TelegramObject):
'height',
'file_unique_id',
'emoji',
'_id_attrs',
)
def __init__(
@ -208,7 +207,6 @@ class StickerSet(TelegramObject):
'title',
'stickers',
'name',
'_id_attrs',
)
def __init__(
@ -286,7 +284,7 @@ class MaskPosition(TelegramObject):
"""
__slots__ = ('point', 'scale', 'x_shift', 'y_shift', '_id_attrs')
__slots__ = ('point', 'scale', 'x_shift', 'y_shift')
FOREHEAD: ClassVar[str] = constants.STICKER_FOREHEAD
""":const:`telegram.constants.STICKER_FOREHEAD`"""

View file

@ -68,7 +68,6 @@ class Venue(TelegramObject):
'foursquare_type',
'foursquare_id',
'google_place_id',
'_id_attrs',
)
def __init__(

View file

@ -77,7 +77,6 @@ class Video(TelegramObject):
'mime_type',
'height',
'file_unique_id',
'_id_attrs',
)
def __init__(

View file

@ -69,7 +69,6 @@ class VideoNote(TelegramObject):
'thumb',
'duration',
'file_unique_id',
'_id_attrs',
)
def __init__(

View file

@ -65,7 +65,6 @@ class Voice(TelegramObject):
'duration',
'mime_type',
'file_unique_id',
'_id_attrs',
)
def __init__(

View file

@ -60,7 +60,7 @@ class ForceReply(ReplyMarkup):
"""
__slots__ = ('selective', 'force_reply', 'input_field_placeholder', '_id_attrs')
__slots__ = ('selective', 'force_reply', 'input_field_placeholder')
def __init__(
self,

View file

@ -74,7 +74,6 @@ class Game(TelegramObject):
'text_entities',
'text',
'animation',
'_id_attrs',
)
def __init__(

View file

@ -45,7 +45,7 @@ class GameHighScore(TelegramObject):
"""
__slots__ = ('position', 'user', 'score', '_id_attrs')
__slots__ = ('position', 'user', 'score')
def __init__(self, position: int, user: User, score: int):
self.position = position

View file

@ -121,7 +121,6 @@ class InlineKeyboardButton(TelegramObject):
'pay',
'switch_inline_query',
'text',
'_id_attrs',
'login_url',
)

View file

@ -45,7 +45,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
"""
__slots__ = ('inline_keyboard', '_id_attrs')
__slots__ = ('inline_keyboard',)
def __init__(self, inline_keyboard: List[List[InlineKeyboardButton]], **_kwargs: Any):
# Required

View file

@ -71,7 +71,7 @@ class InlineQuery(TelegramObject):
"""
__slots__ = ('bot', 'location', 'chat_type', 'id', 'offset', 'from_user', 'query', '_id_attrs')
__slots__ = ('bot', 'location', 'chat_type', 'id', 'offset', 'from_user', 'query')
def __init__(
self,

View file

@ -46,7 +46,7 @@ class InlineQueryResult(TelegramObject):
"""
__slots__ = ('type', 'id', '_id_attrs')
__slots__ = ('type', 'id')
def __init__(self, type: str, id: str, **_kwargs: Any):
# Required

View file

@ -46,7 +46,7 @@ class InputContactMessageContent(InputMessageContent):
"""
__slots__ = ('vcard', 'first_name', 'last_name', 'phone_number', '_id_attrs')
__slots__ = ('vcard', 'first_name', 'last_name', 'phone_number')
def __init__(
self,

View file

@ -144,7 +144,6 @@ class InputInvoiceMessageContent(InputMessageContent):
'send_phone_number_to_provider',
'send_email_to_provider',
'is_flexible',
'_id_attrs',
)
def __init__(

View file

@ -60,7 +60,7 @@ class InputLocationMessageContent(InputMessageContent):
"""
__slots__ = ('longitude', 'horizontal_accuracy', 'proximity_alert_radius', 'live_period',
'latitude', 'heading', '_id_attrs')
'latitude', 'heading')
# fmt: on
def __init__(

View file

@ -59,7 +59,7 @@ class InputTextMessageContent(InputMessageContent):
"""
__slots__ = ('disable_web_page_preview', 'parse_mode', 'entities', 'message_text', '_id_attrs')
__slots__ = ('disable_web_page_preview', 'parse_mode', 'entities', 'message_text')
def __init__(
self,

View file

@ -69,7 +69,6 @@ class InputVenueMessageContent(InputMessageContent):
'foursquare_type',
'google_place_id',
'latitude',
'_id_attrs',
)
def __init__(

View file

@ -58,7 +58,7 @@ class KeyboardButton(TelegramObject):
"""
__slots__ = ('request_location', 'request_contact', 'request_poll', 'text', '_id_attrs')
__slots__ = ('request_location', 'request_contact', 'request_poll', 'text')
def __init__(
self,

View file

@ -37,7 +37,7 @@ class KeyboardButtonPollType(TelegramObject):
create a poll of any type.
"""
__slots__ = ('type', '_id_attrs')
__slots__ = ('type',)
def __init__(self, type: str = None, **_kwargs: Any): # pylint: disable=W0622
self.type = type

View file

@ -69,7 +69,7 @@ class LoginUrl(TelegramObject):
"""
__slots__ = ('bot_username', 'request_write_access', 'url', 'forward_text', '_id_attrs')
__slots__ = ('bot_username', 'request_write_access', 'url', 'forward_text')
def __init__(
self,

View file

@ -412,7 +412,6 @@ class Message(TelegramObject):
'voice_chat_scheduled',
'is_automatic_forward',
'has_protected_content',
'_id_attrs',
)
ATTACHMENT_TYPES: ClassVar[List[str]] = [

View file

@ -44,7 +44,7 @@ class MessageAutoDeleteTimerChanged(TelegramObject):
"""
__slots__ = ('message_auto_delete_time', '_id_attrs')
__slots__ = ('message_auto_delete_time',)
def __init__(
self,

View file

@ -60,7 +60,7 @@ class MessageEntity(TelegramObject):
"""
__slots__ = ('length', 'url', 'user', 'type', 'language', 'offset', '_id_attrs')
__slots__ = ('length', 'url', 'user', 'type', 'language', 'offset')
def __init__(
self,

View file

@ -32,7 +32,7 @@ class MessageId(TelegramObject):
message_id (:obj:`int`): Unique message identifier
"""
__slots__ = ('message_id', '_id_attrs')
__slots__ = ('message_id',)
def __init__(self, message_id: int, **_kwargs: Any):
self.message_id = int(message_id)

View file

@ -21,13 +21,12 @@
from typing import ClassVar
from telegram import constants
from telegram.utils.deprecate import set_new_attribute_deprecated
class ParseMode:
"""This object represents a Telegram Message Parse Modes."""
__slots__ = ('__dict__',)
__slots__ = ()
MARKDOWN: ClassVar[str] = constants.PARSEMODE_MARKDOWN
""":const:`telegram.constants.PARSEMODE_MARKDOWN`\n
@ -40,6 +39,3 @@ class ParseMode:
""":const:`telegram.constants.PARSEMODE_MARKDOWN_V2`"""
HTML: ClassVar[str] = constants.PARSEMODE_HTML
""":const:`telegram.constants.PARSEMODE_HTML`"""
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)

View file

@ -137,7 +137,6 @@ class EncryptedCredentials(TelegramObject):
'secret',
'bot',
'data',
'_id_attrs',
'_decrypted_secret',
'_decrypted_data',
)

View file

@ -130,7 +130,6 @@ class EncryptedPassportElement(TelegramObject):
'reverse_side',
'front_side',
'data',
'_id_attrs',
)
def __init__(

View file

@ -51,7 +51,7 @@ class PassportData(TelegramObject):
"""
__slots__ = ('bot', 'credentials', 'data', '_decrypted_data', '_id_attrs')
__slots__ = ('bot', 'credentials', 'data', '_decrypted_data')
def __init__(
self,

View file

@ -46,7 +46,7 @@ class PassportElementError(TelegramObject):
"""
# All subclasses of this class won't have _id_attrs in slots since it's added here.
__slots__ = ('message', 'source', 'type', '_id_attrs')
__slots__ = ('message', 'source', 'type')
def __init__(self, source: str, type: str, message: str, **_kwargs: Any):
# Required

View file

@ -65,7 +65,6 @@ class PassportFile(TelegramObject):
'file_size',
'_credentials',
'file_unique_id',
'_id_attrs',
)
def __init__(

View file

@ -59,7 +59,6 @@ class Invoice(TelegramObject):
'title',
'description',
'total_amount',
'_id_attrs',
)
def __init__(

View file

@ -45,7 +45,7 @@ class LabeledPrice(TelegramObject):
"""
__slots__ = ('label', '_id_attrs', 'amount')
__slots__ = ('label', 'amount')
def __init__(self, label: str, amount: int, **_kwargs: Any):
self.label = label

View file

@ -49,7 +49,7 @@ class OrderInfo(TelegramObject):
"""
__slots__ = ('email', 'shipping_address', 'phone_number', 'name', '_id_attrs')
__slots__ = ('email', 'shipping_address', 'phone_number', 'name')
def __init__(
self,

View file

@ -76,7 +76,6 @@ class PreCheckoutQuery(TelegramObject):
'total_amount',
'id',
'from_user',
'_id_attrs',
)
def __init__(

View file

@ -52,7 +52,6 @@ class ShippingAddress(TelegramObject):
__slots__ = (
'post_code',
'city',
'_id_attrs',
'country_code',
'street_line2',
'street_line1',

View file

@ -46,7 +46,7 @@ class ShippingOption(TelegramObject):
"""
__slots__ = ('prices', 'title', 'id', '_id_attrs')
__slots__ = ('prices', 'title', 'id')
def __init__(
self,

View file

@ -54,7 +54,7 @@ class ShippingQuery(TelegramObject):
"""
__slots__ = ('bot', 'invoice_payload', 'shipping_address', 'id', 'from_user', '_id_attrs')
__slots__ = ('bot', 'invoice_payload', 'shipping_address', 'id', 'from_user')
def __init__(
self,

View file

@ -70,7 +70,6 @@ class SuccessfulPayment(TelegramObject):
'telegram_payment_charge_id',
'provider_payment_charge_id',
'total_amount',
'_id_attrs',
)
def __init__(

View file

@ -48,7 +48,7 @@ class PollOption(TelegramObject):
"""
__slots__ = ('voter_count', 'text', '_id_attrs')
__slots__ = ('voter_count', 'text')
def __init__(self, text: str, voter_count: int, **_kwargs: Any):
self.text = text
@ -80,7 +80,7 @@ class PollAnswer(TelegramObject):
"""
__slots__ = ('option_ids', 'user', 'poll_id', '_id_attrs')
__slots__ = ('option_ids', 'user', 'poll_id')
def __init__(self, poll_id: str, user: User, option_ids: List[int], **_kwargs: Any):
self.poll_id = poll_id
@ -164,7 +164,6 @@ class Poll(TelegramObject):
'explanation',
'question',
'correct_option_id',
'_id_attrs',
)
def __init__(

View file

@ -46,7 +46,7 @@ class ProximityAlertTriggered(TelegramObject):
"""
__slots__ = ('traveler', 'distance', 'watcher', '_id_attrs')
__slots__ = ('traveler', 'distance', 'watcher')
def __init__(self, traveler: User, watcher: User, distance: int, **_kwargs: Any):
self.traveler = traveler

View file

@ -81,7 +81,6 @@ class ReplyKeyboardMarkup(ReplyMarkup):
'resize_keyboard',
'one_time_keyboard',
'input_field_placeholder',
'_id_attrs',
)
def __init__(

View file

@ -156,7 +156,6 @@ class Update(TelegramObject):
'my_chat_member',
'chat_member',
'chat_join_request',
'_id_attrs',
)
MESSAGE = constants.UPDATE_MESSAGE

View file

@ -108,7 +108,6 @@ class User(TelegramObject):
'id',
'bot',
'language_code',
'_id_attrs',
)
def __init__(

View file

@ -44,7 +44,7 @@ class UserProfilePhotos(TelegramObject):
"""
__slots__ = ('photos', 'total_count', '_id_attrs')
__slots__ = ('photos', 'total_count')
def __init__(self, total_count: int, photos: List[List[PhotoSize]], **_kwargs: Any):
# Required

View file

@ -16,9 +16,7 @@
#
# 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 facilitates the deprecation of functions."""
import warnings
"""This module contains a class which is used for deprecation warnings."""
# We use our own DeprecationWarning since they are muted by default and "UserWarning" makes it
@ -28,20 +26,3 @@ class TelegramDeprecationWarning(Warning):
"""Custom warning class for deprecations in this library."""
__slots__ = ()
# Function to warn users that setting custom attributes is deprecated (Use only in __setattr__!)
# Checks if a custom attribute is added by checking length of dictionary before & after
# assigning attribute. This is the fastest way to do it (I hope!).
def set_new_attribute_deprecated(self: object, key: str, value: object) -> None:
"""Warns the user if they set custom attributes on PTB objects."""
org = len(self.__dict__)
object.__setattr__(self, key, value)
new = len(self.__dict__)
if new > org:
warnings.warn(
f"Setting custom attributes such as {key!r} on objects such as "
f"{self.__class__.__name__!r} of the PTB library is deprecated.",
TelegramDeprecationWarning,
stacklevel=3,
)

View file

@ -544,7 +544,7 @@ class DefaultValue(Generic[DVType]):
"""
__slots__ = ('value', '__dict__')
__slots__ = ('value',)
def __init__(self, value: DVType = None):
self.value = value

View file

@ -70,7 +70,6 @@ from telegram.error import (
Unauthorized,
)
from telegram.utils.types import JSONDict
from telegram.utils.deprecate import set_new_attribute_deprecated
def _render_part(self: RequestField, name: str, value: str) -> str: # pylint: disable=W0613
@ -112,7 +111,7 @@ class Request:
"""
__slots__ = ('_connect_timeout', '_con_pool_size', '_con_pool', '__dict__')
__slots__ = ('_connect_timeout', '_con_pool_size', '_con_pool')
def __init__(
self,
@ -192,9 +191,6 @@ class Request:
self._con_pool = mgr
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
@property
def con_pool_size(self) -> int:
"""The size of the connection pool used."""

View file

@ -64,7 +64,7 @@ class VoiceChatEnded(TelegramObject):
"""
__slots__ = ('duration', '_id_attrs')
__slots__ = ('duration',)
def __init__(self, duration: int, **_kwargs: Any) -> None:
self.duration = int(duration) if duration is not None else None
@ -93,7 +93,7 @@ class VoiceChatParticipantsInvited(TelegramObject):
"""
__slots__ = ('users', '_id_attrs')
__slots__ = ('users',)
def __init__(self, users: List[User], **_kwargs: Any) -> None:
self.users = users
@ -140,7 +140,7 @@ class VoiceChatScheduled(TelegramObject):
"""
__slots__ = ('start_date', '_id_attrs')
__slots__ = ('start_date',)
def __init__(self, start_date: dtm.datetime, **_kwargs: Any) -> None:
self.start_date = start_date

View file

@ -71,7 +71,6 @@ class WebhookInfo(TelegramObject):
'last_error_message',
'pending_update_count',
'has_custom_certificate',
'_id_attrs',
)
def __init__(

View file

@ -44,6 +44,7 @@ from telegram import (
ChosenInlineResult,
File,
ChatPermissions,
Bot,
)
from telegram.ext import (
Dispatcher,
@ -56,6 +57,7 @@ from telegram.ext import (
)
from telegram.error import BadRequest
from telegram.utils.helpers import DefaultValue, DEFAULT_NONE
from telegram.utils.request import Request
from tests.bots import get_bot
@ -89,14 +91,22 @@ def bot_info():
return get_bot()
# Below Dict* classes are used to monkeypatch attributes since parent classes don't have __dict__
class DictRequest(Request):
pass
class DictExtBot(ExtBot):
pass
class DictBot(Bot):
pass
@pytest.fixture(scope='session')
def bot(bot_info):
class DictExtBot(
ExtBot
): # Subclass Bot to allow monkey patching of attributes and functions, would
pass # come into effect when we __dict__ is dropped from slots
return DictExtBot(bot_info['token'], private_key=PRIVATE_KEY)
return DictExtBot(bot_info['token'], private_key=PRIVATE_KEY, request=DictRequest())
DEFAULT_BOTS = {}
@ -230,7 +240,7 @@ def make_bot(bot_info, **kwargs):
"""
Tests are executed on tg.ext.ExtBot, as that class only extends the functionality of tg.bot
"""
return ExtBot(bot_info['token'], private_key=PRIVATE_KEY, **kwargs)
return ExtBot(bot_info['token'], private_key=PRIVATE_KEY, request=DictRequest(), **kwargs)
CMD_PATTERN = re.compile(r'/[\da-z_]{1,32}(?:@\w{1,32})?')
@ -361,9 +371,9 @@ def mro_slots():
return [
attr
for cls in _class.__class__.__mro__[:-1]
if hasattr(cls, '__slots__') # ABC doesn't have slots in py 3.7 and below
if hasattr(cls, '__slots__') # The Exception class doesn't have slots
for attr in cls.__slots__
if attr != '__dict__'
if attr != '__dict__' # left here for classes which still has __dict__
]
return _mro_slots

View file

@ -57,13 +57,10 @@ class TestAnimation:
file_size = 4127
caption = "Test *animation*"
def test_slot_behaviour(self, animation, recwarn, mro_slots):
def test_slot_behaviour(self, animation, mro_slots):
for attr in animation.__slots__:
assert getattr(animation, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not animation.__dict__, f"got missing slot(s): {animation.__dict__}"
assert len(mro_slots(animation)) == len(set(mro_slots(animation))), "duplicate slot"
animation.custom, animation.file_name = 'should give warning', self.file_name
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
def test_creation(self, animation):
assert isinstance(animation, Animation)

View file

@ -59,13 +59,10 @@ class TestAudio:
audio_file_id = '5a3128a4d2a04750b5b58397f3b5e812'
audio_file_unique_id = 'adc3145fd2e84d95b64d68eaa22aa33e'
def test_slot_behaviour(self, audio, recwarn, mro_slots):
def test_slot_behaviour(self, audio, mro_slots):
for attr in audio.__slots__:
assert getattr(audio, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not audio.__dict__, f"got missing slot(s): {audio.__dict__}"
assert len(mro_slots(audio)) == len(set(mro_slots(audio))), "duplicate slot"
audio.custom, audio.file_name = 'should give warning', self.file_name
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
def test_creation(self, audio):
# Make sure file has been uploaded.

View file

@ -145,20 +145,10 @@ class TestBot:
"""
@pytest.mark.parametrize('inst', ['bot', "default_bot"], indirect=True)
def test_slot_behaviour(self, inst, recwarn, mro_slots):
def test_slot_behaviour(self, inst, mro_slots):
for attr in inst.__slots__:
assert getattr(inst, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not inst.__dict__, f"got missing slots: {inst.__dict__}"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
inst.custom, inst.base_url = 'should give warning', inst.base_url
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
class CustomBot(Bot):
pass # Tests that setting custom attributes of Bot subclass doesn't raise warning
a = CustomBot(inst.token)
a.my_custom = 'no error!'
assert len(recwarn) == 1
@pytest.mark.parametrize(
'token',

View file

@ -31,13 +31,10 @@ class TestBotCommand:
command = 'start'
description = 'A command'
def test_slot_behaviour(self, bot_command, recwarn, mro_slots):
def test_slot_behaviour(self, bot_command, mro_slots):
for attr in bot_command.__slots__:
assert getattr(bot_command, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not bot_command.__dict__, f"got missing slot(s): {bot_command.__dict__}"
assert len(mro_slots(bot_command)) == len(set(mro_slots(bot_command))), "duplicate slot"
bot_command.custom, bot_command.command = 'should give warning', self.command
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
def test_de_json(self, bot):
json_dict = {'command': self.command, 'description': self.description}

View file

@ -113,15 +113,12 @@ def bot_command_scope(scope_class_and_type, chat_id):
# All the scope types are very similar, so we test everything via parametrization
class TestBotCommandScope:
def test_slot_behaviour(self, bot_command_scope, mro_slots, recwarn):
def test_slot_behaviour(self, bot_command_scope, mro_slots):
for attr in bot_command_scope.__slots__:
assert getattr(bot_command_scope, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert not bot_command_scope.__dict__, f"got missing slot(s): {bot_command_scope.__dict__}"
assert len(mro_slots(bot_command_scope)) == len(
set(mro_slots(bot_command_scope))
), "duplicate slot"
bot_command_scope.custom, bot_command_scope.type = 'warning!', bot_command_scope.type
assert len(recwarn) == 1 and 'custom' in str(recwarn[0].message), recwarn.list
def test_de_json(self, bot, scope_class_and_type, chat_id):
cls = scope_class_and_type[0]

View file

@ -38,7 +38,7 @@ CallbackContext.refresh_data is tested in TestBasePersistence
class TestCallbackContext:
def test_slot_behaviour(self, cdp, recwarn, mro_slots):
def test_slot_behaviour(self, cdp, mro_slots, recwarn):
c = CallbackContext(cdp)
for attr in c.__slots__:
assert getattr(c, attr, 'err') != 'err', f"got extra slot '{attr}'"

View file

@ -38,15 +38,13 @@ def callback_data_cache(bot):
class TestInvalidCallbackData:
def test_slot_behaviour(self, mro_slots, recwarn):
def test_slot_behaviour(self, mro_slots):
invalid_callback_data = InvalidCallbackData()
for attr in invalid_callback_data.__slots__:
assert getattr(invalid_callback_data, attr, 'err') != 'err', f"got extra slot '{attr}'"
assert len(mro_slots(invalid_callback_data)) == len(
set(mro_slots(invalid_callback_data))
), "duplicate slot"
with pytest.raises(AttributeError):
invalid_callback_data.custom
class TestKeyboardData:
@ -57,8 +55,6 @@ class TestKeyboardData:
assert len(mro_slots(keyboard_data)) == len(
set(mro_slots(keyboard_data))
), "duplicate slot"
with pytest.raises(AttributeError):
keyboard_data.custom = 42
class TestCallbackDataCache:
@ -73,8 +69,6 @@ class TestCallbackDataCache:
assert len(mro_slots(callback_data_cache)) == len(
set(mro_slots(callback_data_cache))
), "duplicate slot"
with pytest.raises(AttributeError):
callback_data_cache.custom = 42
@pytest.mark.parametrize('maxsize', [1, 5, 2048])
def test_init_maxsize(self, maxsize, bot):

Some files were not shown because too many files have changed in this diff Show more