Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,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:
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,4 @@ repos:
- id: pyupgrade
files: ^(telegram|examples|tests)/.*\.py$
args:
- --py36-plus
- --py37-plus
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
31 changes: 19 additions & 12 deletions telegram/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@
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
Expand All @@ -37,22 +36,28 @@
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())

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()
Expand All @@ -76,7 +81,7 @@ def de_json(cls: Type[TO], data: Optional[JSONDict], bot: 'Bot') -> Optional[TO]

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]]:
Expand Down Expand Up @@ -132,6 +137,7 @@ def to_dict(self) -> JSONDict:
return data

def __eq__(self, other: object) -> bool:
# pylint: disable=no-member
if isinstance(other, self.__class__):
if self._id_attrs == ():
warnings.warn(
Expand All @@ -144,9 +150,10 @@ def __eq__(self, other: object) -> bool:
" 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__()
8 changes: 0 additions & 8 deletions telegram/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,6 @@ def __init__(
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]:
Expand Down
2 changes: 1 addition & 1 deletion telegram/botcommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion telegram/botcommandscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`"""
Expand Down
1 change: 0 additions & 1 deletion telegram/callbackquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ class CallbackQuery(TelegramObject):
'from_user',
'inline_message_id',
'data',
'_id_attrs',
)

def __init__(
Expand Down
1 change: 0 additions & 1 deletion telegram/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ class Chat(TelegramObject):
'linked_chat_id',
'all_members_are_administrators',
'message_auto_delete_time',
'_id_attrs',
)

SENDER: ClassVar[str] = constants.CHAT_SENDER
Expand Down
6 changes: 1 addition & 5 deletions telegram/chataction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -65,6 +64,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)
1 change: 0 additions & 1 deletion telegram/chatinvitelink.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ class ChatInviteLink(TelegramObject):
'is_revoked',
'expire_date',
'member_limit',
'_id_attrs',
)

def __init__(
Expand Down
2 changes: 1 addition & 1 deletion telegram/chatlocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ChatLocation(TelegramObject):

"""

__slots__ = ('location', '_id_attrs', 'address')
__slots__ = ('location', 'address')

def __init__(
self,
Expand Down
1 change: 0 additions & 1 deletion telegram/chatmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,6 @@ class ChatMember(TelegramObject):
'can_manage_chat',
'can_manage_voice_chats',
'until_date',
'_id_attrs',
)

ADMINISTRATOR: ClassVar[str] = constants.CHATMEMBER_ADMINISTRATOR
Expand Down
1 change: 0 additions & 1 deletion telegram/chatmemberupdated.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ class ChatMemberUpdated(TelegramObject):
'old_chat_member',
'new_chat_member',
'invite_link',
'_id_attrs',
)

def __init__(
Expand Down
1 change: 0 additions & 1 deletion telegram/chatpermissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion telegram/choseninlineresult.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion telegram/dice.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion telegram/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
12 changes: 0 additions & 12 deletions telegram/ext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,17 +27,6 @@
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
Expand Down
48 changes: 14 additions & 34 deletions telegram/ext/basepersistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -169,15 +159,15 @@ def update_callback_data_replace_bot(data: CDCData) -> None:
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__(
Expand All @@ -188,16 +178,6 @@ def __init__(

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.

Expand Down
1 change: 0 additions & 1 deletion telegram/ext/conversationhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@


class _ConversationTimeoutContext:
# '__dict__' is not included since this a private class
__slots__ = ('conversation_key', 'update', 'dispatcher', 'callback_context')

def __init__(
Expand Down
5 changes: 0 additions & 5 deletions telegram/ext/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

import pytz

from telegram.utils.deprecate import set_new_attribute_deprecated
from telegram.utils.helpers import DEFAULT_NONE
from telegram.utils.types import ODVInput

Expand Down Expand Up @@ -67,7 +66,6 @@ class Defaults:
'_allow_sending_without_reply',
'_parse_mode',
'_api_defaults',
'__dict__',
)

def __init__(
Expand Down Expand Up @@ -108,9 +106,6 @@ def __init__(
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
Expand Down
Loading