Refactor Module Structure and Tests for Star Payments Classes (#4615)

This commit is contained in:
Bibo-Joshi 2024-12-29 17:58:37 +01:00 committed by GitHub
parent 4f255b6e21
commit df20e49db1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 1962 additions and 1656 deletions

View file

@ -271,6 +271,17 @@ __all__ = (
"warnings",
)
from telegram._payment.stars.startransactions import StarTransaction, StarTransactions
from telegram._payment.stars.transactionpartner import (
TransactionPartner,
TransactionPartnerAffiliateProgram,
TransactionPartnerFragment,
TransactionPartnerOther,
TransactionPartnerTelegramAds,
TransactionPartnerTelegramApi,
TransactionPartnerUser,
)
from . import _version, constants, error, helpers, request, warnings
from ._birthdate import Birthdate
from ._bot import Bot
@ -470,21 +481,12 @@ from ._payment.refundedpayment import RefundedPayment
from ._payment.shippingaddress import ShippingAddress
from ._payment.shippingoption import ShippingOption
from ._payment.shippingquery import ShippingQuery
from ._payment.stars import (
AffiliateInfo,
from ._payment.stars.affiliateinfo import AffiliateInfo
from ._payment.stars.revenuewithdrawalstate import (
RevenueWithdrawalState,
RevenueWithdrawalStateFailed,
RevenueWithdrawalStatePending,
RevenueWithdrawalStateSucceeded,
StarTransaction,
StarTransactions,
TransactionPartner,
TransactionPartnerAffiliateProgram,
TransactionPartnerFragment,
TransactionPartnerOther,
TransactionPartnerTelegramAds,
TransactionPartnerTelegramApi,
TransactionPartnerUser,
)
from ._payment.successfulpayment import SuccessfulPayment
from ._poll import InputPollOption, Poll, PollAnswer, PollOption

View file

@ -81,7 +81,7 @@ from telegram._inline.preparedinlinemessage import PreparedInlineMessage
from telegram._menubutton import MenuButton
from telegram._message import Message
from telegram._messageid import MessageId
from telegram._payment.stars import StarTransactions
from telegram._payment.stars.startransactions import StarTransactions
from telegram._poll import InputPollOption, Poll
from telegram._reaction import ReactionType, ReactionTypeCustomEmoji, ReactionTypeEmoji
from telegram._reply import ReplyParameters

View file

@ -1,795 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains the classes for Telegram Stars transactions."""
import datetime as dtm
from collections.abc import Sequence
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
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class RevenueWithdrawalState(TelegramObject):
"""This object escribes the state of a revenue withdrawal operation. Currently, it can be one
of:
* :class:`telegram.RevenueWithdrawalStatePending`
* :class:`telegram.RevenueWithdrawalStateSucceeded`
* :class:`telegram.RevenueWithdrawalStateFailed`
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.4
Args:
type (:obj:`str`): The type of the state.
Attributes:
type (:obj:`str`): The type of the state.
"""
__slots__ = ("type",)
PENDING: Final[str] = constants.RevenueWithdrawalStateType.PENDING
""":const:`telegram.constants.RevenueWithdrawalStateType.PENDING`"""
SUCCEEDED: Final[str] = constants.RevenueWithdrawalStateType.SUCCEEDED
""":const:`telegram.constants.RevenueWithdrawalStateType.SUCCEEDED`"""
FAILED: Final[str] = constants.RevenueWithdrawalStateType.FAILED
""":const:`telegram.constants.RevenueWithdrawalStateType.FAILED`"""
def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.RevenueWithdrawalStateType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["RevenueWithdrawalState"]:
"""Converts JSON data to the appropriate :class:`RevenueWithdrawalState` object, i.e. takes
care of selecting the correct subclass.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
data = cls._parse_data(data)
if not data:
return None
_class_mapping: dict[str, type[RevenueWithdrawalState]] = {
cls.PENDING: RevenueWithdrawalStatePending,
cls.SUCCEEDED: RevenueWithdrawalStateSucceeded,
cls.FAILED: RevenueWithdrawalStateFailed,
}
if cls is RevenueWithdrawalState and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
return super().de_json(data=data, bot=bot)
class RevenueWithdrawalStatePending(RevenueWithdrawalState):
"""The withdrawal is in progress.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.PENDING`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=RevenueWithdrawalState.PENDING, api_kwargs=api_kwargs)
self._freeze()
class RevenueWithdrawalStateSucceeded(RevenueWithdrawalState):
"""The withdrawal succeeded.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`date` are equal.
.. versionadded:: 21.4
Args:
date (:obj:`datetime.datetime`): Date the withdrawal was completed as a datetime object.
url (:obj:`str`): An HTTPS URL that can be used to see transaction details.
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.SUCCEEDED`.
date (:obj:`datetime.datetime`): Date the withdrawal was completed as a datetime object.
url (:obj:`str`): An HTTPS URL that can be used to see transaction details.
"""
__slots__ = ("date", "url")
def __init__(
self,
date: dtm.datetime,
url: str,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=RevenueWithdrawalState.SUCCEEDED, api_kwargs=api_kwargs)
with self._unfrozen():
self.date: dtm.datetime = date
self.url: str = url
self._id_attrs = (
self.type,
self.date,
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["RevenueWithdrawalStateSucceeded"]:
"""See :meth:`telegram.RevenueWithdrawalState.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class RevenueWithdrawalStateFailed(RevenueWithdrawalState):
"""The withdrawal failed and the transaction was refunded.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.FAILED`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=RevenueWithdrawalState.FAILED, api_kwargs=api_kwargs)
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:: 21.9
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 be 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`): 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 be 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:
* :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
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.4
Args:
type (:obj:`str`): The type of the transaction partner.
Attributes:
type (:obj:`str`): The type of the transaction partner.
"""
__slots__ = ("type",)
AFFILIATE_PROGRAM: Final[str] = constants.TransactionPartnerType.AFFILIATE_PROGRAM
""":const:`telegram.constants.TransactionPartnerType.AFFILIATE_PROGRAM`
.. versionadded:: 21.9
"""
FRAGMENT: Final[str] = constants.TransactionPartnerType.FRAGMENT
""":const:`telegram.constants.TransactionPartnerType.FRAGMENT`"""
OTHER: Final[str] = constants.TransactionPartnerType.OTHER
""":const:`telegram.constants.TransactionPartnerType.OTHER`"""
TELEGRAM_ADS: Final[str] = constants.TransactionPartnerType.TELEGRAM_ADS
""":const:`telegram.constants.TransactionPartnerType.TELEGRAM_ADS`"""
TELEGRAM_API: Final[str] = constants.TransactionPartnerType.TELEGRAM_API
""":const:`telegram.constants.TransactionPartnerType.TELEGRAM_API`"""
USER: Final[str] = constants.TransactionPartnerType.USER
""":const:`telegram.constants.TransactionPartnerType.USER`"""
def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.TransactionPartnerType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartner"]:
"""Converts JSON data to the appropriate :class:`TransactionPartner` object, i.e. takes
care of selecting the correct subclass.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
data = cls._parse_data(data)
if data is None:
return None
if not data and cls is TransactionPartner:
return None
_class_mapping: dict[str, type[TransactionPartner]] = {
cls.AFFILIATE_PROGRAM: TransactionPartnerAffiliateProgram,
cls.FRAGMENT: TransactionPartnerFragment,
cls.USER: TransactionPartnerUser,
cls.TELEGRAM_ADS: TransactionPartnerTelegramAds,
cls.TELEGRAM_API: TransactionPartnerTelegramApi,
cls.OTHER: TransactionPartnerOther,
}
if cls is TransactionPartner and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
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:: 21.9
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,
commission_per_mille: int,
sponsor_user: Optional["User"] = None,
*,
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.
.. versionadded:: 21.4
Args:
withdrawal_state (:class:`telegram.RevenueWithdrawalState`, optional): State of the
transaction if the transaction is outgoing.
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.FRAGMENT`.
withdrawal_state (:class:`telegram.RevenueWithdrawalState`): Optional. State of the
transaction if the transaction is outgoing.
"""
__slots__ = ("withdrawal_state",)
def __init__(
self,
withdrawal_state: Optional["RevenueWithdrawalState"] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=TransactionPartner.FRAGMENT, api_kwargs=api_kwargs)
with self._unfrozen():
self.withdrawal_state: Optional[RevenueWithdrawalState] = withdrawal_state
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerFragment"]:
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if not data:
return None
data["withdrawal_state"] = RevenueWithdrawalState.de_json(
data.get("withdrawal_state"), bot
)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class TransactionPartnerUser(TransactionPartner):
"""Describes a transaction with a user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`user` are equal.
.. versionadded:: 21.4
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:: 21.9
invoice_payload (:obj:`str`, optional): Bot-specified invoice payload.
subscription_period (:class:`datetime.timedelta`, optional): The duration of the paid
subscription
.. versionadded:: 21.8
paid_media (Sequence[:class:`telegram.PaidMedia`], optional): Information about the paid
media bought by the user.
.. versionadded:: 21.5
paid_media_payload (:obj:`str`, optional): Bot-specified paid media payload.
.. versionadded:: 21.6
gift (:class:`telegram.Gift`, optional): The gift sent to the user by the bot
.. versionadded:: 21.8
Attributes:
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:: 21.9
invoice_payload (:obj:`str`): Optional. Bot-specified invoice payload.
subscription_period (:class:`datetime.timedelta`): Optional. The duration of the paid
subscription
.. versionadded:: 21.8
paid_media (tuple[:class:`telegram.PaidMedia`]): Optional. Information about the paid
media bought by the user.
.. versionadded:: 21.5
paid_media_payload (:obj:`str`): Optional. Bot-specified paid media payload.
.. versionadded:: 21.6
gift (:class:`telegram.Gift`): Optional. The gift sent to the user by the bot
.. versionadded:: 21.8
"""
__slots__ = (
"affiliate",
"gift",
"invoice_payload",
"paid_media",
"paid_media_payload",
"subscription_period",
"user",
)
def __init__(
self,
user: "User",
invoice_payload: Optional[str] = None,
paid_media: Optional[Sequence[PaidMedia]] = None,
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:
super().__init__(type=TransactionPartner.USER, api_kwargs=api_kwargs)
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
self.subscription_period: Optional[dtm.timedelta] = subscription_period
self.gift: Optional[Gift] = gift
self._id_attrs = (
self.type,
self.user,
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerUser"]:
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if not data:
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)
if (sp := data.get("subscription_period")) is not None
else None
)
data["gift"] = Gift.de_json(data.get("gift"), bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class TransactionPartnerOther(TransactionPartner):
"""Describes a transaction with an unknown partner.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.OTHER`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.OTHER, api_kwargs=api_kwargs)
self._freeze()
class TransactionPartnerTelegramAds(TransactionPartner):
"""Describes a withdrawal transaction to the Telegram Ads platform.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.TELEGRAM_ADS`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.TELEGRAM_ADS, api_kwargs=api_kwargs)
self._freeze()
class TransactionPartnerTelegramApi(TransactionPartner):
"""Describes a transaction with payment for
`paid broadcasting <https://core.telegram.org/bots/api#paid-broadcasts>`_.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_count` is equal.
.. versionadded:: 21.7
Args:
request_count (:obj:`int`): The number of successful requests that exceeded regular limits
and were therefore billed.
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.TELEGRAM_API`.
request_count (:obj:`int`): The number of successful requests that exceeded regular limits
and were therefore billed.
"""
__slots__ = ("request_count",)
def __init__(self, request_count: int, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.TELEGRAM_API, api_kwargs=api_kwargs)
with self._unfrozen():
self.request_count: int = request_count
self._id_attrs = (self.request_count,)
class StarTransaction(TelegramObject):
"""Describes a Telegram Star transaction.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id`, :attr:`source`, and :attr:`receiver` are equal.
.. versionadded:: 21.4
Args:
id (:obj:`str`): Unique identifier of the transaction. Coincides with the identifer
of the original transaction for refund transactions.
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
: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:: 21.9
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).
Only for incoming transactions.
receiver (:class:`telegram.TransactionPartner`, optional): Receiver of an outgoing
transaction (e.g., a user for a purchase refund, Fragment for a withdrawal). Only for
outgoing transactions.
Attributes:
id (:obj:`str`): Unique identifier of the transaction. Coincides with the identifer
of the original transaction for refund transactions.
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
: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:: 21.9
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).
Only for incoming transactions.
receiver (:class:`telegram.TransactionPartner`): Optional. Receiver of an outgoing
transaction (e.g., a user for a purchase refund, Fragment for a withdrawal). Only for
outgoing transactions.
"""
__slots__ = ("amount", "date", "id", "nanostar_amount", "receiver", "source")
def __init__(
self,
id: str,
amount: int,
date: dtm.datetime,
source: Optional[TransactionPartner] = None,
receiver: Optional[TransactionPartner] = None,
nanostar_amount: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.amount: int = amount
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,
self.source,
self.receiver,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StarTransaction"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
data["source"] = TransactionPartner.de_json(data.get("source"), bot)
data["receiver"] = TransactionPartner.de_json(data.get("receiver"), bot)
return super().de_json(data=data, bot=bot)
class StarTransactions(TelegramObject):
"""
Contains a list of Telegram Star transactions.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`transactions` are equal.
.. versionadded:: 21.4
Args:
transactions (Sequence[:class:`telegram.StarTransaction`]): The list of transactions.
Attributes:
transactions (tuple[:class:`telegram.StarTransaction`]): The list of transactions.
"""
__slots__ = ("transactions",)
def __init__(
self, transactions: Sequence[StarTransaction], *, api_kwargs: Optional[JSONDict] = None
):
super().__init__(api_kwargs=api_kwargs)
self.transactions: tuple[StarTransaction, ...] = parse_sequence_arg(transactions)
self._id_attrs = (self.transactions,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StarTransactions"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["transactions"] = StarTransaction.de_list(data.get("transactions"), bot)
return super().de_json(data=data, bot=bot)

View file

View file

@ -0,0 +1,121 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains the classes for Telegram Stars affiliates."""
from typing import TYPE_CHECKING, Optional
from telegram._chat import Chat
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
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:: 21.9
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 be 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`): 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 be 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)

View file

@ -0,0 +1,188 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains the classes for Telegram Stars Revenue Withdrawals."""
import datetime as dtm
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils import enum
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
if TYPE_CHECKING:
from telegram import Bot
class RevenueWithdrawalState(TelegramObject):
"""This object describes the state of a revenue withdrawal operation. Currently, it can be one
of:
* :class:`telegram.RevenueWithdrawalStatePending`
* :class:`telegram.RevenueWithdrawalStateSucceeded`
* :class:`telegram.RevenueWithdrawalStateFailed`
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.4
Args:
type (:obj:`str`): The type of the state.
Attributes:
type (:obj:`str`): The type of the state.
"""
__slots__ = ("type",)
PENDING: Final[str] = constants.RevenueWithdrawalStateType.PENDING
""":const:`telegram.constants.RevenueWithdrawalStateType.PENDING`"""
SUCCEEDED: Final[str] = constants.RevenueWithdrawalStateType.SUCCEEDED
""":const:`telegram.constants.RevenueWithdrawalStateType.SUCCEEDED`"""
FAILED: Final[str] = constants.RevenueWithdrawalStateType.FAILED
""":const:`telegram.constants.RevenueWithdrawalStateType.FAILED`"""
def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.RevenueWithdrawalStateType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["RevenueWithdrawalState"]:
"""Converts JSON data to the appropriate :class:`RevenueWithdrawalState` object, i.e. takes
care of selecting the correct subclass.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
data = cls._parse_data(data)
if (cls is RevenueWithdrawalState and not data) or data is None:
return None
_class_mapping: dict[str, type[RevenueWithdrawalState]] = {
cls.PENDING: RevenueWithdrawalStatePending,
cls.SUCCEEDED: RevenueWithdrawalStateSucceeded,
cls.FAILED: RevenueWithdrawalStateFailed,
}
if cls is RevenueWithdrawalState and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
return super().de_json(data=data, bot=bot)
class RevenueWithdrawalStatePending(RevenueWithdrawalState):
"""The withdrawal is in progress.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.PENDING`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=RevenueWithdrawalState.PENDING, api_kwargs=api_kwargs)
self._freeze()
class RevenueWithdrawalStateSucceeded(RevenueWithdrawalState):
"""The withdrawal succeeded.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`date` are equal.
.. versionadded:: 21.4
Args:
date (:obj:`datetime.datetime`): Date the withdrawal was completed as a datetime object.
url (:obj:`str`): An HTTPS URL that can be used to see transaction details.
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.SUCCEEDED`.
date (:obj:`datetime.datetime`): Date the withdrawal was completed as a datetime object.
url (:obj:`str`): An HTTPS URL that can be used to see transaction details.
"""
__slots__ = ("date", "url")
def __init__(
self,
date: dtm.datetime,
url: str,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=RevenueWithdrawalState.SUCCEEDED, api_kwargs=api_kwargs)
with self._unfrozen():
self.date: dtm.datetime = date
self.url: str = url
self._id_attrs = (
self.type,
self.date,
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["RevenueWithdrawalStateSucceeded"]:
"""See :meth:`telegram.RevenueWithdrawalState.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class RevenueWithdrawalStateFailed(RevenueWithdrawalState):
"""The withdrawal failed and the transaction was refunded.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the state, always
:tg-const:`telegram.RevenueWithdrawalState.FAILED`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=RevenueWithdrawalState.FAILED, api_kwargs=api_kwargs)
self._freeze()

