From 5e924014debbb6c696ee33aac1bfe477ba51e87b Mon Sep 17 00:00:00 2001 From: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com> Date: Sat, 14 May 2022 15:50:12 +0200 Subject: [PATCH] Add Tuple Based Version Info and Rename `telegram.bot_api_version` to `telegram.__bot_api_version__` (#3030) --- docs/source/conf.py | 12 +++++- docs/source/telegram.rst | 9 ++++ setup.py | 7 ++-- telegram/__init__.py | 31 +++++++++++++- telegram/_version.py | 44 ++++++++++++++++++-- telegram/constants.py | 54 ++++++++++++++++++------ tests/test_constants.py | 18 ++++++++ tests/test_version.py | 88 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 239 insertions(+), 24 deletions(-) create mode 100644 tests/test_version.py diff --git a/docs/source/conf.py b/docs/source/conf.py index d7c46faec..a421560d1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -444,9 +444,17 @@ class TGConstXRefRole(PyXRefRole): if isinstance(value, telegram.constants.FileSizeLimit): return f"{int(value.value / 1e6)} MB", target return repr(value.value), target - # Just for Bot API version number auto add in constants: - if isinstance(value, str) and target == "telegram.constants.BOT_API_VERSION": + # Just for (Bot API) versions number auto add in constants: + if isinstance(value, str) and target in ( + "telegram.constants.BOT_API_VERSION", + "telegram.__version__", + ): return value, target + if isinstance(value, tuple) and target in ( + "telegram.constants.BOT_API_VERSION_INFO", + "telegram.__version_info__", + ): + return repr(value), target sphinx_logger.warning( f"%s:%d: WARNING: Did not convert reference %s. :{CONSTANTS_ROLE}: is not supposed" " to be used with this type of target.", diff --git a/docs/source/telegram.rst b/docs/source/telegram.rst index 8b6fa05fe..9ddcbaecb 100644 --- a/docs/source/telegram.rst +++ b/docs/source/telegram.rst @@ -1,6 +1,15 @@ telegram package ================ +Version Constants +----------------- + +.. automodule:: telegram + :members: __version__, __version_info__, __bot_api_version__, __bot_api_version_info__ + +Available Types +--------------- + .. toctree:: telegram.animation diff --git a/setup.py b/setup.py index 5ff13249a..395b0fae6 100644 --- a/setup.py +++ b/setup.py @@ -42,10 +42,9 @@ def get_setup_kwargs(raw=False): raw_ext = "-raw" if raw else "" readme = Path(f'README{"_RAW" if raw else ""}.rst') - with Path("telegram/_version.py").open() as fh: - for line in fh.readlines(): - if line.startswith("__version__"): - exec(line) + version_file = Path("telegram/_version.py").read_text() + first_part = version_file.split("# SETUP.PY MARKER")[0] + exec(first_part) kwargs = dict( script_name=f"setup{raw_ext}.py", diff --git a/telegram/__init__.py b/telegram/__init__.py index d9f182e23..238e94f5f 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -21,10 +21,13 @@ __author__ = "devs@python-telegram-bot.org" __all__ = ( # Keep this alphabetically ordered + "__bot_api_version__", + "__bot_api_version_info__", + "__version__", + "__version_info__", "Animation", "Audio", "Bot", - "bot_api_version", "BotCommand", "BotCommandScope", "BotCommandScopeAllChatAdministrators", @@ -308,7 +311,31 @@ from ._telegramobject import TelegramObject from ._update import Update from ._user import User from ._userprofilephotos import UserProfilePhotos -from ._version import __version__, bot_api_version # noqa: F401 +from . import _version + +#: :obj:`str`: The version of the `python-telegram-bot` library as string. +#: To get detailed information about the version number, please use :data:`__version_info__` +#: instead. +__version__ = _version.__version__ +#: :class:`typing.NamedTuple`: A tuple containing the five components of the version number: +#: `major`, `minor`, `micro`, `releaselevel`, and `serial`. +#: All values except `releaselevel` are integers. +#: The release level is ``'alpha'``, ``'beta'``, ``'candidate'``, or ``'final'``. +#: The components can also be accessed by name, so ``__version_info__[0]`` is equivalent to +#: ``__version_info__.major`` and so on. +#: +#: .. versionadded:: 20.0 +__version_info__ = _version.__version_info__ +#: :obj:`str`: Shortcut for :const:`telegram.constants.BOT_API_VERSION`. +#: +#: .. versionchanged:: 20.0 +#: This constant was previously named ``bot_api_version``. +__bot_api_version__ = _version.__bot_api_version__ +#: :class:`typing.NamedTuple`: Shortcut for :const:`telegram.constants.BOT_API_VERSION_INFO`. +#: +#: .. versionadded:: 20.0 +__bot_api_version_info__ = _version.__bot_api_version_info__ + from ._videochat import ( VideoChatEnded, VideoChatParticipantsInvited, diff --git a/telegram/_version.py b/telegram/_version.py index c263ab4e2..62f50213d 100644 --- a/telegram/_version.py +++ b/telegram/_version.py @@ -17,9 +17,47 @@ # 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=missing-module-docstring +from typing import NamedTuple -__version__ = "20.0a0" +__all__ = ("__version__", "__version_info__", "__bot_api_version__", "__bot_api_version_info__") -from telegram import constants -bot_api_version = constants.BOT_API_VERSION # pylint: disable=invalid-name +class Version(NamedTuple): + """Copies the behavior of sys.version_info. + serial is always 0 for stable releases. + """ + + major: int + minor: int + micro: int + releaselevel: str # Literal['alpha', 'beta', 'candidate', 'final'] + serial: int + + def _rl_shorthand(self) -> str: + return { + "alpha": "a", + "beta": "b", + "candidate": "rc", + }[self.releaselevel] + + def __str__(self) -> str: + version = f"{self.major}.{self.minor}" + if self.micro != 0: + version = f"{version}.{self.micro}" + if self.releaselevel != "final": + version = f"{version}{self._rl_shorthand()}{self.serial}" + + return version + + +__version_info__ = Version(major=20, minor=0, micro=0, releaselevel="alpha", serial=0) +__version__ = str(__version_info__) + +# # SETUP.PY MARKER +# Lines above this line will be `exec`-cuted in setup.py. Make sure that this only contains +# std-lib imports! + +from telegram import constants # noqa: E402 # pylint: disable=wrong-import-position + +__bot_api_version__ = constants.BOT_API_VERSION +__bot_api_version_info__ = constants.BOT_API_VERSION_INFO diff --git a/telegram/constants.py b/telegram/constants.py index 9f08973fb..564118121 100644 --- a/telegram/constants.py +++ b/telegram/constants.py @@ -20,23 +20,17 @@ Unless noted otherwise, all constants in this module were extracted from the `Telegram Bots FAQ `_ and `Telegram Bots API `_. +Most of the following constants are related to specific classes or topics and are grouped into +enums. If they are related to a specific class, then they are also available as attributes of +those classes. + .. versionchanged:: 20.0 Since v20.0, most of the constants in this module are grouped into enums. - -Attributes: - BOT_API_VERSION (:obj:`str`): :tg-const:`telegram.constants.BOT_API_VERSION`. Telegram Bot API - version supported by this version of `python-telegram-bot`. Also available as - ``telegram.bot_api_version``. - - .. versionadded:: 13.4 - SUPPORTED_WEBHOOK_PORTS (List[:obj:`int`]): [443, 80, 88, 8443] - -The following constants are related to specific classes or topics and are grouped into enums. If -they are related to a specific class, then they are also available as attributes of those classes. """ __all__ = [ "BOT_API_VERSION", + "BOT_API_VERSION_INFO", "BotCommandScopeType", "CallbackQueryLimit", "ChatAction", @@ -66,14 +60,48 @@ __all__ = [ ] from enum import IntEnum -from typing import List +from typing import List, NamedTuple from telegram._utils.enum import StringEnum -BOT_API_VERSION = "6.0" + +class _BotAPIVersion(NamedTuple): + """Similar behavior to sys.version_info. + So far TG has only published X.Y releases. We can add X.Y.Z(a(S)) if needed. + """ + + major: int + minor: int + + def __repr__(self) -> str: + """Unfortunately calling super().__repr__ doesn't work with typing.NamedTuple, so we + do this manually. + """ + return f"BotAPIVersion(major={self.major}, minor={self.minor})" + + def __str__(self) -> str: + return f"{self.major}.{self.minor}" + + +#: :class:`typing.NamedTuple`: A tuple containing the two components of the version number: +# ``major`` and ``minor``. Both values are integers. +#: The components can also be accessed by name, so ``BOT_API_VERSION_INFO[0]`` is equivalent +#: to ``BOT_API_VERSION_INFO.major`` and so on. Also available as +#: :data:`telegram.__bot_api_version_info__`. +#: +#: .. versionadded:: 20.0 +BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=0) +#: :obj:`str`: Telegram Bot API +#: version supported by this version of `python-telegram-bot`. Also available as +#: :data:`telegram.__bot_api_version__`. +#: +#: .. versionadded:: 13.4 +BOT_API_VERSION = str(BOT_API_VERSION_INFO) # constants above this line are tested +#: List[:obj:`int`]: Ports supported by +#: :paramref:`telegram.Bot.set_webhook.url`. SUPPORTED_WEBHOOK_PORTS: List[int] = [443, 80, 88, 8443] diff --git a/tests/test_constants.py b/tests/test_constants.py index 9e6927b90..2d346e780 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -113,3 +113,21 @@ class TestConstants: match = "Media_caption_too_long" with pytest.raises(BadRequest, match=match), data_file("telegram.png").open("rb") as f: await bot.send_photo(photo=f, caption=bad_caption, chat_id=chat_id) + + def test_bot_api_version_and_info(self): + assert constants.BOT_API_VERSION == str(constants.BOT_API_VERSION_INFO) + assert constants.BOT_API_VERSION_INFO == tuple( + int(x) for x in constants.BOT_API_VERSION.split(".") + ) + + def test_bot_api_version_info(self): + vi = constants.BOT_API_VERSION_INFO + assert isinstance(vi, tuple) + assert repr(vi) == f"BotAPIVersion(major={vi[0]}, minor={vi[1]})" + assert vi == (vi[0], vi[1]) + assert not (vi < (vi[0], vi[1])) + assert vi < (vi[0], vi[1] + 1) + assert vi < (vi[0] + 1, vi[1]) + assert vi < (vi[0] + 1, vi[1] + 1) + assert vi[0] == vi.major + assert vi[1] == vi.minor diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 000000000..766b3083f --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2022 +# Leandro Toledo de Souza +# +# 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 ( + __version_info__, + __version__, + __bot_api_version_info__, + __bot_api_version__, + constants, +) +from telegram._version import Version + + +class TestVersion: + def test_bot_api_version_and_info(self): + assert __bot_api_version__ is constants.BOT_API_VERSION + assert __bot_api_version_info__ is constants.BOT_API_VERSION_INFO + + def test_version_and_info(self): + assert __version__ == str(__version_info__) + + @pytest.mark.parametrize( + "version,expected", + [ + (Version(1, 2, 3, "alpha", 4), "1.2.3a4"), + (Version(2, 3, 4, "beta", 5), "2.3.4b5"), + (Version(1, 2, 3, "candidate", 4), "1.2.3rc4"), + (Version(1, 2, 0, "alpha", 4), "1.2a4"), + (Version(2, 3, 0, "beta", 5), "2.3b5"), + (Version(1, 2, 0, "candidate", 4), "1.2rc4"), + (Version(1, 2, 3, "final", 0), "1.2.3"), + (Version(1, 2, 0, "final", 0), "1.2"), + ], + ) + def test_version_str(self, version, expected): + assert str(version) == expected + + @pytest.mark.parametrize("use_tuple", (True, False)) + def test_version_info(self, use_tuple): + version = Version(1, 2, 3, "beta", 4) + assert isinstance(version, tuple) + assert version.major == version[0] + assert version.minor == version[1] + assert version.micro == version[2] + assert version.releaselevel == version[3] + assert version.serial == version[4] + + class TestClass: + def __new__(cls, *args): + if use_tuple: + return tuple(args) + return Version(*args) + + assert isinstance(TestClass(1, 2, 3, "beta", 4), tuple if use_tuple else Version) + assert version == TestClass(1, 2, 3, "beta", 4) + assert not (version < TestClass(1, 2, 3, "beta", 4)) + assert version > TestClass(1, 2, 3, "beta", 3) + assert version > TestClass(1, 2, 3, "alpha", 4) + assert version < TestClass(1, 2, 3, "candidate", 0) + assert version < TestClass(1, 2, 3, "final", 0) + assert version < TestClass(1, 2, 4, "final", 0) + assert version < TestClass(1, 3, 4, "final", 0) + + assert version < (1, 3) + assert version >= (1, 2, 3, "alpha") + assert version > (1, 1) + assert version <= (1, 2, 3, "beta", 4) + assert version < (1, 2, 3, "candidate", 4) + assert not (version > (1, 2, 3, "candidate", 4)) + assert version < (1, 2, 4) + assert version > (1, 2, 2)