From 1c510e3721819151c7854f6da53c8d809547d796 Mon Sep 17 00:00:00 2001 From: aelkheir Date: Mon, 1 Apr 2024 18:07:48 +0300 Subject: [PATCH 01/15] Add `BusinessIntro` class and `Chat.business_intro`. --- telegram/__init__.py | 3 ++- telegram/_business.py | 57 +++++++++++++++++++++++++++++++++++++++++++ telegram/_chat.py | 17 +++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index 6b9d513b361..1dc7d4f954f 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -36,6 +36,7 @@ "BotName", "BotShortDescription", "BusinessConnection", + "BusinessIntro", "BusinessMessagesDeleted", "CallbackGame", "CallbackQuery", @@ -240,7 +241,7 @@ ) from ._botdescription import BotDescription, BotShortDescription from ._botname import BotName -from ._business import BusinessConnection, BusinessMessagesDeleted +from ._business import BusinessConnection, BusinessIntro, BusinessMessagesDeleted from ._callbackquery import CallbackQuery from ._chat import Chat from ._chatadministratorrights import ChatAdministratorRights diff --git a/telegram/_business.py b/telegram/_business.py index e7ec28855b9..e027a9895f0 100644 --- a/telegram/_business.py +++ b/telegram/_business.py @@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Optional, Sequence from telegram._chat import Chat +from telegram._files.sticker import Sticker from telegram._telegramobject import TelegramObject from telegram._user import User from telegram._utils.argumentparsing import parse_sequence_arg @@ -183,3 +184,59 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessMess data["chat"] = Chat.de_json(data.get("chat"), bot) return super().de_json(data=data, bot=bot) + + +class BusinessIntro(TelegramObject): + """ + This object represents the intro of a business account. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal, if their + :attr:`title`, :attr:`message` and :attr:`sticker` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + title (:obj:`str`): Optional. Title text of the business intro. + message (:obj:`str`): Optional. Message text of the business intro. + sticker (:class:`telegram.Sticker`): Optional. Sticker of the business intro. + + Attributes: + title (:obj:`str`): Optional. Title text of the business intro. + message (:obj:`str`): Optional. Message text of the business intro. + sticker (:class:`telegram.Sticker`): Optional. Sticker of the business intro. + + """ + + __slots__ = ( + "message", + "sticker", + "title", + ) + + def __init__( + self, + title: Optional[str] = None, + message: Optional[str] = None, + sticker: Optional[Sticker] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.title: Optional[str] = title + self.message: Optional[str] = message + self.sticker: Optional[Sticker] = sticker + + self._id_attrs = (self.title, self.message, self.sticker) + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessIntro"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["sticker"] = Sticker.de_json(data.get("sticker"), bot) + + return super().de_json(data=data, bot=bot) diff --git a/telegram/_chat.py b/telegram/_chat.py index 741b0650d0c..2a776a1bff0 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -44,6 +44,7 @@ Animation, Audio, Bot, + BusinessIntro, ChatInviteLink, ChatMember, Contact, @@ -169,6 +170,11 @@ class Chat(TelegramObject): only in :meth:`telegram.Bot.get_chat`. .. versionadded:: 20.0 + business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with + business accounts, the intro of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + + .. versionadded:: NEXT.VERSION available_reactions (Sequence[:class:`telegram.ReactionType`], optional): List of available reactions allowed in the chat. If omitted, then all of :const:`telegram.constants.ReactionEmoji` are allowed. Returned only in @@ -312,6 +318,11 @@ class Chat(TelegramObject): obtained via :meth:`~telegram.Bot.get_chat`. .. versionadded:: 20.0 + business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with + business accounts, the intro of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + + .. versionadded:: NEXT.VERSION available_reactions (Tuple[:class:`telegram.ReactionType`]): Optional. List of available reactions allowed in the chat. If omitted, then all of :const:`telegram.constants.ReactionEmoji` are allowed. Returned only in @@ -383,6 +394,7 @@ class Chat(TelegramObject): "available_reactions", "background_custom_emoji_id", "bio", + "business_intro", "can_set_sticker_set", "custom_emoji_sticker_set_name", "description", @@ -470,6 +482,7 @@ def __init__( has_visible_history: Optional[bool] = None, unrestrict_boost_count: Optional[int] = None, custom_emoji_sticker_set_name: Optional[str] = None, + business_intro: Optional["BusinessIntro"] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -519,6 +532,7 @@ def __init__( self.profile_background_custom_emoji_id: Optional[str] = profile_background_custom_emoji_id self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name + self.business_intro: Optional["BusinessIntro"] = business_intro self._id_attrs = (self.id,) @@ -587,6 +601,9 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Chat"]: data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot) data["location"] = ChatLocation.de_json(data.get("location"), bot) data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot) + from telegram import BusinessIntro # pylint: disable=import-outside-toplevel + + data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot) api_kwargs = {} # This is a deprecated field that TG still returns for backwards compatibility From 0a9a6dd5cd135bb1b8cf538421ffaf46865148bf Mon Sep 17 00:00:00 2001 From: aelkheir Date: Mon, 1 Apr 2024 18:40:47 +0300 Subject: [PATCH 02/15] Add `business_intro` tests. --- tests/test_business.py | 61 +++++++++++++++++++++++++++++++++++++++++- tests/test_chat.py | 6 +++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/tests/test_business.py b/tests/test_business.py index 5514847a753..0651f1c0291 100644 --- a/tests/test_business.py +++ b/tests/test_business.py @@ -20,7 +20,14 @@ import pytest -from telegram import BusinessConnection, BusinessMessagesDeleted, Chat, User +from telegram import ( + BusinessConnection, + BusinessIntro, + BusinessMessagesDeleted, + Chat, + Sticker, + User, +) from telegram._utils.datetime import UTC, to_timestamp from tests.auxil.slots import mro_slots @@ -35,6 +42,9 @@ class TestBusinessBase: message_ids = (123, 321) business_connection_id = "123" chat = Chat(123, "test_chat") + title = "Business Title" + message = "Business description" + sticker = Sticker("sticker_id", "unique_id", 50, 50, True, False, Sticker.REGULAR) @pytest.fixture(scope="module") @@ -58,6 +68,15 @@ def business_messages_deleted(): ) +@pytest.fixture(scope="module") +def business_intro(): + return BusinessIntro( + TestBusinessBase.title, + TestBusinessBase.message, + TestBusinessBase.sticker, + ) + + class TestBusinessConnectionWithoutRequest(TestBusinessBase): def test_slots(self, business_connection): bc = business_connection @@ -170,3 +189,43 @@ def test_equality(self): assert bmd1 != bmd3 assert hash(bmd1) != hash(bmd3) + + +class TestBusinessIntro(TestBusinessBase): + def test_slots(self, business_intro): + intro = business_intro + for attr in intro.__slots__: + assert getattr(intro, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(intro)) == len(set(mro_slots(intro))), "duplicate slot" + + def test_to_dict(self, business_intro): + intro_dict = business_intro.to_dict() + assert isinstance(intro_dict, dict) + assert intro_dict["title"] == self.title + assert intro_dict["message"] == self.message + assert intro_dict["sticker"] == self.sticker.to_dict() + + def test_de_json(self): + json_dict = { + "title": self.title, + "message": self.message, + "sticker": self.sticker.to_dict(), + } + intro = BusinessIntro.de_json(json_dict, None) + assert intro.title == self.title + assert intro.message == self.message + assert intro.sticker == self.sticker + assert intro.api_kwargs == {} + assert isinstance(intro, BusinessIntro) + + def test_equality(self): + intro1 = BusinessIntro(self.title, self.message, self.sticker) + intro2 = BusinessIntro(self.title, self.message, self.sticker) + intro3 = BusinessIntro("Other Business", self.message, self.sticker) + + assert intro1 == intro2 + assert hash(intro1) == hash(intro2) + assert intro1 is not intro2 + + assert intro1 != intro3 + assert hash(intro1) != hash(intro3) diff --git a/tests/test_chat.py b/tests/test_chat.py index 7db747146a8..f8d4c7ef5e2 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -22,6 +22,7 @@ from telegram import ( Bot, + BusinessIntro, Chat, ChatLocation, ChatPermissions, @@ -74,6 +75,7 @@ def chat(bot): profile_background_custom_emoji_id=TestChatBase.profile_background_custom_emoji_id, unrestrict_boost_count=TestChatBase.unrestrict_boost_count, custom_emoji_sticker_set_name=TestChatBase.custom_emoji_sticker_set_name, + business_intro=TestChatBase.business_intro, ) chat.set_bot(bot) chat._unfreeze() @@ -113,6 +115,7 @@ class TestChatBase: ReactionTypeEmoji(ReactionEmoji.THUMBS_DOWN), ReactionTypeCustomEmoji("custom_emoji_id"), ] + business_intro = BusinessIntro("Title", "Description", None) accent_color_id = 1 background_custom_emoji_id = "background_custom_emoji_id" profile_accent_color_id = 2 @@ -139,6 +142,7 @@ def test_de_json(self, bot): "permissions": self.permissions.to_dict(), "slow_mode_delay": self.slow_mode_delay, "bio": self.bio, + "business_intro": self.business_intro.to_dict(), "has_protected_content": self.has_protected_content, "has_visible_history": self.has_visible_history, "has_private_forwards": self.has_private_forwards, @@ -174,6 +178,7 @@ def test_de_json(self, bot): assert chat.permissions == self.permissions assert chat.slow_mode_delay == self.slow_mode_delay assert chat.bio == self.bio + assert chat.business_intro == self.business_intro assert chat.has_protected_content == self.has_protected_content assert chat.has_visible_history == self.has_visible_history assert chat.has_private_forwards == self.has_private_forwards @@ -234,6 +239,7 @@ def test_to_dict(self, chat): assert chat_dict["permissions"] == chat.permissions.to_dict() assert chat_dict["slow_mode_delay"] == chat.slow_mode_delay assert chat_dict["bio"] == chat.bio + assert chat_dict["business_intro"] == chat.business_intro.to_dict() assert chat_dict["has_private_forwards"] == chat.has_private_forwards assert chat_dict["has_protected_content"] == chat.has_protected_content assert chat_dict["has_visible_history"] == chat.has_visible_history From 1a5259a474eaa16da8a8ebd53b2d79a1b4a8f77e Mon Sep 17 00:00:00 2001 From: aelkheir Date: Mon, 1 Apr 2024 18:42:16 +0300 Subject: [PATCH 03/15] Add toc entry for `BusinessIntro`. --- docs/source/telegram.at-tree.rst | 1 + docs/source/telegram.businessintro.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docs/source/telegram.businessintro.rst diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 19b9831cc76..2d333f5a275 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -19,6 +19,7 @@ Available Types telegram.botname telegram.botshortdescription telegram.businessconnection + telegram.businessintro telegram.businessmessagesdeleted telegram.callbackquery telegram.chat diff --git a/docs/source/telegram.businessintro.rst b/docs/source/telegram.businessintro.rst new file mode 100644 index 00000000000..4870258e5b4 --- /dev/null +++ b/docs/source/telegram.businessintro.rst @@ -0,0 +1,6 @@ +BusinessIntro +================== + +.. autoclass:: telegram.BusinessIntro + :members: + :show-inheritance: \ No newline at end of file From 11e1cb283e572bc3952c8efc4273c6a82e220c47 Mon Sep 17 00:00:00 2001 From: aelkheir Date: Tue, 2 Apr 2024 14:18:21 +0300 Subject: [PATCH 04/15] Add `BusinessLocation` and `Chat.business_location`. --- telegram/__init__.py | 3 ++- telegram/_business.py | 52 +++++++++++++++++++++++++++++++++++++++++++ telegram/_chat.py | 22 ++++++++++++++++-- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index 1dc7d4f954f..f204f7ee6fd 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -37,6 +37,7 @@ "BotShortDescription", "BusinessConnection", "BusinessIntro", + "BusinessLocation", "BusinessMessagesDeleted", "CallbackGame", "CallbackQuery", @@ -241,7 +242,7 @@ ) from ._botdescription import BotDescription, BotShortDescription from ._botname import BotName -from ._business import BusinessConnection, BusinessIntro, BusinessMessagesDeleted +from ._business import BusinessConnection, BusinessIntro, BusinessLocation, BusinessMessagesDeleted from ._callbackquery import CallbackQuery from ._chat import Chat from ._chatadministratorrights import ChatAdministratorRights diff --git a/telegram/_business.py b/telegram/_business.py index 6d1287a1777..e94a6ea8d22 100644 --- a/telegram/_business.py +++ b/telegram/_business.py @@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Optional, Sequence from telegram._chat import Chat +from telegram._files.location import Location from telegram._files.sticker import Sticker from telegram._telegramobject import TelegramObject from telegram._user import User @@ -240,3 +241,54 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessIntr data["sticker"] = Sticker.de_json(data.get("sticker"), bot) return super().de_json(data=data, bot=bot) + + +class BusinessLocation(TelegramObject): + """ + This object represents the location of a business account. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal, if their + :attr:`address` and :attr:`location` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + address (:obj:`str`): Address of the business. + location (:class:`telegram.Location`): Optional. Location of the business. + + Attributes: + address (:obj:`str`): Address of the business. + location (:class:`telegram.Location`): Optional. Location of the business. + + """ + + __slots__ = ( + "address", + "location", + ) + + def __init__( + self, + address: str, + location: Optional[Location] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.address: str = address + self.location: Optional[Location] = location + + self._id_attrs = (self.address, self.location) + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessLocation"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["location"] = Location.de_json(data.get("location"), bot) + + return super().de_json(data=data, bot=bot) diff --git a/telegram/_chat.py b/telegram/_chat.py index 2a776a1bff0..a2512cc3686 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -45,6 +45,7 @@ Audio, Bot, BusinessIntro, + BusinessLocation, ChatInviteLink, ChatMember, Contact, @@ -174,6 +175,11 @@ class Chat(TelegramObject): business accounts, the intro of the business. Returned only in :meth:`telegram.Bot.get_chat`. + .. versionadded:: NEXT.VERSION + business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with + business accounts, the location of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + .. versionadded:: NEXT.VERSION available_reactions (Sequence[:class:`telegram.ReactionType`], optional): List of available reactions allowed in the chat. If omitted, then all of @@ -322,6 +328,11 @@ class Chat(TelegramObject): business accounts, the intro of the business. Returned only in :meth:`telegram.Bot.get_chat`. + .. versionadded:: NEXT.VERSION + business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with + business accounts, the location of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + .. versionadded:: NEXT.VERSION available_reactions (Tuple[:class:`telegram.ReactionType`]): Optional. List of available reactions allowed in the chat. If omitted, then all of @@ -395,6 +406,7 @@ class Chat(TelegramObject): "background_custom_emoji_id", "bio", "business_intro", + "business_location", "can_set_sticker_set", "custom_emoji_sticker_set_name", "description", @@ -483,6 +495,7 @@ def __init__( unrestrict_boost_count: Optional[int] = None, custom_emoji_sticker_set_name: Optional[str] = None, business_intro: Optional["BusinessIntro"] = None, + business_location: Optional["BusinessLocation"] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -533,6 +546,7 @@ def __init__( self.unrestrict_boost_count: Optional[int] = unrestrict_boost_count self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name self.business_intro: Optional["BusinessIntro"] = business_intro + self.business_location: Optional["BusinessLocation"] = business_location self._id_attrs = (self.id,) @@ -595,15 +609,19 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Chat"]: ) data["photo"] = ChatPhoto.de_json(data.get("photo"), bot) - from telegram import Message # pylint: disable=import-outside-toplevel + from telegram import ( # pylint: disable=import-outside-toplevel + BusinessIntro, + BusinessLocation, + Message, + ) data["pinned_message"] = Message.de_json(data.get("pinned_message"), bot) data["permissions"] = ChatPermissions.de_json(data.get("permissions"), bot) data["location"] = ChatLocation.de_json(data.get("location"), bot) data["available_reactions"] = ReactionType.de_list(data.get("available_reactions"), bot) - from telegram import BusinessIntro # pylint: disable=import-outside-toplevel data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot) + data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot) api_kwargs = {} # This is a deprecated field that TG still returns for backwards compatibility From 3111a69edfe3b4c5d777a20218734486d30382d8 Mon Sep 17 00:00:00 2001 From: aelkheir Date: Tue, 2 Apr 2024 14:19:38 +0300 Subject: [PATCH 05/15] Add tests for `business_location`. --- tests/test_business.py | 57 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/tests/test_business.py b/tests/test_business.py index 0651f1c0291..6df691f821c 100644 --- a/tests/test_business.py +++ b/tests/test_business.py @@ -23,8 +23,10 @@ from telegram import ( BusinessConnection, BusinessIntro, + BusinessLocation, BusinessMessagesDeleted, Chat, + Location, Sticker, User, ) @@ -45,6 +47,8 @@ class TestBusinessBase: title = "Business Title" message = "Business description" sticker = Sticker("sticker_id", "unique_id", 50, 50, True, False, Sticker.REGULAR) + address = "address" + location = Location(-23.691288, 46.788279) @pytest.fixture(scope="module") @@ -77,6 +81,14 @@ def business_intro(): ) +@pytest.fixture(scope="module") +def business_location(): + return BusinessLocation( + TestBusinessBase.address, + TestBusinessBase.location, + ) + + class TestBusinessConnectionWithoutRequest(TestBusinessBase): def test_slots(self, business_connection): bc = business_connection @@ -192,14 +204,14 @@ def test_equality(self): class TestBusinessIntro(TestBusinessBase): - def test_slots(self, business_intro): - intro = business_intro + def test_slots(self, business_location): + intro = business_location for attr in intro.__slots__: assert getattr(intro, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(intro)) == len(set(mro_slots(intro))), "duplicate slot" - def test_to_dict(self, business_intro): - intro_dict = business_intro.to_dict() + def test_to_dict(self, business_location): + intro_dict = business_location.to_dict() assert isinstance(intro_dict, dict) assert intro_dict["title"] == self.title assert intro_dict["message"] == self.message @@ -229,3 +241,40 @@ def test_equality(self): assert intro1 != intro3 assert hash(intro1) != hash(intro3) + + +class TestBusinessLocation(TestBusinessBase): + def test_slots(self, business_location): + inst = business_location + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_to_dict(self, business_location): + blc_dict = business_location.to_dict() + assert isinstance(blc_dict, dict) + assert blc_dict["address"] == self.address + assert blc_dict["location"] == self.location.to_dict() + + def test_de_json(self): + json_dict = { + "address": self.address, + "location": self.location.to_dict(), + } + blc = BusinessLocation.de_json(json_dict, None) + assert blc.address == self.address + assert blc.location == self.location + assert blc.api_kwargs == {} + assert isinstance(blc, BusinessLocation) + + def test_equality(self): + blc1 = BusinessLocation(self.address, self.location) + blc2 = BusinessLocation(self.address, self.location) + blc3 = BusinessLocation("Other Address", self.location) + + assert blc1 == blc2 + assert hash(blc1) == hash(blc2) + assert blc1 is not blc2 + + assert blc1 != blc3 + assert hash(blc1) != hash(blc3) From 3ea8c15650fec6ec4a8afb493023e8c55be92d4e Mon Sep 17 00:00:00 2001 From: aelkheir Date: Tue, 2 Apr 2024 14:20:40 +0300 Subject: [PATCH 06/15] Add doc entries for `BisnessLocation`. --- docs/source/telegram.at-tree.rst | 1 + docs/source/telegram.businesslocation.rst | 6 ++++++ 2 files changed, 7 insertions(+) create mode 100644 docs/source/telegram.businesslocation.rst diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 2d333f5a275..41bc9e9c1d3 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -20,6 +20,7 @@ Available Types telegram.botshortdescription telegram.businessconnection telegram.businessintro + telegram.businesslocation telegram.businessmessagesdeleted telegram.callbackquery telegram.chat diff --git a/docs/source/telegram.businesslocation.rst b/docs/source/telegram.businesslocation.rst new file mode 100644 index 00000000000..1a1b8893b65 --- /dev/null +++ b/docs/source/telegram.businesslocation.rst @@ -0,0 +1,6 @@ +BusinessLocation +================== + +.. autoclass:: telegram.BusinessLocation + :members: + :show-inheritance: \ No newline at end of file From 2f6188809abdbfd860d17c9dde6ffb03e426e738 Mon Sep 17 00:00:00 2001 From: aelkheir Date: Tue, 2 Apr 2024 14:23:11 +0300 Subject: [PATCH 07/15] Freeze added classes (cause of a test failure). --- telegram/_business.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/telegram/_business.py b/telegram/_business.py index e94a6ea8d22..56e2fff8703 100644 --- a/telegram/_business.py +++ b/telegram/_business.py @@ -230,6 +230,8 @@ def __init__( self._id_attrs = (self.title, self.message, self.sticker) + self._freeze() + @classmethod def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessIntro"]: """See :meth:`telegram.TelegramObject.de_json`.""" @@ -281,6 +283,8 @@ def __init__( self._id_attrs = (self.address, self.location) + self._freeze() + @classmethod def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessLocation"]: """See :meth:`telegram.TelegramObject.de_json`.""" From ee35700a66eb23f126223140e6e35fa0eaadac3b Mon Sep 17 00:00:00 2001 From: aelkheir Date: Tue, 2 Apr 2024 15:10:15 +0300 Subject: [PATCH 08/15] Fix accidental fixture rename. --- tests/test_business.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_business.py b/tests/test_business.py index 6df691f821c..c16234156b9 100644 --- a/tests/test_business.py +++ b/tests/test_business.py @@ -204,14 +204,14 @@ def test_equality(self): class TestBusinessIntro(TestBusinessBase): - def test_slots(self, business_location): - intro = business_location + def test_slots(self, business_intro): + intro = business_intro for attr in intro.__slots__: assert getattr(intro, attr, "err") != "err", f"got extra slot '{attr}'" assert len(mro_slots(intro)) == len(set(mro_slots(intro))), "duplicate slot" - def test_to_dict(self, business_location): - intro_dict = business_location.to_dict() + def test_to_dict(self, business_intro): + intro_dict = business_intro.to_dict() assert isinstance(intro_dict, dict) assert intro_dict["title"] == self.title assert intro_dict["message"] == self.message From ee43314975ecf7c3d6dd4807cce83ddb2464589e Mon Sep 17 00:00:00 2001 From: aelkheir Date: Tue, 2 Apr 2024 17:50:19 +0300 Subject: [PATCH 09/15] Add `BusinessOpeningHours` and related code. Add the classes `BusinessOpeningHours` and `BusinessOpeningHoursInterval` and the field `business_opening_hours` to the class `Chat`. --- telegram/__init__.py | 11 ++++- telegram/_business.py | 102 ++++++++++++++++++++++++++++++++++++++++-- telegram/_chat.py | 18 ++++++++ 3 files changed, 127 insertions(+), 4 deletions(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index f204f7ee6fd..f12649ced9e 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -39,6 +39,8 @@ "BusinessIntro", "BusinessLocation", "BusinessMessagesDeleted", + "BusinessOpeningHours", + "BusinessOpeningHoursInterval", "CallbackGame", "CallbackQuery", "Chat", @@ -242,7 +244,14 @@ ) from ._botdescription import BotDescription, BotShortDescription from ._botname import BotName -from ._business import BusinessConnection, BusinessIntro, BusinessLocation, BusinessMessagesDeleted +from ._business import ( + BusinessConnection, + BusinessIntro, + BusinessLocation, + BusinessMessagesDeleted, + BusinessOpeningHours, + BusinessOpeningHoursInterval, +) from ._callbackquery import CallbackQuery from ._chat import Chat from ._chatadministratorrights import ChatAdministratorRights diff --git a/telegram/_business.py b/telegram/_business.py index 56e2fff8703..e3cce1c7b6d 100644 --- a/telegram/_business.py +++ b/telegram/_business.py @@ -20,7 +20,7 @@ """This module contains the Telegram Business related classes.""" from datetime import datetime -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._chat import Chat from telegram._files.location import Location @@ -206,7 +206,6 @@ class BusinessIntro(TelegramObject): title (:obj:`str`): Optional. Title text of the business intro. message (:obj:`str`): Optional. Message text of the business intro. sticker (:class:`telegram.Sticker`): Optional. Sticker of the business intro. - """ __slots__ = ( @@ -262,7 +261,6 @@ class BusinessLocation(TelegramObject): Attributes: address (:obj:`str`): Address of the business. location (:class:`telegram.Location`): Optional. Location of the business. - """ __slots__ = ( @@ -296,3 +294,101 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessLoca data["location"] = Location.de_json(data.get("location"), bot) return super().de_json(data=data, bot=bot) + + +class BusinessOpeningHoursInterval(TelegramObject): + """ + This object represents the time intervals describing business opening hours. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal, if their + :attr:`opening_minute` and :attr:`closing_minute` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, + marking the start of the time interval during which the business is open; 0 - 7 24 60. + closing_minute (:obj:`int`): The minute's + sequence number in a week, starting on Monday, marking the end of the time interval + during which the business is open; 0 - 8 24 60 + + Attributes: + opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, + marking the start of the time interval during which the business is open; 0 - 7 24 60. + closing_minute (:obj:`int`): The minute's + sequence number in a week, starting on Monday, marking the end of the time interval + during which the business is open; 0 - 8 24 60 + """ + + __slots__ = ("closing_minute", "opening_minute") + + def __init__( + self, + opening_minute: int, + closing_minute: int, + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.opening_minute: int = opening_minute + self.closing_minute: int = closing_minute + + self._id_attrs = (self.opening_minute, self.closing_minute) + + self._freeze() + + +class BusinessOpeningHours(TelegramObject): + """ + This object represents the opening hours of a business account. + + Objects of this class are comparable in terms of equality. + Two objects of this class are considered equal, if their + :attr:`time_zone_name` and :attr:`opening_hours` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + time_zone_name (:obj:`str`): Unique name of the time zone for which the opening + hours are defined. + opening_hours (Sequence[:class:`telegram.BusinessOpeningHoursInterval`]): List of + time intervals describing business opening hours. + + Attributes: + time_zone_name (:obj:`str`): Unique name of the time zone for which the opening + hours are defined. + opening_hours (Sequence[:class:`telegram.BusinessOpeningHoursInterval`]): List of + time intervals describing business opening hours. + """ + + __slots__ = ("opening_hours", "time_zone_name") + + def __init__( + self, + time_zone_name: str, + opening_hours: Sequence[BusinessOpeningHoursInterval], + *, + api_kwargs: Optional[JSONDict] = None, + ): + super().__init__(api_kwargs=api_kwargs) + self.time_zone_name: str = time_zone_name + self.opening_hours: Tuple[BusinessOpeningHoursInterval, ...] = tuple(opening_hours) + + self._id_attrs = (self.time_zone_name, self.opening_hours) + + self._freeze() + + @classmethod + def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["BusinessOpeningHours"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["opening_hours"] = BusinessOpeningHoursInterval.de_list( + data.get("opening_hours"), bot + ) + + return super().de_json(data=data, bot=bot) diff --git a/telegram/_chat.py b/telegram/_chat.py index a2512cc3686..2a731204b59 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -46,6 +46,7 @@ Bot, BusinessIntro, BusinessLocation, + BusinessOpeningHours, ChatInviteLink, ChatMember, Contact, @@ -180,6 +181,11 @@ class Chat(TelegramObject): business accounts, the location of the business. Returned only in :meth:`telegram.Bot.get_chat`. + .. versionadded:: NEXT.VERSION + business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private + chats with business accounts, the opening hours of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + .. versionadded:: NEXT.VERSION available_reactions (Sequence[:class:`telegram.ReactionType`], optional): List of available reactions allowed in the chat. If omitted, then all of @@ -333,6 +339,11 @@ class Chat(TelegramObject): business accounts, the location of the business. Returned only in :meth:`telegram.Bot.get_chat`. + .. versionadded:: NEXT.VERSION + business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private + chats with business accounts, the opening hours of the business. Returned only in + :meth:`telegram.Bot.get_chat`. + .. versionadded:: NEXT.VERSION available_reactions (Tuple[:class:`telegram.ReactionType`]): Optional. List of available reactions allowed in the chat. If omitted, then all of @@ -407,6 +418,7 @@ class Chat(TelegramObject): "bio", "business_intro", "business_location", + "business_opening_hours", "can_set_sticker_set", "custom_emoji_sticker_set_name", "description", @@ -496,6 +508,7 @@ def __init__( custom_emoji_sticker_set_name: Optional[str] = None, business_intro: Optional["BusinessIntro"] = None, business_location: Optional["BusinessLocation"] = None, + business_opening_hours: Optional["BusinessOpeningHours"] = None, *, api_kwargs: Optional[JSONDict] = None, ): @@ -547,6 +560,7 @@ def __init__( self.custom_emoji_sticker_set_name: Optional[str] = custom_emoji_sticker_set_name self.business_intro: Optional["BusinessIntro"] = business_intro self.business_location: Optional["BusinessLocation"] = business_location + self.business_opening_hours: Optional["BusinessOpeningHours"] = business_opening_hours self._id_attrs = (self.id,) @@ -612,6 +626,7 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Chat"]: from telegram import ( # pylint: disable=import-outside-toplevel BusinessIntro, BusinessLocation, + BusinessOpeningHours, Message, ) @@ -622,6 +637,9 @@ def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Chat"]: data["business_intro"] = BusinessIntro.de_json(data.get("business_intro"), bot) data["business_location"] = BusinessLocation.de_json(data.get("business_location"), bot) + data["business_opening_hours"] = BusinessOpeningHours.de_json( + data.get("business_opening_hours"), bot + ) api_kwargs = {} # This is a deprecated field that TG still returns for backwards compatibility From 88ce7114702bfb2620e741e8627ebc262afcded5 Mon Sep 17 00:00:00 2001 From: aelkheir Date: Tue, 2 Apr 2024 17:54:03 +0300 Subject: [PATCH 10/15] Add tests --- tests/test_business.py | 98 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/test_business.py b/tests/test_business.py index c16234156b9..d6e58ead372 100644 --- a/tests/test_business.py +++ b/tests/test_business.py @@ -25,6 +25,8 @@ BusinessIntro, BusinessLocation, BusinessMessagesDeleted, + BusinessOpeningHours, + BusinessOpeningHoursInterval, Chat, Location, Sticker, @@ -49,6 +51,12 @@ class TestBusinessBase: sticker = Sticker("sticker_id", "unique_id", 50, 50, True, False, Sticker.REGULAR) address = "address" location = Location(-23.691288, 46.788279) + opening_minute = 0 + closing_minute = 60 + time_zone_name = "Country/City" + opening_hours = [ + BusinessOpeningHoursInterval(opening, opening + 60) for opening in (0, 24 * 60) + ] @pytest.fixture(scope="module") @@ -89,6 +97,22 @@ def business_location(): ) +@pytest.fixture(scope="module") +def business_opening_hours_interval(): + return BusinessOpeningHoursInterval( + TestBusinessBase.opening_minute, + TestBusinessBase.closing_minute, + ) + + +@pytest.fixture(scope="module") +def business_opening_hours(): + return BusinessOpeningHours( + TestBusinessBase.time_zone_name, + TestBusinessBase.opening_hours, + ) + + class TestBusinessConnectionWithoutRequest(TestBusinessBase): def test_slots(self, business_connection): bc = business_connection @@ -278,3 +302,77 @@ def test_equality(self): assert blc1 != blc3 assert hash(blc1) != hash(blc3) + + +class TestBusinessOpeningHoursInterval(TestBusinessBase): + def test_slots(self, business_opening_hours_interval): + inst = business_opening_hours_interval + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_to_dict(self, business_opening_hours_interval): + bohi_dict = business_opening_hours_interval.to_dict() + assert isinstance(bohi_dict, dict) + assert bohi_dict["opening_minute"] == self.opening_minute + assert bohi_dict["closing_minute"] == self.closing_minute + + def test_de_json(self): + json_dict = { + "opening_minute": self.opening_minute, + "closing_minute": self.closing_minute, + } + bohi = BusinessOpeningHoursInterval.de_json(json_dict, None) + assert bohi.opening_minute == self.opening_minute + assert bohi.closing_minute == self.closing_minute + assert bohi.api_kwargs == {} + assert isinstance(bohi, BusinessOpeningHoursInterval) + + def test_equality(self): + bohi1 = BusinessOpeningHoursInterval(self.opening_minute, self.closing_minute) + bohi2 = BusinessOpeningHoursInterval(self.opening_minute, self.closing_minute) + bohi3 = BusinessOpeningHoursInterval(61, 100) + + assert bohi1 == bohi2 + assert hash(bohi1) == hash(bohi2) + assert bohi1 is not bohi2 + + assert bohi1 != bohi3 + assert hash(bohi1) != hash(bohi3) + + +class TestBusinessOpeningHours(TestBusinessBase): + def test_slots(self, business_opening_hours): + inst = business_opening_hours + for attr in inst.__slots__: + assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" + assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot" + + def test_to_dict(self, business_opening_hours): + boh_dict = business_opening_hours.to_dict() + assert isinstance(boh_dict, dict) + assert boh_dict["time_zone_name"] == self.time_zone_name + assert boh_dict["opening_hours"] == [opening.to_dict() for opening in self.opening_hours] + + def test_de_json(self): + json_dict = { + "time_zone_name": self.time_zone_name, + "opening_hours": [opening.to_dict() for opening in self.opening_hours], + } + boh = BusinessOpeningHours.de_json(json_dict, None) + assert boh.time_zone_name == self.time_zone_name + assert boh.opening_hours == tuple(self.opening_hours) + assert boh.api_kwargs == {} + assert isinstance(boh, BusinessOpeningHours) + + def test_equality(self): + boh1 = BusinessOpeningHours(self.time_zone_name, self.opening_hours) + boh2 = BusinessOpeningHours(self.time_zone_name, self.opening_hours) + boh3 = BusinessOpeningHours("Other/Timezone", self.opening_hours) + + assert boh1 == boh2 + assert hash(boh1) == hash(boh2) + assert boh1 is not boh2 + + assert boh1 != boh3 + assert hash(boh1) != hash(boh3) From da3df0e465d63b93b46c48ed4a83db0bb8f73164 Mon Sep 17 00:00:00 2001 From: aelkheir Date: Tue, 2 Apr 2024 17:55:28 +0300 Subject: [PATCH 11/15] Add doc refs --- docs/source/telegram.at-tree.rst | 2 ++ docs/source/telegram.businessopeninghours.rst | 6 ++++++ docs/source/telegram.businessopeninghoursinterval.rst | 6 ++++++ 3 files changed, 14 insertions(+) create mode 100644 docs/source/telegram.businessopeninghours.rst create mode 100644 docs/source/telegram.businessopeninghoursinterval.rst diff --git a/docs/source/telegram.at-tree.rst b/docs/source/telegram.at-tree.rst index 41bc9e9c1d3..17e562e9d38 100644 --- a/docs/source/telegram.at-tree.rst +++ b/docs/source/telegram.at-tree.rst @@ -21,6 +21,8 @@ Available Types telegram.businessconnection telegram.businessintro telegram.businesslocation + telegram.businessopeninghours + telegram.businessopeninghoursinterval telegram.businessmessagesdeleted telegram.callbackquery telegram.chat diff --git a/docs/source/telegram.businessopeninghours.rst b/docs/source/telegram.businessopeninghours.rst new file mode 100644 index 00000000000..cab989c8475 --- /dev/null +++ b/docs/source/telegram.businessopeninghours.rst @@ -0,0 +1,6 @@ +BusinessOpeningHours +==================== + +.. autoclass:: telegram.BusinessOpeningHours + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.businessopeninghoursinterval.rst b/docs/source/telegram.businessopeninghoursinterval.rst new file mode 100644 index 00000000000..241379dbcfb --- /dev/null +++ b/docs/source/telegram.businessopeninghoursinterval.rst @@ -0,0 +1,6 @@ +BusinessOpeningHoursInterval +============================ + +.. autoclass:: telegram.BusinessOpeningHoursInterval + :members: + :show-inheritance: \ No newline at end of file From eb8d32b2be10a45e0bd07307bcbacb24a08a4d9f Mon Sep 17 00:00:00 2001 From: aelkheir Date: Wed, 3 Apr 2024 14:58:45 +0300 Subject: [PATCH 12/15] Adress review comments. --- telegram/_business.py | 40 ++++++++++++++++++++++++++++------------ telegram/_chat.py | 12 ++++++------ tests/test_business.py | 16 ++++++++-------- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/telegram/_business.py b/telegram/_business.py index e3cce1c7b6d..66e6ba1d336 100644 --- a/telegram/_business.py +++ b/telegram/_business.py @@ -20,7 +20,7 @@ """This module contains the Telegram Business related classes.""" from datetime import datetime -from typing import TYPE_CHECKING, Optional, Sequence, Tuple +from typing import TYPE_CHECKING, Optional, Sequence from telegram._chat import Chat from telegram._files.location import Location @@ -198,9 +198,9 @@ class BusinessIntro(TelegramObject): .. versionadded:: NEXT.VERSION Args: - title (:obj:`str`): Optional. Title text of the business intro. - message (:obj:`str`): Optional. Message text of the business intro. - sticker (:class:`telegram.Sticker`): Optional. Sticker of the business intro. + title (:obj:`str`, optional): Title text of the business intro. + message (:obj:`str`, optional): Message text of the business intro. + sticker (:class:`telegram.Sticker`, optional): Sticker of the business intro. Attributes: title (:obj:`str`): Optional. Title text of the business intro. @@ -250,13 +250,13 @@ class BusinessLocation(TelegramObject): Objects of this class are comparable in terms of equality. Two objects of this class are considered equal, if their - :attr:`address` and :attr:`location` are equal. + :attr:`address` is equal. .. versionadded:: NEXT.VERSION Args: address (:obj:`str`): Address of the business. - location (:class:`telegram.Location`): Optional. Location of the business. + location (:class:`telegram.Location`, optional): Location of the business. Attributes: address (:obj:`str`): Address of the business. @@ -279,7 +279,7 @@ def __init__( self.address: str = address self.location: Optional[Location] = location - self._id_attrs = (self.address, self.location) + self._id_attrs = (self.address,) self._freeze() @@ -306,19 +306,33 @@ class BusinessOpeningHoursInterval(TelegramObject): .. versionadded:: NEXT.VERSION + Examples: + A day has (24 * 60 =) 1440 minutes, a week has (7 * 1440 =) 10080 minutes. + Starting the the minute's sequence from Monday, example values of + :attr:`opening_minute`, :attr:`closing_minute` will map to the following day times: + + * 0 is Monday 12:00 AM + * 60 is Monday 01:00 AM + * 1440 is Tuesday 12:00 AM + * An opening at Monday from 09:00 AM till 05:00 PM, has (9 * 60 =) 540 - (17 * 60 =) 1020 + as opening and closing hours respectively + + Args: opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, - marking the start of the time interval during which the business is open; 0 - 7 24 60. + marking the start of the time interval during which the business is open; + 0 - 7 * 24 * 60. closing_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, marking the end of the time interval - during which the business is open; 0 - 8 24 60 + during which the business is open; 0 - 8 * 24 * 60 Attributes: opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, - marking the start of the time interval during which the business is open; 0 - 7 24 60. + marking the start of the time interval during which the business is open; + 0 - 7 * 24 * 60. closing_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, marking the end of the time interval - during which the business is open; 0 - 8 24 60 + during which the business is open; 0 - 8 * 24 * 60 """ __slots__ = ("closing_minute", "opening_minute") @@ -373,7 +387,9 @@ def __init__( ): super().__init__(api_kwargs=api_kwargs) self.time_zone_name: str = time_zone_name - self.opening_hours: Tuple[BusinessOpeningHoursInterval, ...] = tuple(opening_hours) + self.opening_hours: Sequence[BusinessOpeningHoursInterval] = parse_sequence_arg( + opening_hours + ) self._id_attrs = (self.time_zone_name, self.opening_hours) diff --git a/telegram/_chat.py b/telegram/_chat.py index 2a731204b59..edd93515fa2 100644 --- a/telegram/_chat.py +++ b/telegram/_chat.py @@ -172,17 +172,17 @@ class Chat(TelegramObject): only in :meth:`telegram.Bot.get_chat`. .. versionadded:: 20.0 - business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with + business_intro (:class:`telegram.BusinessIntro`, optional): For private chats with business accounts, the intro of the business. Returned only in :meth:`telegram.Bot.get_chat`. .. versionadded:: NEXT.VERSION - business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with + business_location (:class:`telegram.BusinessLocation`, optional): For private chats with business accounts, the location of the business. Returned only in :meth:`telegram.Bot.get_chat`. .. versionadded:: NEXT.VERSION - business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private + business_opening_hours (:class:`telegram.BusinessOpeningHours`, optional): For private chats with business accounts, the opening hours of the business. Returned only in :meth:`telegram.Bot.get_chat`. @@ -330,17 +330,17 @@ class Chat(TelegramObject): obtained via :meth:`~telegram.Bot.get_chat`. .. versionadded:: 20.0 - business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with + business_intro (:class:`telegram.BusinessIntro`): Optional. For private chats with business accounts, the intro of the business. Returned only in :meth:`telegram.Bot.get_chat`. .. versionadded:: NEXT.VERSION - business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with + business_location (:class:`telegram.BusinessLocation`): Optional. For private chats with business accounts, the location of the business. Returned only in :meth:`telegram.Bot.get_chat`. .. versionadded:: NEXT.VERSION - business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private + business_opening_hours (:class:`telegram.BusinessOpeningHours`): Optional. For private chats with business accounts, the opening hours of the business. Returned only in :meth:`telegram.Bot.get_chat`. diff --git a/tests/test_business.py b/tests/test_business.py index d6e58ead372..52d0c61c9f6 100644 --- a/tests/test_business.py +++ b/tests/test_business.py @@ -227,8 +227,8 @@ def test_equality(self): assert hash(bmd1) != hash(bmd3) -class TestBusinessIntro(TestBusinessBase): - def test_slots(self, business_intro): +class TestBusinessIntroWithoutRequest(TestBusinessBase): + def test_slot_behaviour(self, business_intro): intro = business_intro for attr in intro.__slots__: assert getattr(intro, attr, "err") != "err", f"got extra slot '{attr}'" @@ -267,8 +267,8 @@ def test_equality(self): assert hash(intro1) != hash(intro3) -class TestBusinessLocation(TestBusinessBase): - def test_slots(self, business_location): +class TestBusinessLocationWithoutRequest(TestBusinessBase): + def test_slot_behaviour(self, business_location): inst = business_location for attr in inst.__slots__: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" @@ -304,8 +304,8 @@ def test_equality(self): assert hash(blc1) != hash(blc3) -class TestBusinessOpeningHoursInterval(TestBusinessBase): - def test_slots(self, business_opening_hours_interval): +class TestBusinessOpeningHoursIntervalWithoutRequest(TestBusinessBase): + def test_slot_behaviour(self, business_opening_hours_interval): inst = business_opening_hours_interval for attr in inst.__slots__: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" @@ -341,8 +341,8 @@ def test_equality(self): assert hash(bohi1) != hash(bohi3) -class TestBusinessOpeningHours(TestBusinessBase): - def test_slots(self, business_opening_hours): +class TestBusinessOpeningHoursWithoutRequest(TestBusinessBase): + def test_slot_behaviour(self, business_opening_hours): inst = business_opening_hours for attr in inst.__slots__: assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'" From e7d6e78a8b12ff9a2d4e5b8d343824b58bb5bc08 Mon Sep 17 00:00:00 2001 From: aelkheir Date: Wed, 3 Apr 2024 15:01:47 +0300 Subject: [PATCH 13/15] Adapt tests in `test_chat` for testing new properties. --- tests/test_chat.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_chat.py b/tests/test_chat.py index f8d4c7ef5e2..215250f0288 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -23,6 +23,9 @@ from telegram import ( Bot, BusinessIntro, + BusinessLocation, + BusinessOpeningHours, + BusinessOpeningHoursInterval, Chat, ChatLocation, ChatPermissions, @@ -76,6 +79,8 @@ def chat(bot): unrestrict_boost_count=TestChatBase.unrestrict_boost_count, custom_emoji_sticker_set_name=TestChatBase.custom_emoji_sticker_set_name, business_intro=TestChatBase.business_intro, + business_location=TestChatBase.business_location, + business_opening_hours=TestChatBase.business_opening_hours, ) chat.set_bot(bot) chat._unfreeze() @@ -116,6 +121,11 @@ class TestChatBase: ReactionTypeCustomEmoji("custom_emoji_id"), ] business_intro = BusinessIntro("Title", "Description", None) + business_location = BusinessLocation("Address", Location(123, 456)) + business_opening_hours = BusinessOpeningHours( + "Country/City", + [BusinessOpeningHoursInterval(opening, opening + 60) for opening in (0, 24 * 60)], + ) accent_color_id = 1 background_custom_emoji_id = "background_custom_emoji_id" profile_accent_color_id = 2 @@ -143,6 +153,8 @@ def test_de_json(self, bot): "slow_mode_delay": self.slow_mode_delay, "bio": self.bio, "business_intro": self.business_intro.to_dict(), + "business_location": self.business_location.to_dict(), + "business_opening_hours": self.business_opening_hours.to_dict(), "has_protected_content": self.has_protected_content, "has_visible_history": self.has_visible_history, "has_private_forwards": self.has_private_forwards, @@ -179,6 +191,8 @@ def test_de_json(self, bot): assert chat.slow_mode_delay == self.slow_mode_delay assert chat.bio == self.bio assert chat.business_intro == self.business_intro + assert chat.business_location == self.business_location + assert chat.business_opening_hours == self.business_opening_hours assert chat.has_protected_content == self.has_protected_content assert chat.has_visible_history == self.has_visible_history assert chat.has_private_forwards == self.has_private_forwards @@ -240,6 +254,8 @@ def test_to_dict(self, chat): assert chat_dict["slow_mode_delay"] == chat.slow_mode_delay assert chat_dict["bio"] == chat.bio assert chat_dict["business_intro"] == chat.business_intro.to_dict() + assert chat_dict["business_location"] == chat.business_location.to_dict() + assert chat_dict["business_opening_hours"] == chat.business_opening_hours.to_dict() assert chat_dict["has_private_forwards"] == chat.has_private_forwards assert chat_dict["has_protected_content"] == chat.has_protected_content assert chat_dict["has_visible_history"] == chat.has_visible_history From 9c1af08d4c6286e85675ef026dafc687d0ce7830 Mon Sep 17 00:00:00 2001 From: Harshil <37377066+harshil21@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:41:07 -0400 Subject: [PATCH 14/15] Update BusinessOpeningHourInterval example --- telegram/_business.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/telegram/_business.py b/telegram/_business.py index 66e6ba1d336..74a57e748dc 100644 --- a/telegram/_business.py +++ b/telegram/_business.py @@ -311,12 +311,15 @@ class BusinessOpeningHoursInterval(TelegramObject): Starting the the minute's sequence from Monday, example values of :attr:`opening_minute`, :attr:`closing_minute` will map to the following day times: - * 0 is Monday 12:00 AM - * 60 is Monday 01:00 AM - * 1440 is Tuesday 12:00 AM - * An opening at Monday from 09:00 AM till 05:00 PM, has (9 * 60 =) 540 - (17 * 60 =) 1020 - as opening and closing hours respectively - + * Monday - 8am to 8:30pm: + - ``opening_minute = 480`` :guilabel:`8 * 60` + - ``closing_minute = 1230`` :guilabel:`20 * 60 + 30` + * Tuesday - 24 hours: + - ``opening_minute = 1440`` :guilabel:`24 * 60` + - ``closing_minute = 2879`` :guilabel:`2 * 24 * 60 - 1` + * Sunday - 12am - 11:58pm: + - ``opening_minute = 8640`` :guilabel:`6 * 24 * 60` + - ``closing_minute = 10078`` :guilabel:`7 * 24 * 60 - 2` Args: opening_minute (:obj:`int`): The minute's sequence number in a week, starting on Monday, From 9784450347064c88321e1c0a1d954174b14b1ce3 Mon Sep 17 00:00:00 2001 From: aelkheir Date: Fri, 5 Apr 2024 17:20:57 +0300 Subject: [PATCH 15/15] Add `{opening/closing}_time` and tests. --- telegram/_business.py | 36 ++++++++++++++++++++++++++++++++++-- tests/test_business.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/telegram/_business.py b/telegram/_business.py index 74a57e748dc..d208de31092 100644 --- a/telegram/_business.py +++ b/telegram/_business.py @@ -20,7 +20,7 @@ """This module contains the Telegram Business related classes.""" from datetime import datetime -from typing import TYPE_CHECKING, Optional, Sequence +from typing import TYPE_CHECKING, Optional, Sequence, Tuple from telegram._chat import Chat from telegram._files.location import Location @@ -338,7 +338,7 @@ class BusinessOpeningHoursInterval(TelegramObject): during which the business is open; 0 - 8 * 24 * 60 """ - __slots__ = ("closing_minute", "opening_minute") + __slots__ = ("_closing_time", "_opening_time", "closing_minute", "opening_minute") def __init__( self, @@ -351,10 +351,42 @@ def __init__( self.opening_minute: int = opening_minute self.closing_minute: int = closing_minute + self._opening_time: Optional[Tuple[int, int, int]] = None + self._closing_time: Optional[Tuple[int, int, int]] = None + self._id_attrs = (self.opening_minute, self.closing_minute) self._freeze() + def _parse_minute(self, minute: int) -> Tuple[int, int, int]: + return (minute // 1440, minute % 1440 // 60, minute % 1440 % 60) + + @property + def opening_time(self) -> Tuple[int, int, int]: + """Convenience attribute. A :obj:`tuple` parsed from :attr:`opening_minute`. It contains + the `weekday`, `hour` and `minute` in the same ranges as :attr:`datetime.datetime.weekday`, + :attr:`datetime.datetime.hour` and :attr:`datetime.datetime.minute` + + Returns: + Tuple[:obj:`int`, :obj:`int`, :obj:`int`]: + """ + if self._opening_time is None: + self._opening_time = self._parse_minute(self.opening_minute) + return self._opening_time + + @property + def closing_time(self) -> Tuple[int, int, int]: + """Convenience attribute. A :obj:`tuple` parsed from :attr:`closing_minute`. It contains + the `weekday`, `hour` and `minute` in the same ranges as :attr:`datetime.datetime.weekday`, + :attr:`datetime.datetime.hour` and :attr:`datetime.datetime.minute` + + Returns: + Tuple[:obj:`int`, :obj:`int`, :obj:`int`]: + """ + if self._closing_time is None: + self._closing_time = self._parse_minute(self.closing_minute) + return self._closing_time + class BusinessOpeningHours(TelegramObject): """ diff --git a/tests/test_business.py b/tests/test_business.py index 52d0c61c9f6..da6838d6d47 100644 --- a/tests/test_business.py +++ b/tests/test_business.py @@ -340,6 +340,40 @@ def test_equality(self): assert bohi1 != bohi3 assert hash(bohi1) != hash(bohi3) + @pytest.mark.parametrize( + ("opening_minute", "expected"), + [ # openings per docstring + (8 * 60, (0, 8, 0)), + (24 * 60, (1, 0, 0)), + (6 * 24 * 60, (6, 0, 0)), + ], + ) + def test_opening_time(self, opening_minute, expected): + bohi = BusinessOpeningHoursInterval(opening_minute, -0) + + opening_time = bohi.opening_time + assert opening_time == expected + + cached = bohi.opening_time + assert cached is opening_time + + @pytest.mark.parametrize( + ("closing_minute", "expected"), + [ # closings per docstring + (20 * 60 + 30, (0, 20, 30)), + (2 * 24 * 60 - 1, (1, 23, 59)), + (7 * 24 * 60 - 2, (6, 23, 58)), + ], + ) + def test_closing_time(self, closing_minute, expected): + bohi = BusinessOpeningHoursInterval(-0, closing_minute) + + closing_time = bohi.closing_time + assert closing_time == expected + + cached = bohi.closing_time + assert cached is closing_time + class TestBusinessOpeningHoursWithoutRequest(TestBusinessBase): def test_slot_behaviour(self, business_opening_hours): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy