8000 Remove `__dict__` from `__slots__` by harshil21 · Pull Request #2619 · python-telegram-bot/python-telegram-bot · GitHub
[go: up one dir, main page]

Skip to content

Remove __dict__ from __slots__ #2619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Aug 19, 2021
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
10000
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
341A
Original file line number Diff line numberDiff 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
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
10000
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
0