mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-01-03 09:49:21 +01:00
Add Python 3.11 to Test Suite & Adapt Enum Behaviour (#3168)
This commit is contained in:
parent
2c84122654
commit
90c0fe948b
7 changed files with 72 additions and 16 deletions
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
|
@ -14,10 +14,22 @@ jobs:
|
||||||
pytest:
|
pytest:
|
||||||
name: pytest
|
name: pytest
|
||||||
runs-on: ${{matrix.os}}
|
runs-on: ${{matrix.os}}
|
||||||
|
continue-on-error: ${{ matrix.experimental }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.7', '3.8', '3.9', '3.10']
|
python-version: ['3.7', '3.8', '3.9', '3.10']
|
||||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
experimental: [false]
|
||||||
|
include:
|
||||||
|
- python-version: 3.11.0-rc.1
|
||||||
|
os: ubuntu-latest
|
||||||
|
experimental: true
|
||||||
|
- python-version: 3.11.0-rc.1
|
||||||
|
os: windows-latest
|
||||||
|
experimental: true
|
||||||
|
- python-version: 3.11.0-rc.1
|
||||||
|
os: macos-latest
|
||||||
|
experimental: true
|
||||||
fail-fast: False
|
fail-fast: False
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
@ -478,6 +478,10 @@ def autodoc_process_bases(app, name, obj, option, bases: list):
|
||||||
bases.insert(0, ":class:`str`")
|
bases.insert(0, ":class:`str`")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if "IntEnum" in base:
|
||||||
|
bases[idx] = ":class:`enum.IntEnum`"
|
||||||
|
continue
|
||||||
|
|
||||||
# Drop generics (at least for now)
|
# Drop generics (at least for now)
|
||||||
if base.endswith("]"):
|
if base.endswith("]"):
|
||||||
base = base.split("[", maxsplit=1)[0]
|
base = base.split("[", maxsplit=1)[0]
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -89,6 +89,7 @@ def get_setup_kwargs(raw=False):
|
||||||
"Programming Language :: Python :: 3.8",
|
"Programming Language :: Python :: 3.8",
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
],
|
],
|
||||||
python_requires=">=3.7",
|
python_requires=">=3.7",
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,30 +23,54 @@ Warning:
|
||||||
user. Changes to this module are not considered breaking changes and may not be documented in
|
user. Changes to this module are not considered breaking changes and may not be documented in
|
||||||
the changelog.
|
the changelog.
|
||||||
"""
|
"""
|
||||||
from enum import Enum
|
import enum as _enum
|
||||||
|
import sys
|
||||||
from typing import Type, TypeVar, Union
|
from typing import Type, TypeVar, Union
|
||||||
|
|
||||||
_A = TypeVar("_A")
|
_A = TypeVar("_A")
|
||||||
_B = TypeVar("_B")
|
_B = TypeVar("_B")
|
||||||
_Enum = TypeVar("_Enum", bound=Enum)
|
_Enum = TypeVar("_Enum", bound=_enum.Enum)
|
||||||
|
|
||||||
|
|
||||||
def get_member(enum: Type[_Enum], value: _A, default: _B) -> Union[_Enum, _A, _B]:
|
def get_member(enum_cls: Type[_Enum], value: _A, default: _B) -> Union[_Enum, _A, _B]:
|
||||||
"""Tries to call ``enum(value)`` to convert the value into an enumeration member.
|
"""Tries to call ``enum_cls(value)`` to convert the value into an enumeration member.
|
||||||
If that fails, the ``default`` is returned.
|
If that fails, the ``default`` is returned.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return enum(value)
|
return enum_cls(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
class StringEnum(str, Enum):
|
# Python 3.11 and above has a different output for mixin classes for IntEnum, StrEnum and IntFlag
|
||||||
"""Helper class for string enums where the value is not important to be displayed on
|
# see https://docs.python.org/3.11/library/enum.html#notes. We want e.g. str(StrEnumTest.FOO) to
|
||||||
stringification.
|
# return "foo" instead of "StrEnumTest.FOO", which is not the case < py3.11
|
||||||
|
class StringEnum(str, _enum.Enum):
|
||||||
|
"""Helper class for string enums where ``str(member)`` prints the value, but ``repr(member)``
|
||||||
|
gives ``EnumName.MEMBER_NAME``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self.__class__.__name__}.{self.name}>"
|
return f"<{self.__class__.__name__}.{self.name}>"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str.__str__(self)
|
||||||
|
|
||||||
|
|
||||||
|
# Apply the __repr__ modification and __str__ fix to IntEnum
|
||||||
|
class IntEnum(_enum.IntEnum):
|
||||||
|
"""Helper class for int 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}>"
|
||||||
|
|
||||||
|
if sys.version_info < (3, 11):
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
|
|
|
@ -25,7 +25,8 @@ enums. If they are related to a specific class, then they are also available as
|
||||||
those classes.
|
those classes.
|
||||||
|
|
||||||
.. versionchanged:: 20.0
|
.. versionchanged:: 20.0
|
||||||
Since v20.0, most of the constants in this module are grouped into enums.
|
|
||||||
|
* Most of the constants in this module are grouped into enums.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -61,10 +62,9 @@ __all__ = [
|
||||||
"UpdateType",
|
"UpdateType",
|
||||||
]
|
]
|
||||||
|
|
||||||
from enum import IntEnum
|
|
||||||
from typing import List, NamedTuple
|
from typing import List, NamedTuple
|
||||||
|
|
||||||
from telegram._utils.enum import StringEnum
|
from telegram._utils.enum import IntEnum, StringEnum
|
||||||
|
|
||||||
|
|
||||||
class _BotAPIVersion(NamedTuple):
|
class _BotAPIVersion(NamedTuple):
|
||||||
|
|
|
@ -58,7 +58,7 @@ def get(name, fallback):
|
||||||
if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None:
|
if GITHUB_ACTION is not None and BOTS is not None and JOB_INDEX is not None:
|
||||||
try:
|
try:
|
||||||
return BOTS[JOB_INDEX][name]
|
return BOTS[JOB_INDEX][name]
|
||||||
except KeyError:
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Otherwise go with the fallback
|
# Otherwise go with the fallback
|
||||||
|
|
|
@ -17,13 +17,12 @@
|
||||||
# You should have received a copy of the GNU Lesser Public License
|
# You should have received a copy of the GNU Lesser Public License
|
||||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||||
import json
|
import json
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from flaky import flaky
|
from flaky import flaky
|
||||||
|
|
||||||
from telegram import constants
|
from telegram import constants
|
||||||
from telegram._utils.enum import StringEnum
|
from telegram._utils.enum import IntEnum, StringEnum
|
||||||
from telegram.error import BadRequest
|
from telegram.error import BadRequest
|
||||||
from tests.conftest import data_file
|
from tests.conftest import data_file
|
||||||
|
|
||||||
|
@ -62,8 +61,24 @@ class TestConstants:
|
||||||
assert json.dumps(IntEnumTest.FOO) == json.dumps(1)
|
assert json.dumps(IntEnumTest.FOO) == json.dumps(1)
|
||||||
|
|
||||||
def test_string_representation(self):
|
def test_string_representation(self):
|
||||||
|
# test __repr__
|
||||||
assert repr(StrEnumTest.FOO) == "<StrEnumTest.FOO>"
|
assert repr(StrEnumTest.FOO) == "<StrEnumTest.FOO>"
|
||||||
assert str(StrEnumTest.FOO) == "StrEnumTest.FOO"
|
|
||||||
|
# test __format__
|
||||||
|
assert f"{StrEnumTest.FOO} this {StrEnumTest.BAR}" == "foo this bar"
|
||||||
|
assert f"{StrEnumTest.FOO:*^10}" == "***foo****"
|
||||||
|
|
||||||
|
# test __str__
|
||||||
|
assert str(StrEnumTest.FOO) == "foo"
|
||||||
|
|
||||||
|
def test_int_representation(self):
|
||||||
|
# test __repr__
|
||||||
|
assert repr(IntEnumTest.FOO) == "<IntEnumTest.FOO>"
|
||||||
|
# test __format__
|
||||||
|
assert f"{IntEnumTest.FOO}/0 is undefined!" == "1/0 is undefined!"
|
||||||
|
assert f"{IntEnumTest.FOO:*^10}" == "****1*****"
|
||||||
|
# test __str__
|
||||||
|
assert str(IntEnumTest.FOO) == "1"
|
||||||
|
|
||||||
def test_string_inheritance(self):
|
def test_string_inheritance(self):
|
||||||
assert isinstance(StrEnumTest.FOO, str)
|
assert isinstance(StrEnumTest.FOO, str)
|
||||||
|
|
Loading…
Reference in a new issue