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

@ -4,7 +4,7 @@ on:
branches:
- master
push:
branches:
branches:
- master
schedule:
# Run monday and friday morning at 03:07 - odd time to spread load on GitHub Actions
@ -14,10 +14,22 @@ jobs:
pytest:
name: pytest
runs-on: ${{matrix.os}}
continue-on-error: ${{ matrix.experimental }}
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
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
steps:
- 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`")
continue
if "IntEnum" in base:
bases[idx] = ":class:`enum.IntEnum`"
continue
# Drop generics (at least for now)
if base.endswith("]"):
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.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
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
the changelog.
"""
from enum import Enum
import enum as _enum
import sys
from typing import Type, TypeVar, Union
_A = TypeVar("_A")
_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]:
"""Tries to call ``enum(value)`` to convert the value into an enumeration member.
def get_member(enum_cls: Type[_Enum], value: _A, default: _B) -> Union[_Enum, _A, _B]:
"""Tries to call ``enum_cls(value)`` to convert the value into an enumeration member.
If that fails, the ``default`` is returned.
"""
try:
return enum(value)
return enum_cls(value)
except ValueError:
return default
class StringEnum(str, Enum):
"""Helper class for string enums where the value is not important to be displayed on
stringification.
# Python 3.11 and above has a different output for mixin classes for IntEnum, StrEnum and IntFlag
# see https://docs.python.org/3.11/library/enum.html#notes. We want e.g. str(StrEnumTest.FOO) to
# 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__ = ()
def __repr__(self) -> str:
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.
.. 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__ = [
@ -61,10 +62,9 @@ __all__ = [
"UpdateType",
]
from enum import IntEnum
from typing import List, NamedTuple
from telegram._utils.enum import StringEnum
from telegram._utils.enum import IntEnum, StringEnum
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:
try:
return BOTS[JOB_INDEX][name]
except KeyError:
except (KeyError, IndexError):
pass
# Otherwise go with the fallback

View file

@ -17,13 +17,12 @@
# 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 json
from enum import IntEnum
import pytest
from flaky import flaky
from telegram import constants
from telegram._utils.enum import StringEnum
from telegram._utils.enum import IntEnum, StringEnum
from telegram.error import BadRequest
from tests.conftest import data_file
@ -62,8 +61,24 @@ class TestConstants:
assert json.dumps(IntEnumTest.FOO) == json.dumps(1)
def test_string_representation(self):
# test __repr__
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):
assert isinstance(StrEnumTest.FOO, str)