Skip to content

Commit

Permalink
Freeze Classes Without Arguments (#3453)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bibo-Joshi committed Jan 1, 2023
1 parent f3a9b74 commit 7b61a30
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 89 deletions.
10 changes: 10 additions & 0 deletions telegram/_forumtopic.py
Expand Up @@ -122,6 +122,11 @@ class ForumTopicClosed(TelegramObject):

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict = None) -> None:
super().__init__(api_kwargs=api_kwargs)

self._freeze()


class ForumTopicReopened(TelegramObject):
"""
Expand All @@ -132,3 +137,8 @@ class ForumTopicReopened(TelegramObject):
"""

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict = None) -> None:
super().__init__(api_kwargs=api_kwargs)

self._freeze()
6 changes: 6 additions & 0 deletions telegram/_games/callbackgame.py
Expand Up @@ -19,9 +19,15 @@
"""This module contains an object that represents a Telegram CallbackGame."""

from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict


class CallbackGame(TelegramObject):
"""A placeholder, currently holds no information. Use BotFather to set up your game."""

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict = None) -> None:
super().__init__(api_kwargs=api_kwargs)

self._freeze()
18 changes: 8 additions & 10 deletions telegram/_inline/inputcontactmessagecontent.py
Expand Up @@ -56,14 +56,12 @@ def __init__(
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
with self._unfrozen():
# Required
self.phone_number = phone_number
self.first_name = first_name
# Optionals
self.last_name = last_name
self.vcard = vcard

# Required
self.phone_number = phone_number
self.first_name = first_name
# Optionals
self.last_name = last_name
self.vcard = vcard

self._id_attrs = (self.phone_number,)

self._freeze()
self._id_attrs = (self.phone_number,)
65 changes: 32 additions & 33 deletions telegram/_inline/inputinvoicemessagecontent.py
Expand Up @@ -200,39 +200,38 @@ def __init__(
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.title = title
self.description = description
self.payload = payload
self.provider_token = provider_token
self.currency = currency
self.prices = parse_sequence_arg(prices)
# Optionals
self.max_tip_amount = max_tip_amount
self.suggested_tip_amounts = parse_sequence_arg(suggested_tip_amounts)
self.provider_data = provider_data
self.photo_url = photo_url
self.photo_size = photo_size
self.photo_width = photo_width
self.photo_height = photo_height
self.need_name = need_name
self.need_phone_number = need_phone_number
self.need_email = need_email
self.need_shipping_address = need_shipping_address
self.send_phone_number_to_provider = send_phone_number_to_provider
self.send_email_to_provider = send_email_to_provider
self.is_flexible = is_flexible

self._id_attrs = (
self.title,
self.description,
self.payload,
self.provider_token,
self.currency,
self.prices,
)

self._freeze()
with self._unfrozen():
# Required
self.title = title
self.description = description
self.payload = payload
self.provider_token = provider_token
self.currency = currency
self.prices = parse_sequence_arg(prices)
# Optionals
self.max_tip_amount = max_tip_amount
self.suggested_tip_amounts = parse_sequence_arg(suggested_tip_amounts)
self.provider_data = provider_data
self.photo_url = photo_url
self.photo_size = photo_size
self.photo_width = photo_width
self.photo_height = photo_height
self.need_name = need_name
self.need_phone_number = need_phone_number
self.need_email = need_email
self.need_shipping_address = need_shipping_address
self.send_phone_number_to_provider = send_phone_number_to_provider
self.send_email_to_provider = send_email_to_provider
self.is_flexible = is_flexible

self._id_attrs = (
self.title,
self.description,
self.payload,
self.provider_token,
self.currency,
self.prices,
)

@classmethod
def de_json(
Expand Down
29 changes: 14 additions & 15 deletions telegram/_inline/inputlocationmessagecontent.py
Expand Up @@ -83,21 +83,20 @@ def __init__(
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.latitude = latitude
self.longitude = longitude

# Optionals
self.live_period = live_period
self.horizontal_accuracy = horizontal_accuracy
self.heading = heading
self.proximity_alert_radius = (
int(proximity_alert_radius) if proximity_alert_radius else None
)

self._id_attrs = (self.latitude, self.longitude)

self._freeze()
with self._unfrozen():
# Required
self.latitude = latitude
self.longitude = longitude

# Optionals
self.live_period = live_period
self.horizontal_accuracy = horizontal_accuracy
self.heading = heading
self.proximity_alert_radius = (
int(proximity_alert_radius) if proximity_alert_radius else None
)

self._id_attrs = (self.latitude, self.longitude)

HORIZONTAL_ACCURACY: ClassVar[int] = constants.LocationLimit.HORIZONTAL_ACCURACY
""":const:`telegram.constants.LocationLimit.HORIZONTAL_ACCURACY`
Expand Down
6 changes: 6 additions & 0 deletions telegram/_inline/inputmessagecontent.py
Expand Up @@ -19,6 +19,7 @@
"""This module contains the classes that represent Telegram InputMessageContent."""

from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict


class InputMessageContent(TelegramObject):
Expand All @@ -32,3 +33,8 @@ class InputMessageContent(TelegramObject):
"""

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict = None) -> None:
super().__init__(api_kwargs=api_kwargs)

self._freeze()
19 changes: 9 additions & 10 deletions telegram/_inline/inputtextmessagecontent.py
Expand Up @@ -78,13 +78,12 @@ def __init__(
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.message_text = message_text
# Optionals
self.parse_mode = parse_mode
self.entities = parse_sequence_arg(entities)
self.disable_web_page_preview = disable_web_page_preview

self._id_attrs = (self.message_text,)

self._freeze()
with self._unfrozen():
# Required
self.message_text = message_text
# Optionals
self.parse_mode = parse_mode
self.entities = parse_sequence_arg(entities)
self.disable_web_page_preview = disable_web_page_preview

self._id_attrs = (self.message_text,)
34 changes: 16 additions & 18 deletions telegram/_inline/inputvenuemessagecontent.py
Expand Up @@ -84,22 +84,20 @@ def __init__(
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
with self._unfrozen():
# Required
self.latitude = latitude
self.longitude = longitude
self.title = title
self.address = address
# Optionals
self.foursquare_id = foursquare_id
self.foursquare_type = foursquare_type
self.google_place_id = google_place_id
self.google_place_type = google_place_type

# Required
self.latitude = latitude
self.longitude = longitude
self.title = title
self.address = address
# Optionals
self.foursquare_id = foursquare_id
self.foursquare_type = foursquare_type
self.google_place_id = google_place_id
self.google_place_type = google_place_type

self._id_attrs = (
self.latitude,
self.longitude,
self.title,
)

self._freeze()
self._id_attrs = (
self.latitude,
self.longitude,
self.title,
)
4 changes: 4 additions & 0 deletions telegram/_telegramobject.py
Expand Up @@ -92,6 +92,10 @@ class TelegramObject:
__INIT_PARAMS_CHECK: Optional[Type["TelegramObject"]] = None

def __init__(self, *, api_kwargs: JSONDict = None) -> None:
# Setting _frozen to `False` here means that classes without arguments still need to
# implement __init__. However, with `True` would mean increased usage of
# `with self._unfrozen()` in the `__init__` of subclasses and we have fewer empty
# classes than classes with arguments.
self._frozen: bool = False
self._id_attrs: Tuple[object, ...] = ()
self._bot: Optional["Bot"] = None
Expand Down
5 changes: 5 additions & 0 deletions telegram/_videochat.py
Expand Up @@ -42,6 +42,11 @@ class VideoChatStarted(TelegramObject):

__slots__ = ()

def __init__(self, *, api_kwargs: JSONDict = None) -> None:
super().__init__(api_kwargs=api_kwargs)

self._freeze()


class VideoChatEnded(TelegramObject):
"""
Expand Down
1 change: 1 addition & 0 deletions tests/test_bot.py
Expand Up @@ -185,6 +185,7 @@ def __init__(
api_kwargs=None,
):
super().__init__(api_kwargs=api_kwargs)
self._unfreeze()
self.message_text = message_text
self.disable_web_page_preview = disable_web_page_preview

Expand Down
14 changes: 11 additions & 3 deletions tests/test_telegramobject.py
Expand Up @@ -428,7 +428,8 @@ def __init__(self, api_kwargs=None):

@pytest.mark.parametrize("cls", TO_SUBCLASSES, ids=[cls.__name__ for cls in TO_SUBCLASSES])
def test_subclasses_are_frozen(self, cls):
if cls.__name__.startswith("_"):
if cls is TelegramObject or cls.__name__.startswith("_"):
# Protected classes don't need to be frozen and neither does the base class
return

# instantiating each subclass would be tedious as some attributes require special init
Expand All @@ -437,10 +438,17 @@ def test_subclasses_are_frozen(self, cls):
source_file = inspect.getsourcefile(cls.__init__)
parents = Path(source_file).parents
is_test_file = Path(__file__).parent.resolve() in parents
if is_test_file or source_file.endswith("telegramobject.py"):
# classes without their own `__init__` can be ignored

if is_test_file:
# If the class is defined in a test file, we don't want to test it.
return

if source_file.endswith("telegramobject.py"):
pytest.fail(
f"{cls.__name__} does not have its own `__init__` "
"and can therefore not be frozen correctly"
)

source_lines, first_line = inspect.getsourcelines(cls.__init__)

# We use regex matching since a simple "if self._freeze() in source_lines[-1]" would also
Expand Down

0 comments on commit 7b61a30

Please sign in to comment.