From 7d1926e7192872a861a3b2341148dd1853caaa57 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:30:52 +0100 Subject: [PATCH 01/10] Bump Bot API Version --- README.rst | 4 ++-- telegram/constants.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 772aa757119..f0482409e2a 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,7 @@ :target: https://pypi.org/project/python-telegram-bot/ :alt: Supported Python versions -.. image:: https://img.shields.io/badge/Bot%20API-8.0-blue?logo=telegram +.. image:: https://img.shields.io/badge/Bot%20API-8.1-blue?logo=telegram :target: https://core.telegram.org/bots/api-changelog :alt: Supported Bot API version @@ -81,7 +81,7 @@ After installing_ the library, be sure to check out the section on `working with Telegram API support ~~~~~~~~~~~~~~~~~~~~ -All types and methods of the Telegram Bot API **8.0** are natively supported by this library. +All types and methods of the Telegram Bot API **8.1** are natively supported by this library. In addition, Bot API functionality not yet natively included can still be used as described `in our wiki `_. Notable Features diff --git a/telegram/constants.py b/telegram/constants.py index 4f0b993f30d..4d4b0175cc4 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -153,7 +153,7 @@ class _AccentColor(NamedTuple): #: :data:`telegram.__bot_api_version_info__`. #: #: .. versionadded:: 20.0 -BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=8, minor=0) +BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=8, minor=1) #: :obj:`str`: Telegram Bot API #: version supported by this version of `python-telegram-bot`. Also available as #: :data:`telegram.__bot_api_version__`. From 5ac7e1ccf8928bff366f9e799786fed2fce26255 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:55:02 +0100 Subject: [PATCH 02/10] `StarTransaction.nanostar_amount` and max price for subscriptions --- docs/auxil/sphinx_hooks.py | 5 +++++ docs/source/telegram.constants.rst | 2 +- telegram/_bot.py | 5 ++++- telegram/_payment/stars.py | 20 ++++++++++++++++--- telegram/_utils/enum.py | 14 +++++++++++++ telegram/constants.py | 32 ++++++++++++++++++++++++++++-- tests/test_stars.py | 5 +++++ 7 files changed, 76 insertions(+), 7 deletions(-) diff --git a/docs/auxil/sphinx_hooks.py b/docs/auxil/sphinx_hooks.py index 6853a7fbe93..dd0bf3aac31 100644 --- a/docs/auxil/sphinx_hooks.py +++ b/docs/auxil/sphinx_hooks.py @@ -187,6 +187,11 @@ def autodoc_process_bases(app, name, obj, option, bases: list) -> None: bases[idx] = ":class:`enum.IntEnum`" continue + if "FloatEnum" in base: + bases[idx] = ":class:`enum.Enum`" + bases.insert(0, ":class:`float`") + continue + # Drop generics (at least for now) if base.endswith("]"): base = base.split("[", maxsplit=1)[0] diff --git a/docs/source/telegram.constants.rst b/docs/source/telegram.constants.rst index 4b5edf51094..ef1e6720107 100644 --- a/docs/source/telegram.constants.rst +++ b/docs/source/telegram.constants.rst @@ -5,5 +5,5 @@ telegram.constants Module :members: :show-inheritance: :no-undoc-members: - :inherited-members: Enum, EnumMeta, str, int + :inherited-members: Enum, EnumMeta, str, int, float :exclude-members: __format__, __new__, __repr__, __str__ diff --git a/telegram/_bot.py b/telegram/_bot.py index 7ba6c9a789f..08fd31f4acc 100644 --- a/telegram/_bot.py +++ b/telegram/_bot.py @@ -8175,7 +8175,10 @@ async def create_invoice_link( ``“XTR”`` (Telegram Stars) if the parameter is used. Currently, it must always be :tg-const:`telegram.constants.InvoiceLimit.SUBSCRIPTION_PERIOD` if specified. Any number of subscriptions can be active for a given bot at the same time, including - multiple concurrent subscriptions from the same user. + multiple concurrent subscriptions from the same user. Subscription price must + not exceed + :tg-const:`telegram.constants.InvoiceLimit.SUBSCRIPTION_MAX_PRICE` + Telegram Stars. .. versionadded:: 21.8 max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the diff --git a/telegram/_payment/stars.py b/telegram/_payment/stars.py index 61b5ded46d6..338702d967f 100644 --- a/telegram/_payment/stars.py +++ b/telegram/_payment/stars.py @@ -499,7 +499,13 @@ class StarTransaction(TelegramObject): of the original transaction for refund transactions. Coincides with :attr:`SuccessfulPayment.telegram_payment_charge_id` for successful incoming payments from users. - amount (:obj:`int`): Number of Telegram Stars transferred by the transaction. + amount (:obj:`int`): Integer amount of Telegram Stars transferred by the transaction. + nanostar_amount (:obj:`int`, optional): The number of + :tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram + Stars transferred by the transaction; from 0 to + :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT` + + .. versionadded:: NEXT.VERSION date (:obj:`datetime.datetime`): Date the transaction was created as a datetime object. source (:class:`telegram.TransactionPartner`, optional): Source of an incoming transaction (e.g., a user purchasing goods or services, Fragment refunding a failed withdrawal). @@ -513,7 +519,13 @@ class StarTransaction(TelegramObject): of the original transaction for refund transactions. Coincides with :attr:`SuccessfulPayment.telegram_payment_charge_id` for successful incoming payments from users. - amount (:obj:`int`): Number of Telegram Stars transferred by the transaction. + amount (:obj:`int`): Integer amount of Telegram Stars transferred by the transaction. + nanostar_amount (:obj:`int`, optional): The number of + :tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram + Stars transferred by the transaction; from 0 to + :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT` + + .. versionadded:: NEXT.VERSION date (:obj:`datetime.datetime`): Date the transaction was created as a datetime object. source (:class:`telegram.TransactionPartner`): Optional. Source of an incoming transaction (e.g., a user purchasing goods or services, Fragment refunding a failed withdrawal). @@ -523,7 +535,7 @@ class StarTransaction(TelegramObject): outgoing transactions. """ - __slots__ = ("amount", "date", "id", "receiver", "source") + __slots__ = ("amount", "date", "id", "nanostar_amount", "receiver", "source") def __init__( self, @@ -532,6 +544,7 @@ def __init__( date: dtm.datetime, source: Optional[TransactionPartner] = None, receiver: Optional[TransactionPartner] = None, + nanostar_amount: Optional[int] = None, *, api_kwargs: Optional[JSONDict] = None, ) -> None: @@ -541,6 +554,7 @@ def __init__( self.date: dtm.datetime = date self.source: Optional[TransactionPartner] = source self.receiver: Optional[TransactionPartner] = receiver + self.nanostar_amount: Optional[int] = nanostar_amount self._id_attrs = ( self.id, diff --git a/telegram/_utils/enum.py b/telegram/_utils/enum.py index e58d3c0cb0a..d7bf9d33e9e 100644 --- a/telegram/_utils/enum.py +++ b/telegram/_utils/enum.py @@ -74,3 +74,17 @@ def __repr__(self) -> str: def __str__(self) -> str: return str(self.value) + + +class FloatEnum(float, _enum.Enum): + """Helper class for float enums where ``str(member)`` prints the value, but ``repr(member)`` + gives ``EnumName.MEMBER_NAME``. + """ + + __slots__ = () + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}.{self.name}>" + + def __str__(self) -> str: + return str.__str__(self) diff --git a/telegram/constants.py b/telegram/constants.py index 4d4b0175cc4..394da5adb6f 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -95,6 +95,7 @@ "ReactionType", "ReplyLimit", "RevenueWithdrawalStateType", + "StarTransactions", "StarTransactionsLimit", "StickerFormat", "StickerLimit", @@ -112,7 +113,7 @@ from typing import Final, NamedTuple, Optional from telegram._utils.datetime import UTC -from telegram._utils.enum import IntEnum, StringEnum +from telegram._utils.enum import FloatEnum, IntEnum, StringEnum class _BotAPIVersion(NamedTuple): @@ -2461,8 +2462,23 @@ class RevenueWithdrawalStateType(StringEnum): """:obj:`str`: A withdrawal failed and the transaction was refunded.""" +class StarTransactions(FloatEnum): + """This enum contains constants for :class:`telegram.StarTransaction`. + The enum members of this enumeration are instances of :class:`float` and can be treated as + such. + + .. versionadded:: NEXT.VERSION + """ + + NANOSTAR_VALUE = 1 / 1000000000 + """:obj:`float`: The value of one nanostar as used in + :attr:`telegram.StarTransaction.nanostar_amount`. + """ + + class StarTransactionsLimit(IntEnum): - """This enum contains limitations for :class:`telegram.Bot.get_star_transactions`. + """This enum contains limitations for :class:`telegram.Bot.get_star_transactions` and + :class:`telegram.StarTransaction`. The enum members of this enumeration are instances of :class:`int` and can be treated as such. .. versionadded:: 21.4 @@ -2478,6 +2494,12 @@ class StarTransactionsLimit(IntEnum): """:obj:`int`: Maximum value allowed for the :paramref:`~telegram.Bot.get_star_transactions.limit` parameter of :meth:`telegram.Bot.get_star_transactions`.""" + NANOSTAR_MAX_AMOUNT = 999999999 + """:obj:`int`: Maximum value allowed for :paramref:`~telegram.StarTransaction.nanostar_amount` + parameter of :class:`telegram.StarTransaction`. + + .. versionadded:: NEXT.VERSION + """ class StickerFormat(StringEnum): @@ -2925,6 +2947,12 @@ class InvoiceLimit(IntEnum): .. versionadded:: 21.8 """ + SUBSCRIPTION_MAX_PRICE = 2500 + """:obj:`int`: The maximum price of a subscription created wtih + :meth:`telegram.Bot.create_invoice_link`. + + .. versionadded:: NEXT.VERSION + """ class UserProfilePhotosLimit(IntEnum): diff --git a/tests/test_stars.py b/tests/test_stars.py index 5fb7a3c4068..dfb6e423dca 100644 --- a/tests/test_stars.py +++ b/tests/test_stars.py @@ -107,6 +107,7 @@ def star_transaction(): return StarTransaction( id="1", amount=1, + nanostar_amount=365, date=to_timestamp(datetime.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC)), source=transaction_partner_user(), receiver=transaction_partner_fragment(), @@ -256,6 +257,7 @@ def revenue_withdrawal_state(rws_scope_class_and_type): class StarTransactionTestBase: id = "2" amount = 2 + nanostar_amount = 365 date = to_timestamp(datetime.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC)) source = TransactionPartnerUser( user=User( @@ -278,6 +280,7 @@ def test_de_json(self, offline_bot): json_dict = { "id": self.id, "amount": self.amount, + "nanostar_amount": self.nanostar_amount, "date": self.date, "source": self.source.to_dict(), "receiver": self.receiver.to_dict(), @@ -287,6 +290,7 @@ def test_de_json(self, offline_bot): assert st.api_kwargs == {} assert st.id == self.id assert st.amount == self.amount + assert st.nanostar_amount == self.nanostar_amount assert st.date == from_timestamp(self.date) assert st.source == self.source assert st.receiver == self.receiver @@ -311,6 +315,7 @@ def test_to_dict(self): expected_dict = { "id": "1", "amount": 1, + "nanostar_amount": 365, "date": st.date, "source": st.source.to_dict(), "receiver": st.receiver.to_dict(), From 1226006cfc42f67219f196563ec560e4de759ebe Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:12:31 +0100 Subject: [PATCH 03/10] `TransactionPartnerAffiliateProgram` --- telegram/_payment/stars.py | 66 ++++++++++++++++++++++++++++++++++++++ telegram/constants.py | 5 +++ tests/test_stars.py | 22 +++++++++++++ 3 files changed, 93 insertions(+) diff --git a/telegram/_payment/stars.py b/telegram/_payment/stars.py index 338702d967f..7114fb1fe7f 100644 --- a/telegram/_payment/stars.py +++ b/telegram/_payment/stars.py @@ -199,8 +199,10 @@ class TransactionPartner(TelegramObject): transactions. Currently, it can be one of: * :class:`TransactionPartnerUser` + * :class:`TransactionPartnerAffiliateProgram` * :class:`TransactionPartnerFragment` * :class:`TransactionPartnerTelegramAds` + * :class:`TransactionPartnerTelegramApi` * :class:`TransactionPartnerOther` Objects of this class are comparable in terms of equality. Two objects of this class are @@ -217,6 +219,11 @@ class TransactionPartner(TelegramObject): __slots__ = ("type",) + AFFILIATE_PROGRAM: Final[str] = constants.TransactionPartnerType.AFFILIATE_PROGRAM + """:const:`telegram.constants.TransactionPartnerType.AFFILIATE_PROGRAM` + + .. versionadded:: NEXT.VERSION + """ FRAGMENT: Final[str] = constants.TransactionPartnerType.FRAGMENT """:const:`telegram.constants.TransactionPartnerType.FRAGMENT`""" OTHER: Final[str] = constants.TransactionPartnerType.OTHER @@ -259,6 +266,7 @@ def de_json( return None _class_mapping: dict[str, type[TransactionPartner]] = { + cls.AFFILIATE_PROGRAM: TransactionPartnerAffiliateProgram, cls.FRAGMENT: TransactionPartnerFragment, cls.USER: TransactionPartnerUser, cls.TELEGRAM_ADS: TransactionPartnerTelegramAds, @@ -272,6 +280,64 @@ def de_json( return super().de_json(data=data, bot=bot) +class TransactionPartnerAffiliateProgram(TransactionPartner): + """Describes the affiliate program that issued the affiliate commission received via this + transaction. + + This object is comparable in terms of equality. Two objects of this class are considered equal, + if their :attr:`commission_per_mille` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + sponsor_user (:class:`telegram.User`, optional): Information about the bot that sponsored + the affiliate program + commission_per_mille (:obj:`int`): The number of Telegram Stars received by the bot for + each 1000 Telegram Stars received by the affiliate program sponsor from referred users. + + Attributes: + type (:obj:`str`): The type of the transaction partner, + always :tg-const:`telegram.TransactionPartner.AFFILIATE_PROGRAM`. + sponsor_user (:class:`telegram.User`): Optional. Information about the bot that sponsored + the affiliate program + commission_per_mille (:obj:`int`): The number of Telegram Stars received by the bot for + each 1000 Telegram Stars received by the affiliate program sponsor from referred users. + """ + + __slots__ = ("commission_per_mille", "sponsor_user") + + def __init__( + self, + sponsor_user: Optional["User"], + commission_per_mille: int, + *, + api_kwargs: Optional[JSONDict] = None, + ) -> None: + super().__init__(type=TransactionPartner.AFFILIATE_PROGRAM, api_kwargs=api_kwargs) + + with self._unfrozen(): + self.sponsor_user: Optional[User] = sponsor_user + self.commission_per_mille: int = commission_per_mille + self._id_attrs = ( + self.type, + self.commission_per_mille, + ) + + @classmethod + def de_json( + cls, data: Optional[JSONDict], bot: Optional["Bot"] = None + ) -> Optional["TransactionPartnerAffiliateProgram"]: + """See :meth:`telegram.TransactionPartner.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["sponsor_user"] = User.de_json(data.get("sponsor_user"), bot) + + return super().de_json(data=data, bot=bot) # type: ignore[return-value] + + class TransactionPartnerFragment(TransactionPartner): """Describes a withdrawal transaction with Fragment. diff --git a/telegram/constants.py b/telegram/constants.py index 394da5adb6f..95f33083ebc 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -2644,6 +2644,11 @@ class TransactionPartnerType(StringEnum): __slots__ = () + AFFILIATE_PROGRAM = "affiliate_program" + """:obj:`str`: Transaction with Affiliate Program. + + .. versionadded:: NEXT.VERSION + """ FRAGMENT = "fragment" """:obj:`str`: Withdrawal transaction with Fragment.""" OTHER = "other" diff --git a/tests/test_stars.py b/tests/test_stars.py index dfb6e423dca..cd98b754f05 100644 --- a/tests/test_stars.py +++ b/tests/test_stars.py @@ -42,6 +42,7 @@ TransactionPartnerUser, User, ) +from telegram._payment.stars import TransactionPartnerAffiliateProgram from telegram._utils.datetime import UTC, from_timestamp, to_timestamp from telegram.constants import RevenueWithdrawalStateType, TransactionPartnerType from tests.auxil.slots import mro_slots @@ -97,6 +98,13 @@ def transaction_partner_user(): ) +def transaction_partner_affiliate_program(): + return TransactionPartnerAffiliateProgram( + sponsor_user=User(id=1, is_bot=True, first_name="first_name", username="username"), + commission_per_mille=42, + ) + + def transaction_partner_fragment(): return TransactionPartnerFragment( withdrawal_state=withdrawal_state_succeeded(), @@ -127,6 +135,7 @@ def star_transactions(): @pytest.fixture( scope="module", params=[ + TransactionPartner.AFFILIATE_PROGRAM, TransactionPartner.FRAGMENT, TransactionPartner.OTHER, TransactionPartner.TELEGRAM_ADS, @@ -141,6 +150,7 @@ def tp_scope_type(request): @pytest.fixture( scope="module", params=[ + TransactionPartnerAffiliateProgram, TransactionPartnerFragment, TransactionPartnerOther, TransactionPartnerTelegramAds, @@ -148,6 +158,7 @@ def tp_scope_type(request): TransactionPartnerUser, ], ids=[ + TransactionPartner.AFFILIATE_PROGRAM, TransactionPartner.FRAGMENT, TransactionPartner.OTHER, TransactionPartner.TELEGRAM_ADS, @@ -162,6 +173,7 @@ def tp_scope_class(request): @pytest.fixture( scope="module", params=[ + (TransactionPartnerAffiliateProgram, TransactionPartner.AFFILIATE_PROGRAM), (TransactionPartnerFragment, TransactionPartner.FRAGMENT), (TransactionPartnerOther, TransactionPartner.OTHER), (TransactionPartnerTelegramAds, TransactionPartner.TELEGRAM_ADS), @@ -169,6 +181,7 @@ def tp_scope_class(request): (TransactionPartnerUser, TransactionPartner.USER), ], ids=[ + TransactionPartner.AFFILIATE_PROGRAM, TransactionPartner.FRAGMENT, TransactionPartner.OTHER, TransactionPartner.TELEGRAM_ADS, @@ -190,6 +203,8 @@ def transaction_partner(tp_scope_class_and_type): "withdrawal_state": TransactionPartnerTestBase.withdrawal_state.to_dict(), "user": TransactionPartnerTestBase.user.to_dict(), "request_count": TransactionPartnerTestBase.request_count, + "sponsor_user": TransactionPartnerTestBase.sponsor_user.to_dict(), + "commission_per_mille": TransactionPartnerTestBase.commission_per_mille, }, bot=None, ) @@ -408,6 +423,8 @@ class TransactionPartnerTestBase: user = transaction_partner_user().user invoice_payload = "payload" request_count = 42 + sponsor_user = transaction_partner_affiliate_program().sponsor_user + commission_per_mille = transaction_partner_affiliate_program().commission_per_mille class TestTransactionPartnerWithoutRequest(TransactionPartnerTestBase): @@ -427,6 +444,8 @@ def test_de_json(self, offline_bot, tp_scope_class_and_type): "withdrawal_state": self.withdrawal_state.to_dict(), "user": self.user.to_dict(), "request_count": self.request_count, + "sponsor_user": self.sponsor_user.to_dict(), + "commission_per_mille": self.commission_per_mille, } tp = TransactionPartner.de_json(json_dict, offline_bot) assert set(tp.api_kwargs.keys()) == { @@ -434,6 +453,8 @@ def test_de_json(self, offline_bot, tp_scope_class_and_type): "withdrawal_state", "invoice_payload", "request_count", + "sponsor_user", + "commission_per_mille", } - set(cls.__slots__) assert isinstance(tp, TransactionPartner) @@ -478,6 +499,7 @@ def test_de_json_subclass(self, tp_scope_class, offline_bot): "withdrawal_state": self.withdrawal_state.to_dict(), "user": self.user.to_dict(), "request_count": self.request_count, + "commission_per_mille": self.commission_per_mille, } assert type(tp_scope_class.de_json(json_dict, offline_bot)) is tp_scope_class From 01c4354b80ccb14b260b69696eacfa94ff4962c3 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:30:44 +0100 Subject: [PATCH 04/10] `AffiliateInfo` --- telegram/__init__.py | 2 + telegram/_payment/stars.py | 93 ++++++++++++++++++++++++++ telegram/constants.py | 10 ++- tests/test_stars.py | 132 ++++++++++++++++++++++++++++++++++++- 4 files changed, 235 insertions(+), 2 deletions(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index 009a51dccd4..f84123adb16 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -20,6 +20,7 @@ __author__ = "devs@python-telegram-bot.org" __all__ = ( + "AffiliateInfo", "Animation", "Audio", "BackgroundFill", @@ -469,6 +470,7 @@ from ._payment.shippingoption import ShippingOption from ._payment.shippingquery import ShippingQuery from ._payment.stars import ( + AffiliateInfo, RevenueWithdrawalState, RevenueWithdrawalStateFailed, RevenueWithdrawalStatePending, diff --git a/telegram/_payment/stars.py b/telegram/_payment/stars.py index 7114fb1fe7f..352264dbba2 100644 --- a/telegram/_payment/stars.py +++ b/telegram/_payment/stars.py @@ -24,6 +24,7 @@ from typing import TYPE_CHECKING, Final, Optional from telegram import constants +from telegram._chat import Chat from telegram._gifts import Gift from telegram._paidmedia import PaidMedia from telegram._telegramobject import TelegramObject @@ -194,6 +195,98 @@ def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None: self._freeze() +class AffiliateInfo(TelegramObject): + """Contains information about the affiliate that received a commission via this transaction. + + Objects of this class are comparable in terms of equality. Two objects of this class are + considered equal, if their :attr:`affiliate_user`, :attr:`affiliate_chat`, + :attr:`commission_per_mille`, :attr:`amount`, and :attr:`nanostar_amount` are equal. + + .. versionadded:: NEXT.VERSION + + Args: + affiliate_user (:class:`telegram.User`, optional): The bot or the user that received an + affiliate commission if it was received by a bot or a user + affiliate_chat (:class:`telegram.Chat`, optional): The chat that received an affiliate + commission if it was received by a chat + commission_per_mille (:obj:`int`): The number of Telegram Stars received by the affiliate + for each 1000 Telegram Stars received by the bot from referred users + amount (:obj:`int`): Integer amount of Telegram Stars received by the affiliate from the + transaction, rounded to 0; can be negative for refunds + nanostar_amount (:obj:`int`, optional): The number of + :tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram + Stars received by the affiliate; from + :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MIN_AMOUNT` to + :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`; + can bee negative for refunds + + Attributes: + affiliate_user (:class:`telegram.User`): Optional. The bot or the user that received an + affiliate commission if it was received by a bot or a user + affiliate_chat (:class:`telegram.Chat`): Optional. The chat that received an affiliate + commission if it was received by a chat + commission_per_mille (:obj:`int`): The number of Telegram Stars received by the affiliate + for each 1000 Telegram Stars received by the bot from referred users + amount (:obj:`int`): Integer amount of Telegram Stars received by the affiliate from the + transaction, rounded to 0; can be negative for refunds + nanostar_amount (:obj:`int`): The number of + :tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram + Stars received by the affiliate; from + :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MIN_AMOUNT` to + :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`; + can bee negative for refunds + """ + + __slots__ = ( + "affiliate_chat", + "affiliate_user", + "amount", + "commission_per_mille", + "nanostar_amount", + ) + + def __init__( + self, + commission_per_mille: int, + amount: int, + affiliate_user: Optional["User"] = None, + affiliate_chat: Optional["Chat"] = None, + nanostar_amount: Optional[int] = None, + *, + api_kwargs: Optional[JSONDict] = None, + ) -> None: + super().__init__(api_kwargs=api_kwargs) + self.affiliate_user: Optional[User] = affiliate_user + self.affiliate_chat: Optional[Chat] = affiliate_chat + self.commission_per_mille: int = commission_per_mille + self.amount: int = amount + self.nanostar_amount: Optional[int] = nanostar_amount + + self._id_attrs = ( + self.affiliate_user, + self.affiliate_chat, + self.commission_per_mille, + self.amount, + self.nanostar_amount, + ) + self._freeze() + + @classmethod + def de_json( + cls, data: Optional[JSONDict], bot: Optional["Bot"] = None + ) -> Optional["AffiliateInfo"]: + """See :meth:`telegram.TelegramObject.de_json`.""" + data = cls._parse_data(data) + + if not data: + return None + + data["affiliate_user"] = User.de_json(data.get("affiliate_user"), bot) + data["affiliate_chat"] = Chat.de_json(data.get("affiliate_chat"), bot) + + return super().de_json(data=data, bot=bot) + + class TransactionPartner(TelegramObject): """This object describes the source of a transaction, or its recipient for outgoing transactions. Currently, it can be one of: diff --git a/telegram/constants.py b/telegram/constants.py index 95f33083ebc..a0e29abd6aa 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -2494,9 +2494,17 @@ class StarTransactionsLimit(IntEnum): """:obj:`int`: Maximum value allowed for the :paramref:`~telegram.Bot.get_star_transactions.limit` parameter of :meth:`telegram.Bot.get_star_transactions`.""" + NANOSTAR_MIN_AMOUNT = -999999999 + """:obj:`int`: Maximum value allowed for :paramref:`~telegram.AffiliateInfo.nanostar_amount` + parameter of :class:`telegram.AffiliateInfo`. + + .. versionadded:: NEXT.VERSION + """ NANOSTAR_MAX_AMOUNT = 999999999 """:obj:`int`: Maximum value allowed for :paramref:`~telegram.StarTransaction.nanostar_amount` - parameter of :class:`telegram.StarTransaction`. + parameter of :class:`telegram.StarTransaction` and + :paramref:`~telegram.AffiliateInfo.nanostar_amount` parameter of + :class:`telegram.AffiliateInfo`. .. versionadded:: NEXT.VERSION """ diff --git a/tests/test_stars.py b/tests/test_stars.py index cd98b754f05..96ac9e71ff7 100644 --- a/tests/test_stars.py +++ b/tests/test_stars.py @@ -23,6 +23,7 @@ import pytest from telegram import ( + Chat, Dice, Gift, PaidMediaPhoto, @@ -42,7 +43,7 @@ TransactionPartnerUser, User, ) -from telegram._payment.stars import TransactionPartnerAffiliateProgram +from telegram._payment.stars import AffiliateInfo, TransactionPartnerAffiliateProgram from telegram._utils.datetime import UTC, from_timestamp, to_timestamp from telegram.constants import RevenueWithdrawalStateType, TransactionPartnerType from tests.auxil.slots import mro_slots @@ -688,3 +689,132 @@ def test_equality(self, revenue_withdrawal_state, offline_bot): assert c != f assert hash(c) != hash(f) + + +@pytest.fixture +def affiliate_info(): + return AffiliateInfo( + affiliate_user=AffiliateInfoTestBase.affiliate_user, + affiliate_chat=AffiliateInfoTestBase.affiliate_chat, + commission_per_mille=AffiliateInfoTestBase.commission_per_mille, + amount=AffiliateInfoTestBase.amount, + nanostar_amount=AffiliateInfoTestBase.nanostar_amount, + ) + + +class AffiliateInfoTestBase: + affiliate_user = User(id=1, is_bot=True, first_name="affiliate_user", username="username") + affiliate_chat = Chat(id=2, type="private", title="affiliate_chat") + commission_per_mille = 13 + amount = 14 + nanostar_amount = -42 + + +class TestAffiliateInfoWithoutRequest(AffiliateInfoTestBase): + def test_slot_behaviour(self, affiliate_info): + inst = affiliate_info + 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_de_json(self, offline_bot): + json_dict = { + "affiliate_user": self.affiliate_user.to_dict(), + "affiliate_chat": self.affiliate_chat.to_dict(), + "commission_per_mille": self.commission_per_mille, + "amount": self.amount, + "nanostar_amount": self.nanostar_amount, + } + ai = AffiliateInfo.de_json(json_dict, offline_bot) + assert ai.api_kwargs == {} + assert ai.affiliate_user == self.affiliate_user + assert ai.affiliate_chat == self.affiliate_chat + assert ai.commission_per_mille == self.commission_per_mille + assert ai.amount == self.amount + assert ai.nanostar_amount == self.nanostar_amount + + assert AffiliateInfo.de_json(None, offline_bot) is None + assert AffiliateInfo.de_json({}, offline_bot) is None + + def test_to_dict(self, affiliate_info): + ai_dict = affiliate_info.to_dict() + + assert isinstance(ai_dict, dict) + assert ai_dict["affiliate_user"] == affiliate_info.affiliate_user.to_dict() + assert ai_dict["affiliate_chat"] == affiliate_info.affiliate_chat.to_dict() + assert ai_dict["commission_per_mille"] == affiliate_info.commission_per_mille + assert ai_dict["amount"] == affiliate_info.amount + assert ai_dict["nanostar_amount"] == affiliate_info.nanostar_amount + + def test_equality(self, affiliate_info, offline_bot): + a = AffiliateInfo( + affiliate_user=self.affiliate_user, + affiliate_chat=self.affiliate_chat, + commission_per_mille=self.commission_per_mille, + amount=self.amount, + nanostar_amount=self.nanostar_amount, + ) + b = AffiliateInfo( + affiliate_user=self.affiliate_user, + affiliate_chat=self.affiliate_chat, + commission_per_mille=self.commission_per_mille, + amount=self.amount, + nanostar_amount=self.nanostar_amount, + ) + c = AffiliateInfo( + affiliate_user=User(id=3, is_bot=True, first_name="first_name", username="username"), + affiliate_chat=self.affiliate_chat, + commission_per_mille=self.commission_per_mille, + amount=self.amount, + nanostar_amount=self.nanostar_amount, + ) + d = AffiliateInfo( + affiliate_user=self.affiliate_user, + affiliate_chat=Chat(id=3, type="private", title="title"), + commission_per_mille=self.commission_per_mille, + amount=self.amount, + nanostar_amount=self.nanostar_amount, + ) + e = AffiliateInfo( + affiliate_user=self.affiliate_user, + affiliate_chat=self.affiliate_chat, + commission_per_mille=1, + amount=self.amount, + nanostar_amount=self.nanostar_amount, + ) + f = AffiliateInfo( + affiliate_user=self.affiliate_user, + affiliate_chat=self.affiliate_chat, + commission_per_mille=self.commission_per_mille, + amount=1, + nanostar_amount=self.nanostar_amount, + ) + g = AffiliateInfo( + affiliate_user=self.affiliate_user, + affiliate_chat=self.affiliate_chat, + commission_per_mille=self.commission_per_mille, + amount=self.amount, + nanostar_amount=1, + ) + h = Dice(4, "emoji") + + assert a == b + assert hash(a) == hash(b) + + assert a != c + assert hash(a) != hash(c) + + assert a != d + assert hash(a) != hash(d) + + assert a != e + assert hash(a) != hash(e) + + assert a != f + assert hash(a) != hash(f) + + assert a != g + assert hash(a) != hash(g) + + assert a != h + assert hash(a) != hash(h) From 45a90010a0d55aaced5475670579c3e2f2517764 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:52:35 +0100 Subject: [PATCH 05/10] `TransactionPartnerUser.affiliate` Fix several forgotten places in `test_stars` on the fly --- telegram/_payment/stars.py | 12 +++++++++ tests/test_stars.py | 53 +++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/telegram/_payment/stars.py b/telegram/_payment/stars.py index 352264dbba2..7641a698e67 100644 --- a/telegram/_payment/stars.py +++ b/telegram/_payment/stars.py @@ -487,6 +487,10 @@ class TransactionPartnerUser(TransactionPartner): Args: user (:class:`telegram.User`): Information about the user. + affiliate (:class:`telegram.AffiliateInfo`, optional): Information about the affiliate that + received a commission via this transaction + + .. versionadded:: NEXT.VERSION invoice_payload (:obj:`str`, optional): Bot-specified invoice payload. subscription_period (:class:`datetime.timedelta`, optional): The duration of the paid subscription @@ -507,6 +511,10 @@ class TransactionPartnerUser(TransactionPartner): type (:obj:`str`): The type of the transaction partner, always :tg-const:`telegram.TransactionPartner.USER`. user (:class:`telegram.User`): Information about the user. + affiliate (:class:`telegram.AffiliateInfo`): Optional. Information about the affiliate that + received a commission via this transaction + + .. versionadded:: NEXT.VERSION invoice_payload (:obj:`str`): Optional. Bot-specified invoice payload. subscription_period (:class:`datetime.timedelta`): Optional. The duration of the paid subscription @@ -526,6 +534,7 @@ class TransactionPartnerUser(TransactionPartner): """ __slots__ = ( + "affiliate", "gift", "invoice_payload", "paid_media", @@ -542,6 +551,7 @@ def __init__( paid_media_payload: Optional[str] = None, subscription_period: Optional[dtm.timedelta] = None, gift: Optional[Gift] = None, + affiliate: Optional[AffiliateInfo] = None, *, api_kwargs: Optional[JSONDict] = None, ) -> None: @@ -549,6 +559,7 @@ def __init__( with self._unfrozen(): self.user: User = user + self.affiliate: Optional[AffiliateInfo] = affiliate self.invoice_payload: Optional[str] = invoice_payload self.paid_media: Optional[tuple[PaidMedia, ...]] = parse_sequence_arg(paid_media) self.paid_media_payload: Optional[str] = paid_media_payload @@ -571,6 +582,7 @@ def de_json( return None data["user"] = User.de_json(data.get("user"), bot) + data["affiliate"] = AffiliateInfo.de_json(data.get("affiliate"), bot) data["paid_media"] = PaidMedia.de_list(data.get("paid_media"), bot=bot) data["subscription_period"] = ( dtm.timedelta(seconds=sp) diff --git a/tests/test_stars.py b/tests/test_stars.py index 96ac9e71ff7..542f24d41a6 100644 --- a/tests/test_stars.py +++ b/tests/test_stars.py @@ -18,6 +18,7 @@ # along with this program. If not, see [http://www.gnu.org/licenses/]. import datetime +from collections.abc import Sequence from copy import deepcopy import pytest @@ -35,6 +36,7 @@ StarTransaction, StarTransactions, Sticker, + TelegramObject, TransactionPartner, TransactionPartnerFragment, TransactionPartnerOther, @@ -69,6 +71,13 @@ def withdrawal_state_pending(): def transaction_partner_user(): return TransactionPartnerUser( user=User(id=1, is_bot=False, first_name="first_name", username="username"), + affiliate=AffiliateInfo( + affiliate_user=User(id=2, is_bot=True, first_name="first_name", username="username"), + affiliate_chat=Chat(id=3, type="private", title="title"), + commission_per_mille=1, + amount=2, + nanostar_amount=3, + ), invoice_payload="payload", paid_media=[ PaidMediaPhoto( @@ -203,9 +212,14 @@ def transaction_partner(tp_scope_class_and_type): "invoice_payload": TransactionPartnerTestBase.invoice_payload, "withdrawal_state": TransactionPartnerTestBase.withdrawal_state.to_dict(), "user": TransactionPartnerTestBase.user.to_dict(), + "affiliate": TransactionPartnerTestBase.affiliate.to_dict(), "request_count": TransactionPartnerTestBase.request_count, "sponsor_user": TransactionPartnerTestBase.sponsor_user.to_dict(), "commission_per_mille": TransactionPartnerTestBase.commission_per_mille, + "gift": TransactionPartnerTestBase.gift.to_dict(), + "paid_media": [m.to_dict() for m in TransactionPartnerTestBase.paid_media], + "paid_media_payload": TransactionPartnerTestBase.paid_media_payload, + "subscription_period": TransactionPartnerTestBase.subscription_period.total_seconds(), }, bot=None, ) @@ -422,10 +436,15 @@ def test_equality(self): class TransactionPartnerTestBase: withdrawal_state = withdrawal_state_succeeded() user = transaction_partner_user().user + affiliate = transaction_partner_user().affiliate invoice_payload = "payload" request_count = 42 sponsor_user = transaction_partner_affiliate_program().sponsor_user commission_per_mille = transaction_partner_affiliate_program().commission_per_mille + gift = transaction_partner_user().gift + paid_media = transaction_partner_user().paid_media + paid_media_payload = transaction_partner_user().paid_media_payload + subscription_period = transaction_partner_user().subscription_period class TestTransactionPartnerWithoutRequest(TransactionPartnerTestBase): @@ -444,6 +463,7 @@ def test_de_json(self, offline_bot, tp_scope_class_and_type): "invoice_payload": self.invoice_payload, "withdrawal_state": self.withdrawal_state.to_dict(), "user": self.user.to_dict(), + "affiliate": self.affiliate.to_dict(), "request_count": self.request_count, "sponsor_user": self.sponsor_user.to_dict(), "commission_per_mille": self.commission_per_mille, @@ -451,6 +471,7 @@ def test_de_json(self, offline_bot, tp_scope_class_and_type): tp = TransactionPartner.de_json(json_dict, offline_bot) assert set(tp.api_kwargs.keys()) == { "user", + "affiliate", "withdrawal_state", "invoice_payload", "request_count", @@ -461,13 +482,9 @@ def test_de_json(self, offline_bot, tp_scope_class_and_type): assert isinstance(tp, TransactionPartner) assert type(tp) is cls assert tp.type == type_ - if "withdrawal_state" in cls.__slots__: - assert tp.withdrawal_state == self.withdrawal_state - if "user" in cls.__slots__: - assert tp.user == self.user - assert tp.invoice_payload == self.invoice_payload - if "request_count" in cls.__slots__: - assert tp.request_count == self.request_count + for key in json_dict: + if key in cls.__slots__: + assert getattr(tp, key) == getattr(self, key) assert cls.de_json(None, offline_bot) is None assert TransactionPartner.de_json({}, offline_bot) is None @@ -478,14 +495,20 @@ def test_de_json_invalid_type(self, offline_bot): "invoice_payload": self.invoice_payload, "withdrawal_state": self.withdrawal_state.to_dict(), "user": self.user.to_dict(), + "affiliate": self.affiliate.to_dict(), "request_count": self.request_count, + "sponsor_user": self.sponsor_user.to_dict(), + "commission_per_mille": self.commission_per_mille, } tp = TransactionPartner.de_json(json_dict, offline_bot) assert tp.api_kwargs == { "withdrawal_state": self.withdrawal_state.to_dict(), "user": self.user.to_dict(), + "affiliate": self.affiliate.to_dict(), "invoice_payload": self.invoice_payload, "request_count": self.request_count, + "sponsor_user": self.sponsor_user.to_dict(), + "commission_per_mille": self.commission_per_mille, } assert type(tp) is TransactionPartner @@ -499,6 +522,7 @@ def test_de_json_subclass(self, tp_scope_class, offline_bot): "invoice_payload": self.invoice_payload, "withdrawal_state": self.withdrawal_state.to_dict(), "user": self.user.to_dict(), + "affiliate": self.affiliate.to_dict(), "request_count": self.request_count, "commission_per_mille": self.commission_per_mille, } @@ -509,11 +533,16 @@ def test_to_dict(self, transaction_partner): assert isinstance(tp_dict, dict) assert tp_dict["type"] == transaction_partner.type - if hasattr(transaction_partner, "user"): - assert tp_dict["user"] == transaction_partner.user.to_dict() - assert tp_dict["invoice_payload"] == transaction_partner.invoice_payload - if hasattr(transaction_partner, "withdrawal_state"): - assert tp_dict["withdrawal_state"] == transaction_partner.withdrawal_state.to_dict() + for attr in transaction_partner.__slots__: + attribute = getattr(transaction_partner, attr) + if isinstance(attribute, TelegramObject): + assert tp_dict[attr] == attribute.to_dict() + elif not isinstance(attribute, str) and isinstance(attribute, Sequence): + assert tp_dict[attr] == [a.to_dict() for a in attribute] + elif isinstance(attribute, datetime.timedelta): + assert tp_dict[attr] == attribute.total_seconds() + else: + assert tp_dict[attr] == attribute def test_type_enum_conversion(self): assert type(TransactionPartner("other").type) is TransactionPartnerType From d534525164aee122b44c22c6158d03ba7c47edb7 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:57:37 +0100 Subject: [PATCH 06/10] Fix test_official --- telegram/__init__.py | 2 ++ telegram/_payment/stars.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index f84123adb16..a827670d66b 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -237,6 +237,7 @@ "TelegramObject", "TextQuote", "TransactionPartner", + "TransactionPartnerAffiliateProgram", "TransactionPartnerFragment", "TransactionPartnerOther", "TransactionPartnerTelegramAds", @@ -478,6 +479,7 @@ StarTransaction, StarTransactions, TransactionPartner, + TransactionPartnerAffiliateProgram, TransactionPartnerFragment, TransactionPartnerOther, TransactionPartnerTelegramAds, diff --git a/telegram/_payment/stars.py b/telegram/_payment/stars.py index 7641a698e67..2a528b1cd6e 100644 --- a/telegram/_payment/stars.py +++ b/telegram/_payment/stars.py @@ -401,8 +401,8 @@ class TransactionPartnerAffiliateProgram(TransactionPartner): def __init__( self, - sponsor_user: Optional["User"], commission_per_mille: int, + sponsor_user: Optional["User"] = None, *, api_kwargs: Optional[JSONDict] = None, ) -> None: From eeb3177cf35b5f081d733152bbf75b5008fb63c9 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:02:38 +0100 Subject: [PATCH 07/10] Missing docs --- docs/source/telegram.affiliateinfo.rst | 6 ++++++ docs/source/telegram.payments-tree.rst | 2 ++ docs/source/telegram.transactionpartneraffiliateprogram.rst | 6 ++++++ 3 files changed, 14 insertions(+) create mode 100644 docs/source/telegram.affiliateinfo.rst create mode 100644 docs/source/telegram.transactionpartneraffiliateprogram.rst diff --git a/docs/source/telegram.affiliateinfo.rst b/docs/source/telegram.affiliateinfo.rst new file mode 100644 index 00000000000..0b2e51863af --- /dev/null +++ b/docs/source/telegram.affiliateinfo.rst @@ -0,0 +1,6 @@ +AffiliateInfo +============= + +.. autoclass:: telegram.AffiliateInfo + :members: + :show-inheritance: \ No newline at end of file diff --git a/docs/source/telegram.payments-tree.rst b/docs/source/telegram.payments-tree.rst index 590a96fdaa5..e8ec7bd3e3b 100644 --- a/docs/source/telegram.payments-tree.rst +++ b/docs/source/telegram.payments-tree.rst @@ -9,6 +9,7 @@ Your bot can accept payments from Telegram users. Please see the `introduction t .. toctree:: :titlesonly: + telegram.affiliateinfo telegram.invoice telegram.labeledprice telegram.orderinfo @@ -25,6 +26,7 @@ Your bot can accept payments from Telegram users. Please see the `introduction t telegram.startransactions telegram.successfulpayment telegram.transactionpartner + telegram.transactionpartneraffiliateprogram telegram.transactionpartnerfragment telegram.transactionpartnerother telegram.transactionpartnertelegramads diff --git a/docs/source/telegram.transactionpartneraffiliateprogram.rst b/docs/source/telegram.transactionpartneraffiliateprogram.rst new file mode 100644 index 00000000000..dfcab6ec22b --- /dev/null +++ b/docs/source/telegram.transactionpartneraffiliateprogram.rst @@ -0,0 +1,6 @@ +TransactionPartnerAffiliateProgram +=================================== + +.. autoclass:: telegram.TransactionPartnerAffiliateProgram + :members: + :show-inheritance: \ No newline at end of file From 7dcb57b24b59c30ddc5c63eb301b6065c6f955b9 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:13:14 +0100 Subject: [PATCH 08/10] missing slots --- telegram/constants.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/telegram/constants.py b/telegram/constants.py index a0e29abd6aa..21871e0da5a 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -2470,6 +2470,8 @@ class StarTransactions(FloatEnum): .. versionadded:: NEXT.VERSION """ + __slots__ = () + NANOSTAR_VALUE = 1 / 1000000000 """:obj:`float`: The value of one nanostar as used in :attr:`telegram.StarTransaction.nanostar_amount`. From 0cc62d4eee28450d47c02b717e49c4a2495bba23 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:24:06 +0100 Subject: [PATCH 09/10] improve coverage --- telegram/_utils/enum.py | 2 +- tests/test_constants.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/telegram/_utils/enum.py b/telegram/_utils/enum.py index d7bf9d33e9e..2f0e77b9b2c 100644 --- a/telegram/_utils/enum.py +++ b/telegram/_utils/enum.py @@ -87,4 +87,4 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__}.{self.name}>" def __str__(self) -> str: - return str.__str__(self) + return str(self.value) diff --git a/tests/test_constants.py b/tests/test_constants.py index dc76bea3aef..45304a78a38 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -24,7 +24,7 @@ import pytest from telegram import Message, constants -from telegram._utils.enum import IntEnum, StringEnum +from telegram._utils.enum import FloatEnum, IntEnum, StringEnum from telegram.error import BadRequest from tests.auxil.build_messages import make_message from tests.auxil.files import data_file @@ -41,6 +41,11 @@ class IntEnumTest(IntEnum): BAR = 2 +class FloatEnumTest(FloatEnum): + FOO = 1.1 + BAR = 2.1 + + class TestConstantsWithoutRequest: """Also test _utils.enum.StringEnum on the fly because tg.constants is currently the only place where that class is used.""" @@ -69,6 +74,7 @@ def test_message_attachment_type(self): def test_to_json(self): assert json.dumps(StrEnumTest.FOO) == json.dumps("foo") assert json.dumps(IntEnumTest.FOO) == json.dumps(1) + assert json.dumps(FloatEnumTest.FOO) == json.dumps(1.1) def test_string_representation(self): # test __repr__ @@ -90,6 +96,15 @@ def test_int_representation(self): # test __str__ assert str(IntEnumTest.FOO) == "1" + def test_float_representation(self): + # test __repr__ + assert repr(FloatEnumTest.FOO) == "" + # test __format__ + assert f"{FloatEnumTest.FOO}/0 is undefined!" == "1.1/0 is undefined!" + assert f"{FloatEnumTest.FOO:*^10}" == "***1.1****" + # test __str__ + assert str(FloatEnumTest.FOO) == "1.1" + def test_string_inheritance(self): assert isinstance(StrEnumTest.FOO, str) assert StrEnumTest.FOO + StrEnumTest.BAR == "foobar" @@ -115,6 +130,18 @@ def test_int_inheritance(self): assert hash(IntEnumTest.FOO) == hash(1) + def test_float_inheritance(self): + assert isinstance(FloatEnumTest.FOO, float) + assert FloatEnumTest.FOO + FloatEnumTest.BAR == 3.2 + + assert FloatEnumTest.FOO == FloatEnumTest.FOO + assert FloatEnumTest.FOO == 1.1 + assert FloatEnumTest.FOO != FloatEnumTest.BAR + assert FloatEnumTest.FOO != 2.1 + assert object() != FloatEnumTest.FOO + + assert hash(FloatEnumTest.FOO) == hash(1.1) + def test_bot_api_version_and_info(self): assert str(constants.BOT_API_VERSION_INFO) == constants.BOT_API_VERSION assert ( From 2dd4068100a8223c747fc2ea4a02ace3d8ac4eb9 Mon Sep 17 00:00:00 2001 From: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:44:00 +0100 Subject: [PATCH 10/10] Thanks for the review! --- telegram/_payment/stars.py | 8 ++++---- telegram/constants.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/telegram/_payment/stars.py b/telegram/_payment/stars.py index 2a528b1cd6e..f62f28822a9 100644 --- a/telegram/_payment/stars.py +++ b/telegram/_payment/stars.py @@ -218,7 +218,7 @@ class AffiliateInfo(TelegramObject): Stars received by the affiliate; from :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MIN_AMOUNT` to :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`; - can bee negative for refunds + can be negative for refunds Attributes: affiliate_user (:class:`telegram.User`): Optional. The bot or the user that received an @@ -229,12 +229,12 @@ class AffiliateInfo(TelegramObject): for each 1000 Telegram Stars received by the bot from referred users amount (:obj:`int`): Integer amount of Telegram Stars received by the affiliate from the transaction, rounded to 0; can be negative for refunds - nanostar_amount (:obj:`int`): The number of + nanostar_amount (:obj:`int`): Optional. The number of :tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram Stars received by the affiliate; from :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MIN_AMOUNT` to :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`; - can bee negative for refunds + can be negative for refunds """ __slots__ = ( @@ -691,7 +691,7 @@ class StarTransaction(TelegramObject): Coincides with :attr:`SuccessfulPayment.telegram_payment_charge_id` for successful incoming payments from users. amount (:obj:`int`): Integer amount of Telegram Stars transferred by the transaction. - nanostar_amount (:obj:`int`, optional): The number of + nanostar_amount (:obj:`int`): Optional. The number of :tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram Stars transferred by the transaction; from 0 to :tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT` diff --git a/telegram/constants.py b/telegram/constants.py index 21871e0da5a..71f9f376661 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -2497,7 +2497,7 @@ class StarTransactionsLimit(IntEnum): :paramref:`~telegram.Bot.get_star_transactions.limit` parameter of :meth:`telegram.Bot.get_star_transactions`.""" NANOSTAR_MIN_AMOUNT = -999999999 - """:obj:`int`: Maximum value allowed for :paramref:`~telegram.AffiliateInfo.nanostar_amount` + """:obj:`int`: Minimum value allowed for :paramref:`~telegram.AffiliateInfo.nanostar_amount` parameter of :class:`telegram.AffiliateInfo`. .. versionadded:: NEXT.VERSION 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