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}} runs-on: ${{matrix.os}}
strategy: strategy:
matrix: 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] os: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: False fail-fast: False
steps: steps:

View file

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

View file

@ -93,7 +93,7 @@ Introduction
This library provides a pure Python interface for the This library provides a pure Python interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_. `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 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 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 This library provides a pure Python, lightweight interface for the
`Telegram Bot API <https://core.telegram.org/bots/api>`_. `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. ``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] [tool.black]
line-length = 99 line-length = 99
target-version = ['py36'] target-version = ['py37']
skip-string-normalization = true skip-string-normalization = true
# We need to force-exclude the negated include pattern # We need to force-exclude the negated include pattern

View file

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

View file

@ -23,10 +23,9 @@ except ImportError:
import json # type: ignore[no-redef] import json # type: ignore[no-redef]
import warnings 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.types import JSONDict
from telegram.utils.deprecate import set_new_attribute_deprecated
if TYPE_CHECKING: if TYPE_CHECKING:
from telegram import Bot from telegram import Bot
@ -37,12 +36,21 @@ TO = TypeVar('TO', bound='TelegramObject', covariant=True)
class TelegramObject: class TelegramObject:
"""Base class for most Telegram objects.""" """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. # Adding slots reduces memory usage & allows for faster attribute access.
# Only instance variables should be added to __slots__. # Only instance variables should be added to __slots__.
# We add __dict__ here for backward compatibility & also to avoid repetition for subclasses. __slots__ = ('_id_attrs',)
__slots__ = ('__dict__',)
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: def __str__(self) -> str:
return str(self.to_dict()) return str(self.to_dict())
@ -50,9 +58,6 @@ class TelegramObject:
def __getitem__(self, item: str) -> object: def __getitem__(self, item: str) -> object:
return getattr(self, item, None) return getattr(self, item, None)
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
@staticmethod @staticmethod
def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]: def _parse_data(data: Optional[JSONDict]) -> Optional[JSONDict]:
return None if data is None else data.copy() return None if data is None else data.copy()
@ -76,7 +81,7 @@ class TelegramObject:
if cls == TelegramObject: if cls == TelegramObject:
return cls() return cls()
return cls(bot=bot, **data) # type: ignore[call-arg] return cls(bot=bot, **data)
@classmethod @classmethod
def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]: def de_list(cls: Type[TO], data: Optional[List[JSONDict]], bot: 'Bot') -> List[Optional[TO]]:
@ -132,6 +137,7 @@ class TelegramObject:
return data return data
def __eq__(self, other: object) -> bool: def __eq__(self, other: object) -> bool:
# pylint: disable=no-member
if isinstance(other, self.__class__): if isinstance(other, self.__class__):
if self._id_attrs == (): if self._id_attrs == ():
warnings.warn( warnings.warn(
@ -144,9 +150,10 @@ class TelegramObject:
" for equivalence." " for equivalence."
) )
return self._id_attrs == other._id_attrs return self._id_attrs == other._id_attrs
return super().__eq__(other) # pylint: disable=no-member return super().__eq__(other)
def __hash__(self) -> int: def __hash__(self) -> int:
# pylint: disable=no-member
if self._id_attrs: 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__() return super().__hash__()

View file

@ -224,14 +224,6 @@ class Bot(TelegramObject):
private_key, password=private_key_password, backend=default_backend() 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( def _insert_defaults(
self, data: Dict[str, object], timeout: ODVInput[float] self, data: Dict[str, object], timeout: ODVInput[float]
) -> Optional[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): def __init__(self, command: str, description: str, **_kwargs: Any):
self.command = command self.command = command

View file

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

View file

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

View file

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

View file

@ -20,13 +20,12 @@
"""This module contains an object that represents a Telegram ChatAction.""" """This module contains an object that represents a Telegram ChatAction."""
from typing import ClassVar from typing import ClassVar
from telegram import constants from telegram import constants
from telegram.utils.deprecate import set_new_attribute_deprecated
class ChatAction: class ChatAction:
"""Helper class to provide constants for different chat actions.""" """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 FIND_LOCATION: ClassVar[str] = constants.CHATACTION_FIND_LOCATION
""":const:`telegram.constants.CHATACTION_FIND_LOCATION`""" """:const:`telegram.constants.CHATACTION_FIND_LOCATION`"""
RECORD_AUDIO: ClassVar[str] = constants.CHATACTION_RECORD_AUDIO RECORD_AUDIO: ClassVar[str] = constants.CHATACTION_RECORD_AUDIO
@ -69,6 +68,3 @@ class ChatAction:
""":const:`telegram.constants.CHATACTION_UPLOAD_VIDEO`""" """:const:`telegram.constants.CHATACTION_UPLOAD_VIDEO`"""
UPLOAD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO_NOTE UPLOAD_VIDEO_NOTE: ClassVar[str] = constants.CHATACTION_UPLOAD_VIDEO_NOTE
""":const:`telegram.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', 'name',
'creates_join_request', 'creates_join_request',
'pending_join_request_count', 'pending_join_request_count',
'_id_attrs',
) )
def __init__( def __init__(

View file

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

View file

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

View file

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

View file

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

View file

@ -82,7 +82,6 @@ class ChatPermissions(TelegramObject):
'can_send_other_messages', 'can_send_other_messages',
'can_invite_users', 'can_invite_users',
'can_send_polls', 'can_send_polls',
'_id_attrs',
'can_send_messages', 'can_send_messages',
'can_send_media_messages', 'can_send_media_messages',
'can_change_info', '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__( def __init__(
self, 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): def __init__(self, value: int, emoji: str, **_kwargs: Any):
self.value = value self.value = value

View file

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

View file

@ -16,7 +16,6 @@
# #
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # 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""" """Extensions over the Telegram Bot API to facilitate bot making"""
from .extbot import ExtBot from .extbot import ExtBot
@ -28,17 +27,6 @@ from .callbackcontext import CallbackContext
from .contexttypes import ContextTypes from .contexttypes import ContextTypes
from .dispatcher import Dispatcher, DispatcherHandlerStop, run_async 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 .jobqueue import JobQueue, Job
from .updater import Updater from .updater import Updater
from .callbackqueryhandler import CallbackQueryHandler from .callbackqueryhandler import CallbackQueryHandler