View file

@ -0,0 +1,172 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains the classes for Telegram Stars transactions."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict
from .transactionpartner import TransactionPartner
if TYPE_CHECKING:
from telegram import Bot
class StarTransaction(TelegramObject):
"""Describes a Telegram Star transaction.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`id`, :attr:`source`, and :attr:`receiver` are equal.
.. versionadded:: 21.4
Args:
id (:obj:`str`): Unique identifier of the transaction. Coincides with the identifer
of the original transaction for refund transactions.
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
: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:: 21.9
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).
Only for incoming transactions.
receiver (:class:`telegram.TransactionPartner`, optional): Receiver of an outgoing
transaction (e.g., a user for a purchase refund, Fragment for a withdrawal). Only for
outgoing transactions.
Attributes:
id (:obj:`str`): Unique identifier of the transaction. Coincides with the identifer
of the original transaction for refund transactions.
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
: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:: 21.9
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).
Only for incoming transactions.
receiver (:class:`telegram.TransactionPartner`): Optional. Receiver of an outgoing
transaction (e.g., a user for a purchase refund, Fragment for a withdrawal). Only for
outgoing transactions.
"""
__slots__ = ("amount", "date", "id", "nanostar_amount", "receiver", "source")
def __init__(
self,
id: str,
amount: int,
date: dtm.datetime,
source: Optional[TransactionPartner] = None,
receiver: Optional[TransactionPartner] = None,
nanostar_amount: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(api_kwargs=api_kwargs)
self.id: str = id
self.amount: int = amount
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,
self.source,
self.receiver,
)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StarTransaction"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if not data:
return None
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["date"] = from_timestamp(data.get("date", None), tzinfo=loc_tzinfo)
data["source"] = TransactionPartner.de_json(data.get("source"), bot)
data["receiver"] = TransactionPartner.de_json(data.get("receiver"), bot)
return super().de_json(data=data, bot=bot)
class StarTransactions(TelegramObject):
"""
Contains a list of Telegram Star transactions.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`transactions` are equal.
.. versionadded:: 21.4
Args:
transactions (Sequence[:class:`telegram.StarTransaction`]): The list of transactions.
Attributes:
transactions (tuple[:class:`telegram.StarTransaction`]): The list of transactions.
"""
__slots__ = ("transactions",)
def __init__(
self, transactions: Sequence[StarTransaction], *, api_kwargs: Optional[JSONDict] = None
):
super().__init__(api_kwargs=api_kwargs)
self.transactions: tuple[StarTransaction, ...] = parse_sequence_arg(transactions)
self._id_attrs = (self.transactions,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["StarTransactions"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["transactions"] = StarTransaction.de_list(data.get("transactions"), bot)
return super().de_json(data=data, bot=bot)

View file

@ -0,0 +1,405 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains the classes for Telegram Stars transaction partners."""
import datetime as dtm
from collections.abc import Sequence
from typing import TYPE_CHECKING, Final, Optional
from telegram import constants
from telegram._gifts import Gift
from telegram._paidmedia import PaidMedia
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.types import JSONDict
from .affiliateinfo import AffiliateInfo
from .revenuewithdrawalstate import RevenueWithdrawalState
if TYPE_CHECKING:
from telegram import Bot
class TransactionPartner(TelegramObject):
"""This object describes the source of a transaction, or its recipient for outgoing
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
considered equal, if their :attr:`type` is equal.
.. versionadded:: 21.4
Args:
type (:obj:`str`): The type of the transaction partner.
Attributes:
type (:obj:`str`): The type of the transaction partner.
"""
__slots__ = ("type",)
AFFILIATE_PROGRAM: Final[str] = constants.TransactionPartnerType.AFFILIATE_PROGRAM
""":const:`telegram.constants.TransactionPartnerType.AFFILIATE_PROGRAM`
.. versionadded:: 21.9
"""
FRAGMENT: Final[str] = constants.TransactionPartnerType.FRAGMENT
""":const:`telegram.constants.TransactionPartnerType.FRAGMENT`"""
OTHER: Final[str] = constants.TransactionPartnerType.OTHER
""":const:`telegram.constants.TransactionPartnerType.OTHER`"""
TELEGRAM_ADS: Final[str] = constants.TransactionPartnerType.TELEGRAM_ADS
""":const:`telegram.constants.TransactionPartnerType.TELEGRAM_ADS`"""
TELEGRAM_API: Final[str] = constants.TransactionPartnerType.TELEGRAM_API
""":const:`telegram.constants.TransactionPartnerType.TELEGRAM_API`"""
USER: Final[str] = constants.TransactionPartnerType.USER
""":const:`telegram.constants.TransactionPartnerType.USER`"""
def __init__(self, type: str, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(api_kwargs=api_kwargs)
self.type: str = enum.get_member(constants.TransactionPartnerType, type, type)
self._id_attrs = (self.type,)
self._freeze()
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartner"]:
"""Converts JSON data to the appropriate :class:`TransactionPartner` object, i.e. takes
care of selecting the correct subclass.
Args:
data (dict[:obj:`str`, ...]): The JSON data.
bot (:class:`telegram.Bot`): The bot associated with this object.
Returns:
The Telegram object.
"""
data = cls._parse_data(data)
if (cls is TransactionPartner and not data) or data is None:
return None
_class_mapping: dict[str, type[TransactionPartner]] = {
cls.AFFILIATE_PROGRAM: TransactionPartnerAffiliateProgram,
cls.FRAGMENT: TransactionPartnerFragment,
cls.USER: TransactionPartnerUser,
cls.TELEGRAM_ADS: TransactionPartnerTelegramAds,
cls.TELEGRAM_API: TransactionPartnerTelegramApi,
cls.OTHER: TransactionPartnerOther,
}
if cls is TransactionPartner and data.get("type") in _class_mapping:
return _class_mapping[data.pop("type")].de_json(data=data, bot=bot)
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:: 21.9
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,
commission_per_mille: int,
sponsor_user: Optional["User"] = None,
*,
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.
.. versionadded:: 21.4
Args:
withdrawal_state (:class:`telegram.RevenueWithdrawalState`, optional): State of the
transaction if the transaction is outgoing.
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.FRAGMENT`.
withdrawal_state (:class:`telegram.RevenueWithdrawalState`): Optional. State of the
transaction if the transaction is outgoing.
"""
__slots__ = ("withdrawal_state",)
def __init__(
self,
withdrawal_state: Optional["RevenueWithdrawalState"] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
super().__init__(type=TransactionPartner.FRAGMENT, api_kwargs=api_kwargs)
with self._unfrozen():
self.withdrawal_state: Optional[RevenueWithdrawalState] = withdrawal_state
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerFragment"]:
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if data is None:
return None
data["withdrawal_state"] = RevenueWithdrawalState.de_json(
data.get("withdrawal_state"), bot
)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class TransactionPartnerUser(TransactionPartner):
"""Describes a transaction with a user.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`user` are equal.
.. versionadded:: 21.4
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:: 21.9
invoice_payload (:obj:`str`, optional): Bot-specified invoice payload.
subscription_period (:class:`datetime.timedelta`, optional): The duration of the paid
subscription
.. versionadded:: 21.8
paid_media (Sequence[:class:`telegram.PaidMedia`], optional): Information about the paid
media bought by the user.
.. versionadded:: 21.5
paid_media_payload (:obj:`str`, optional): Bot-specified paid media payload.
.. versionadded:: 21.6
gift (:class:`telegram.Gift`, optional): The gift sent to the user by the bot
.. versionadded:: 21.8
Attributes:
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:: 21.9
invoice_payload (:obj:`str`): Optional. Bot-specified invoice payload.
subscription_period (:class:`datetime.timedelta`): Optional. The duration of the paid
subscription
.. versionadded:: 21.8
paid_media (tuple[:class:`telegram.PaidMedia`]): Optional. Information about the paid
media bought by the user.
.. versionadded:: 21.5
paid_media_payload (:obj:`str`): Optional. Bot-specified paid media payload.
.. versionadded:: 21.6
gift (:class:`telegram.Gift`): Optional. The gift sent to the user by the bot
.. versionadded:: 21.8
"""
__slots__ = (
"affiliate",
"gift",
"invoice_payload",
"paid_media",
"paid_media_payload",
"subscription_period",
"user",
)
def __init__(
self,
user: "User",
invoice_payload: Optional[str] = None,
paid_media: Optional[Sequence[PaidMedia]] = None,
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:
super().__init__(type=TransactionPartner.USER, api_kwargs=api_kwargs)
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
self.subscription_period: Optional[dtm.timedelta] = subscription_period
self.gift: Optional[Gift] = gift
self._id_attrs = (
self.type,
self.user,
)
@classmethod
def de_json(
cls, data: Optional[JSONDict], bot: Optional["Bot"] = None
) -> Optional["TransactionPartnerUser"]:
"""See :meth:`telegram.TransactionPartner.de_json`."""
data = cls._parse_data(data)
if not data:
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)
if (sp := data.get("subscription_period")) is not None
else None
)
data["gift"] = Gift.de_json(data.get("gift"), bot)
return super().de_json(data=data, bot=bot) # type: ignore[return-value]
class TransactionPartnerOther(TransactionPartner):
"""Describes a transaction with an unknown partner.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.OTHER`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.OTHER, api_kwargs=api_kwargs)
self._freeze()
class TransactionPartnerTelegramAds(TransactionPartner):
"""Describes a withdrawal transaction to the Telegram Ads platform.
.. versionadded:: 21.4
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.TELEGRAM_ADS`.
"""
__slots__ = ()
def __init__(self, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.TELEGRAM_ADS, api_kwargs=api_kwargs)
self._freeze()
class TransactionPartnerTelegramApi(TransactionPartner):
"""Describes a transaction with payment for
`paid broadcasting <https://core.telegram.org/bots/api#paid-broadcasts>`_.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`request_count` is equal.
.. versionadded:: 21.7
Args:
request_count (:obj:`int`): The number of successful requests that exceeded regular limits
and were therefore billed.
Attributes:
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.TELEGRAM_API`.
request_count (:obj:`int`): The number of successful requests that exceeded regular limits
and were therefore billed.
"""
__slots__ = ("request_count",)
def __init__(self, request_count: int, *, api_kwargs: Optional[JSONDict] = None) -> None:
super().__init__(type=TransactionPartner.TELEGRAM_API, api_kwargs=api_kwargs)
with self._unfrozen():
self.request_count: int = request_count
self._id_attrs = (self.request_count,)

View file

View file

@ -0,0 +1,151 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import AffiliateInfo, Chat, Dice, User
from tests.auxil.slots import mro_slots
@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)

View file

@ -0,0 +1,235 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime as dtm
import pytest
from telegram import (
Dice,
RevenueWithdrawalState,
RevenueWithdrawalStateFailed,
RevenueWithdrawalStatePending,
RevenueWithdrawalStateSucceeded,
)
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import RevenueWithdrawalStateType
from tests.auxil.slots import mro_slots
@pytest.fixture
def revenue_withdrawal_state():
return RevenueWithdrawalState(RevenueWithdrawalStateTestBase.state)
@pytest.fixture
def revenue_withdrawal_state_pending():
return RevenueWithdrawalStatePending()
@pytest.fixture
def revenue_withdrawal_state_succeeded():
return RevenueWithdrawalStateSucceeded(
date=RevenueWithdrawalStateTestBase.date, url=RevenueWithdrawalStateTestBase.url
)
@pytest.fixture
def revenue_withdrawal_state_failed():
return RevenueWithdrawalStateFailed()
class RevenueWithdrawalStateTestBase:
state = "failed"
date = dtm.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC)
url = "url"
class TestRevenueWithdrawalStateWithoutRequest(RevenueWithdrawalStateTestBase):
def test_slot_behaviour(self, revenue_withdrawal_state):
inst = revenue_withdrawal_state
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_type_enum_conversion(self):
assert type(RevenueWithdrawalState("failed").type) is RevenueWithdrawalStateType
assert RevenueWithdrawalState("unknown").type == "unknown"
def test_de_json(self, offline_bot):
json_dict = {"type": "unknown"}
rws = RevenueWithdrawalState.de_json(json_dict, offline_bot)
assert rws.api_kwargs == {}
assert rws.type == "unknown"
assert RevenueWithdrawalState.de_json(None, offline_bot) is None
assert RevenueWithdrawalState.de_json({}, offline_bot) is None
@pytest.mark.parametrize(
("state", "subclass"),
[
("pending", RevenueWithdrawalStatePending),
("succeeded", RevenueWithdrawalStateSucceeded),
("failed", RevenueWithdrawalStateFailed),
],
)
def test_de_json_subclass(self, offline_bot, state, subclass):
json_dict = {"type": state, "date": to_timestamp(self.date), "url": self.url}
rws = RevenueWithdrawalState.de_json(json_dict, offline_bot)
assert type(rws) is subclass
assert set(rws.api_kwargs.keys()) == {"date", "url"} - set(subclass.__slots__)
assert rws.type == state
def test_to_dict(self, revenue_withdrawal_state):
json_dict = revenue_withdrawal_state.to_dict()
assert json_dict == {"type": self.state}
def test_equality(self, revenue_withdrawal_state):
a = revenue_withdrawal_state
b = RevenueWithdrawalState(self.state)
c = RevenueWithdrawalState("pending")
d = Dice(value=1, 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)
class TestRevenueWithdrawalStatePendingWithoutRequest(RevenueWithdrawalStateTestBase):
def test_slot_behaviour(self, revenue_withdrawal_state_pending):
inst = revenue_withdrawal_state_pending
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 = {}
rws = RevenueWithdrawalStatePending.de_json(json_dict, offline_bot)
assert rws.api_kwargs == {}
assert rws.type == "pending"
assert RevenueWithdrawalStatePending.de_json(None, offline_bot) is None
def test_to_dict(self, revenue_withdrawal_state_pending):
json_dict = revenue_withdrawal_state_pending.to_dict()
assert json_dict == {"type": "pending"}
def test_equality(self, revenue_withdrawal_state_pending):
a = revenue_withdrawal_state_pending
b = RevenueWithdrawalStatePending()
c = RevenueWithdrawalState("unknown")
d = Dice(value=1, 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)
class TestRevenueWithdrawalStateSucceededWithoutRequest(RevenueWithdrawalStateTestBase):
state = RevenueWithdrawalStateType.SUCCEEDED
def test_slot_behaviour(self, revenue_withdrawal_state_succeeded):
inst = revenue_withdrawal_state_succeeded
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 = {"date": to_timestamp(self.date), "url": self.url}
rws = RevenueWithdrawalStateSucceeded.de_json(json_dict, offline_bot)
assert rws.api_kwargs == {}
assert rws.type == "succeeded"
assert rws.date == self.date
assert rws.url == self.url
assert RevenueWithdrawalStateSucceeded.de_json(None, offline_bot) is None
def test_to_dict(self, revenue_withdrawal_state_succeeded):
json_dict = revenue_withdrawal_state_succeeded.to_dict()
assert json_dict["type"] == "succeeded"
assert json_dict["date"] == to_timestamp(self.date)
assert json_dict["url"] == self.url
def test_equality(self, revenue_withdrawal_state_succeeded):
a = revenue_withdrawal_state_succeeded
b = RevenueWithdrawalStateSucceeded(date=self.date, url=self.url)
c = RevenueWithdrawalStateSucceeded(date=self.date, url="other_url")
d = RevenueWithdrawalStateSucceeded(
date=dtm.datetime(1900, 1, 1, 0, 0, 0, 0, tzinfo=UTC), url=self.url
)
e = Dice(value=1, 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)
class TestRevenueWithdrawalStateFailedWithoutRequest(RevenueWithdrawalStateTestBase):
state = RevenueWithdrawalStateType.FAILED
def test_slot_behaviour(self, revenue_withdrawal_state_failed):
inst = revenue_withdrawal_state_failed
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 = {}
rws = RevenueWithdrawalStateFailed.de_json(json_dict, offline_bot)
assert rws.api_kwargs == {}
assert rws.type == "failed"
assert RevenueWithdrawalStateFailed.de_json(None, offline_bot) is None
def test_to_dict(self, revenue_withdrawal_state_failed):
json_dict = revenue_withdrawal_state_failed.to_dict()
assert json_dict == {"type": "failed"}
def test_equality(self, revenue_withdrawal_state_failed):
a = revenue_withdrawal_state_failed
b = RevenueWithdrawalStateFailed()
c = RevenueWithdrawalState("unknown")
d = Dice(value=1, 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)

View file

@ -0,0 +1,207 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime as dtm
import pytest
from telegram import (
StarTransaction,
StarTransactions,
TransactionPartnerOther,
TransactionPartnerUser,
User,
)
from telegram._utils.datetime import UTC, from_timestamp, to_timestamp
from tests.auxil.slots import mro_slots
def star_transaction_factory():
return StarTransaction(
id=StarTransactionTestBase.id,
amount=StarTransactionTestBase.amount,
nanostar_amount=StarTransactionTestBase.nanostar_amount,
date=from_timestamp(StarTransactionTestBase.date),
source=StarTransactionTestBase.source,
receiver=StarTransactionTestBase.receiver,
)
@pytest.fixture
def star_transaction():
return star_transaction_factory()
@pytest.fixture
def star_transactions():
return StarTransactions(
transactions=[
star_transaction_factory(),
star_transaction_factory(),
]
)
class StarTransactionTestBase:
id = "2"
amount = 2
nanostar_amount = 365
date = to_timestamp(dtm.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC))
source = TransactionPartnerUser(
user=User(
id=2,
is_bot=False,
first_name="first_name",
),
)
receiver = TransactionPartnerOther()
class TestStarTransactionWithoutRequest(StarTransactionTestBase):
def test_slot_behaviour(self, star_transaction):
inst = star_transaction
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 = {
"id": self.id,
"amount": self.amount,
"nanostar_amount": self.nanostar_amount,
"date": self.date,
"source": self.source.to_dict(),
"receiver": self.receiver.to_dict(),
}
st = StarTransaction.de_json(json_dict, offline_bot)
st_none = StarTransaction.de_json(None, 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
assert st_none is None
def test_de_json_star_transaction_localization(
self, tz_bot, offline_bot, raw_bot, star_transaction
):
json_dict = star_transaction.to_dict()
st_raw = StarTransaction.de_json(json_dict, raw_bot)
st_bot = StarTransaction.de_json(json_dict, offline_bot)
st_tz = StarTransaction.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
st_offset = st_tz.date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(st_tz.date.replace(tzinfo=None))
assert st_raw.date.tzinfo == UTC
assert st_bot.date.tzinfo == UTC
assert st_offset == tz_bot_offset
def test_to_dict(self, star_transaction):
expected_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(),
}
assert star_transaction.to_dict() == expected_dict
def test_equality(self):
a = StarTransaction(
id=self.id,
amount=self.amount,
date=self.date,
source=self.source,
receiver=self.receiver,
)
b = StarTransaction(
id=self.id,
amount=self.amount,
date=None,
source=self.source,
receiver=self.receiver,
)
c = StarTransaction(
id="3",
amount=3,
date=to_timestamp(dtm.datetime.utcnow()),
source=TransactionPartnerUser(
user=User(
id=3,
is_bot=False,
first_name="first_name",
),
),
receiver=TransactionPartnerOther(),
)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
class StarTransactionsTestBase:
transactions = [star_transaction_factory(), star_transaction_factory()]
class TestStarTransactionsWithoutRequest(StarTransactionsTestBase):
def test_slot_behaviour(self, star_transactions):
inst = star_transactions
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 = {
"transactions": [t.to_dict() for t in self.transactions],
}
st = StarTransactions.de_json(json_dict, offline_bot)
st_none = StarTransactions.de_json(None, offline_bot)
assert st.api_kwargs == {}
assert st.transactions == tuple(self.transactions)
assert st_none is None
def test_to_dict(self, star_transactions):
expected_dict = {
"transactions": [t.to_dict() for t in self.transactions],
}
assert star_transactions.to_dict() == expected_dict
def test_equality(self, star_transaction):
a = StarTransactions(
transactions=self.transactions,
)
b = StarTransactions(
transactions=self.transactions,
)
c = StarTransactions(
transactions=[star_transaction],
)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)

View file

@ -0,0 +1,469 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime as dtm
import pytest
from telegram import (
AffiliateInfo,
Gift,
PaidMediaVideo,
RevenueWithdrawalStatePending,
Sticker,
TransactionPartner,
TransactionPartnerAffiliateProgram,
TransactionPartnerFragment,
TransactionPartnerOther,
TransactionPartnerTelegramAds,
TransactionPartnerTelegramApi,
TransactionPartnerUser,
User,
Video,
)
from telegram.constants import TransactionPartnerType
from tests.auxil.slots import mro_slots
@pytest.fixture
def transaction_partner():
return TransactionPartner(TransactionPartnerTestBase.type)
class TransactionPartnerTestBase:
type = TransactionPartnerType.AFFILIATE_PROGRAM
commission_per_mille = 42
sponsor_user = User(
id=1,
is_bot=False,
first_name="sponsor",
last_name="user",
)
withdrawal_state = RevenueWithdrawalStatePending()
user = User(
id=2,
is_bot=False,
first_name="user",
last_name="user",
)
invoice_payload = "invoice_payload"
paid_media = (
PaidMediaVideo(
video=Video(
file_id="file_id",
file_unique_id="file_unique_id",
width=10,
height=10,
duration=10,
)
),
)
paid_media_payload = "paid_media_payload"
subscription_period = dtm.timedelta(days=1)
gift = Gift(
id="gift_id",
sticker=Sticker(
file_id="file_id",
file_unique_id="file_unique_id",
width=10,
height=10,
is_animated=False,
is_video=False,
type="type",
),
total_count=42,
remaining_count=42,
star_count=42,
)
affiliate = AffiliateInfo(
commission_per_mille=42,
amount=42,
)
request_count = 42
class TestTransactionPartnerWithoutRequest(TransactionPartnerTestBase):
def test_slot_behaviour(self, transaction_partner):
inst = transaction_partner
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_type_enum_conversion(self, transaction_partner):
assert type(TransactionPartner("affiliate_program").type) is TransactionPartnerType
assert TransactionPartner("unknown").type == "unknown"
def test_de_json(self, offline_bot):
data = {"type": "unknown"}
transaction_partner = TransactionPartner.de_json(data, offline_bot)
assert transaction_partner.api_kwargs == {}
assert transaction_partner.type == "unknown"
assert TransactionPartner.de_json(None, offline_bot) is None
assert TransactionPartner.de_json({}, offline_bot) is None
@pytest.mark.parametrize(
("tp_type", "subclass"),
[
("affiliate_program", TransactionPartnerAffiliateProgram),
("fragment", TransactionPartnerFragment),
("user", TransactionPartnerUser),
("telegram_ads", TransactionPartnerTelegramAds),
("telegram_api", TransactionPartnerTelegramApi),
("other", TransactionPartnerOther),
],
)
def test_subclass(self, offline_bot, tp_type, subclass):
json_dict = {
"type": tp_type,
"commission_per_mille": self.commission_per_mille,
"user": self.user.to_dict(),
"request_count": self.request_count,
}
tp = TransactionPartner.de_json(json_dict, offline_bot)
assert type(tp) is subclass
assert set(tp.api_kwargs.keys()) == set(json_dict.keys()) - set(subclass.__slots__) - {
"type"
}
assert tp.type == tp_type
def test_to_dict(self, transaction_partner):
data = transaction_partner.to_dict()
assert data == {"type": self.type}
def test_equality(self, transaction_partner):
a = transaction_partner
b = TransactionPartner(self.type)
c = TransactionPartner("unknown")
d = User(id=1, is_bot=False, first_name="user", last_name="user")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
@pytest.fixture
def transaction_partner_affiliate_program():
return TransactionPartnerAffiliateProgram(
commission_per_mille=TransactionPartnerTestBase.commission_per_mille,
sponsor_user=TransactionPartnerTestBase.sponsor_user,
)
class TestTransactionPartnerAffiliateProgramWithoutRequest(TransactionPartnerTestBase):
type = TransactionPartnerType.AFFILIATE_PROGRAM
def test_slot_behaviour(self, transaction_partner_affiliate_program):
inst = transaction_partner_affiliate_program
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 = {
"commission_per_mille": self.commission_per_mille,
"sponsor_user": self.sponsor_user.to_dict(),
}
tp = TransactionPartnerAffiliateProgram.de_json(json_dict, offline_bot)
assert tp.api_kwargs == {}
assert tp.type == "affiliate_program"
assert tp.commission_per_mille == self.commission_per_mille
assert tp.sponsor_user == self.sponsor_user
assert TransactionPartnerAffiliateProgram.de_json(None, offline_bot) is None
assert TransactionPartnerAffiliateProgram.de_json({}, offline_bot) is None
def test_to_dict(self, transaction_partner_affiliate_program):
json_dict = transaction_partner_affiliate_program.to_dict()
assert json_dict["type"] == self.type
assert json_dict["commission_per_mille"] == self.commission_per_mille
assert json_dict["sponsor_user"] == self.sponsor_user.to_dict()
def test_equality(self, transaction_partner_affiliate_program):
a = transaction_partner_affiliate_program
b = TransactionPartnerAffiliateProgram(
commission_per_mille=self.commission_per_mille,
)
c = TransactionPartnerAffiliateProgram(
commission_per_mille=0,
)
d = User(id=1, is_bot=False, first_name="user", last_name="user")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
@pytest.fixture
def transaction_partner_fragment():
return TransactionPartnerFragment(
withdrawal_state=TransactionPartnerTestBase.withdrawal_state,
)
class TestTransactionPartnerFragmentWithoutRequest(TransactionPartnerTestBase):
type = TransactionPartnerType.FRAGMENT
def test_slot_behaviour(self, transaction_partner_fragment):
inst = transaction_partner_fragment
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 = {"withdrawal_state": self.withdrawal_state.to_dict()}
tp = TransactionPartnerFragment.de_json(json_dict, offline_bot)
assert tp.api_kwargs == {}
assert tp.type == "fragment"
assert tp.withdrawal_state == self.withdrawal_state
assert TransactionPartnerFragment.de_json(None, offline_bot) is None
def test_to_dict(self, transaction_partner_fragment):
json_dict = transaction_partner_fragment.to_dict()
assert json_dict["type"] == self.type
assert json_dict["withdrawal_state"] == self.withdrawal_state.to_dict()
def test_equality(self, transaction_partner_fragment):
a = transaction_partner_fragment
b = TransactionPartnerFragment(withdrawal_state=self.withdrawal_state)
c = TransactionPartnerFragment(withdrawal_state=None)
d = User(id=1, is_bot=False, first_name="user", last_name="user")
assert a == b
assert hash(a) == hash(b)
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
@pytest.fixture
def transaction_partner_user():
return TransactionPartnerUser(
user=TransactionPartnerTestBase.user,
invoice_payload=TransactionPartnerTestBase.invoice_payload,
paid_media=TransactionPartnerTestBase.paid_media,
paid_media_payload=TransactionPartnerTestBase.paid_media_payload,
subscription_period=TransactionPartnerTestBase.subscription_period,
)
class TestTransactionPartnerUserWithoutRequest(TransactionPartnerTestBase):
type = TransactionPartnerType.USER
def test_slot_behaviour(self, transaction_partner_user):
inst = transaction_partner_user
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 = {
"user": self.user.to_dict(),
"invoice_payload": self.invoice_payload,
"paid_media": [pm.to_dict() for pm in self.paid_media],
"paid_media_payload": self.paid_media_payload,
"subscription_period": self.subscription_period.total_seconds(),
}
tp = TransactionPartnerUser.de_json(json_dict, offline_bot)
assert tp.api_kwargs == {}
assert tp.type == "user"
assert tp.user == self.user
assert tp.invoice_payload == self.invoice_payload
assert tp.paid_media == self.paid_media
assert tp.paid_media_payload == self.paid_media_payload
assert tp.subscription_period == self.subscription_period
assert TransactionPartnerUser.de_json(None, offline_bot) is None
assert TransactionPartnerUser.de_json({}, offline_bot) is None
def test_to_dict(self, transaction_partner_user):
json_dict = transaction_partner_user.to_dict()
assert json_dict["type"] == self.type
assert json_dict["user"] == self.user.to_dict()
assert json_dict["invoice_payload"] == self.invoice_payload
assert json_dict["paid_media"] == [pm.to_dict() for pm in self.paid_media]
assert json_dict["paid_media_payload"] == self.paid_media_payload
assert json_dict["subscription_period"] == self.subscription_period.total_seconds()
def test_equality(self, transaction_partner_user):
a = transaction_partner_user
b = TransactionPartnerUser(
user=self.user,
)
c = TransactionPartnerUser(
user=User(id=1, is_bot=False, first_name="user", last_name="user"),
)
d = User(id=1, is_bot=False, first_name="user", last_name="user")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
@pytest.fixture
def transaction_partner_other():
return TransactionPartnerOther()
class TestTransactionPartnerOtherWithoutRequest(TransactionPartnerTestBase):
type = TransactionPartnerType.OTHER
def test_slot_behaviour(self, transaction_partner_other):
inst = transaction_partner_other
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 = {}
tp = TransactionPartnerOther.de_json(json_dict, offline_bot)
assert tp.api_kwargs == {}
assert tp.type == "other"
assert TransactionPartnerOther.de_json(None, offline_bot) is None
def test_to_dict(self, transaction_partner_other):
json_dict = transaction_partner_other.to_dict()
assert json_dict == {"type": self.type}
def test_equality(self, transaction_partner_other):
a = transaction_partner_other
b = TransactionPartnerOther()
c = TransactionPartnerOther()
d = User(id=1, is_bot=False, first_name="user", last_name="user")
assert a == b
assert hash(a) == hash(b)
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
@pytest.fixture
def transaction_partner_telegram_ads():
return TransactionPartnerTelegramAds()
class TestTransactionPartnerTelegramAdsWithoutRequest(TransactionPartnerTestBase):
type = TransactionPartnerType.TELEGRAM_ADS
def test_slot_behaviour(self, transaction_partner_telegram_ads):
inst = transaction_partner_telegram_ads
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 = {}
tp = TransactionPartnerTelegramAds.de_json(json_dict, offline_bot)
assert tp.api_kwargs == {}
assert tp.type == "telegram_ads"
assert TransactionPartnerTelegramAds.de_json(None, offline_bot) is None
def test_to_dict(self, transaction_partner_telegram_ads):
json_dict = transaction_partner_telegram_ads.to_dict()
assert json_dict == {"type": self.type}
def test_equality(self, transaction_partner_telegram_ads):
a = transaction_partner_telegram_ads
b = TransactionPartnerTelegramAds()
c = TransactionPartnerTelegramAds()
d = User(id=1, is_bot=False, first_name="user", last_name="user")
assert a == b
assert hash(a) == hash(b)
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)
@pytest.fixture
def transaction_partner_telegram_api():
return TransactionPartnerTelegramApi(
request_count=TransactionPartnerTestBase.request_count,
)
class TestTransactionPartnerTelegramApiWithoutRequest(TransactionPartnerTestBase):
type = TransactionPartnerType.TELEGRAM_API
def test_slot_behaviour(self, transaction_partner_telegram_api):
inst = transaction_partner_telegram_api
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 = {"request_count": self.request_count}
tp = TransactionPartnerTelegramApi.de_json(json_dict, offline_bot)
assert tp.api_kwargs == {}
assert tp.type == "telegram_api"
assert tp.request_count == self.request_count
assert TransactionPartnerTelegramApi.de_json(None, offline_bot) is None
def test_to_dict(self, transaction_partner_telegram_api):
json_dict = transaction_partner_telegram_api.to_dict()
assert json_dict["type"] == self.type
assert json_dict["request_count"] == self.request_count
def test_equality(self, transaction_partner_telegram_api):
a = transaction_partner_telegram_api
b = TransactionPartnerTelegramApi(
request_count=self.request_count,
)
c = TransactionPartnerTelegramApi(
request_count=0,
)
d = User(id=1, is_bot=False, first_name="user", last_name="user")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -1,849 +0,0 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime as dtm
from collections.abc import Sequence
from copy import deepcopy
import pytest
from telegram import (
Chat,
Dice,
Gift,
PaidMediaPhoto,
PhotoSize,
RevenueWithdrawalState,
RevenueWithdrawalStateFailed,
RevenueWithdrawalStatePending,
RevenueWithdrawalStateSucceeded,
StarTransaction,
StarTransactions,
Sticker,
TelegramObject,
TransactionPartner,
TransactionPartnerFragment,
TransactionPartnerOther,
TransactionPartnerTelegramAds,
TransactionPartnerTelegramApi,
TransactionPartnerUser,
User,
)
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
def withdrawal_state_succeeded():
return RevenueWithdrawalStateSucceeded(
date=dtm.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC),
url="url",
)
@pytest.fixture
def withdrawal_state_failed():
return RevenueWithdrawalStateFailed()
@pytest.fixture
def withdrawal_state_pending():
return RevenueWithdrawalStatePending()
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(
photo=[
PhotoSize(
file_id="file_id", width=1, height=1, file_unique_id="file_unique_id"
)
]
)
],
paid_media_payload="payload",
subscription_period=dtm.timedelta(days=1),
gift=Gift(
id="some_id",
sticker=Sticker(
file_id="file_id",
file_unique_id="file_unique_id",
width=512,
height=512,
is_animated=False,
is_video=False,
type="regular",
),
star_count=5,
total_count=10,
remaining_count=5,
),
)
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(),
)
def star_transaction():
return StarTransaction(
id="1",
amount=1,
nanostar_amount=365,
date=to_timestamp(dtm.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC)),
source=transaction_partner_user(),
receiver=transaction_partner_fragment(),
)
@pytest.fixture
def star_transactions():
return StarTransactions(
transactions=[
star_transaction(),
star_transaction(),
]
)
@pytest.fixture(
scope="module",
params=[
TransactionPartner.AFFILIATE_PROGRAM,
TransactionPartner.FRAGMENT,
TransactionPartner.OTHER,
TransactionPartner.TELEGRAM_ADS,
TransactionPartner.TELEGRAM_API,
TransactionPartner.USER,
],
)
def tp_scope_type(request):
return request.param
@pytest.fixture(
scope="module",
params=[
TransactionPartnerAffiliateProgram,
TransactionPartnerFragment,
TransactionPartnerOther,
TransactionPartnerTelegramAds,
TransactionPartnerTelegramApi,
TransactionPartnerUser,
],
ids=[
TransactionPartner.AFFILIATE_PROGRAM,
TransactionPartner.FRAGMENT,
TransactionPartner.OTHER,
TransactionPartner.TELEGRAM_ADS,
TransactionPartner.TELEGRAM_API,
TransactionPartner.USER,
],
)
def tp_scope_class(request):
return request.param
@pytest.fixture(
scope="module",
params=[
(TransactionPartnerAffiliateProgram, TransactionPartner.AFFILIATE_PROGRAM),
(TransactionPartnerFragment, TransactionPartner.FRAGMENT),
(TransactionPartnerOther, TransactionPartner.OTHER),
(TransactionPartnerTelegramAds, TransactionPartner.TELEGRAM_ADS),
(TransactionPartnerTelegramApi, TransactionPartner.TELEGRAM_API),
(TransactionPartnerUser, TransactionPartner.USER),
],
ids=[
TransactionPartner.AFFILIATE_PROGRAM,
TransactionPartner.FRAGMENT,
TransactionPartner.OTHER,
TransactionPartner.TELEGRAM_ADS,
TransactionPartner.TELEGRAM_API,
TransactionPartner.USER,
],
)
def tp_scope_class_and_type(request):
return request.param
@pytest.fixture(scope="module")
def transaction_partner(tp_scope_class_and_type):
# We use de_json here so that we don't have to worry about which class gets which arguments
return tp_scope_class_and_type[0].de_json(
{
"type": tp_scope_class_and_type[1],
"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,
)
@pytest.fixture(
scope="module",
params=[
RevenueWithdrawalState.FAILED,
RevenueWithdrawalState.SUCCEEDED,
RevenueWithdrawalState.PENDING,
],
)
def rws_scope_type(request):
return request.param
@pytest.fixture(
scope="module",
params=[
RevenueWithdrawalStateFailed,
RevenueWithdrawalStateSucceeded,
RevenueWithdrawalStatePending,
],
ids=[
RevenueWithdrawalState.FAILED,
RevenueWithdrawalState.SUCCEEDED,
RevenueWithdrawalState.PENDING,
],
)
def rws_scope_class(request):
return request.param
@pytest.fixture(
scope="module",
params=[
(RevenueWithdrawalStateFailed, RevenueWithdrawalState.FAILED),
(RevenueWithdrawalStateSucceeded, RevenueWithdrawalState.SUCCEEDED),
(RevenueWithdrawalStatePending, RevenueWithdrawalState.PENDING),
],
ids=[
RevenueWithdrawalState.FAILED,
RevenueWithdrawalState.SUCCEEDED,
RevenueWithdrawalState.PENDING,
],
)
def rws_scope_class_and_type(request):
return request.param
@pytest.fixture(scope="module")
def revenue_withdrawal_state(rws_scope_class_and_type):
# We use de_json here so that we don't have to worry about which class gets which arguments
return rws_scope_class_and_type[0].de_json(
{
"type": rws_scope_class_and_type[1],
"date": to_timestamp(RevenueWithdrawalStateTestBase.date),
"url": RevenueWithdrawalStateTestBase.url,
},
bot=None,
)
class StarTransactionTestBase:
id = "2"
amount = 2
nanostar_amount = 365
date = to_timestamp(dtm.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC))
source = TransactionPartnerUser(
user=User(
id=2,
is_bot=False,
first_name="first_name",
),
)
receiver = TransactionPartnerOther()
class TestStarTransactionWithoutRequest(StarTransactionTestBase):
def test_slot_behaviour(self):
inst = star_transaction()
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 = {
"id": self.id,
"amount": self.amount,
"nanostar_amount": self.nanostar_amount,
"date": self.date,
"source": self.source.to_dict(),
"receiver": self.receiver.to_dict(),
}
st = StarTransaction.de_json(json_dict, offline_bot)
st_none = StarTransaction.de_json(None, 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
assert st_none is None
def test_de_json_star_transaction_localization(self, tz_bot, offline_bot, raw_bot):
json_dict = star_transaction().to_dict()
st_raw = StarTransaction.de_json(json_dict, raw_bot)
st_bot = StarTransaction.de_json(json_dict, offline_bot)
st_tz = StarTransaction.de_json(json_dict, tz_bot)
# comparing utcoffsets because comparing timezones is unpredicatable
st_offset = st_tz.date.utcoffset()
tz_bot_offset = tz_bot.defaults.tzinfo.utcoffset(st_tz.date.replace(tzinfo=None))
assert st_raw.date.tzinfo == UTC
assert st_bot.date.tzinfo == UTC
assert st_offset == tz_bot_offset
def test_to_dict(self):
st = star_transaction()
expected_dict = {
"id": "1",
"amount": 1,
"nanostar_amount": 365,
"date": st.date,
"source": st.source.to_dict(),
"receiver": st.receiver.to_dict(),
}
assert st.to_dict() == expected_dict
def test_equality(self):
a = StarTransaction(
id=self.id,
amount=self.amount,
date=self.date,
source=self.source,
receiver=self.receiver,
)
b = StarTransaction(
id=self.id,
amount=self.amount,
date=None,
source=self.source,
receiver=self.receiver,
)
c = StarTransaction(
id="3",
amount=3,
date=to_timestamp(dtm.datetime.utcnow()),
source=TransactionPartnerUser(
user=User(
id=3,
is_bot=False,
first_name="first_name",
),
),
receiver=TransactionPartnerOther(),
)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
class StarTransactionsTestBase:
transactions = [star_transaction(), star_transaction()]
class TestStarTransactionsWithoutRequest(StarTransactionsTestBase):
def test_slot_behaviour(self, star_transactions):
inst = star_transactions
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 = {
"transactions": [t.to_dict() for t in self.transactions],
}
st = StarTransactions.de_json(json_dict, offline_bot)
st_none = StarTransactions.de_json(None, offline_bot)
assert st.api_kwargs == {}
assert st.transactions == tuple(self.transactions)
assert st_none is None
def test_to_dict(self, star_transactions):
expected_dict = {
"transactions": [t.to_dict() for t in self.transactions],
}
assert star_transactions.to_dict() == expected_dict
def test_equality(self):
a = StarTransactions(
transactions=self.transactions,
)
b = StarTransactions(
transactions=self.transactions,
)
c = StarTransactions(
transactions=[star_transaction()],
)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
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):
def test_slot_behaviour(self, transaction_partner):
inst = transaction_partner
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, tp_scope_class_and_type):
cls = tp_scope_class_and_type[0]
type_ = tp_scope_class_and_type[1]
json_dict = {
"type": 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,
}
tp = TransactionPartner.de_json(json_dict, offline_bot)
assert set(tp.api_kwargs.keys()) == {
"user",
"affiliate",
"withdrawal_state",
"invoice_payload",
"request_count",
"sponsor_user",
"commission_per_mille",
} - set(cls.__slots__)
assert isinstance(tp, TransactionPartner)
assert type(tp) is cls
assert tp.type == type_
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
def test_de_json_invalid_type(self, offline_bot):
json_dict = {
"type": "invalid",
"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
assert tp.type == "invalid"
def test_de_json_subclass(self, tp_scope_class, offline_bot):
"""This makes sure that e.g. TransactionPartnerUser(data) never returns a
TransactionPartnerFragment instance."""
json_dict = {
"type": "invalid",
"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,
}
assert type(tp_scope_class.de_json(json_dict, offline_bot)) is tp_scope_class
def test_to_dict(self, transaction_partner):
tp_dict = transaction_partner.to_dict()
assert isinstance(tp_dict, dict)
assert tp_dict["type"] == transaction_partner.type
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, dtm.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
assert TransactionPartner("unknown").type == "unknown"
def test_equality(self, transaction_partner, offline_bot):
a = TransactionPartner("base_type")
b = TransactionPartner("base_type")
c = transaction_partner
d = deepcopy(transaction_partner)
e = 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 c == d
assert hash(c) == hash(d)
assert c != e
assert hash(c) != hash(e)
if hasattr(c, "user"):
json_dict = c.to_dict()
json_dict["user"] = User(2, "something", True).to_dict()
f = c.__class__.de_json(json_dict, offline_bot)
assert c != f
assert hash(c) != hash(f)
if hasattr(c, "request_count"):
json_dict = c.to_dict()
json_dict["request_count"] = 1
f = c.__class__.de_json(json_dict, offline_bot)
assert c != f
assert hash(c) != hash(f)
class TestTransactionPartnerUserWithoutRequest(TransactionPartnerTestBase):
def test_de_json_required(self, offline_bot):
json_dict = {
"user": transaction_partner_user().user.to_dict(),
}
tp = TransactionPartnerUser.de_json(json_dict, offline_bot)
assert tp.api_kwargs == {}
assert tp.user == transaction_partner_user().user
# This test is here mainly to check that the below cases work
assert tp.subscription_period is None
assert tp.gift is None
class RevenueWithdrawalStateTestBase:
date = dtm.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC)
url = "url"
class TestRevenueWithdrawalStateWithoutRequest(RevenueWithdrawalStateTestBase):
def test_slot_behaviour(self, revenue_withdrawal_state):
inst = revenue_withdrawal_state
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, rws_scope_class_and_type):
cls = rws_scope_class_and_type[0]
type_ = rws_scope_class_and_type[1]
json_dict = {
"type": type_,
"date": to_timestamp(self.date),
"url": self.url,
}
rws = RevenueWithdrawalState.de_json(json_dict, offline_bot)
assert set(rws.api_kwargs.keys()) == {"date", "url"} - set(cls.__slots__)
assert isinstance(rws, RevenueWithdrawalState)
assert type(rws) is cls
assert rws.type == type_
if "date" in cls.__slots__:
assert rws.date == self.date
if "url" in cls.__slots__:
assert rws.url == self.url
assert cls.de_json(None, offline_bot) is None
assert RevenueWithdrawalState.de_json({}, offline_bot) is None
def test_de_json_invalid_type(self, offline_bot):
json_dict = {
"type": "invalid",
"date": to_timestamp(self.date),
"url": self.url,
}
rws = RevenueWithdrawalState.de_json(json_dict, offline_bot)
assert rws.api_kwargs == {
"date": to_timestamp(self.date),
"url": self.url,
}
assert type(rws) is RevenueWithdrawalState
assert rws.type == "invalid"
def test_de_json_subclass(self, rws_scope_class, offline_bot):
"""This makes sure that e.g. RevenueWithdrawalState(data) never returns a
RevenueWithdrawalStateFailed instance."""
json_dict = {
"type": "invalid",
"date": to_timestamp(self.date),
"url": self.url,
}
assert type(rws_scope_class.de_json(json_dict, offline_bot)) is rws_scope_class
def test_to_dict(self, revenue_withdrawal_state):
rws_dict = revenue_withdrawal_state.to_dict()
assert isinstance(rws_dict, dict)
assert rws_dict["type"] == revenue_withdrawal_state.type
if hasattr(revenue_withdrawal_state, "date"):
assert rws_dict["date"] == to_timestamp(revenue_withdrawal_state.date)
if hasattr(revenue_withdrawal_state, "url"):
assert rws_dict["url"] == revenue_withdrawal_state.url
def test_type_enum_conversion(self):
assert type(RevenueWithdrawalState("failed").type) is RevenueWithdrawalStateType
assert RevenueWithdrawalState("unknown").type == "unknown"
def test_equality(self, revenue_withdrawal_state, offline_bot):
a = RevenueWithdrawalState("base_type")
b = RevenueWithdrawalState("base_type")
c = revenue_withdrawal_state
d = deepcopy(revenue_withdrawal_state)
e = 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 c == d
assert hash(c) == hash(d)
assert c != e
assert hash(c) != hash(e)
if hasattr(c, "url"):
json_dict = c.to_dict()
json_dict["url"] = "something"
f = c.__class__.de_json(json_dict, offline_bot)
assert c == f
assert hash(c) == hash(f)
if hasattr(c, "date"):
json_dict = c.to_dict()
json_dict["date"] = to_timestamp(dtm.datetime.utcnow())
f = c.__class__.de_json(json_dict, 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)