Add Python 3.11 to Test Suite & Adapt Enum Behaviour (#3168)

This commit is contained in:
Harshil 2022-08-17 21:54:50 +05:30 committed by GitHub
parent 2c84122654
commit 90c0fe948b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 72 additions and 16 deletions

View file

@ -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

View file

@ -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]

View file

@ -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",
) )

View file

@ -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)

View file

@ -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):

View file

@ -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

View file

@ -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)