View file

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

View file

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

View file

@ -48,7 +48,7 @@ from telegram.ext.callbackcontext import CallbackContext
from telegram.ext.handler import Handler from telegram.ext.handler import Handler
import telegram.ext.extbot import telegram.ext.extbot
from telegram.ext.callbackdatacache import CallbackDataCache 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.ext.utils.promise import Promise
from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE from telegram.utils.helpers import DefaultValue, DEFAULT_FALSE
from telegram.ext.utils.types import CCT, UD, CD, BD from telegram.ext.utils.types import CCT, UD, CD, BD
@ -312,17 +312,6 @@ class Dispatcher(Generic[CCT, UD, CD, BD]):
else: else:
self._set_singleton(None) 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 @property
def exception_event(self) -> Event: # skipcq: PY-D0003 def exception_event(self) -> Event: # skipcq: PY-D0003
return self.__exception_event return self.__exception_event

View file

@ -75,14 +75,6 @@ class ExtBot(telegram.bot.Bot):
__slots__ = ('arbitrary_callback_data', 'callback_data_cache') __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__( def __init__(
self, self,
token: str, token: str,
@ -265,7 +257,7 @@ class ExtBot(telegram.bot.Bot):
# different places # different places
new_result = copy(result) new_result = copy(result)
markup = self._replace_keyboard(result.reply_markup) # type: ignore[attr-defined] 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) results.append(new_result)
return results, next_offset return results, next_offset

View file

@ -23,7 +23,6 @@ import re
import warnings import warnings
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from sys import version_info as py_ver
from threading import Lock from threading import Lock
from typing import ( from typing import (
Dict, Dict,
@ -51,7 +50,7 @@ __all__ = [
'XORFilter', 'XORFilter',
] ]
from telegram.utils.deprecate import TelegramDeprecationWarning, set_new_attribute_deprecated from telegram.utils.deprecate import TelegramDeprecationWarning
from telegram.utils.types import SLT from telegram.utils.types import SLT
DataDict = Dict[str, list] DataDict = Dict[str, list]
@ -113,12 +112,10 @@ class BaseFilter(ABC):
(depends on the handler). (depends on the handler).
""" """
if py_ver < (3, 7): __slots__ = ('_name', '_data_filter')
__slots__ = ('_name', '_data_filter')
else:
__slots__ = ('_name', '_data_filter', '__dict__') # type: ignore[assignment]
def __new__(cls, *args: object, **kwargs: object) -> 'BaseFilter': # pylint: disable=W0613 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 = super().__new__(cls)
instance._name = None instance._name = None
instance._data_filter = False instance._data_filter = False
@ -141,18 +138,6 @@ class BaseFilter(ABC):
def __invert__(self) -> 'BaseFilter': def __invert__(self) -> 'BaseFilter':
return InvertedFilter(self) 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 @property
def data_filter(self) -> bool: def data_filter(self) -> bool:
return self._data_filter return self._data_filter
@ -437,10 +422,7 @@ class Filters:
""" """
__slots__ = ('__dict__',) __slots__ = ()
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
class _All(MessageFilter): class _All(MessageFilter):
__slots__ = () __slots__ = ()

View file

@ -19,9 +19,6 @@
"""This module contains the base class for handlers as used by the Dispatcher.""" """This module contains the base class for handlers as used by the Dispatcher."""
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, Union, Generic 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 import Update
from telegram.ext.utils.promise import Promise 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 __slots__ = (
if py_ver < (3, 7): 'callback',
__slots__ = ( 'pass_update_queue',
'callback', 'pass_job_queue',
'pass_update_queue', 'pass_user_data',
'pass_job_queue', 'pass_chat_data',
'pass_user_data', 'run_async',
'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__',
)
def __init__( def __init__(
self, self,
@ -130,17 +115,6 @@ class Handler(Generic[UT, CCT], ABC):
self.pass_chat_data = pass_chat_data self.pass_chat_data = pass_chat_data
self.run_async = run_async 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 @abstractmethod
def check_update(self, update: object) -> Optional[Union[bool, object]]: 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.ext.callbackcontext import CallbackContext
from telegram.utils.types import JSONDict from telegram.utils.types import JSONDict
from telegram.utils.deprecate import set_new_attribute_deprecated
if TYPE_CHECKING: if TYPE_CHECKING:
from telegram import Bot from telegram import Bot
@ -50,7 +49,7 @@ class JobQueue:
""" """
__slots__ = ('_dispatcher', 'logger', 'scheduler', '__dict__') __slots__ = ('_dispatcher', 'logger', 'scheduler')
def __init__(self) -> None: def __init__(self) -> None:
self._dispatcher: 'Dispatcher' = None # type: ignore[assignment] self._dispatcher: 'Dispatcher' = None # type: ignore[assignment]
@ -67,9 +66,6 @@ class JobQueue:
logging.getLogger('apscheduler.executors.default').addFilter(aps_log_filter) logging.getLogger('apscheduler.executors.default').addFilter(aps_log_filter)
self.scheduler.add_listener(self._dispatch_error, EVENT_JOB_ERROR) 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']]: def _build_args(self, job: 'Job') -> List[Union[CallbackContext, 'Bot', 'Job']]:
if self._dispatcher.use_context: if self._dispatcher.use_context:
return [self._dispatcher.context_types.context.from_job(job, self._dispatcher)] return [self._dispatcher.context_types.context.from_job(job, self._dispatcher)]
@ -560,7 +556,6 @@ class Job:
'_removed', '_removed',
'_enabled', '_enabled',
'job', 'job',
'__dict__',
) )
def __init__( def __init__(
@ -582,9 +577,6 @@ class Job:
self.job = cast(APSJob, job) # skipcq: PTC-W0052 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: def run(self, dispatcher: 'Dispatcher') -> None:
"""Executes the callback function independently of the jobs schedule.""" """Executes the callback function independently of the jobs schedule."""
try: try:

View file

@ -42,7 +42,7 @@ from typing import (
from telegram import Bot, TelegramError from telegram import Bot, TelegramError
from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized from telegram.error import InvalidToken, RetryAfter, TimedOut, Unauthorized
from telegram.ext import Dispatcher, JobQueue, ContextTypes, ExtBot 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.helpers import get_signal_name, DEFAULT_FALSE, DefaultValue
from telegram.utils.request import Request from telegram.utils.request import Request
from telegram.ext.utils.types import CCT, UD, CD, BD from telegram.ext.utils.types import CCT, UD, CD, BD
@ -149,7 +149,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
'httpd', 'httpd',
'__lock', '__lock',
'__threads', '__threads',
'__dict__',
) )
@overload @overload
@ -328,14 +327,6 @@ class Updater(Generic[CCT, UD, CD, BD]):
self.__lock = Lock() self.__lock = Lock()
self.__threads: List[Thread] = [] 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: def _init_thread(self, target: Callable, name: str, *args: object, **kwargs: object) -> None:
thr = Thread( thr = Thread(
target=self._thread_wrapper, target=self._thread_wrapper,

View file

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

View file

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

View file

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

View file

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

View file

@ -71,7 +71,6 @@ class ChatPhoto(TelegramObject):
'small_file_id', 'small_file_id',
'small_file_unique_id', 'small_file_unique_id',
'big_file_id', 'big_file_id',
'_id_attrs',
) )
def __init__( 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__( def __init__(
self, self,

View file

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

View file

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

View file

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

View file

@ -63,7 +63,6 @@ class Location(TelegramObject):
'live_period', 'live_period',
'latitude', 'latitude',
'heading', 'heading',
'_id_attrs',
) )
def __init__( 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__( def __init__(
self, self,

View file

@ -97,7 +97,6 @@ class Sticker(TelegramObject):
'height', 'height',
'file_unique_id', 'file_unique_id',
'emoji', 'emoji',
'_id_attrs',
) )
def __init__( def __init__(
@ -208,7 +207,6 @@ class StickerSet(TelegramObject):
'title', 'title',
'stickers', 'stickers',
'name', 'name',
'_id_attrs',
) )
def __init__( 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 FOREHEAD: ClassVar[str] = constants.STICKER_FOREHEAD
""":const:`telegram.constants.STICKER_FOREHEAD`""" """:const:`telegram.constants.STICKER_FOREHEAD`"""

View file

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

View file

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

View file

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

View file

@ -65,7 +65,6 @@ class Voice(TelegramObject):
'duration', 'duration',
'mime_type', 'mime_type',
'file_unique_id', 'file_unique_id',
'_id_attrs',
) )
def __init__( 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__( def __init__(
self, self,

View file

@ -74,7 +74,6 @@ class Game(TelegramObject):
'text_entities', 'text_entities',
'text', 'text',
'animation', 'animation',
'_id_attrs',
) )
def __init__( 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): def __init__(self, position: int, user: User, score: int):
self.position = position self.position = position

View file

@ -121,7 +121,6 @@ class InlineKeyboardButton(TelegramObject):
'pay', 'pay',
'switch_inline_query', 'switch_inline_query',
'text', 'text',
'_id_attrs',
'login_url', '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): def __init__(self, inline_keyboard: List[List[InlineKeyboardButton]], **_kwargs: Any):
# Required # 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__( def __init__(
self, 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): def __init__(self, type: str, id: str, **_kwargs: Any):
# Required # 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__( def __init__(
self, self,

View file

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

View file

@ -60,7 +60,7 @@ class InputLocationMessageContent(InputMessageContent):
""" """
__slots__ = ('longitude', 'horizontal_accuracy', 'proximity_alert_radius', 'live_period', __slots__ = ('longitude', 'horizontal_accuracy', 'proximity_alert_radius', 'live_period',
'latitude', 'heading', '_id_attrs') 'latitude', 'heading')
# fmt: on # fmt: on
def __init__( 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__( def __init__(
self, self,

View file

@ -69,7 +69,6 @@ class InputVenueMessageContent(InputMessageContent):
'foursquare_type', 'foursquare_type',
'google_place_id', 'google_place_id',
'latitude', 'latitude',
'_id_attrs',
) )
def __init__( 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__( def __init__(
self, self,

View file

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

View file

@ -412,7 +412,6 @@ class Message(TelegramObject):
'voice_chat_scheduled', 'voice_chat_scheduled',
'is_automatic_forward', 'is_automatic_forward',
'has_protected_content', 'has_protected_content',
'_id_attrs',
) )
ATTACHMENT_TYPES: ClassVar[List[str]] = [ 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__( def __init__(
self, 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__( def __init__(
self, self,

View file

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

View file

@ -21,13 +21,12 @@
from typing import ClassVar from typing import ClassVar
from telegram import constants from telegram import constants
from telegram.utils.deprecate import set_new_attribute_deprecated
class ParseMode: class ParseMode:
"""This object represents a Telegram Message Parse Modes.""" """This object represents a Telegram Message Parse Modes."""
__slots__ = ('__dict__',) __slots__ = ()
MARKDOWN: ClassVar[str] = constants.PARSEMODE_MARKDOWN MARKDOWN: ClassVar[str] = constants.PARSEMODE_MARKDOWN
""":const:`telegram.constants.PARSEMODE_MARKDOWN`\n """:const:`telegram.constants.PARSEMODE_MARKDOWN`\n
@ -40,6 +39,3 @@ class ParseMode:
""":const:`telegram.constants.PARSEMODE_MARKDOWN_V2`""" """:const:`telegram.constants.PARSEMODE_MARKDOWN_V2`"""
HTML: ClassVar[str] = constants.PARSEMODE_HTML HTML: ClassVar[str] = constants.PARSEMODE_HTML
""":const:`telegram.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', 'secret',
'bot', 'bot',
'data', 'data',
'_id_attrs',
'_decrypted_secret', '_decrypted_secret',
'_decrypted_data', '_decrypted_data',
) )

View file

@ -130,7 +130,6 @@ class EncryptedPassportElement(TelegramObject):
'reverse_side', 'reverse_side',
'front_side', 'front_side',
'data', 'data',
'_id_attrs',
) )
def __init__( 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__( def __init__(
self, 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. # 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): def __init__(self, source: str, type: str, message: str, **_kwargs: Any):
# Required # Required

View file

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

View file

@ -59,7 +59,6 @@ class Invoice(TelegramObject):
'title', 'title',
'description', 'description',
'total_amount', 'total_amount',
'_id_attrs',
) )
def __init__( 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): def __init__(self, label: str, amount: int, **_kwargs: Any):
self.label = label 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__( def __init__(
self, self,

View file

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

View file

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

View file

@ -46,7 +46,7 @@ class ShippingOption(TelegramObject):
""" """
__slots__ = ('prices', 'title', 'id', '_id_attrs') __slots__ = ('prices', 'title', 'id')
def __init__( def __init__(
self, 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__( def __init__(
self, self,

View file

@ -70,7 +70,6 @@ class SuccessfulPayment(TelegramObject):
'telegram_payment_charge_id', 'telegram_payment_charge_id',
'provider_payment_charge_id', 'provider_payment_charge_id',
'total_amount', 'total_amount',
'_id_attrs',
) )
def __init__( 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): def __init__(self, text: str, voter_count: int, **_kwargs: Any):
self.text = text 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): def __init__(self, poll_id: str, user: User, option_ids: List[int], **_kwargs: Any):
self.poll_id = poll_id self.poll_id = poll_id
@ -164,7 +164,6 @@ class Poll(TelegramObject):
'explanation', 'explanation',
'question', 'question',
'correct_option_id', 'correct_option_id',
'_id_attrs',
) )
def __init__( 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): def __init__(self, traveler: User, watcher: User, distance: int, **_kwargs: Any):
self.traveler = traveler self.traveler = traveler

View file

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

View file

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

View file

@ -108,7 +108,6 @@ class User(TelegramObject):
'id', 'id',
'bot', 'bot',
'language_code', 'language_code',
'_id_attrs',
) )
def __init__( 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): def __init__(self, total_count: int, photos: List[List[PhotoSize]], **_kwargs: Any):
# Required # Required

View file

@ -16,9 +16,7 @@
# #
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module facilitates the deprecation of functions.""" """This module contains a class which is used for deprecation warnings."""
import warnings
# We use our own DeprecationWarning since they are muted by default and "UserWarning" makes it # 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.""" """Custom warning class for deprecations in this library."""
__slots__ = () __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): def __init__(self, value: DVType = None):
self.value = value self.value = value

View file

@ -70,7 +70,6 @@ from telegram.error import (
Unauthorized, Unauthorized,
) )
from telegram.utils.types import JSONDict 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 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__( def __init__(
self, self,
@ -192,9 +191,6 @@ class Request:
self._con_pool = mgr self._con_pool = mgr
def __setattr__(self, key: str, value: object) -> None:
set_new_attribute_deprecated(self, key, value)
@property @property
def con_pool_size(self) -> int: def con_pool_size(self) -> int:
"""The size of the connection pool used.""" """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: def __init__(self, duration: int, **_kwargs: Any) -> None:
self.duration = int(duration) if duration is not None else 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: def __init__(self, users: List[User], **_kwargs: Any) -> None:
self.users = users 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: def __init__(self, start_date: dtm.datetime, **_kwargs: Any) -> None:
self.start_date = start_date self.start_date = start_date

View file

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

View file

@ -44,6 +44,7 @@ from telegram import (
ChosenInlineResult, ChosenInlineResult,
File, File,
ChatPermissions, ChatPermissions,
Bot,
) )
from telegram.ext import ( from telegram.ext import (
Dispatcher, Dispatcher,
@ -56,6 +57,7 @@ from telegram.ext import (
) )
from telegram.error import BadRequest from telegram.error import BadRequest
from telegram.utils.helpers import DefaultValue, DEFAULT_NONE from telegram.utils.helpers import DefaultValue, DEFAULT_NONE
from telegram.utils.request import Request
from tests.bots import get_bot from tests.bots import get_bot
@ -89,14 +91,22 @@ def bot_info():
return get_bot() 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') @pytest.fixture(scope='session')
def bot(bot_info): def bot(bot_info):
class DictExtBot( return DictExtBot(bot_info['token'], private_key=PRIVATE_KEY, request=DictRequest())
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)
DEFAULT_BOTS = {} 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 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})?') CMD_PATTERN = re.compile(r'/[\da-z_]{1,32}(?:@\w{1,32})?')
@ -361,9 +371,9 @@ def mro_slots():
return [ return [
attr attr
for cls in _class.__class__.__mro__[:-1] 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__ for attr in cls.__slots__
if attr != '__dict__' if attr != '__dict__' # left here for classes which still has __dict__
] ]
return _mro_slots return _mro_slots

View file

@ -57,13 +57,10 @@ class TestAnimation:
file_size = 4127 file_size = 4127
caption = "Test *animation*" 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__: for attr in animation.__slots__:
assert getattr(animation, attr, 'err') != 'err', f"got extra slot '{attr}'" 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" 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): def test_creation(self, animation):
assert isinstance(animation, Animation) assert isinstance(animation, Animation)

View file

@ -59,13 +59,10 @@ class TestAudio:
audio_file_id = '5a3128a4d2a04750b5b58397f3b5e812' audio_file_id = '5a3128a4d2a04750b5b58397f3b5e812'
audio_file_unique_id = 'adc3145fd2e84d95b64d68eaa22aa33e' 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__: for attr in audio.__slots__:
assert getattr(audio, attr, 'err') != 'err', f"got extra slot '{attr}'" 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" 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): def test_creation(self, audio):
# Make sure file has been uploaded. # Make sure file has been uploaded.

View file

@ -145,20 +145,10 @@ class TestBot:
""" """
@pytest.mark.parametrize('inst', ['bot', "default_bot"], indirect=True) @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__: for attr in inst.__slots__:
assert getattr(inst, attr, 'err') != 'err', f"got extra slot '{attr}'" 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" 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( @pytest.mark.parametrize(
'token', 'token',

View file

@ -31,13 +31,10 @@ class TestBotCommand:
command = 'start' command = 'start'
description = 'A command' 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__: for attr in bot_command.__slots__:
assert getattr(bot_command, attr, 'err') != 'err', f"got extra slot '{attr}'" 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" 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): def test_de_json(self, bot):
json_dict = {'command': self.command, 'description': self.description} 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 # All the scope types are very similar, so we test everything via parametrization
class TestBotCommandScope: 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__: for attr in bot_command_scope.__slots__:
assert getattr(bot_command_scope, attr, 'err') != 'err', f"got extra slot '{attr}'" 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( assert len(mro_slots(bot_command_scope)) == len(
set(mro_slots(bot_command_scope)) set(mro_slots(bot_command_scope))
), "duplicate slot" ), "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): def test_de_json(self, bot, scope_class_and_type, chat_id):
cls = scope_class_and_type[0] cls = scope_class_and_type[0]

View file

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

View file

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

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