Compare commits

...

9 Commits

Author SHA1 Message Date
Bibo-Joshi 2ac52018c2 Bump Version to v21.9 (#4601) 2024-12-07 13:39:41 +01:00
dependabot[bot] ca7a30963d Update aiolimiter requirement from ~=1.1.0 to >=1.1,<1.3 (#4595)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2024-12-07 11:31:52 +01:00
dependabot[bot] c42a230b96 Bump pytest from 8.3.3 to 8.3.4 (#4596)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
2024-12-07 11:15:53 +01:00
Luis Pérez ce9742a602 Use MessageLimit.DEEP_LINK_LENGTH in helpers.create_deep_linked_url (#4597) 2024-12-07 10:25:13 +01:00
Bibo-Joshi 43279543a3 Full Support for Bot API 8.1 (#4594) 2024-12-07 10:20:08 +01:00
Luis Pérez eda2172617 Allow Sequence Input for allowed_updates in Application and Updater Methods (#4589) 2024-12-04 21:20:12 +01:00
dependabot[bot] 89dfa37dbf Bump codecov/codecov-action from 4 to 5 (#4585)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-04 21:04:47 +01:00
Luis Pérez 3709c2fa93 Bump pylint to v3.3.2 to Improve Python 3.13 Support (#4590) 2024-12-04 20:54:03 +01:00
dependabot[bot] da93fe94ae Bump srvaroa/labeler from 1.11.1 to 1.12.0 (#4586)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 06:17:52 +01:00
25 changed files with 579 additions and 53 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
pull-requests: write # for srvaroa/labeler to add labels in PR
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@v1.11.1
- uses: srvaroa/labeler@v1.12.0
# Config file at .github/labeler.yml
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+1 -1
View File
@@ -87,7 +87,7 @@ jobs:
.test_report_optionals_junit.xml
- name: Submit coverage
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
env_vars: OS,PYTHON
name: ${{ matrix.os }}-${{ matrix.python-version }}
+4 -4
View File
@@ -16,7 +16,7 @@ repos:
- tornado~=6.4
- APScheduler~=3.10.4
- cachetools>=5.3.3,<5.5.0
- aiolimiter~=1.1.0
- aiolimiter~=1.1,<1.3
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.4.2
hooks:
@@ -29,7 +29,7 @@ repos:
hooks:
- id: flake8
- repo: https://github.com/PyCQA/pylint
rev: v3.2.4
rev: v3.3.2
hooks:
- id: pylint
files: ^(?!(tests|docs)).*\.py$
@@ -38,7 +38,7 @@ repos:
- tornado~=6.4
- APScheduler~=3.10.4
- cachetools>=5.3.3,<5.5.0
- aiolimiter~=1.1.0
- aiolimiter~=1.1,<1.3
- . # this basically does `pip install -e .`
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.1
@@ -54,7 +54,7 @@ repos:
- tornado~=6.4
- APScheduler~=3.10.4
- cachetools>=5.3.3,<5.5.0
- aiolimiter~=1.1.0
- aiolimiter~=1.1,<1.3
- . # this basically does `pip install -e .`
- id: mypy
name: mypy-examples
+1
View File
@@ -81,6 +81,7 @@ The following wonderful people contributed directly or indirectly to this projec
- `LRezende <https://github.com/lrezende>`_
- `Luca Bellanti <https://github.com/Trifase>`_
- `Lucas Molinari <https://github.com/lucasmolinari>`_
- `Luis Pérez <https://github.com/nemacysts>`_
- `macrojames <https://github.com/macrojames>`_
- `Matheus Lemos <https://github.com/mlemosf>`_
- `Michael Dix <https://github.com/Eisberge>`_
+27
View File
@@ -4,6 +4,33 @@
Changelog
=========
Version 21.9
============
*Released 2024-12-07*
This is the technical changelog for version 21.9. More elaborate release notes can be found in the news channel `@pythontelegrambotchannel <https://t.me/pythontelegrambotchannel>`_.
Major Changes
-------------
- Full Support for Bot API 8.1 (:pr:`4594` closes :issue:`4592`)
Minor Changes
-------------
- Use ``MessageLimit.DEEP_LINK_LENGTH`` in ``helpers.create_deep_linked_url`` (:pr:`4597` by `nemacysts <https://github.com/nemacysts>`_)
- Allow ``Sequence`` Input for ``allowed_updates`` in ``Application`` and ``Updater`` Methods (:pr:`4589` by `nemacysts <https://github.com/nemacysts>`_)
Dependency Updates
------------------
- Update ``aiolimiter`` requirement from ~=1.1.0 to >=1.1,<1.3 (:pr:`4595`)
- Bump ``pytest`` from 8.3.3 to 8.3.4 (:pr:`4596`)
- Bump ``codecov/codecov-action`` from 4 to 5 (:pr:`4585`)
- Bump ``pylint`` to v3.3.2 to Improve Python 3.13 Support (:pr:`4590` by `nemacysts <https://github.com/nemacysts>`_)
- Bump ``srvaroa/labeler`` from 1.11.1 to 1.12.0 (:pr:`4586`)
Version 21.8
============
*Released 2024-12-01*
+3 -3
View File
@@ -11,7 +11,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-8.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-8.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API version
@@ -81,7 +81,7 @@ After installing_ the library, be sure to check out the section on `working with
Telegram API support
~~~~~~~~~~~~~~~~~~~~
All types and methods of the Telegram Bot API **8.0** are natively supported by this library.
All types and methods of the Telegram Bot API **8.1** are natively supported by this library.
In addition, Bot API functionality not yet natively included can still be used as described `in our wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Bot-API-Forward-Compatibility>`_.
Notable Features
@@ -155,7 +155,7 @@ PTB can be installed with optional dependencies:
* ``pip install "python-telegram-bot[passport]"`` installs the `cryptography>=39.0.1 <https://cryptography.io/en/stable>`_ library. Use this, if you want to use Telegram Passport related functionality.
* ``pip install "python-telegram-bot[socks]"`` installs `httpx[socks] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to work behind a Socks5 server.
* ``pip install "python-telegram-bot[http2]"`` installs `httpx[http2] <https://www.python-httpx.org/#dependencies>`_. Use this, if you want to use HTTP/2.
* ``pip install "python-telegram-bot[rate-limiter]"`` installs `aiolimiter~=1.1.0 <https://aiolimiter.readthedocs.io/en/stable/>`_. Use this, if you want to use ``telegram.ext.AIORateLimiter``.
* ``pip install "python-telegram-bot[rate-limiter]"`` installs `aiolimiter~=1.1,<1.3 <https://aiolimiter.readthedocs.io/en/stable/>`_. Use this, if you want to use ``telegram.ext.AIORateLimiter``.
* ``pip install "python-telegram-bot[webhooks]"`` installs the `tornado~=6.4 <https://www.tornadoweb.org/en/stable/>`_ library. Use this, if you want to use ``telegram.ext.Updater.start_webhook``/``telegram.ext.Application.run_webhook``.
* ``pip install "python-telegram-bot[callback-data]"`` installs the `cachetools>=5.3.3,<5.6.0 <https://cachetools.readthedocs.io/en/latest/>`_ library. Use this, if you want to use `arbitrary callback_data <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Arbitrary-callback_data>`_.
* ``pip install "python-telegram-bot[job-queue]"`` installs the `APScheduler~=3.10.4 <https://apscheduler.readthedocs.io/en/3.x/>`_ library and enforces `pytz>=2018.6 <https://pypi.org/project/pytz/>`_, where ``pytz`` is a dependency of ``APScheduler``. Use this, if you want to use the ``telegram.ext.JobQueue``.
+5
View File
@@ -187,6 +187,11 @@ def autodoc_process_bases(app, name, obj, option, bases: list) -> None:
bases[idx] = ":class:`enum.IntEnum`"
continue
if "FloatEnum" in base:
bases[idx] = ":class:`enum.Enum`"
bases.insert(0, ":class:`float`")
continue
# Drop generics (at least for now)
if base.endswith("]"):
base = base.split("[", maxsplit=1)[0]
+6
View File
@@ -0,0 +1,6 @@
AffiliateInfo
=============
.. autoclass:: telegram.AffiliateInfo
:members:
:show-inheritance:
+1 -1
View File
@@ -5,5 +5,5 @@ telegram.constants Module
:members:
:show-inheritance:
:no-undoc-members:
:inherited-members: Enum, EnumMeta, str, int
:inherited-members: Enum, EnumMeta, str, int, float
:exclude-members: __format__, __new__, __repr__, __str__
+2
View File
@@ -9,6 +9,7 @@ Your bot can accept payments from Telegram users. Please see the `introduction t
.. toctree::
:titlesonly:
telegram.affiliateinfo
telegram.invoice
telegram.labeledprice
telegram.orderinfo
@@ -25,6 +26,7 @@ Your bot can accept payments from Telegram users. Please see the `introduction t
telegram.startransactions
telegram.successfulpayment
telegram.transactionpartner
telegram.transactionpartneraffiliateprogram
telegram.transactionpartnerfragment
telegram.transactionpartnerother
telegram.transactionpartnertelegramads
@@ -0,0 +1,6 @@
TransactionPartnerAffiliateProgram
===================================
.. autoclass:: telegram.TransactionPartnerAffiliateProgram
:members:
:show-inheritance:
+3 -2
View File
@@ -86,7 +86,7 @@ passport = [
"cffi >= 1.17.0rc1; python_version > '3.12'"
]
rate-limiter = [
"aiolimiter~=1.1.0",
"aiolimiter>=1.1,<1.3",
]
socks = [
"httpx[socks]",
@@ -149,7 +149,8 @@ enable = ["useless-suppression"]
disable = ["duplicate-code", "too-many-arguments", "too-many-public-methods",
"too-few-public-methods", "broad-exception-caught", "too-many-instance-attributes",
"fixme", "missing-function-docstring", "missing-class-docstring", "too-many-locals",
"too-many-lines", "too-many-branches", "too-many-statements", "cyclic-import"
"too-many-lines", "too-many-branches", "too-many-statements", "cyclic-import",
"too-many-positional-arguments",
]
[tool.pylint.main]
+1 -1
View File
@@ -4,7 +4,7 @@
build
# For the test suite
pytest==8.3.3
pytest==8.3.4
# needed because pytest doesn't come with native support for coroutines as tests
pytest-asyncio==0.21.2
+4
View File
@@ -20,6 +20,7 @@
__author__ = "devs@python-telegram-bot.org"
__all__ = (
"AffiliateInfo",
"Animation",
"Audio",
"BackgroundFill",
@@ -236,6 +237,7 @@ __all__ = (
"TelegramObject",
"TextQuote",
"TransactionPartner",
"TransactionPartnerAffiliateProgram",
"TransactionPartnerFragment",
"TransactionPartnerOther",
"TransactionPartnerTelegramAds",
@@ -469,6 +471,7 @@ from ._payment.shippingaddress import ShippingAddress
from ._payment.shippingoption import ShippingOption
from ._payment.shippingquery import ShippingQuery
from ._payment.stars import (
AffiliateInfo,
RevenueWithdrawalState,
RevenueWithdrawalStateFailed,
RevenueWithdrawalStatePending,
@@ -476,6 +479,7 @@ from ._payment.stars import (
StarTransaction,
StarTransactions,
TransactionPartner,
TransactionPartnerAffiliateProgram,
TransactionPartnerFragment,
TransactionPartnerOther,
TransactionPartnerTelegramAds,
+4 -1
View File
@@ -8175,7 +8175,10 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
``XTR`` (Telegram Stars) if the parameter is used. Currently, it must always be
:tg-const:`telegram.constants.InvoiceLimit.SUBSCRIPTION_PERIOD` if specified. Any
number of subscriptions can be active for a given bot at the same time, including
multiple concurrent subscriptions from the same user.
multiple concurrent subscriptions from the same user. Subscription price must
not exceed
:tg-const:`telegram.constants.InvoiceLimit.SUBSCRIPTION_MAX_PRICE`
Telegram Stars.
.. versionadded:: 21.8
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the
+188 -3
View File
@@ -24,6 +24,7 @@ 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
@@ -194,13 +195,107 @@ class RevenueWithdrawalStateFailed(RevenueWithdrawalState):
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
@@ -217,6 +312,11 @@ class TransactionPartner(TelegramObject):
__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
@@ -259,6 +359,7 @@ class TransactionPartner(TelegramObject):
return None
_class_mapping: dict[str, type[TransactionPartner]] = {
cls.AFFILIATE_PROGRAM: TransactionPartnerAffiliateProgram,
cls.FRAGMENT: TransactionPartnerFragment,
cls.USER: TransactionPartnerUser,
cls.TELEGRAM_ADS: TransactionPartnerTelegramAds,
@@ -272,6 +373,64 @@ class TransactionPartner(TelegramObject):
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.
@@ -328,6 +487,10 @@ class TransactionPartnerUser(TransactionPartner):
Args:
user (:class:`telegram.User`): Information about the user.
affiliate (:class:`telegram.AffiliateInfo`, optional): Information about the affiliate that
received a commission via this transaction
.. versionadded:: 21.9
invoice_payload (:obj:`str`, optional): Bot-specified invoice payload.
subscription_period (:class:`datetime.timedelta`, optional): The duration of the paid
subscription
@@ -348,6 +511,10 @@ class TransactionPartnerUser(TransactionPartner):
type (:obj:`str`): The type of the transaction partner,
always :tg-const:`telegram.TransactionPartner.USER`.
user (:class:`telegram.User`): Information about the user.
affiliate (:class:`telegram.AffiliateInfo`): Optional. Information about the affiliate that
received a commission via this transaction
.. versionadded:: 21.9
invoice_payload (:obj:`str`): Optional. Bot-specified invoice payload.
subscription_period (:class:`datetime.timedelta`): Optional. The duration of the paid
subscription
@@ -367,6 +534,7 @@ class TransactionPartnerUser(TransactionPartner):
"""
__slots__ = (
"affiliate",
"gift",
"invoice_payload",
"paid_media",
@@ -383,6 +551,7 @@ class TransactionPartnerUser(TransactionPartner):
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:
@@ -390,6 +559,7 @@ class TransactionPartnerUser(TransactionPartner):
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
@@ -412,6 +582,7 @@ class TransactionPartnerUser(TransactionPartner):
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)
@@ -499,7 +670,13 @@ class StarTransaction(TelegramObject):
of the original transaction for refund transactions.
Coincides with :attr:`SuccessfulPayment.telegram_payment_charge_id` for
successful incoming payments from users.
amount (:obj:`int`): Number of Telegram Stars transferred by the transaction.
amount (:obj:`int`): Integer amount of Telegram Stars transferred by the transaction.
nanostar_amount (:obj:`int`, optional): The number of
:tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram
Stars transferred by the transaction; from 0 to
:tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`
.. versionadded:: 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).
@@ -513,7 +690,13 @@ class StarTransaction(TelegramObject):
of the original transaction for refund transactions.
Coincides with :attr:`SuccessfulPayment.telegram_payment_charge_id` for
successful incoming payments from users.
amount (:obj:`int`): Number of Telegram Stars transferred by the transaction.
amount (:obj:`int`): Integer amount of Telegram Stars transferred by the transaction.
nanostar_amount (:obj:`int`): Optional. The number of
:tg-const:`~telegram.constants.StarTransactions.NANOSTAR_VALUE` shares of Telegram
Stars transferred by the transaction; from 0 to
:tg-const:`~telegram.constants.StarTransactionsLimit.NANOSTAR_MAX_AMOUNT`
.. versionadded:: 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).
@@ -523,7 +706,7 @@ class StarTransaction(TelegramObject):
outgoing transactions.
"""
__slots__ = ("amount", "date", "id", "receiver", "source")
__slots__ = ("amount", "date", "id", "nanostar_amount", "receiver", "source")
def __init__(
self,
@@ -532,6 +715,7 @@ class StarTransaction(TelegramObject):
date: dtm.datetime,
source: Optional[TransactionPartner] = None,
receiver: Optional[TransactionPartner] = None,
nanostar_amount: Optional[int] = None,
*,
api_kwargs: Optional[JSONDict] = None,
) -> None:
@@ -541,6 +725,7 @@ class StarTransaction(TelegramObject):
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,
+14
View File
@@ -74,3 +74,17 @@ class IntEnum(_enum.IntEnum): # pylint: disable=invalid-slots
def __str__(self) -> str:
return str(self.value)
class FloatEnum(float, _enum.Enum):
"""Helper class for float enums where ``str(member)`` prints the value, but ``repr(member)``
gives ``EnumName.MEMBER_NAME``.
"""
__slots__ = ()
def __repr__(self) -> str:
return f"<{self.__class__.__name__}.{self.name}>"
def __str__(self) -> str:
return str(self.value)
+1 -1
View File
@@ -51,6 +51,6 @@ class Version(NamedTuple):
__version_info__: Final[Version] = Version(
major=21, minor=8, micro=0, releaselevel="final", serial=0
major=21, minor=9, micro=0, releaselevel="final", serial=0
)
__version__: Final[str] = str(__version_info__)
+3 -2
View File
@@ -17,6 +17,7 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram WebhookInfo."""
from collections.abc import Sequence
from datetime import datetime
from typing import TYPE_CHECKING, Optional
@@ -60,7 +61,7 @@ class WebhookInfo(TelegramObject):
most recent error that happened when trying to deliver an update via webhook.
max_connections (:obj:`int`, optional): Maximum allowed number of simultaneous HTTPS
connections to the webhook for update delivery.
allowed_updates (Sequence[:obj:`str`], optional): A list of update types the bot is
allowed_updates (Sequence[:obj:`str`], optional): A sequence of update types the bot is
subscribed to. Defaults to all update types, except
:attr:`telegram.Update.chat_member`.
@@ -90,7 +91,7 @@ class WebhookInfo(TelegramObject):
most recent error that happened when trying to deliver an update via webhook.
max_connections (:obj:`int`): Optional. Maximum allowed number of simultaneous HTTPS
connections to the webhook for update delivery.
allowed_updates (tuple[:obj:`str`]): Optional. A list of update types the bot is
allowed_updates (tuple[:obj:`str`]): Optional. A tuple of update types the bot is
subscribed to. Defaults to all update types, except
:attr:`telegram.Update.chat_member`.
+46 -4
View File
@@ -95,6 +95,7 @@ __all__ = [
"ReactionType",
"ReplyLimit",
"RevenueWithdrawalStateType",
"StarTransactions",
"StarTransactionsLimit",
"StickerFormat",
"StickerLimit",
@@ -112,7 +113,7 @@ from enum import Enum
from typing import Final, NamedTuple, Optional
from telegram._utils.datetime import UTC
from telegram._utils.enum import IntEnum, StringEnum
from telegram._utils.enum import FloatEnum, IntEnum, StringEnum
class _BotAPIVersion(NamedTuple):
@@ -153,7 +154,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=8, minor=0)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=8, minor=1)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -1835,7 +1836,6 @@ class MessageLimit(IntEnum):
:paramref:`~telegram.Bot.edit_message_text.text` parameter of
:meth:`telegram.Bot.edit_message_text`.
"""
# TODO this constant is not used. helpers.py contains 64 as a number
DEEP_LINK_LENGTH = 64
""":obj:`int`: Maximum number of characters for a deep link."""
# TODO this constant is not used anywhere
@@ -2461,8 +2461,25 @@ class RevenueWithdrawalStateType(StringEnum):
""":obj:`str`: A withdrawal failed and the transaction was refunded."""
class StarTransactions(FloatEnum):
"""This enum contains constants for :class:`telegram.StarTransaction`.
The enum members of this enumeration are instances of :class:`float` and can be treated as
such.
.. versionadded:: 21.9
"""
__slots__ = ()
NANOSTAR_VALUE = 1 / 1000000000
""":obj:`float`: The value of one nanostar as used in
:attr:`telegram.StarTransaction.nanostar_amount`.
"""
class StarTransactionsLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.Bot.get_star_transactions`.
"""This enum contains limitations for :class:`telegram.Bot.get_star_transactions` and
:class:`telegram.StarTransaction`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: 21.4
@@ -2478,6 +2495,20 @@ class StarTransactionsLimit(IntEnum):
""":obj:`int`: Maximum value allowed for the
:paramref:`~telegram.Bot.get_star_transactions.limit` parameter of
:meth:`telegram.Bot.get_star_transactions`."""
NANOSTAR_MIN_AMOUNT = -999999999
""":obj:`int`: Minimum value allowed for :paramref:`~telegram.AffiliateInfo.nanostar_amount`
parameter of :class:`telegram.AffiliateInfo`.
.. versionadded:: 21.9
"""
NANOSTAR_MAX_AMOUNT = 999999999
""":obj:`int`: Maximum value allowed for :paramref:`~telegram.StarTransaction.nanostar_amount`
parameter of :class:`telegram.StarTransaction` and
:paramref:`~telegram.AffiliateInfo.nanostar_amount` parameter of
:class:`telegram.AffiliateInfo`.
.. versionadded:: 21.9
"""
class StickerFormat(StringEnum):
@@ -2622,6 +2653,11 @@ class TransactionPartnerType(StringEnum):
__slots__ = ()
AFFILIATE_PROGRAM = "affiliate_program"
""":obj:`str`: Transaction with Affiliate Program.
.. versionadded:: 21.9
"""
FRAGMENT = "fragment"
""":obj:`str`: Withdrawal transaction with Fragment."""
OTHER = "other"
@@ -2925,6 +2961,12 @@ class InvoiceLimit(IntEnum):
.. versionadded:: 21.8
"""
SUBSCRIPTION_MAX_PRICE = 2500
""":obj:`int`: The maximum price of a subscription created wtih
:meth:`telegram.Bot.create_invoice_link`.
.. versionadded:: 21.9
"""
class UserProfilePhotosLimit(IntEnum):
+10 -4
View File
@@ -746,7 +746,7 @@ class Application(
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
allowed_updates: Optional[list[str]] = None,
allowed_updates: Optional[Sequence[str]] = None,
drop_pending_updates: Optional[bool] = None,
close_loop: bool = True,
stop_signals: ODVInput[Sequence[int]] = DEFAULT_NONE,
@@ -823,8 +823,11 @@ class Application(
:meth:`telegram.ext.ApplicationBuilder.get_updates_pool_timeout`.
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
Telegram servers before actually starting to poll. Default is :obj:`False`.
allowed_updates (list[:obj:`str`], optional): Passed to
allowed_updates (Sequence[:obj:`str`], optional): Passed to
:meth:`telegram.Bot.get_updates`.
.. versionchanged:: 21.9
Accepts any :class:`collections.abc.Sequence` as input instead of just a list
close_loop (:obj:`bool`, optional): If :obj:`True`, the current event loop will be
closed upon shutdown. Defaults to :obj:`True`.
@@ -888,7 +891,7 @@ class Application(
key: Optional[Union[str, Path]] = None,
bootstrap_retries: int = 0,
webhook_url: Optional[str] = None,
allowed_updates: Optional[list[str]] = None,
allowed_updates: Optional[Sequence[str]] = None,
drop_pending_updates: Optional[bool] = None,
ip_address: Optional[str] = None,
max_connections: int = 40,
@@ -954,8 +957,11 @@ class Application(
webhook_url (:obj:`str`, optional): Explicitly specify the webhook url. Useful behind
NAT, reverse proxy, etc. Default is derived from :paramref:`listen`,
:paramref:`port`, :paramref:`url_path`, :paramref:`cert`, and :paramref:`key`.
allowed_updates (list[:obj:`str`], optional): Passed to
allowed_updates (Sequence[:obj:`str`], optional): Passed to
:meth:`telegram.Bot.set_webhook`.
.. versionchanged:: 21.9
Accepts any :class:`collections.abc.Sequence` as input instead of just a list
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
Telegram servers before actually starting to poll. Default is :obj:`False`.
ip_address (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`.
+15 -8
View File
@@ -17,10 +17,11 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains the class Updater, which tries to make creating Telegram bots intuitive."""
import asyncio
import contextlib
import ssl
from collections.abc import Coroutine
from collections.abc import Coroutine, Sequence
from pathlib import Path
from types import TracebackType
from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, Union
@@ -210,7 +211,7 @@ class Updater(contextlib.AbstractAsyncContextManager["Updater"]):
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
allowed_updates: Optional[list[str]] = None,
allowed_updates: Optional[Sequence[str]] = None,
drop_pending_updates: Optional[bool] = None,
error_callback: Optional[Callable[[TelegramError], None]] = None,
) -> "asyncio.Queue[object]":
@@ -265,8 +266,11 @@ class Updater(contextlib.AbstractAsyncContextManager["Updater"]):
Deprecated in favor of setting the timeout via
:meth:`telegram.ext.ApplicationBuilder.get_updates_pool_timeout` or
:paramref:`telegram.Bot.get_updates_request`.
allowed_updates (list[:obj:`str`], optional): Passed to
allowed_updates (Sequence[:obj:`str`], optional): Passed to
:meth:`telegram.Bot.get_updates`.
.. versionchanged:: 21.9
Accepts any :class:`collections.abc.Sequence` as input instead of just a list
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
Telegram servers before actually starting to poll. Default is :obj:`False`.
@@ -344,7 +348,7 @@ class Updater(contextlib.AbstractAsyncContextManager["Updater"]):
pool_timeout: ODVInput[float],
bootstrap_retries: int,
drop_pending_updates: Optional[bool],
allowed_updates: Optional[list[str]],
allowed_updates: Optional[Sequence[str]],
ready: asyncio.Event,
error_callback: Optional[Callable[[TelegramError], None]],
) -> None:
@@ -457,7 +461,7 @@ class Updater(contextlib.AbstractAsyncContextManager["Updater"]):
key: Optional[Union[str, Path]] = None,
bootstrap_retries: int = 0,
webhook_url: Optional[str] = None,
allowed_updates: Optional[list[str]] = None,
allowed_updates: Optional[Sequence[str]] = None,
drop_pending_updates: Optional[bool] = None,
ip_address: Optional[str] = None,
max_connections: int = 40,
@@ -516,8 +520,11 @@ class Updater(contextlib.AbstractAsyncContextManager["Updater"]):
Defaults to :obj:`None`.
.. versionadded :: 13.4
allowed_updates (list[:obj:`str`], optional): Passed to
allowed_updates (Sequence[:obj:`str`], optional): Passed to
:meth:`telegram.Bot.set_webhook`. Defaults to :obj:`None`.
.. versionchanged:: 21.9
Accepts any :class:`collections.abc.Sequence` as input instead of just a list
max_connections (:obj:`int`, optional): Passed to
:meth:`telegram.Bot.set_webhook`. Defaults to ``40``.
@@ -624,7 +631,7 @@ class Updater(contextlib.AbstractAsyncContextManager["Updater"]):
port: int,
url_path: str,
bootstrap_retries: int,
allowed_updates: Optional[list[str]],
allowed_updates: Optional[Sequence[str]],
cert: Optional[Union[str, Path]] = None,
key: Optional[Union[str, Path]] = None,
drop_pending_updates: Optional[bool] = None,
@@ -767,7 +774,7 @@ class Updater(contextlib.AbstractAsyncContextManager["Updater"]):
self,
max_retries: int,
webhook_url: Optional[str],
allowed_updates: Optional[list[str]],
allowed_updates: Optional[Sequence[str]],
drop_pending_updates: Optional[bool] = None,
cert: Optional[bytes] = None,
bootstrap_interval: float = 1,
+7 -4
View File
@@ -36,7 +36,7 @@ from html import escape
from typing import TYPE_CHECKING, Optional, Union
from telegram._utils.types import MarkdownVersion
from telegram.constants import MessageType
from telegram.constants import MessageLimit, MessageType
if TYPE_CHECKING:
from telegram import Message, Update
@@ -173,7 +173,8 @@ def create_deep_linked_url(
:obj:`str`: An URL to start the bot with specific parameters.
Raises:
:exc:`ValueError`: If the length of the :paramref:`payload` exceeds 64 characters,
:exc:`ValueError`: If the length of the :paramref:`payload` exceeds \
:tg-const:`telegram.constants.MessageLimit.DEEP_LINK_LENGTH` characters,
contains invalid characters, or if the :paramref:`bot_username` is less than 4
characters.
"""
@@ -184,8 +185,10 @@ def create_deep_linked_url(
if not payload:
return base_url
if len(payload) > 64:
raise ValueError("The deep-linking payload must not exceed 64 characters.")
if len(payload) > MessageLimit.DEEP_LINK_LENGTH:
raise ValueError(
f"The deep-linking payload must not exceed {MessageLimit.DEEP_LINK_LENGTH} characters."
)
if not re.match(r"^[A-Za-z0-9_-]+$", payload):
raise ValueError(
+28 -1
View File
@@ -24,7 +24,7 @@ import re
import pytest
from telegram import Message, constants
from telegram._utils.enum import IntEnum, StringEnum
from telegram._utils.enum import FloatEnum, IntEnum, StringEnum
from telegram.error import BadRequest
from tests.auxil.build_messages import make_message
from tests.auxil.files import data_file
@@ -41,6 +41,11 @@ class IntEnumTest(IntEnum):
BAR = 2
class FloatEnumTest(FloatEnum):
FOO = 1.1
BAR = 2.1
class TestConstantsWithoutRequest:
"""Also test _utils.enum.StringEnum on the fly because tg.constants is currently the only
place where that class is used."""
@@ -69,6 +74,7 @@ class TestConstantsWithoutRequest:
def test_to_json(self):
assert json.dumps(StrEnumTest.FOO) == json.dumps("foo")
assert json.dumps(IntEnumTest.FOO) == json.dumps(1)
assert json.dumps(FloatEnumTest.FOO) == json.dumps(1.1)
def test_string_representation(self):
# test __repr__
@@ -90,6 +96,15 @@ class TestConstantsWithoutRequest:
# test __str__
assert str(IntEnumTest.FOO) == "1"
def test_float_representation(self):
# test __repr__
assert repr(FloatEnumTest.FOO) == "<FloatEnumTest.FOO>"
# test __format__
assert f"{FloatEnumTest.FOO}/0 is undefined!" == "1.1/0 is undefined!"
assert f"{FloatEnumTest.FOO:*^10}" == "***1.1****"
# test __str__
assert str(FloatEnumTest.FOO) == "1.1"
def test_string_inheritance(self):
assert isinstance(StrEnumTest.FOO, str)
assert StrEnumTest.FOO + StrEnumTest.BAR == "foobar"
@@ -115,6 +130,18 @@ class TestConstantsWithoutRequest:
assert hash(IntEnumTest.FOO) == hash(1)
def test_float_inheritance(self):
assert isinstance(FloatEnumTest.FOO, float)
assert FloatEnumTest.FOO + FloatEnumTest.BAR == 3.2
assert FloatEnumTest.FOO == FloatEnumTest.FOO
assert FloatEnumTest.FOO == 1.1
assert FloatEnumTest.FOO != FloatEnumTest.BAR
assert FloatEnumTest.FOO != 2.1
assert object() != FloatEnumTest.FOO
assert hash(FloatEnumTest.FOO) == hash(1.1)
def test_bot_api_version_and_info(self):
assert str(constants.BOT_API_VERSION_INFO) == constants.BOT_API_VERSION
assert (
+198 -12
View File
@@ -18,11 +18,13 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime
from collections.abc import Sequence
from copy import deepcopy
import pytest
from telegram import (
Chat,
Dice,
Gift,
PaidMediaPhoto,
@@ -34,6 +36,7 @@ from telegram import (
StarTransaction,
StarTransactions,
Sticker,
TelegramObject,
TransactionPartner,
TransactionPartnerFragment,
TransactionPartnerOther,
@@ -42,6 +45,7 @@ from telegram import (
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
@@ -67,6 +71,13 @@ def withdrawal_state_pending():
def transaction_partner_user():
return TransactionPartnerUser(
user=User(id=1, is_bot=False, first_name="first_name", username="username"),
affiliate=AffiliateInfo(
affiliate_user=User(id=2, is_bot=True, first_name="first_name", username="username"),
affiliate_chat=Chat(id=3, type="private", title="title"),
commission_per_mille=1,
amount=2,
nanostar_amount=3,
),
invoice_payload="payload",
paid_media=[
PaidMediaPhoto(
@@ -97,6 +108,13 @@ def transaction_partner_user():
)
def transaction_partner_affiliate_program():
return TransactionPartnerAffiliateProgram(
sponsor_user=User(id=1, is_bot=True, first_name="first_name", username="username"),
commission_per_mille=42,
)
def transaction_partner_fragment():
return TransactionPartnerFragment(
withdrawal_state=withdrawal_state_succeeded(),
@@ -107,6 +125,7 @@ def star_transaction():
return StarTransaction(
id="1",
amount=1,
nanostar_amount=365,
date=to_timestamp(datetime.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC)),
source=transaction_partner_user(),
receiver=transaction_partner_fragment(),
@@ -126,6 +145,7 @@ def star_transactions():
@pytest.fixture(
scope="module",
params=[
TransactionPartner.AFFILIATE_PROGRAM,
TransactionPartner.FRAGMENT,
TransactionPartner.OTHER,
TransactionPartner.TELEGRAM_ADS,
@@ -140,6 +160,7 @@ def tp_scope_type(request):
@pytest.fixture(
scope="module",
params=[
TransactionPartnerAffiliateProgram,
TransactionPartnerFragment,
TransactionPartnerOther,
TransactionPartnerTelegramAds,
@@ -147,6 +168,7 @@ def tp_scope_type(request):
TransactionPartnerUser,
],
ids=[
TransactionPartner.AFFILIATE_PROGRAM,
TransactionPartner.FRAGMENT,
TransactionPartner.OTHER,
TransactionPartner.TELEGRAM_ADS,
@@ -161,6 +183,7 @@ def tp_scope_class(request):
@pytest.fixture(
scope="module",
params=[
(TransactionPartnerAffiliateProgram, TransactionPartner.AFFILIATE_PROGRAM),
(TransactionPartnerFragment, TransactionPartner.FRAGMENT),
(TransactionPartnerOther, TransactionPartner.OTHER),
(TransactionPartnerTelegramAds, TransactionPartner.TELEGRAM_ADS),
@@ -168,6 +191,7 @@ def tp_scope_class(request):
(TransactionPartnerUser, TransactionPartner.USER),
],
ids=[
TransactionPartner.AFFILIATE_PROGRAM,
TransactionPartner.FRAGMENT,
TransactionPartner.OTHER,
TransactionPartner.TELEGRAM_ADS,
@@ -188,7 +212,14 @@ def transaction_partner(tp_scope_class_and_type):
"invoice_payload": TransactionPartnerTestBase.invoice_payload,
"withdrawal_state": TransactionPartnerTestBase.withdrawal_state.to_dict(),
"user": TransactionPartnerTestBase.user.to_dict(),
"affiliate": TransactionPartnerTestBase.affiliate.to_dict(),
"request_count": TransactionPartnerTestBase.request_count,
"sponsor_user": TransactionPartnerTestBase.sponsor_user.to_dict(),
"commission_per_mille": TransactionPartnerTestBase.commission_per_mille,
"gift": TransactionPartnerTestBase.gift.to_dict(),
"paid_media": [m.to_dict() for m in TransactionPartnerTestBase.paid_media],
"paid_media_payload": TransactionPartnerTestBase.paid_media_payload,
"subscription_period": TransactionPartnerTestBase.subscription_period.total_seconds(),
},
bot=None,
)
@@ -256,6 +287,7 @@ def revenue_withdrawal_state(rws_scope_class_and_type):
class StarTransactionTestBase:
id = "2"
amount = 2
nanostar_amount = 365
date = to_timestamp(datetime.datetime(2024, 1, 1, 0, 0, 0, 0, tzinfo=UTC))
source = TransactionPartnerUser(
user=User(
@@ -278,6 +310,7 @@ class TestStarTransactionWithoutRequest(StarTransactionTestBase):
json_dict = {
"id": self.id,
"amount": self.amount,
"nanostar_amount": self.nanostar_amount,
"date": self.date,
"source": self.source.to_dict(),
"receiver": self.receiver.to_dict(),
@@ -287,6 +320,7 @@ class TestStarTransactionWithoutRequest(StarTransactionTestBase):
assert st.api_kwargs == {}
assert st.id == self.id
assert st.amount == self.amount
assert st.nanostar_amount == self.nanostar_amount
assert st.date == from_timestamp(self.date)
assert st.source == self.source
assert st.receiver == self.receiver
@@ -311,6 +345,7 @@ class TestStarTransactionWithoutRequest(StarTransactionTestBase):
expected_dict = {
"id": "1",
"amount": 1,
"nanostar_amount": 365,
"date": st.date,
"source": st.source.to_dict(),
"receiver": st.receiver.to_dict(),
@@ -401,8 +436,15 @@ class TestStarTransactionsWithoutRequest(StarTransactionsTestBase):
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):
@@ -421,26 +463,28 @@ class TestTransactionPartnerWithoutRequest(TransactionPartnerTestBase):
"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_
if "withdrawal_state" in cls.__slots__:
assert tp.withdrawal_state == self.withdrawal_state
if "user" in cls.__slots__:
assert tp.user == self.user
assert tp.invoice_payload == self.invoice_payload
if "request_count" in cls.__slots__:
assert tp.request_count == self.request_count
for key in json_dict:
if key in cls.__slots__:
assert getattr(tp, key) == getattr(self, key)
assert cls.de_json(None, offline_bot) is None
assert TransactionPartner.de_json({}, offline_bot) is None
@@ -451,14 +495,20 @@ class TestTransactionPartnerWithoutRequest(TransactionPartnerTestBase):
"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
@@ -472,7 +522,9 @@ class TestTransactionPartnerWithoutRequest(TransactionPartnerTestBase):
"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
@@ -481,11 +533,16 @@ class TestTransactionPartnerWithoutRequest(TransactionPartnerTestBase):
assert isinstance(tp_dict, dict)
assert tp_dict["type"] == transaction_partner.type
if hasattr(transaction_partner, "user"):
assert tp_dict["user"] == transaction_partner.user.to_dict()
assert tp_dict["invoice_payload"] == transaction_partner.invoice_payload
if hasattr(transaction_partner, "withdrawal_state"):
assert tp_dict["withdrawal_state"] == transaction_partner.withdrawal_state.to_dict()
for attr in transaction_partner.__slots__:
attribute = getattr(transaction_partner, attr)
if isinstance(attribute, TelegramObject):
assert tp_dict[attr] == attribute.to_dict()
elif not isinstance(attribute, str) and isinstance(attribute, Sequence):
assert tp_dict[attr] == [a.to_dict() for a in attribute]
elif isinstance(attribute, datetime.timedelta):
assert tp_dict[attr] == attribute.total_seconds()
else:
assert tp_dict[attr] == attribute
def test_type_enum_conversion(self):
assert type(TransactionPartner("other").type) is TransactionPartnerType
@@ -661,3 +718,132 @@ class TestRevenueWithdrawalStateWithoutRequest(RevenueWithdrawalStateTestBase):
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)