From 42a64c03ec5c443f2a5c2ee4284622f5d1f5326c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 17 Jan 2022 13:58:40 +0100 Subject: [PATCH] Revert "bpo-40066: [Enum] update str() and format() output (GH-30582)" (GH-30632) This reverts commit acf7403f9baea3ae1119fc6b4a3298522188bf96. --- Doc/howto/enum.rst | 272 +- Doc/library/enum.rst | 265 +- Doc/library/ssl.rst | 4 +- Lib/enum.py | 605 ++-- Lib/inspect.py | 30 +- Lib/plistlib.py | 3 +- Lib/re.py | 2 - Lib/ssl.py | 1 + Lib/test/test_enum.py | 2896 +++++++++-------- Lib/test/test_signal.py | 2 +- Lib/test/test_socket.py | 12 +- Lib/test/test_ssl.py | 8 +- Lib/test/test_unicode.py | 6 +- .../2022-01-13-11-41-24.bpo-40066.1QuVli.rst | 2 - 14 files changed, 2021 insertions(+), 2087 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2022-01-13-11-41-24.bpo-40066.1QuVli.rst diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index fa0e2283ebc..6c09b9925c1 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -2,10 +2,15 @@ Enum HOWTO ========== +:Author: Ethan Furman + .. _enum-basic-tutorial: .. currentmodule:: enum +Basic Enum Tutorial +------------------- + An :class:`Enum` is a set of symbolic names bound to unique values. They are similar to global variables, but they offer a more useful :func:`repr()`, grouping, type-safety, and a few other features. @@ -23,14 +28,6 @@ selection of values. For example, the days of the week:: ... SATURDAY = 6 ... SUNDAY = 7 - Or perhaps the RGB primary colors:: - - >>> from enum import Enum - >>> class Color(Enum): - ... RED = 1 - ... GREEN = 2 - ... BLUE = 3 - As you can see, creating an :class:`Enum` is as simple as writing a class that inherits from :class:`Enum` itself. @@ -44,14 +41,13 @@ important, but either way that value can be used to get the corresponding member:: >>> Weekday(3) - + Weekday.WEDNESDAY -As you can see, the ``repr()`` of a member shows the enum name, the member name, -and the value. The ``str()`` of a member shows only the enum name and member -name:: +As you can see, the ``repr()`` of a member shows the enum name and the +member name. The ``str()`` on a member shows only its name:: >>> print(Weekday.THURSDAY) - Weekday.THURSDAY + THURSDAY The *type* of an enumeration member is the enum it belongs to:: @@ -101,8 +97,8 @@ The complete :class:`Weekday` enum now looks like this:: Now we can find out what today is! Observe:: >>> from datetime import date - >>> Weekday.from_date(date.today()) # doctest: +SKIP - + >>> Weekday.from_date(date.today()) + Weekday.TUESDAY Of course, if you're reading this on some other day, you'll see that day instead. @@ -128,21 +124,21 @@ Just like the original :class:`Weekday` enum above, we can have a single selecti >>> first_week_day = Weekday.MONDAY >>> first_week_day - + Weekday.MONDAY But :class:`Flag` also allows us to combine several members into a single variable:: >>> weekend = Weekday.SATURDAY | Weekday.SUNDAY >>> weekend - + Weekday.SATURDAY|Weekday.SUNDAY You can even iterate over a :class:`Flag` variable:: >>> for day in weekend: ... print(day) - Weekday.SATURDAY - Weekday.SUNDAY + SATURDAY + SUNDAY Okay, let's get some chores set up:: @@ -177,7 +173,6 @@ yourself some work and use :func:`auto()` for the values:: .. _enum-advanced-tutorial: - Programmatic access to enumeration members and their attributes --------------------------------------------------------------- @@ -186,16 +181,16 @@ situations where ``Color.RED`` won't do because the exact color is not known at program-writing time). ``Enum`` allows such access:: >>> Color(1) - + Color.RED >>> Color(3) - + Color.BLUE If you want to access enum members by *name*, use item access:: >>> Color['RED'] - + Color.RED >>> Color['GREEN'] - + Color.GREEN If you have an enum member and need its :attr:`name` or :attr:`value`:: @@ -217,7 +212,7 @@ Having two enum members with the same name is invalid:: ... Traceback (most recent call last): ... - TypeError: 'SQUARE' already defined as 2 + TypeError: 'SQUARE' already defined as: 2 However, an enum member can have other names associated with it. Given two entries ``A`` and ``B`` with the same value (and ``A`` defined first), ``B`` @@ -232,11 +227,11 @@ By-name lookup of ``B`` will also return the member ``A``:: ... ALIAS_FOR_SQUARE = 2 ... >>> Shape.SQUARE - + Shape.SQUARE >>> Shape.ALIAS_FOR_SQUARE - + Shape.SQUARE >>> Shape(2) - + Shape.SQUARE .. note:: @@ -304,7 +299,7 @@ Iteration Iterating over the members of an enum does not provide the aliases:: >>> list(Shape) - [, , ] + [Shape.SQUARE, Shape.DIAMOND, Shape.CIRCLE] The special attribute ``__members__`` is a read-only ordered mapping of names to members. It includes all names defined in the enumeration, including the @@ -313,10 +308,10 @@ aliases:: >>> for name, member in Shape.__members__.items(): ... name, member ... - ('SQUARE', ) - ('DIAMOND', ) - ('CIRCLE', ) - ('ALIAS_FOR_SQUARE', ) + ('SQUARE', Shape.SQUARE) + ('DIAMOND', Shape.DIAMOND) + ('CIRCLE', Shape.CIRCLE) + ('ALIAS_FOR_SQUARE', Shape.SQUARE) The ``__members__`` attribute can be used for detailed programmatic access to the enumeration members. For example, finding all the aliases:: @@ -365,8 +360,8 @@ below):: Allowed members and attributes of enumerations ---------------------------------------------- -Most of the examples above use integers for enumeration values. Using integers -is short and handy (and provided by default by the `Functional API`_), but not +Most of the examples above use integers for enumeration values. Using integers is +short and handy (and provided by default by the `Functional API`_), but not strictly enforced. In the vast majority of use-cases, one doesn't care what the actual value of an enumeration is. But if the value *is* important, enumerations can have arbitrary values. @@ -394,7 +389,7 @@ usual. If we have this enumeration:: Then:: >>> Mood.favorite_mood() - + Mood.HAPPY >>> Mood.HAPPY.describe() ('HAPPY', 3) >>> str(Mood.FUNKY) @@ -430,7 +425,7 @@ any members. So this is forbidden:: ... Traceback (most recent call last): ... - TypeError: cannot extend + TypeError: MoreColor: cannot extend enumeration 'Color' But this is allowed:: @@ -481,9 +476,11 @@ The :class:`Enum` class is callable, providing the following functional API:: >>> Animal >>> Animal.ANT - + Animal.ANT + >>> Animal.ANT.value + 1 >>> list(Animal) - [, , , ] + [Animal.ANT, Animal.BEE, Animal.CAT, Animal.DOG] The semantics of this API resemble :class:`~collections.namedtuple`. The first argument of the call to :class:`Enum` is the name of the enumeration. @@ -628,7 +625,16 @@ StrEnum The second variation of :class:`Enum` that is provided is also a subclass of :class:`str`. Members of a :class:`StrEnum` can be compared to strings; by extension, string enumerations of different types can also be compared -to each other. +to each other. :class:`StrEnum` exists to help avoid the problem of getting +an incorrect member:: + + >>> from enum import StrEnum + >>> class Directions(StrEnum): + ... NORTH = 'north', # notice the trailing comma + ... SOUTH = 'south' + +Before :class:`StrEnum`, ``Directions.NORTH`` would have been the :class:`tuple` +``('north',)``. .. versionadded:: 3.11 @@ -639,8 +645,9 @@ IntFlag The next variation of :class:`Enum` provided, :class:`IntFlag`, is also based on :class:`int`. The difference being :class:`IntFlag` members can be combined using the bitwise operators (&, \|, ^, ~) and the result is still an -:class:`IntFlag` member, if possible. Like :class:`IntEnum`, :class:`IntFlag` -members are also integers and can be used wherever an :class:`int` is used. +:class:`IntFlag` member, if possible. However, as the name implies, :class:`IntFlag` +members also subclass :class:`int` and can be used wherever an :class:`int` is +used. .. note:: @@ -663,7 +670,7 @@ Sample :class:`IntFlag` class:: ... X = 1 ... >>> Perm.R | Perm.W - + Perm.R|Perm.W >>> Perm.R + Perm.W 6 >>> RW = Perm.R | Perm.W @@ -678,11 +685,11 @@ It is also possible to name the combinations:: ... X = 1 ... RWX = 7 >>> Perm.RWX - + Perm.RWX >>> ~Perm.RWX - + Perm(0) >>> Perm(7) - + Perm.RWX .. note:: @@ -695,7 +702,7 @@ Another important difference between :class:`IntFlag` and :class:`Enum` is that if no flags are set (the value is 0), its boolean evaluation is :data:`False`:: >>> Perm.R & Perm.X - + Perm(0) >>> bool(Perm.R & Perm.X) False @@ -703,7 +710,7 @@ Because :class:`IntFlag` members are also subclasses of :class:`int` they can be combined with them (but may lose :class:`IntFlag` membership:: >>> Perm.X | 4 - + Perm.R|Perm.X >>> Perm.X | 8 9 @@ -719,7 +726,7 @@ be combined with them (but may lose :class:`IntFlag` membership:: :class:`IntFlag` members can also be iterated over:: >>> list(RW) - [, ] + [Perm.R, Perm.W] .. versionadded:: 3.11 @@ -746,7 +753,7 @@ flags being set, the boolean evaluation is :data:`False`:: ... GREEN = auto() ... >>> Color.RED & Color.GREEN - + Color(0) >>> bool(Color.RED & Color.GREEN) False @@ -760,7 +767,7 @@ while combinations of flags won't:: ... WHITE = RED | BLUE | GREEN ... >>> Color.WHITE - + Color.WHITE Giving a name to the "no flags set" condition does not change its boolean value:: @@ -772,7 +779,7 @@ value:: ... GREEN = auto() ... >>> Color.BLACK - + Color.BLACK >>> bool(Color.BLACK) False @@ -780,7 +787,7 @@ value:: >>> purple = Color.RED | Color.BLUE >>> list(purple) - [, ] + [Color.RED, Color.BLUE] .. versionadded:: 3.11 @@ -805,16 +812,16 @@ simple to implement independently:: pass This demonstrates how similar derived enumerations can be defined; for example -a :class:`FloatEnum` that mixes in :class:`float` instead of :class:`int`. +a :class:`StrEnum` that mixes in :class:`str` instead of :class:`int`. Some rules: 1. When subclassing :class:`Enum`, mix-in types must appear before :class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum` example above. -2. Mix-in types must be subclassable. For example, :class:`bool` and - :class:`range` are not subclassable and will throw an error during Enum - creation if used as the mix-in type. +2. Mix-in types must be subclassable. For example, + :class:`bool` and :class:`range` are not subclassable + and will throw an error during Enum creation if used as the mix-in type. 3. While :class:`Enum` can have members of any type, once you mix in an additional type, all the members must have values of that type, e.g. :class:`int` above. This restriction does not apply to mix-ins which only @@ -822,18 +829,15 @@ Some rules: 4. When another data type is mixed in, the :attr:`value` attribute is *not the same* as the enum member itself, although it is equivalent and will compare equal. -5. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's +5. %-style formatting: `%s` and `%r` call the :class:`Enum` class's :meth:`__str__` and :meth:`__repr__` respectively; other codes (such as - ``%i`` or ``%h`` for IntEnum) treat the enum member as its mixed-in type. + `%i` or `%h` for IntEnum) treat the enum member as its mixed-in type. 6. :ref:`Formatted string literals `, :meth:`str.format`, - and :func:`format` will use the enum's :meth:`__str__` method. - -.. note:: - - Because :class:`IntEnum`, :class:`IntFlag`, and :class:`StrEnum` are - designed to be drop-in replacements for existing constants, their - :meth:`__str__` method has been reset to their data types - :meth:`__str__` method. + and :func:`format` will use the mixed-in type's :meth:`__format__` + unless :meth:`__str__` or :meth:`__format__` is overridden in the subclass, + in which case the overridden methods or :class:`Enum` methods will be used. + Use the !s and !r format codes to force usage of the :class:`Enum` class's + :meth:`__str__` and :meth:`__repr__` methods. When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------ @@ -862,10 +866,10 @@ want one of them to be the value:: ... >>> print(Coordinate['PY']) - Coordinate.PY + PY >>> print(Coordinate(3)) - Coordinate.VY + VY Finer Points @@ -923,8 +927,8 @@ and raise an error if the two do not match:: Traceback (most recent call last): ... TypeError: member order does not match _order_: - ['RED', 'BLUE', 'GREEN'] - ['RED', 'GREEN', 'BLUE'] + ['RED', 'BLUE', 'GREEN'] + ['RED', 'GREEN', 'BLUE'] .. note:: @@ -945,36 +949,35 @@ but remain normal attributes. """""""""""""""""""" Enum members are instances of their enum class, and are normally accessed as -``EnumClass.member``. In Python versions ``3.5`` to ``3.10`` you could access -members from other members -- this practice was discouraged, and in ``3.11`` -:class:`Enum` returns to not allowing it:: +``EnumClass.member``. In Python versions ``3.5`` to ``3.9`` you could access +members from other members -- this practice was discouraged, and in ``3.12`` +:class:`Enum` will return to not allowing it, while in ``3.10`` and ``3.11`` +it will raise a :exc:`DeprecationWarning`:: >>> class FieldTypes(Enum): ... name = 0 ... value = 1 ... size = 2 ... - >>> FieldTypes.value.size - Traceback (most recent call last): - ... - AttributeError: member has no attribute 'size' - + >>> FieldTypes.value.size # doctest: +SKIP + DeprecationWarning: accessing one member from another is not supported, + and will be disabled in 3.12 + .. versionchanged:: 3.5 -.. versionchanged:: 3.11 Creating members that are mixed with other data types """"""""""""""""""""""""""""""""""""""""""""""""""""" When subclassing other data types, such as :class:`int` or :class:`str`, with -an :class:`Enum`, all values after the ``=`` are passed to that data type's +an :class:`Enum`, all values after the `=` are passed to that data type's constructor. For example:: - >>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> integer - ... example = '11', 16 # so x='11' and base=16 - ... - >>> MyEnum.example.value # and hex(11) is... + >>> class MyEnum(IntEnum): + ... example = '11', 16 # '11' will be interpreted as a hexadecimal + ... # number + >>> MyEnum.example.value 17 @@ -997,12 +1000,13 @@ Plain :class:`Enum` classes always evaluate as :data:`True`. """"""""""""""""""""""""""""" If you give your enum subclass extra methods, like the `Planet`_ -class below, those methods will show up in a :func:`dir` of the member, -but not of the class:: +class below, those methods will show up in a :func:`dir` of the member and the +class. Attributes defined in an :func:`__init__` method will only show up in a +:func:`dir` of the member:: - >>> dir(Planet) # doctest: +SKIP - ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__'] - >>> dir(Planet.EARTH) # doctest: +SKIP + >>> dir(Planet) + ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__init__', '__members__', '__module__', 'surface_gravity'] + >>> dir(Planet.EARTH) ['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value'] @@ -1021,10 +1025,19 @@ are comprised of a single bit:: ... CYAN = GREEN | BLUE ... >>> Color(3) # named combination - + Color.YELLOW >>> Color(7) # not named combination - + Color.RED|Color.GREEN|Color.BLUE +``StrEnum`` and :meth:`str.__str__` +""""""""""""""""""""""""""""""""""" + +An important difference between :class:`StrEnum` and other Enums is the +:meth:`__str__` method; because :class:`StrEnum` members are strings, some +parts of Python will read the string data directly, while others will call +:meth:`str()`. To make those two operations have the same result, +:meth:`StrEnum.__str__` will be the same as :meth:`str.__str__` so that +``str(StrEnum.member) == StrEnum.member`` is true. ``Flag`` and ``IntFlag`` minutia """""""""""""""""""""""""""""""" @@ -1047,16 +1060,16 @@ the following are true: - only canonical flags are returned during iteration:: >>> list(Color.WHITE) - [, , ] + [Color.RED, Color.GREEN, Color.BLUE] - negating a flag or flag set returns a new flag/flag set with the corresponding positive integer value:: >>> Color.BLUE - + Color.BLUE >>> ~Color.BLUE - + Color.RED|Color.GREEN - names of pseudo-flags are constructed from their members' names:: @@ -1066,29 +1079,25 @@ the following are true: - multi-bit flags, aka aliases, can be returned from operations:: >>> Color.RED | Color.BLUE - + Color.PURPLE >>> Color(7) # or Color(-1) - + Color.WHITE >>> Color(0) - + Color.BLACK -- membership / containment checking: zero-valued flags are always considered - to be contained:: +- membership / containment checking has changed slightly -- zero-valued flags + are never considered to be contained:: >>> Color.BLACK in Color.WHITE - True + False - otherwise, only if all bits of one flag are in the other flag will True - be returned:: + otherwise, if all bits of one flag are in the other flag, True is returned:: >>> Color.PURPLE in Color.WHITE True - >>> Color.GREEN in Color.PURPLE - False - There is a new boundary mechanism that controls how out-of-range / invalid bits are handled: ``STRICT``, ``CONFORM``, ``EJECT``, and ``KEEP``: @@ -1172,7 +1181,7 @@ Using :class:`auto` would look like:: ... GREEN = auto() ... >>> Color.GREEN - + Using :class:`object` @@ -1185,24 +1194,10 @@ Using :class:`object` would look like:: ... GREEN = object() ... BLUE = object() ... - >>> Color.GREEN # doctest: +SKIP - > - -This is also a good example of why you might want to write your own -:meth:`__repr__`:: - - >>> class Color(Enum): - ... RED = object() - ... GREEN = object() - ... BLUE = object() - ... def __repr__(self): - ... return "<%s.%s>" % (self.__class__.__name__, self._name_) - ... >>> Color.GREEN - Using a descriptive string """""""""""""""""""""""""" @@ -1214,7 +1209,9 @@ Using a string as the value would look like:: ... BLUE = 'too fast!' ... >>> Color.GREEN - + + >>> Color.GREEN.value + 'go' Using a custom :meth:`__new__` @@ -1235,7 +1232,9 @@ Using an auto-numbering :meth:`__new__` would look like:: ... BLUE = () ... >>> Color.GREEN - + + >>> Color.GREEN.value + 2 To make a more general purpose ``AutoNumber``, add ``*args`` to the signature:: @@ -1258,7 +1257,7 @@ to handle any extra arguments:: ... BLEACHED_CORAL = () # New color, no Pantone code yet! ... >>> Swatch.SEA_GREEN - + >>> Swatch.SEA_GREEN.pantone '1246' >>> Swatch.BLEACHED_CORAL.pantone @@ -1385,9 +1384,30 @@ An example to show the :attr:`_ignore_` attribute in use:: ... Period['day_%d' % i] = i ... >>> list(Period)[:2] - [, ] + [Period.day_0, Period.day_1] >>> list(Period)[-2:] - [, ] + [Period.day_365, Period.day_366] + + +Conforming input to Flag +^^^^^^^^^^^^^^^^^^^^^^^^ + +To create a :class:`Flag` enum that is more resilient to out-of-bounds results +from mathematical operations, you can use the :attr:`FlagBoundary.CONFORM` +setting:: + + >>> from enum import Flag, CONFORM, auto + >>> class Weekday(Flag, boundary=CONFORM): + ... MONDAY = auto() + ... TUESDAY = auto() + ... WEDNESDAY = auto() + ... THURSDAY = auto() + ... FRIDAY = auto() + ... SATURDAY = auto() + ... SUNDAY = auto() + >>> today = Weekday.TUESDAY + >>> Weekday(today + 22) # what day is three weeks from tomorrow? + >>> Weekday.WEDNESDAY .. _enumtype-examples: diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 906c60bc3ef..8bb19dcdf2b 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -31,7 +31,7 @@ An enumeration: * uses *call* syntax to return members by value * uses *index* syntax to return members by name -Enumerations are created either by using :keyword:`class` syntax, or by +Enumerations are created either by using the :keyword:`class` syntax, or by using function-call syntax:: >>> from enum import Enum @@ -45,7 +45,7 @@ using function-call syntax:: >>> # functional syntax >>> Color = Enum('Color', ['RED', 'GREEN', 'BLUE']) -Even though we can use :keyword:`class` syntax to create Enums, Enums +Even though we can use the :keyword:`class` syntax to create Enums, Enums are not normal Python classes. See :ref:`How are Enums different? ` for more details. @@ -53,7 +53,7 @@ are not normal Python classes. See - The class :class:`Color` is an *enumeration* (or *enum*) - The attributes :attr:`Color.RED`, :attr:`Color.GREEN`, etc., are - *enumeration members* (or *members*) and are functionally constants. + *enumeration members* (or *enum members*) and are functionally constants. - The enum members have *names* and *values* (the name of :attr:`Color.RED` is ``RED``, the value of :attr:`Color.BLUE` is ``3``, etc.) @@ -110,10 +110,15 @@ Module Contents :class:`StrEnum` defaults to the lower-cased version of the member name, while other Enums default to 1 and increase from there. - :func:`property` + :func:`global_enum` + + :class:`Enum` class decorator to apply the appropriate global `__repr__`, + and export its members into the global name space. + + :func:`.property` Allows :class:`Enum` members to have attributes without conflicting with - member names. + other members' names. :func:`unique` @@ -126,7 +131,7 @@ Module Contents .. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto`` -.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``FlagBoundary``, ``property`` +.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``FlagBoundary`` --------------- @@ -140,11 +145,6 @@ Data Types to subclass *EnumType* -- see :ref:`Subclassing EnumType ` for details. - *EnumType* is responsible for setting the correct :meth:`__repr__`, - :meth:`__str__`, :meth:`__format__`, and :meth:`__reduce__` methods on the - final *enum*, as well as creating the enum members, properly handling - duplicates, providing iteration over the enum class, etc. - .. method:: EnumType.__contains__(cls, member) Returns ``True`` if member belongs to the ``cls``:: @@ -162,31 +162,32 @@ Data Types .. method:: EnumType.__dir__(cls) Returns ``['__class__', '__doc__', '__members__', '__module__']`` and the - names of the members in *cls*:: + names of the members in ``cls``. User-defined methods and methods from + mixin classes will also be included:: >>> dir(Color) - ['BLUE', 'GREEN', 'RED', '__class__', '__contains__', '__doc__', '__getitem__', '__init_subclass__', '__iter__', '__len__', '__members__', '__module__', '__name__', '__qualname__'] + ['BLUE', 'GREEN', 'RED', '__class__', '__doc__', '__members__', '__module__'] .. method:: EnumType.__getattr__(cls, name) Returns the Enum member in *cls* matching *name*, or raises an :exc:`AttributeError`:: >>> Color.GREEN - + Color.GREEN .. method:: EnumType.__getitem__(cls, name) - Returns the Enum member in *cls* matching *name*, or raises an :exc:`KeyError`:: + Returns the Enum member in *cls* matching *name*, or raises a :exc:`KeyError`:: >>> Color['BLUE'] - + Color.BLUE .. method:: EnumType.__iter__(cls) Returns each member in *cls* in definition order:: >>> list(Color) - [, , ] + [Color.RED, Color.GREEN, Color.BLUE] .. method:: EnumType.__len__(cls) @@ -200,7 +201,7 @@ Data Types Returns each member in *cls* in reverse definition order:: >>> list(reversed(Color)) - [, , ] + [Color.BLUE, Color.GREEN, Color.RED] .. class:: Enum @@ -231,7 +232,7 @@ Data Types .. attribute:: Enum._ignore_ ``_ignore_`` is only used during creation and is removed from the - enumeration once creation is complete. + enumeration once that is complete. ``_ignore_`` is a list of names that will not become members, and whose names will also be removed from the completed enumeration. See @@ -260,7 +261,7 @@ Data Types .. method:: Enum.__dir__(self) Returns ``['__class__', '__doc__', '__module__', 'name', 'value']`` and - any public methods defined on *self.__class__*:: + any public methods defined on ``self.__class__`` or a mixin class:: >>> from datetime import date >>> class Weekday(Enum): @@ -275,7 +276,7 @@ Data Types ... def today(cls): ... print('today is %s' % cls(date.today().isoweekday()).name) >>> dir(Weekday.SATURDAY) - ['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'today', 'value'] + ['__class__', '__doc__', '__module__', 'name', 'today', 'value'] .. method:: Enum._generate_next_value_(name, start, count, last_values) @@ -297,11 +298,6 @@ Data Types >>> PowersOfThree.SECOND.value 6 - .. method:: Enum.__init_subclass__(cls, \**kwds) - - A *classmethod* that is used to further configure subsequent subclasses. - By default, does nothing. - .. method:: Enum._missing_(cls, value) A *classmethod* for looking up values not found in *cls*. By default it @@ -321,55 +317,42 @@ Data Types >>> Build.DEBUG.value 'debug' >>> Build('deBUG') - + Build.DEBUG .. method:: Enum.__repr__(self) Returns the string used for *repr()* calls. By default, returns the - *Enum* name, member name, and value, but can be overridden:: + *Enum* name and the member name, but can be overridden:: - >>> class OtherStyle(Enum): - ... ALTERNATE = auto() - ... OTHER = auto() - ... SOMETHING_ELSE = auto() + >>> class OldStyle(Enum): + ... RETRO = auto() + ... OLD_SCHOOl = auto() + ... YESTERYEAR = auto() ... def __repr__(self): ... cls_name = self.__class__.__name__ - ... return f'{cls_name}.{self.name}' - >>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}" - (OtherStyle.ALTERNATE, 'OtherStyle.ALTERNATE', 'OtherStyle.ALTERNATE') + ... return f'<{cls_name}.{self.name}: {self.value}>' + >>> OldStyle.RETRO + .. method:: Enum.__str__(self) Returns the string used for *str()* calls. By default, returns the - *Enum* name and member name, but can be overridden:: + member name, but can be overridden:: - >>> class OtherStyle(Enum): - ... ALTERNATE = auto() - ... OTHER = auto() - ... SOMETHING_ELSE = auto() + >>> class OldStyle(Enum): + ... RETRO = auto() + ... OLD_SCHOOl = auto() + ... YESTERYEAR = auto() ... def __str__(self): - ... return f'{self.name}' - >>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}" - (, 'ALTERNATE', 'ALTERNATE') + ... cls_name = self.__class__.__name__ + ... return f'{cls_name}.{self.name}' + >>> OldStyle.RETRO + OldStyle.RETRO - .. method:: Enum.__format__(self) +.. note:: - Returns the string used for *format()* and *f-string* calls. By default, - returns :meth:`__str__` returns, but can be overridden:: - - >>> class OtherStyle(Enum): - ... ALTERNATE = auto() - ... OTHER = auto() - ... SOMETHING_ELSE = auto() - ... def __format__(self, spec): - ... return f'{self.name}' - >>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}" - (, 'OtherStyle.ALTERNATE', 'ALTERNATE') - - .. note:: - - Using :class:`auto` with :class:`Enum` results in integers of increasing value, - starting with ``1``. + Using :class:`auto` with :class:`Enum` results in integers of increasing value, + starting with ``1``. .. class:: IntEnum @@ -384,7 +367,7 @@ Data Types ... TWO = 2 ... THREE = 3 >>> Numbers.THREE - + Numbers.THREE >>> Numbers.ONE + Numbers.TWO 3 >>> Numbers.THREE + 5 @@ -392,14 +375,10 @@ Data Types >>> Numbers.THREE == 3 True - .. note:: +.. note:: - Using :class:`auto` with :class:`IntEnum` results in integers of increasing - value, starting with ``1``. - - .. versionchanged:: 3.11 :meth:`__str__` is now :func:`int.__str__` to - better support the *replacement of existing constants* use-case. - :meth:`__format__` was already :func:`int.__format__` for that same reason. + Using :class:`auto` with :class:`IntEnum` results in integers of increasing value, + starting with ``1``. .. class:: StrEnum @@ -413,16 +392,13 @@ Data Types instead of ``isinstance(str, unknown)``), and in those locations you will need to use ``str(StrEnum.member)``. - .. note:: - Using :class:`auto` with :class:`StrEnum` results in the lower-cased member - name as the value. +.. note:: - .. note:: :meth:`__str__` is :func:`str.__str__` to better support the - *replacement of existing constants* use-case. :meth:`__format__` is likewise - :func:`int.__format__` for that same reason. + Using :class:`auto` with :class:`StrEnum` results in values of the member name, + lower-cased. - .. versionadded:: 3.11 +.. versionadded:: 3.11 .. class:: Flag @@ -455,9 +431,9 @@ Data Types Returns all contained members:: >>> list(Color.RED) - [] + [Color.RED] >>> list(purple) - [, ] + [Color.RED, Color.BLUE] .. method:: __len__(self): @@ -485,52 +461,42 @@ Data Types Returns current flag binary or'ed with other:: >>> Color.RED | Color.GREEN - + Color.RED|Color.GREEN .. method:: __and__(self, other) Returns current flag binary and'ed with other:: >>> purple & white - + Color.RED|Color.BLUE >>> purple & Color.GREEN - + 0x0 .. method:: __xor__(self, other) Returns current flag binary xor'ed with other:: >>> purple ^ white - + Color.GREEN >>> purple ^ Color.GREEN - + Color.RED|Color.GREEN|Color.BLUE .. method:: __invert__(self): Returns all the flags in *type(self)* that are not in self:: >>> ~white - + 0x0 >>> ~purple - + Color.GREEN >>> ~Color.RED - + Color.GREEN|Color.BLUE - .. method:: _numeric_repr_ +.. note:: - Function used to format any remaining unnamed numeric values. Default is - the value's repr; common choices are :func:`hex` and :func:`oct`. + Using :class:`auto` with :class:`Flag` results in integers that are powers + of two, starting with ``1``. - .. note:: - - Using :class:`auto` with :class:`Flag` results in integers that are powers - of two, starting with ``1``. - - .. versionchanged:: 3.11 The *repr()* of zero-valued flags has changed. It - is now:: - - >>> Color(0) - .. class:: IntFlag @@ -543,9 +509,9 @@ Data Types ... GREEN = auto() ... BLUE = auto() >>> Color.RED & 2 - + 0x0 >>> Color.RED | 2 - + Color.RED|Color.GREEN If any integer operation is performed with an *IntFlag* member, the result is not an *IntFlag*:: @@ -558,25 +524,15 @@ Data Types * the result is a valid *IntFlag*: an *IntFlag* is returned * the result is not a valid *IntFlag*: the result depends on the *FlagBoundary* setting - The *repr()* of unnamed zero-valued flags has changed. It is now: - - >>> Color(0) - - - .. note:: - - Using :class:`auto` with :class:`IntFlag` results in integers that are powers - of two, starting with ``1``. - - .. versionchanged:: 3.11 :meth:`__str__` is now :func:`int.__str__` to - better support the *replacement of existing constants* use-case. - :meth:`__format__` was already :func:`int.__format__` for that same reason. +.. note:: + Using :class:`auto` with :class:`IntFlag` results in integers that are powers + of two, starting with ``1``. .. class:: EnumCheck *EnumCheck* contains the options used by the :func:`verify` decorator to ensure - various constraints; failed constraints result in a :exc:`ValueError`. + various constraints; failed constraints result in a :exc:`TypeError`. .. attribute:: UNIQUE @@ -626,11 +582,11 @@ Data Types ... ValueError: invalid Flag 'Color': aliases WHITE and NEON are missing combined values of 0x18 [use enum.show_flag_values(value) for details] - .. note:: +.. note:: - CONTINUOUS and NAMED_FLAGS are designed to work with integer-valued members. + CONTINUOUS and NAMED_FLAGS are designed to work with integer-valued members. - .. versionadded:: 3.11 +.. versionadded:: 3.11 .. class:: FlagBoundary @@ -650,7 +606,7 @@ Data Types >>> StrictFlag(2**2 + 2**4) Traceback (most recent call last): ... - ValueError: invalid value 20 + ValueError: StrictFlag: invalid value: 20 given 0b0 10100 allowed 0b0 00111 @@ -665,7 +621,7 @@ Data Types ... GREEN = auto() ... BLUE = auto() >>> ConformFlag(2**2 + 2**4) - + ConformFlag.BLUE .. attribute:: EJECT @@ -691,52 +647,12 @@ Data Types ... GREEN = auto() ... BLUE = auto() >>> KeepFlag(2**2 + 2**4) - + KeepFlag.BLUE|0x10 .. versionadded:: 3.11 --------------- -Supported ``__dunder__`` names -"""""""""""""""""""""""""""""" - -:attr:`__members__` is a read-only ordered mapping of ``member_name``:``member`` -items. It is only available on the class. - -:meth:`__new__`, if specified, must create and return the enum members; it is -also a very good idea to set the member's :attr:`_value_` appropriately. Once -all the members are created it is no longer used. - - -Supported ``_sunder_`` names -"""""""""""""""""""""""""""" - -- ``_name_`` -- name of the member -- ``_value_`` -- value of the member; can be set / modified in ``__new__`` - -- ``_missing_`` -- a lookup function used when a value is not found; may be - overridden -- ``_ignore_`` -- a list of names, either as a :class:`list` or a :class:`str`, - that will not be transformed into members, and will be removed from the final - class -- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent - (class attribute, removed during class creation) -- ``_generate_next_value_`` -- used to get an appropriate value for an enum - member; may be overridden - - .. note:: - - For standard :class:`Enum` classes the next value chosen is the last value seen - incremented by one. - - For :class:`Flag` classes the next value chosen will be the next highest - power-of-two, regardless of the last value seen. - -.. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_`` -.. versionadded:: 3.7 ``_ignore_`` - ---------------- - Utilities and Decorators ------------------------ @@ -752,6 +668,15 @@ Utilities and Decorators ``_generate_next_value_`` can be overridden to customize the values used by *auto*. +.. decorator:: global_enum + + A :keyword:`class` decorator specifically for enumerations. It replaces the + :meth:`__repr__` method with one that shows *module_name*.*member_name*. It + also injects the members, and their aliases, into the global namespace they + were defined in. + +.. versionadded:: 3.11 + .. decorator:: property A decorator similar to the built-in *property*, but specifically for @@ -763,7 +688,7 @@ Utilities and Decorators *Enum* class, and *Enum* subclasses can define members with the names ``value`` and ``name``. - .. versionadded:: 3.11 +.. versionadded:: 3.11 .. decorator:: unique @@ -789,7 +714,7 @@ Utilities and Decorators :class:`EnumCheck` are used to specify which constraints should be checked on the decorated enumeration. - .. versionadded:: 3.11 +.. versionadded:: 3.11 --------------- @@ -801,20 +726,14 @@ Notes These three enum types are designed to be drop-in replacements for existing integer- and string-based values; as such, they have extra limitations: - - ``__str__`` uses the value and not the name of the enum member + - ``format()`` will use the value of the enum member, unless ``__str__`` + has been overridden - - ``__format__``, because it uses ``__str__``, will also use the value of - the enum member instead of its name + - ``StrEnum.__str__`` uses the value and not the name of the enum member - If you do not need/want those limitations, you can either create your own - base class by mixing in the ``int`` or ``str`` type yourself:: + If you do not need/want those limitations, you can create your own base + class by mixing in the ``int`` or ``str`` type yourself:: >>> from enum import Enum >>> class MyIntEnum(int, Enum): ... pass - - or you can reassign the appropriate :meth:`str`, etc., in your enum:: - - >>> from enum import IntEnum - >>> class MyIntEnum(IntEnum): - ... __str__ = IntEnum.__str__ diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 4d8488a4a28..eb33d7e1778 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -2070,7 +2070,7 @@ to speed up repeated connections from the same clients. :attr:`SSLContext.verify_flags` returns :class:`VerifyFlags` flags: >>> ssl.create_default_context().verify_flags # doctest: +SKIP - + ssl.VERIFY_X509_TRUSTED_FIRST .. attribute:: SSLContext.verify_mode @@ -2082,7 +2082,7 @@ to speed up repeated connections from the same clients. :attr:`SSLContext.verify_mode` returns :class:`VerifyMode` enum: >>> ssl.create_default_context().verify_mode - + ssl.CERT_REQUIRED .. index:: single: certificates diff --git a/Lib/enum.py b/Lib/enum.py index 772e1eac0e1..93ea1bea36d 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -1,16 +1,16 @@ import sys -import builtins as bltns from types import MappingProxyType, DynamicClassAttribute from operator import or_ as _or_ from functools import reduce +from builtins import property as _bltin_property, bin as _bltin_bin __all__ = [ 'EnumType', 'EnumMeta', - 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum', + 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'auto', 'unique', 'property', 'verify', 'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP', - 'global_flag_repr', 'global_enum_repr', 'global_str', 'global_enum', + 'global_flag_repr', 'global_enum_repr', 'global_enum', 'EnumCheck', 'CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE', ] @@ -18,7 +18,7 @@ __all__ = [ # Dummy value for Enum and Flag as there are explicit checks for them # before they have been created. # This is also why there are checks in EnumType like `if Enum is not None` -Enum = Flag = EJECT = _stdlib_enums = ReprEnum = None +Enum = Flag = EJECT = None def _is_descriptor(obj): """ @@ -116,9 +116,9 @@ def bin(num, max_bits=None): ceiling = 2 ** (num).bit_length() if num >= 0: - s = bltns.bin(num + ceiling).replace('1', '0', 1) + s = _bltin_bin(num + ceiling).replace('1', '0', 1) else: - s = bltns.bin(~num ^ (ceiling - 1) + ceiling) + s = _bltin_bin(~num ^ (ceiling - 1) + ceiling) sign = s[:3] digits = s[3:] if max_bits is not None: @@ -126,19 +126,6 @@ def bin(num, max_bits=None): digits = (sign[-1] * max_bits + digits)[-max_bits:] return "%s %s" % (sign, digits) -def _dedent(text): - """ - Like textwrap.dedent. Rewritten because we cannot import textwrap. - """ - lines = text.split('\n') - blanks = 0 - for i, ch in enumerate(lines[0]): - if ch != ' ': - break - for j, l in enumerate(lines): - lines[j] = l[i:] - return '\n'.join(lines) - _auto_null = object() class auto: @@ -162,12 +149,22 @@ class property(DynamicClassAttribute): return ownerclass._member_map_[self.name] except KeyError: raise AttributeError( - '%r has no attribute %r' % (ownerclass, self.name) + '%s: no class attribute %r' % (ownerclass.__name__, self.name) ) else: if self.fget is None: + # check for member + if self.name in ownerclass._member_map_: + import warnings + warnings.warn( + "accessing one member from another is not supported, " + " and will be disabled in 3.12", + DeprecationWarning, + stacklevel=2, + ) + return ownerclass._member_map_[self.name] raise AttributeError( - '%r member has no attribute %r' % (ownerclass, self.name) + '%s: no instance attribute %r' % (ownerclass.__name__, self.name) ) else: return self.fget(instance) @@ -175,7 +172,7 @@ class property(DynamicClassAttribute): def __set__(self, instance, value): if self.fset is None: raise AttributeError( - " cannot set attribute %r" % (self.clsname, self.name) + "%s: cannot set instance attribute %r" % (self.clsname, self.name) ) else: return self.fset(instance, value) @@ -183,7 +180,7 @@ class property(DynamicClassAttribute): def __delete__(self, instance): if self.fdel is None: raise AttributeError( - " cannot delete attribute %r" % (self.clsname, self.name) + "%s: cannot delete instance attribute %r" % (self.clsname, self.name) ) else: return self.fdel(instance) @@ -331,7 +328,7 @@ class _EnumDict(dict): elif _is_sunder(key): if key not in ( '_order_', - '_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_', + '_generate_next_value_', '_missing_', '_ignore_', '_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_', ): raise ValueError( @@ -361,13 +358,13 @@ class _EnumDict(dict): key = '_order_' elif key in self._member_names: # descriptor overwriting an enum? - raise TypeError('%r already defined as %r' % (key, self[key])) + raise TypeError('%r already defined as: %r' % (key, self[key])) elif key in self._ignore: pass elif not _is_descriptor(value): if key in self: # enum overwriting a descriptor? - raise TypeError('%r already defined as %r' % (key, self[key])) + raise TypeError('%r already defined as: %r' % (key, self[key])) if isinstance(value, auto): if value.value == _auto_null: value.value = self._generate_next_value( @@ -398,7 +395,7 @@ class EnumType(type): @classmethod def __prepare__(metacls, cls, bases, **kwds): # check that previous enum members do not exist - metacls._check_for_existing_members_(cls, bases) + metacls._check_for_existing_members(cls, bases) # create the namespace dict enum_dict = _EnumDict() enum_dict._cls_name = cls @@ -416,10 +413,9 @@ class EnumType(type): # inherited __new__ unless a new __new__ is defined (or the resulting # class will fail). # + # remove any keys listed in _ignore_ if _simple: return super().__new__(metacls, cls, bases, classdict, **kwds) - # - # remove any keys listed in _ignore_ classdict.setdefault('_ignore_', []).append('_ignore_') ignore = classdict['_ignore_'] for key in ignore: @@ -431,8 +427,8 @@ class EnumType(type): # check for illegal enum names (any others?) invalid_names = set(member_names) & {'mro', ''} if invalid_names: - raise ValueError('invalid enum member name(s) '.format( - ','.join(repr(n) for n in invalid_names))) + raise ValueError('Invalid enum member name: {0}'.format( + ','.join(invalid_names))) # # adjust the sunders _order_ = classdict.pop('_order_', None) @@ -462,8 +458,6 @@ class EnumType(type): classdict['_value2member_map_'] = {} classdict['_unhashable_values_'] = [] classdict['_member_type_'] = member_type - # now set the __repr__ for the value - classdict['_value_repr_'] = metacls._find_data_repr_(cls, bases) # # Flag structures (will be removed if final class is not a Flag classdict['_boundary_'] = ( @@ -473,6 +467,10 @@ class EnumType(type): classdict['_flag_mask_'] = flag_mask classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1 classdict['_inverted_'] = None + # + # create a default docstring if one has not been provided + if '__doc__' not in classdict: + classdict['__doc__'] = 'An enumeration.' try: exc = None enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) @@ -483,140 +481,18 @@ class EnumType(type): if exc is not None: raise exc # - # update classdict with any changes made by __init_subclass__ - classdict.update(enum_class.__dict__) - # - # create a default docstring if one has not been provided - if enum_class.__doc__ is None: - if not member_names: - enum_class.__doc__ = classdict['__doc__'] = _dedent("""\ - Create a collection of name/value pairs. - - Example enumeration: - - >>> class Color(Enum): - ... RED = 1 - ... BLUE = 2 - ... GREEN = 3 - - Access them by: - - - attribute access:: - - >>> Color.RED - - - - value lookup: - - >>> Color(1) - - - - name lookup: - - >>> Color['RED'] - - - Enumerations can be iterated over, and know how many members they have: - - >>> len(Color) - 3 - - >>> list(Color) - [, , ] - - Methods can be added to enumerations, and members can have their own - attributes -- see the documentation for details. - """) - else: - member = list(enum_class)[0] - enum_length = len(enum_class) - cls_name = enum_class.__name__ - if enum_length == 1: - list_line = 'list(%s)' % cls_name - list_repr = '[<%s.%s: %r>]' % (cls_name, member.name, member.value) - elif enum_length == 2: - member2 = list(enum_class)[1] - list_line = 'list(%s)' % cls_name - list_repr = '[<%s.%s: %r>, <%s.%s: %r>]' % ( - cls_name, member.name, member.value, - cls_name, member2.name, member2.value, - ) - else: - member2 = list(enum_class)[1] - member3 = list(enum_class)[2] - list_line = 'list(%s)%s' % (cls_name, ('','[:3]')[enum_length > 3]) - list_repr = '[<%s.%s: %r>, <%s.%s: %r>, <%s.%s: %r>]' % ( - cls_name, member.name, member.value, - cls_name, member2.name, member2.value, - cls_name, member3.name, member3.value, - ) - enum_class.__doc__ = classdict['__doc__'] = _dedent("""\ - A collection of name/value pairs. - - Access them by: - - - attribute access:: - - >>> %s.%s - <%s.%s: %r> - - - value lookup: - - >>> %s(%r) - <%s.%s: %r> - - - name lookup: - - >>> %s[%r] - <%s.%s: %r> - - Enumerations can be iterated over, and know how many members they have: - - >>> len(%s) - %r - - >>> %s - %s - - Methods can be added to enumerations, and members can have their own - attributes -- see the documentation for details. - """ - % (cls_name, member.name, - cls_name, member.name, member.value, - cls_name, member.value, - cls_name, member.name, member.value, - cls_name, member.name, - cls_name, member.name, member.value, - cls_name, enum_length, - list_line, list_repr, - )) - # # double check that repr and friends are not the mixin's or various # things break (such as pickle) # however, if the method is defined in the Enum itself, don't replace # it - # - # Also, special handling for ReprEnum - if ReprEnum is not None and ReprEnum in bases: - if member_type is object: - raise TypeError( - 'ReprEnum subclasses must be mixed with a data type (i.e.' - ' int, str, float, etc.)' - ) - if '__format__' not in classdict: - enum_class.__format__ = member_type.__format__ - classdict['__format__'] = enum_class.__format__ - if '__str__' not in classdict: - method = member_type.__str__ - if method is object.__str__: - # if member_type does not define __str__, object.__str__ will use - # its __repr__ instead, so we'll also use its __repr__ - method = member_type.__repr__ - enum_class.__str__ = method - classdict['__str__'] = enum_class.__str__ for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): - if name not in classdict: - setattr(enum_class, name, getattr(first_enum, name)) + if name in classdict: + continue + class_method = getattr(enum_class, name) + obj_method = getattr(member_type, name, None) + enum_method = getattr(first_enum, name, None) + if obj_method is not None and obj_method is class_method: + setattr(enum_class, name, enum_method) # # replace any other __new__ with our own (as long as Enum is not None, # anyway) -- again, this is to support pickle @@ -687,13 +563,13 @@ class EnumType(type): # _order_ step 4: verify that _order_ and _member_names_ match if _order_ != enum_class._member_names_: raise TypeError( - 'member order does not match _order_:\n %r\n %r' + 'member order does not match _order_:\n%r\n%r' % (enum_class._member_names_, _order_) ) # return enum_class - def __bool__(cls): + def __bool__(self): """ classes/types should always be True. """ @@ -738,13 +614,6 @@ class EnumType(type): ) def __contains__(cls, member): - """ - Return True if member is a member of this enum - raises TypeError if member is not an enum member - - note: in 3.12 TypeError will no longer be raised, and True will also be - returned if member is the value of a member in this enum - """ if not isinstance(member, Enum): import warnings warnings.warn( @@ -762,33 +631,60 @@ class EnumType(type): # nicer error message when someone tries to delete an attribute # (see issue19025). if attr in cls._member_map_: - raise AttributeError("%r cannot delete member %r." % (cls.__name__, attr)) + raise AttributeError("%s: cannot delete Enum member %r." % (cls.__name__, attr)) super().__delattr__(attr) - def __dir__(cls): - # TODO: check for custom __init__, __new__, __format__, __repr__, __str__, __init_subclass__ - # on object-based enums - if cls._member_type_ is object: - interesting = set(cls._member_names_) - if cls._new_member_ is not object.__new__: - interesting.add('__new__') - if cls.__init_subclass__ is not object.__init_subclass__: - interesting.add('__init_subclass__') - for method in ('__init__', '__format__', '__repr__', '__str__'): - if getattr(cls, method) not in (getattr(Enum, method), getattr(Flag, method)): - interesting.add(method) - return sorted(set([ - '__class__', '__contains__', '__doc__', '__getitem__', - '__iter__', '__len__', '__members__', '__module__', - '__name__', '__qualname__', - ]) | interesting - ) - else: - # return whatever mixed-in data type has - return sorted(set( - dir(cls._member_type_) - + cls._member_names_ - )) + def __dir__(self): + # Start off with the desired result for dir(Enum) + cls_dir = {'__class__', '__doc__', '__members__', '__module__'} + add_to_dir = cls_dir.add + mro = self.__mro__ + this_module = globals().values() + is_from_this_module = lambda cls: any(cls is thing for thing in this_module) + first_enum_base = next(cls for cls in mro if is_from_this_module(cls)) + enum_dict = Enum.__dict__ + sentinel = object() + # special-case __new__ + ignored = {'__new__', *filter(_is_sunder, enum_dict)} + add_to_ignored = ignored.add + + # We want these added to __dir__ + # if and only if they have been user-overridden + enum_dunders = set(filter(_is_dunder, enum_dict)) + + for cls in mro: + # Ignore any classes defined in this module + if cls is object or is_from_this_module(cls): + continue + + cls_lookup = cls.__dict__ + + # If not an instance of EnumType, + # ensure all attributes excluded from that class's `dir()` are ignored here. + if not isinstance(cls, EnumType): + cls_lookup = set(cls_lookup).intersection(dir(cls)) + + for attr_name in cls_lookup: + # Already seen it? Carry on + if attr_name in cls_dir or attr_name in ignored: + continue + # Sunders defined in Enum.__dict__ are already in `ignored`, + # But sunders defined in a subclass won't be (we want all sunders excluded). + elif _is_sunder(attr_name): + add_to_ignored(attr_name) + # Not an "enum dunder"? Add it to dir() output. + elif attr_name not in enum_dunders: + add_to_dir(attr_name) + # Is an "enum dunder", and is defined by a class from enum.py? Ignore it. + elif getattr(self, attr_name) is getattr(first_enum_base, attr_name, sentinel): + add_to_ignored(attr_name) + # Is an "enum dunder", and is either user-defined or defined by a mixin class? + # Add it to dir() output. + else: + add_to_dir(attr_name) + + # sort the output before returning it, so that the result is deterministic. + return sorted(cls_dir) def __getattr__(cls, name): """ @@ -807,24 +703,18 @@ class EnumType(type): raise AttributeError(name) from None def __getitem__(cls, name): - """ - Return the member matching `name`. - """ return cls._member_map_[name] def __iter__(cls): """ - Return members in definition order. + Returns members in definition order. """ return (cls._member_map_[name] for name in cls._member_names_) def __len__(cls): - """ - Return the number of members (no aliases) - """ return len(cls._member_names_) - @bltns.property + @_bltin_property def __members__(cls): """ Returns a mapping of member name->value. @@ -842,7 +732,7 @@ class EnumType(type): def __reversed__(cls): """ - Return members in reverse definition order. + Returns members in reverse definition order. """ return (cls._member_map_[name] for name in reversed(cls._member_names_)) @@ -856,7 +746,7 @@ class EnumType(type): """ member_map = cls.__dict__.get('_member_map_', {}) if name in member_map: - raise AttributeError('cannot reassign member %r' % (name, )) + raise AttributeError('Cannot reassign member %r.' % (name, )) super().__setattr__(name, value) def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1, boundary=None): @@ -911,7 +801,8 @@ class EnumType(type): return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary) - def _convert_(cls, name, module, filter, source=None, *, boundary=None, as_global=False): + def _convert_(cls, name, module, filter, source=None, *, boundary=None): + """ Create a new Enum subclass that replaces a collection of global constants """ @@ -943,25 +834,22 @@ class EnumType(type): tmp_cls = type(name, (object, ), body) cls = _simple_enum(etype=cls, boundary=boundary or KEEP)(tmp_cls) cls.__reduce_ex__ = _reduce_ex_by_global_name - if as_global: - global_enum(cls) - else: - sys.modules[cls.__module__].__dict__.update(cls.__members__) + global_enum(cls) module_globals[name] = cls return cls - @classmethod - def _check_for_existing_members_(mcls, class_name, bases): + @staticmethod + def _check_for_existing_members(class_name, bases): for chain in bases: for base in chain.__mro__: if issubclass(base, Enum) and base._member_names_: raise TypeError( - " cannot extend %r" - % (class_name, base) + "%s: cannot extend enumeration %r" + % (class_name, base.__name__) ) @classmethod - def _get_mixins_(mcls, class_name, bases): + def _get_mixins_(cls, class_name, bases): """ Returns the type for creating enum members, and the first inherited enum class. @@ -971,7 +859,30 @@ class EnumType(type): if not bases: return object, Enum - mcls._check_for_existing_members_(class_name, bases) + def _find_data_type(bases): + data_types = set() + for chain in bases: + candidate = None + for base in chain.__mro__: + if base is object: + continue + elif issubclass(base, Enum): + if base._member_type_ is not object: + data_types.add(base._member_type_) + break + elif '__new__' in base.__dict__: + if issubclass(base, Enum): + continue + data_types.add(candidate or base) + break + else: + candidate = candidate or base + if len(data_types) > 1: + raise TypeError('%r: too many data types: %r' % (class_name, data_types)) + elif data_types: + return data_types.pop() + else: + return None # ensure final parent class is an Enum derivative, find any concrete # data type, and check that Enum has no members @@ -979,51 +890,12 @@ class EnumType(type): if not issubclass(first_enum, Enum): raise TypeError("new enumerations should be created as " "`EnumName([mixin_type, ...] [data_type,] enum_type)`") - member_type = mcls._find_data_type_(class_name, bases) or object + cls._check_for_existing_members(class_name, bases) + member_type = _find_data_type(bases) or object return member_type, first_enum - @classmethod - def _find_data_repr_(mcls, class_name, bases): - for chain in bases: - for base in chain.__mro__: - if base is object: - continue - elif issubclass(base, Enum): - # if we hit an Enum, use it's _value_repr_ - return base._value_repr_ - elif '__repr__' in base.__dict__: - # this is our data repr - return base.__dict__['__repr__'] - return None - - @classmethod - def _find_data_type_(mcls, class_name, bases): - data_types = set() - for chain in bases: - candidate = None - for base in chain.__mro__: - if base is object: - continue - elif issubclass(base, Enum): - if base._member_type_ is not object: - data_types.add(base._member_type_) - break - elif '__new__' in base.__dict__: - if issubclass(base, Enum): - continue - data_types.add(candidate or base) - break - else: - candidate = candidate or base - if len(data_types) > 1: - raise TypeError('too many data types for %r: %r' % (class_name, data_types)) - elif data_types: - return data_types.pop() - else: - return None - - @classmethod - def _find_new_(mcls, classdict, member_type, first_enum): + @staticmethod + def _find_new_(classdict, member_type, first_enum): """ Returns the __new__ to be used for creating the enum members. @@ -1071,42 +943,9 @@ EnumMeta = EnumType class Enum(metaclass=EnumType): """ - Create a collection of name/value pairs. + Generic enumeration. - Example enumeration: - - >>> class Color(Enum): - ... RED = 1 - ... BLUE = 2 - ... GREEN = 3 - - Access them by: - - - attribute access:: - - >>> Color.RED - - - - value lookup: - - >>> Color(1) - - - - name lookup: - - >>> Color['RED'] - - - Enumerations can be iterated over, and know how many members they have: - - >>> len(Color) - 3 - - >>> list(Color) - [, , ] - - Methods can be added to enumerations, and members can have their own - attributes -- see the documentation for details. + Derive from this class to define new enumerations. """ def __new__(cls, value): @@ -1160,9 +999,6 @@ class Enum(metaclass=EnumType): exc = None ve_exc = None - def __init__(self, *args, **kwds): - pass - def _generate_next_value_(name, start, count, last_values): """ Generate the next value when not given. @@ -1185,44 +1021,47 @@ class Enum(metaclass=EnumType): return None def __repr__(self): - v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__ - return "<%s.%s: %s>" % (self.__class__.__name__, self._name_, v_repr(self._value_)) + return "%s.%s" % ( self.__class__.__name__, self._name_) def __str__(self): - return "%s.%s" % (self.__class__.__name__, self._name_, ) + return "%s" % (self._name_, ) def __dir__(self): """ Returns all members and all public methods """ - if self.__class__._member_type_ is object: - interesting = set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value']) - else: - interesting = set(object.__dir__(self)) - for name in getattr(self, '__dict__', []): - if name[0] != '_': - interesting.add(name) - for cls in self.__class__.mro(): - for name, obj in cls.__dict__.items(): - if name[0] == '_': - continue - if isinstance(obj, property): - # that's an enum.property - if obj.fget is not None or name not in self._member_map_: - interesting.add(name) - else: - # in case it was added by `dir(self)` - interesting.discard(name) - else: - interesting.add(name) - names = sorted( - set(['__class__', '__doc__', '__eq__', '__hash__', '__module__']) - | interesting - ) - return names + cls = type(self) + to_exclude = {'__members__', '__init__', '__new__', *cls._member_names_} + filtered_self_dict = (name for name in self.__dict__ if not name.startswith('_')) + return sorted({'name', 'value', *dir(cls), *filtered_self_dict} - to_exclude) def __format__(self, format_spec): - return str.__format__(str(self), format_spec) + """ + Returns format using actual value type unless __str__ has been overridden. + """ + # mixed-in Enums should use the mixed-in type's __format__, otherwise + # we can get strange results with the Enum name showing up instead of + # the value + # + # pure Enum branch, or branch with __str__ explicitly overridden + str_overridden = type(self).__str__ not in (Enum.__str__, IntEnum.__str__, Flag.__str__) + if self._member_type_ is object or str_overridden: + cls = str + val = str(self) + # mix-in branch + else: + if not format_spec or format_spec in ('{}','{:}'): + import warnings + warnings.warn( + "in 3.12 format() will use the enum member, not the enum member's value;\n" + "use a format specifier, such as :d for an integer-based Enum, to maintain " + "the current display", + DeprecationWarning, + stacklevel=2, + ) + cls = self._member_type_ + val = self._value_ + return cls.__format__(val, format_spec) def __hash__(self): return hash(self._name_) @@ -1249,25 +1088,34 @@ class Enum(metaclass=EnumType): return self._value_ -class ReprEnum(Enum): - """ - Only changes the repr(), leaving str() and format() to the mixed-in type. - """ - - -class IntEnum(int, ReprEnum): +class IntEnum(int, Enum): """ Enum where members are also (and must be) ints """ + def __str__(self): + return "%s" % (self._name_, ) -class StrEnum(str, ReprEnum): + def __format__(self, format_spec): + """ + Returns format using actual value unless __str__ has been overridden. + """ + str_overridden = type(self).__str__ != IntEnum.__str__ + if str_overridden: + cls = str + val = str(self) + else: + cls = self._member_type_ + val = self._value_ + return cls.__format__(val, format_spec) + + +class StrEnum(str, Enum): """ Enum where members are also (and must be) strings """ def __new__(cls, *values): - "values must already be of type `str`" if len(values) > 3: raise TypeError('too many arguments for str(): %r' % (values, )) if len(values) == 1: @@ -1287,6 +1135,10 @@ class StrEnum(str, ReprEnum): member._value_ = value return member + __str__ = str.__str__ + + __format__ = str.__format__ + def _generate_next_value_(name, start, count, last_values): """ Return the lower-cased version of the member name. @@ -1317,8 +1169,6 @@ class Flag(Enum, boundary=STRICT): Support for flags """ - _numeric_repr_ = repr - def _generate_next_value_(name, start, count, last_values): """ Generate the next value when not given. @@ -1334,7 +1184,7 @@ class Flag(Enum, boundary=STRICT): try: high_bit = _high_bit(last_value) except Exception: - raise TypeError('invalid flag value %r' % last_value) from None + raise TypeError('Invalid Flag value: %r' % last_value) from None return 2 ** (high_bit+1) @classmethod @@ -1382,8 +1232,8 @@ class Flag(Enum, boundary=STRICT): if cls._boundary_ is STRICT: max_bits = max(value.bit_length(), flag_mask.bit_length()) raise ValueError( - "%r invalid value %r\n given %s\n allowed %s" % ( - cls, value, bin(value, max_bits), bin(flag_mask, max_bits), + "%s: invalid value: %r\n given %s\n allowed %s" % ( + cls.__name__, value, bin(value, max_bits), bin(flag_mask, max_bits), )) elif cls._boundary_ is CONFORM: value = value & flag_mask @@ -1397,7 +1247,7 @@ class Flag(Enum, boundary=STRICT): ) else: raise ValueError( - '%r unknown flag boundary %r' % (cls, cls._boundary_, ) + 'unknown flag boundary: %r' % (cls._boundary_, ) ) if value < 0: neg_value = value @@ -1424,7 +1274,7 @@ class Flag(Enum, boundary=STRICT): m._name_ for m in cls._iter_member_(member_value) ]) if unknown: - pseudo_member._name_ += '|%s' % cls._numeric_repr_(unknown) + pseudo_member._name_ += '|0x%x' % unknown else: pseudo_member._name_ = None # use setdefault in case another thread already created a composite @@ -1442,8 +1292,10 @@ class Flag(Enum, boundary=STRICT): """ if not isinstance(other, self.__class__): raise TypeError( - "unsupported operand type(s) for 'in': %r and %r" % ( + "unsupported operand type(s) for 'in': '%s' and '%s'" % ( type(other).__qualname__, self.__class__.__qualname__)) + if other._value_ == 0 or self._value_ == 0: + return False return other._value_ & self._value_ == other._value_ def __iter__(self): @@ -1457,18 +1309,27 @@ class Flag(Enum, boundary=STRICT): def __repr__(self): cls_name = self.__class__.__name__ - v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__ if self._name_ is None: - return "<%s: %s>" % (cls_name, v_repr(self._value_)) + return "0x%x" % (self._value_, ) + if _is_single_bit(self._value_): + return '%s.%s' % (cls_name, self._name_) + if self._boundary_ is not FlagBoundary.KEEP: + return '%s.' % cls_name + ('|%s.' % cls_name).join(self.name.split('|')) else: - return "<%s.%s: %s>" % (cls_name, self._name_, v_repr(self._value_)) + name = [] + for n in self._name_.split('|'): + if n.startswith('0'): + name.append(n) + else: + name.append('%s.%s' % (cls_name, n)) + return '|'.join(name) def __str__(self): - cls_name = self.__class__.__name__ + cls = self.__class__ if self._name_ is None: - return '%s(%r)' % (cls_name, self._value_) + return '%s(%x)' % (cls.__name__, self._value_) else: - return "%s.%s" % (cls_name, self._name_) + return self._name_ def __bool__(self): return bool(self._value_) @@ -1501,11 +1362,20 @@ class Flag(Enum, boundary=STRICT): return self._inverted_ -class IntFlag(int, ReprEnum, Flag, boundary=EJECT): +class IntFlag(int, Flag, boundary=EJECT): """ Support for integer-based Flags """ + def __format__(self, format_spec): + """ + Returns format using actual value unless __str__ has been overridden. + """ + str_overridden = type(self).__str__ != Flag.__str__ + value = self + if not str_overridden: + value = self._value_ + return int.__format__(value, format_spec) def __or__(self, other): if isinstance(other, self.__class__): @@ -1542,7 +1412,6 @@ class IntFlag(int, ReprEnum, Flag, boundary=EJECT): __rxor__ = __xor__ __invert__ = Flag.__invert__ - def _high_bit(value): """ returns index of highest bit, or -1 if value is zero or negative @@ -1587,7 +1456,7 @@ def global_flag_repr(self): module = self.__class__.__module__.split('.')[-1] cls_name = self.__class__.__name__ if self._name_ is None: - return "%s.%s(%r)" % (module, cls_name, self._value_) + return "%s.%s(0x%x)" % (module, cls_name, self._value_) if _is_single_bit(self): return '%s.%s' % (module, self._name_) if self._boundary_ is not FlagBoundary.KEEP: @@ -1595,22 +1464,14 @@ def global_flag_repr(self): else: name = [] for n in self._name_.split('|'): - if n[0].isdigit(): + if n.startswith('0'): name.append(n) else: name.append('%s.%s' % (module, n)) return '|'.join(name) -def global_str(self): - """ - use enum_name instead of class.enum_name - """ - if self._name_ is None: - return "%s(%r)" % (cls_name, self._value_) - else: - return self._name_ -def global_enum(cls, update_str=False): +def global_enum(cls): """ decorator that makes the repr() of an enum member reference its module instead of its class; also exports all members to the enum's module's @@ -1620,8 +1481,6 @@ def global_enum(cls, update_str=False): cls.__repr__ = global_flag_repr else: cls.__repr__ = global_enum_repr - if not issubclass(cls, ReprEnum) or update_str: - cls.__str__ = global_str sys.modules[cls.__module__].__dict__.update(cls.__members__) return cls @@ -1663,7 +1522,6 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None): body['_value2member_map_'] = value2member_map = {} body['_unhashable_values_'] = [] body['_member_type_'] = member_type = etype._member_type_ - body['_value_repr_'] = etype._value_repr_ if issubclass(etype, Flag): body['_boundary_'] = boundary or etype._boundary_ body['_flag_mask_'] = None @@ -1685,8 +1543,13 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None): # it enum_class = type(cls_name, (etype, ), body, boundary=boundary, _simple=True) for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): - if name not in body: - setattr(enum_class, name, getattr(etype, name)) + if name in body: + continue + class_method = getattr(enum_class, name) + obj_method = getattr(member_type, name, None) + enum_method = getattr(etype, name, None) + if obj_method is not None and obj_method is class_method: + setattr(enum_class, name, enum_method) gnv_last_values = [] if issubclass(enum_class, Flag): # Flag / IntFlag @@ -1897,8 +1760,8 @@ def _test_simple_enum(checked_enum, simple_enum): + list(simple_enum._member_map_.keys()) ) for key in set(checked_keys + simple_keys): - if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__'): - # keys known to be different, or very long + if key in ('__module__', '_member_map_', '_value2member_map_'): + # keys known to be different continue elif key in member_names: # members are checked below @@ -2019,5 +1882,3 @@ def _old_convert_(etype, name, module, filter, source=None, *, boundary=None): cls.__reduce_ex__ = _reduce_ex_by_global_name cls.__repr__ = global_enum_repr return cls - -_stdlib_enums = IntEnum, StrEnum, IntFlag diff --git a/Lib/inspect.py b/Lib/inspect.py index 8236698b8de..5d33f0d445f 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2567,21 +2567,15 @@ class _empty: class _ParameterKind(enum.IntEnum): - POSITIONAL_ONLY = 'positional-only' - POSITIONAL_OR_KEYWORD = 'positional or keyword' - VAR_POSITIONAL = 'variadic positional' - KEYWORD_ONLY = 'keyword-only' - VAR_KEYWORD = 'variadic keyword' + POSITIONAL_ONLY = 0 + POSITIONAL_OR_KEYWORD = 1 + VAR_POSITIONAL = 2 + KEYWORD_ONLY = 3 + VAR_KEYWORD = 4 - def __new__(cls, description): - value = len(cls.__members__) - member = int.__new__(cls, value) - member._value_ = value - member.description = description - return member - - def __str__(self): - return self.name + @property + def description(self): + return _PARAM_NAME_MAPPING[self] _POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY _POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD @@ -2589,6 +2583,14 @@ _VAR_POSITIONAL = _ParameterKind.VAR_POSITIONAL _KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY _VAR_KEYWORD = _ParameterKind.VAR_KEYWORD +_PARAM_NAME_MAPPING = { + _POSITIONAL_ONLY: 'positional-only', + _POSITIONAL_OR_KEYWORD: 'positional or keyword', + _VAR_POSITIONAL: 'variadic positional', + _KEYWORD_ONLY: 'keyword-only', + _VAR_KEYWORD: 'variadic keyword' +} + class Parameter: """Represents a parameter in a function signature. diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 4862355b225..3ab71edc320 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -61,8 +61,7 @@ import struct from xml.parsers.expat import ParserCreate -PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__) -globals().update(PlistFormat.__members__) +PlistFormat = enum.global_enum(enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)) class UID: diff --git a/Lib/re.py b/Lib/re.py index a7ab9b37067..ea41217ce08 100644 --- a/Lib/re.py +++ b/Lib/re.py @@ -155,8 +155,6 @@ class RegexFlag: # sre extensions (experimental, don't rely on these) TEMPLATE = T = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation - __str__ = object.__str__ - _numeric_repr_ = hex # sre exception error = sre_compile.error diff --git a/Lib/ssl.py b/Lib/ssl.py index dafb70a6786..207925166ef 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -119,6 +119,7 @@ from _ssl import ( ) from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION + _IntEnum._convert_( '_SSLMethod', __name__, lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23', diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index a0953fb960f..43f98c1c1ef 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -6,18 +6,15 @@ import pydoc import sys import unittest import threading -import builtins as bltns from collections import OrderedDict -from datetime import date from enum import Enum, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto from enum import STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum -from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum +from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS from io import StringIO from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL from test import support from test.support import ALWAYS_EQ from test.support import threading_helper -from textwrap import dedent from datetime import timedelta python_version = sys.version_info[:2] @@ -110,12 +107,6 @@ def test_pickle_exception(assertion, exception, obj): class TestHelpers(unittest.TestCase): # _is_descriptor, _is_sunder, _is_dunder - sunder_names = '_bad_', '_good_', '_what_ho_' - dunder_names = '__mal__', '__bien__', '__que_que__' - private_names = '_MyEnum__private', '_MyEnum__still_private' - private_and_sunder_names = '_MyEnum__private_', '_MyEnum__also_private_' - random_names = 'okay', '_semi_private', '_weird__', '_MyEnum__' - def test_is_descriptor(self): class foo: pass @@ -125,36 +116,21 @@ class TestHelpers(unittest.TestCase): setattr(obj, attr, 1) self.assertTrue(enum._is_descriptor(obj)) - def test_sunder(self): - for name in self.sunder_names + self.private_and_sunder_names: - self.assertTrue(enum._is_sunder(name), '%r is a not sunder name?' % name) - for name in self.dunder_names + self.private_names + self.random_names: - self.assertFalse(enum._is_sunder(name), '%r is a sunder name?' % name) + def test_is_sunder(self): for s in ('_a_', '_aa_'): self.assertTrue(enum._is_sunder(s)) + for s in ('a', 'a_', '_a', '__a', 'a__', '__a__', '_a__', '__a_', '_', '__', '___', '____', '_____',): self.assertFalse(enum._is_sunder(s)) - def test_dunder(self): - for name in self.dunder_names: - self.assertTrue(enum._is_dunder(name), '%r is a not dunder name?' % name) - for name in self.sunder_names + self.private_names + self.private_and_sunder_names + self.random_names: - self.assertFalse(enum._is_dunder(name), '%r is a dunder name?' % name) + def test_is_dunder(self): for s in ('__a__', '__aa__'): self.assertTrue(enum._is_dunder(s)) for s in ('a', 'a_', '_a', '__a', 'a__', '_a_', '_a__', '__a_', '_', '__', '___', '____', '_____',): self.assertFalse(enum._is_dunder(s)) - - def test_is_private(self): - for name in self.private_names + self.private_and_sunder_names: - self.assertTrue(enum._is_private('MyEnum', name), '%r is a not private name?') - for name in self.sunder_names + self.dunder_names + self.random_names: - self.assertFalse(enum._is_private('MyEnum', name), '%r is a private name?') - - # for subclassing tests class classproperty: @@ -190,629 +166,7 @@ class HeadlightsC(IntFlag, boundary=enum.CONFORM): # tests -class _EnumTests: - """ - Test for behavior that is the same across the different types of enumerations. - """ - - values = None - - def setUp(self): - class BaseEnum(self.enum_type): - @enum.property - def first(self): - return '%s is first!' % self.name - class MainEnum(BaseEnum): - first = auto() - second = auto() - third = auto() - if issubclass(self.enum_type, Flag): - dupe = 3 - else: - dupe = third - self.MainEnum = MainEnum - # - class NewStrEnum(self.enum_type): - def __str__(self): - return self.name.upper() - first = auto() - self.NewStrEnum = NewStrEnum - # - class NewFormatEnum(self.enum_type): - def __format__(self, spec): - return self.name.upper() - first = auto() - self.NewFormatEnum = NewFormatEnum - # - class NewStrFormatEnum(self.enum_type): - def __str__(self): - return self.name.title() - def __format__(self, spec): - return ''.join(reversed(self.name)) - first = auto() - self.NewStrFormatEnum = NewStrFormatEnum - # - class NewBaseEnum(self.enum_type): - def __str__(self): - return self.name.title() - def __format__(self, spec): - return ''.join(reversed(self.name)) - class NewSubEnum(NewBaseEnum): - first = auto() - self.NewSubEnum = NewSubEnum - # - self.is_flag = False - self.names = ['first', 'second', 'third'] - if issubclass(MainEnum, StrEnum): - self.values = self.names - elif MainEnum._member_type_ is str: - self.values = ['1', '2', '3'] - elif issubclass(self.enum_type, Flag): - self.values = [1, 2, 4] - self.is_flag = True - self.dupe2 = MainEnum(5) - else: - self.values = self.values or [1, 2, 3] - # - if not getattr(self, 'source_values', False): - self.source_values = self.values - - def assertFormatIsValue(self, spec, member): - self.assertEqual(spec.format(member), spec.format(member.value)) - - def assertFormatIsStr(self, spec, member): - self.assertEqual(spec.format(member), spec.format(str(member))) - - def test_attribute_deletion(self): - class Season(self.enum_type): - SPRING = auto() - SUMMER = auto() - AUTUMN = auto() - # - def spam(cls): - pass - # - self.assertTrue(hasattr(Season, 'spam')) - del Season.spam - self.assertFalse(hasattr(Season, 'spam')) - # - with self.assertRaises(AttributeError): - del Season.SPRING - with self.assertRaises(AttributeError): - del Season.DRY - with self.assertRaises(AttributeError): - del Season.SPRING.name - - def test_basics(self): - TE = self.MainEnum - if self.is_flag: - self.assertEqual(repr(TE), "") - self.assertEqual(str(TE), "") - self.assertEqual(format(TE), "") - self.assertTrue(TE(5) is self.dupe2) - else: - self.assertEqual(repr(TE), "") - self.assertEqual(str(TE), "") - self.assertEqual(format(TE), "") - self.assertEqual(list(TE), [TE.first, TE.second, TE.third]) - self.assertEqual( - [m.name for m in TE], - self.names, - ) - self.assertEqual( - [m.value for m in TE], - self.values, - ) - self.assertEqual( - [m.first for m in TE], - ['first is first!', 'second is first!', 'third is first!'] - ) - for member, name in zip(TE, self.names, strict=True): - self.assertIs(TE[name], member) - for member, value in zip(TE, self.values, strict=True): - self.assertIs(TE(value), member) - if issubclass(TE, StrEnum): - self.assertTrue(TE.dupe is TE('third') is TE['dupe']) - elif TE._member_type_ is str: - self.assertTrue(TE.dupe is TE('3') is TE['dupe']) - elif issubclass(TE, Flag): - self.assertTrue(TE.dupe is TE(3) is TE['dupe']) - else: - self.assertTrue(TE.dupe is TE(self.values[2]) is TE['dupe']) - - def test_bool_is_true(self): - class Empty(self.enum_type): - pass - self.assertTrue(Empty) - # - self.assertTrue(self.MainEnum) - for member in self.MainEnum: - self.assertTrue(member) - - def test_changing_member_fails(self): - MainEnum = self.MainEnum - with self.assertRaises(AttributeError): - self.MainEnum.second = 'really first' - - @unittest.skipIf( - python_version >= (3, 12), - '__contains__ now returns True/False for all inputs', - ) - def test_contains_er(self): - MainEnum = self.MainEnum - self.assertIn(MainEnum.third, MainEnum) - with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - self.source_values[1] in MainEnum - with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 'first' in MainEnum - val = MainEnum.dupe - self.assertIn(val, MainEnum) - # - class OtherEnum(Enum): - one = auto() - two = auto() - self.assertNotIn(OtherEnum.two, MainEnum) - - @unittest.skipIf( - python_version < (3, 12), - '__contains__ works only with enum memmbers before 3.12', - ) - def test_contains_tf(self): - MainEnum = self.MainEnum - self.assertIn(MainEnum.first, MainEnum) - self.assertTrue(self.source_values[0] in MainEnum) - self.assertFalse('first' in MainEnum) - val = MainEnum.dupe - self.assertIn(val, MainEnum) - # - class OtherEnum(Enum): - one = auto() - two = auto() - self.assertNotIn(OtherEnum.two, MainEnum) - - def test_dir_on_class(self): - TE = self.MainEnum - self.assertEqual(set(dir(TE)), set(enum_dir(TE))) - - def test_dir_on_item(self): - TE = self.MainEnum - self.assertEqual(set(dir(TE.first)), set(member_dir(TE.first))) - - def test_dir_with_added_behavior(self): - class Test(self.enum_type): - this = auto() - these = auto() - def wowser(self): - return ("Wowser! I'm %s!" % self.name) - self.assertTrue('wowser' not in dir(Test)) - self.assertTrue('wowser' in dir(Test.this)) - - def test_dir_on_sub_with_behavior_on_super(self): - # see issue22506 - class SuperEnum(self.enum_type): - def invisible(self): - return "did you see me?" - class SubEnum(SuperEnum): - sample = auto() - self.assertTrue('invisible' not in dir(SubEnum)) - self.assertTrue('invisible' in dir(SubEnum.sample)) - - def test_dir_on_sub_with_behavior_including_instance_dict_on_super(self): - # see issue40084 - class SuperEnum(self.enum_type): - def __new__(cls, *value, **kwds): - new = self.enum_type._member_type_.__new__ - if self.enum_type._member_type_ is object: - obj = new(cls) - else: - if isinstance(value[0], tuple): - create_value ,= value[0] - else: - create_value = value - obj = new(cls, *create_value) - obj._value_ = value[0] if len(value) == 1 else value - obj.description = 'test description' - return obj - class SubEnum(SuperEnum): - sample = self.source_values[1] - self.assertTrue('description' not in dir(SubEnum)) - self.assertTrue('description' in dir(SubEnum.sample), dir(SubEnum.sample)) - - def test_enum_in_enum_out(self): - Main = self.MainEnum - self.assertIs(Main(Main.first), Main.first) - - def test_hash(self): - MainEnum = self.MainEnum - mapping = {} - mapping[MainEnum.first] = '1225' - mapping[MainEnum.second] = '0315' - mapping[MainEnum.third] = '0704' - self.assertEqual(mapping[MainEnum.second], '0315') - - def test_invalid_names(self): - with self.assertRaises(ValueError): - class Wrong(self.enum_type): - mro = 9 - with self.assertRaises(ValueError): - class Wrong(self.enum_type): - _create_= 11 - with self.assertRaises(ValueError): - class Wrong(self.enum_type): - _get_mixins_ = 9 - with self.assertRaises(ValueError): - class Wrong(self.enum_type): - _find_new_ = 1 - with self.assertRaises(ValueError): - class Wrong(self.enum_type): - _any_name_ = 9 - - def test_object_str_override(self): - "check that setting __str__ to object's is not reset to Enum's" - class Generic(self.enum_type): - item = self.source_values[2] - def __repr__(self): - return "%s.test" % (self._name_, ) - __str__ = object.__str__ - self.assertEqual(str(Generic.item), 'item.test') - - def test_overridden_str(self): - NS = self.NewStrEnum - self.assertEqual(str(NS.first), NS.first.name.upper()) - self.assertEqual(format(NS.first), NS.first.name.upper()) - - def test_overridden_str_format(self): - NSF = self.NewStrFormatEnum - self.assertEqual(str(NSF.first), NSF.first.name.title()) - self.assertEqual(format(NSF.first), ''.join(reversed(NSF.first.name))) - - def test_overridden_str_format_inherited(self): - NSE = self.NewSubEnum - self.assertEqual(str(NSE.first), NSE.first.name.title()) - self.assertEqual(format(NSE.first), ''.join(reversed(NSE.first.name))) - - def test_programmatic_function_string(self): - MinorEnum = self.enum_type('MinorEnum', 'june july august') - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - values = self.values - if self.enum_type is StrEnum: - values = ['june','july','august'] - for month, av in zip('june july august'.split(), values): - e = MinorEnum[month] - self.assertEqual(e.value, av, list(MinorEnum)) - self.assertEqual(e.name, month) - if MinorEnum._member_type_ is not object and issubclass(MinorEnum, MinorEnum._member_type_): - self.assertEqual(e, av) - else: - self.assertNotEqual(e, av) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - self.assertIs(e, MinorEnum(av)) - - def test_programmatic_function_string_list(self): - MinorEnum = self.enum_type('MinorEnum', ['june', 'july', 'august']) - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - values = self.values - if self.enum_type is StrEnum: - values = ['june','july','august'] - for month, av in zip('june july august'.split(), values): - e = MinorEnum[month] - self.assertEqual(e.value, av) - self.assertEqual(e.name, month) - if MinorEnum._member_type_ is not object and issubclass(MinorEnum, MinorEnum._member_type_): - self.assertEqual(e, av) - else: - self.assertNotEqual(e, av) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - self.assertIs(e, MinorEnum(av)) - - def test_programmatic_function_iterable(self): - MinorEnum = self.enum_type( - 'MinorEnum', - (('june', self.source_values[0]), ('july', self.source_values[1]), ('august', self.source_values[2])) - ) - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - for month, av in zip('june july august'.split(), self.values): - e = MinorEnum[month] - self.assertEqual(e.value, av) - self.assertEqual(e.name, month) - if MinorEnum._member_type_ is not object and issubclass(MinorEnum, MinorEnum._member_type_): - self.assertEqual(e, av) - else: - self.assertNotEqual(e, av) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - self.assertIs(e, MinorEnum(av)) - - def test_programmatic_function_from_dict(self): - MinorEnum = self.enum_type( - 'MinorEnum', - OrderedDict((('june', self.source_values[0]), ('july', self.source_values[1]), ('august', self.source_values[2]))) - ) - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - for month, av in zip('june july august'.split(), self.values): - e = MinorEnum[month] - if MinorEnum._member_type_ is not object and issubclass(MinorEnum, MinorEnum._member_type_): - self.assertEqual(e, av) - else: - self.assertNotEqual(e, av) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - self.assertIs(e, MinorEnum(av)) - - def test_repr(self): - TE = self.MainEnum - if self.is_flag: - self.assertEqual(repr(TE(0)), "") - self.assertEqual(repr(TE.dupe), "") - self.assertEqual(repr(self.dupe2), "") - elif issubclass(TE, StrEnum): - self.assertEqual(repr(TE.dupe), "") - else: - self.assertEqual(repr(TE.dupe), "" % (self.values[2], ), TE._value_repr_) - for name, value, member in zip(self.names, self.values, TE, strict=True): - self.assertEqual(repr(member), "" % (member.name, member.value)) - - def test_repr_override(self): - class Generic(self.enum_type): - first = auto() - second = auto() - third = auto() - def __repr__(self): - return "don't you just love shades of %s?" % self.name - self.assertEqual( - repr(Generic.third), - "don't you just love shades of third?", - ) - - def test_inherited_repr(self): - class MyEnum(self.enum_type): - def __repr__(self): - return "My name is %s." % self.name - class MySubEnum(MyEnum): - this = auto() - that = auto() - theother = auto() - self.assertEqual(repr(MySubEnum.that), "My name is that.") - - def test_reversed_iteration_order(self): - self.assertEqual( - list(reversed(self.MainEnum)), - [self.MainEnum.third, self.MainEnum.second, self.MainEnum.first], - ) - -class _PlainOutputTests: - - def test_str(self): - TE = self.MainEnum - if self.is_flag: - self.assertEqual(str(TE.dupe), "MainEnum.dupe") - self.assertEqual(str(self.dupe2), "MainEnum.first|third") - else: - self.assertEqual(str(TE.dupe), "MainEnum.third") - for name, value, member in zip(self.names, self.values, TE, strict=True): - self.assertEqual(str(member), "MainEnum.%s" % (member.name, )) - - def test_format(self): - TE = self.MainEnum - if self.is_flag: - self.assertEqual(format(TE.dupe), "MainEnum.dupe") - self.assertEqual(format(self.dupe2), "MainEnum.first|third") - else: - self.assertEqual(format(TE.dupe), "MainEnum.third") - for name, value, member in zip(self.names, self.values, TE, strict=True): - self.assertEqual(format(member), "MainEnum.%s" % (member.name, )) - - def test_overridden_format(self): - NF = self.NewFormatEnum - self.assertEqual(str(NF.first), "NewFormatEnum.first", '%s %r' % (NF.__str__, NF.first)) - self.assertEqual(format(NF.first), "FIRST") - - def test_format_specs(self): - TE = self.MainEnum - self.assertFormatIsStr('{}', TE.second) - self.assertFormatIsStr('{:}', TE.second) - self.assertFormatIsStr('{:20}', TE.second) - self.assertFormatIsStr('{:^20}', TE.second) - self.assertFormatIsStr('{:>20}', TE.second) - self.assertFormatIsStr('{:<20}', TE.second) - self.assertFormatIsStr('{:5.2}', TE.second) - - -class _MixedOutputTests: - - def test_str(self): - TE = self.MainEnum - if self.is_flag: - self.assertEqual(str(TE.dupe), "MainEnum.dupe") - self.assertEqual(str(self.dupe2), "MainEnum.first|third") - else: - self.assertEqual(str(TE.dupe), "MainEnum.third") - for name, value, member in zip(self.names, self.values, TE, strict=True): - self.assertEqual(str(member), "MainEnum.%s" % (member.name, )) - - def test_format(self): - TE = self.MainEnum - if self.is_flag: - self.assertEqual(format(TE.dupe), "MainEnum.dupe") - self.assertEqual(format(self.dupe2), "MainEnum.first|third") - else: - self.assertEqual(format(TE.dupe), "MainEnum.third") - for name, value, member in zip(self.names, self.values, TE, strict=True): - self.assertEqual(format(member), "MainEnum.%s" % (member.name, )) - - def test_overridden_format(self): - NF = self.NewFormatEnum - self.assertEqual(str(NF.first), "NewFormatEnum.first") - self.assertEqual(format(NF.first), "FIRST") - - def test_format_specs(self): - TE = self.MainEnum - self.assertFormatIsStr('{}', TE.first) - self.assertFormatIsStr('{:}', TE.first) - self.assertFormatIsStr('{:20}', TE.first) - self.assertFormatIsStr('{:^20}', TE.first) - self.assertFormatIsStr('{:>20}', TE.first) - self.assertFormatIsStr('{:<20}', TE.first) - self.assertFormatIsStr('{:5.2}', TE.first) - - -class _MinimalOutputTests: - - def test_str(self): - TE = self.MainEnum - if self.is_flag: - self.assertEqual(str(TE.dupe), "3") - self.assertEqual(str(self.dupe2), "5") - else: - self.assertEqual(str(TE.dupe), str(self.values[2])) - for name, value, member in zip(self.names, self.values, TE, strict=True): - self.assertEqual(str(member), str(value)) - - def test_format(self): - TE = self.MainEnum - if self.is_flag: - self.assertEqual(format(TE.dupe), "3") - self.assertEqual(format(self.dupe2), "5") - else: - self.assertEqual(format(TE.dupe), format(self.values[2])) - for name, value, member in zip(self.names, self.values, TE, strict=True): - self.assertEqual(format(member), format(value)) - - def test_overridden_format(self): - NF = self.NewFormatEnum - self.assertEqual(str(NF.first), str(self.values[0])) - self.assertEqual(format(NF.first), "FIRST") - - def test_format_specs(self): - TE = self.MainEnum - self.assertFormatIsValue('{}', TE.third) - self.assertFormatIsValue('{:}', TE.third) - self.assertFormatIsValue('{:20}', TE.third) - self.assertFormatIsValue('{:^20}', TE.third) - self.assertFormatIsValue('{:>20}', TE.third) - self.assertFormatIsValue('{:<20}', TE.third) - if TE._member_type_ is float: - self.assertFormatIsValue('{:n}', TE.third) - self.assertFormatIsValue('{:5.2}', TE.third) - self.assertFormatIsValue('{:f}', TE.third) - - -class _FlagTests: - - def test_default_missing_with_wrong_type_value(self): - with self.assertRaisesRegex( - ValueError, - "'RED' is not a valid TestFlag.Color", - ) as ctx: - self.MainEnum('RED') - self.assertIs(ctx.exception.__context__, None) - -class TestPlainEnum(_EnumTests, _PlainOutputTests, unittest.TestCase): - enum_type = Enum - - -class TestPlainFlag(_EnumTests, _PlainOutputTests, unittest.TestCase): - enum_type = Flag - - -class TestIntEnum(_EnumTests, _MinimalOutputTests, unittest.TestCase): - enum_type = IntEnum - - -class TestStrEnum(_EnumTests, _MinimalOutputTests, unittest.TestCase): - enum_type = StrEnum - - -class TestIntFlag(_EnumTests, _MinimalOutputTests, unittest.TestCase): - enum_type = IntFlag - - -class TestMixedInt(_EnumTests, _MixedOutputTests, unittest.TestCase): - class enum_type(int, Enum): pass - - -class TestMixedStr(_EnumTests, _MixedOutputTests, unittest.TestCase): - class enum_type(str, Enum): pass - - -class TestMixedIntFlag(_EnumTests, _MixedOutputTests, unittest.TestCase): - class enum_type(int, Flag): pass - - -class TestMixedDate(_EnumTests, _MixedOutputTests, unittest.TestCase): - - values = [date(2021, 12, 25), date(2020, 3, 15), date(2019, 11, 27)] - source_values = [(2021, 12, 25), (2020, 3, 15), (2019, 11, 27)] - - class enum_type(date, Enum): - def _generate_next_value_(name, start, count, last_values): - values = [(2021, 12, 25), (2020, 3, 15), (2019, 11, 27)] - return values[count] - - -class TestMinimalDate(_EnumTests, _MinimalOutputTests, unittest.TestCase): - - values = [date(2023, 12, 1), date(2016, 2, 29), date(2009, 1, 1)] - source_values = [(2023, 12, 1), (2016, 2, 29), (2009, 1, 1)] - - class enum_type(date, ReprEnum): - def _generate_next_value_(name, start, count, last_values): - values = [(2023, 12, 1), (2016, 2, 29), (2009, 1, 1)] - return values[count] - - -class TestMixedFloat(_EnumTests, _MixedOutputTests, unittest.TestCase): - - values = [1.1, 2.2, 3.3] - - class enum_type(float, Enum): - def _generate_next_value_(name, start, count, last_values): - values = [1.1, 2.2, 3.3] - return values[count] - - -class TestMinimalFloat(_EnumTests, _MinimalOutputTests, unittest.TestCase): - - values = [4.4, 5.5, 6.6] - - class enum_type(float, ReprEnum): - def _generate_next_value_(name, start, count, last_values): - values = [4.4, 5.5, 6.6] - return values[count] - - -class TestSpecial(unittest.TestCase): - """ - various operations that are not attributable to every possible enum - """ +class TestEnum(unittest.TestCase): def setUp(self): class Season(Enum): @@ -821,7 +175,13 @@ class TestSpecial(unittest.TestCase): AUTUMN = 3 WINTER = 4 self.Season = Season - # + + class Konstants(float, Enum): + E = 2.7182818 + PI = 3.1415926 + TAU = 2 * PI + self.Konstants = Konstants + class Grades(IntEnum): A = 5 B = 4 @@ -829,20 +189,451 @@ class TestSpecial(unittest.TestCase): D = 2 F = 0 self.Grades = Grades - # + class Directional(str, Enum): EAST = 'east' WEST = 'west' NORTH = 'north' SOUTH = 'south' self.Directional = Directional - # + from datetime import date class Holiday(date, Enum): NEW_YEAR = 2013, 1, 1 IDES_OF_MARCH = 2013, 3, 15 self.Holiday = Holiday + class DateEnum(date, Enum): pass + self.DateEnum = DateEnum + + class FloatEnum(float, Enum): pass + self.FloatEnum = FloatEnum + + class Wowser(Enum): + this = 'that' + these = 'those' + def wowser(self): + """Wowser docstring""" + return ("Wowser! I'm %s!" % self.name) + @classmethod + def classmethod_wowser(cls): pass + @staticmethod + def staticmethod_wowser(): pass + self.Wowser = Wowser + + class IntWowser(IntEnum): + this = 1 + these = 2 + def wowser(self): + """Wowser docstring""" + return ("Wowser! I'm %s!" % self.name) + @classmethod + def classmethod_wowser(cls): pass + @staticmethod + def staticmethod_wowser(): pass + self.IntWowser = IntWowser + + class FloatWowser(float, Enum): + this = 3.14 + these = 4.2 + def wowser(self): + """Wowser docstring""" + return ("Wowser! I'm %s!" % self.name) + @classmethod + def classmethod_wowser(cls): pass + @staticmethod + def staticmethod_wowser(): pass + self.FloatWowser = FloatWowser + + class WowserNoMembers(Enum): + def wowser(self): pass + @classmethod + def classmethod_wowser(cls): pass + @staticmethod + def staticmethod_wowser(): pass + class SubclassOfWowserNoMembers(WowserNoMembers): pass + self.WowserNoMembers = WowserNoMembers + self.SubclassOfWowserNoMembers = SubclassOfWowserNoMembers + + class IntWowserNoMembers(IntEnum): + def wowser(self): pass + @classmethod + def classmethod_wowser(cls): pass + @staticmethod + def staticmethod_wowser(): pass + self.IntWowserNoMembers = IntWowserNoMembers + + class FloatWowserNoMembers(float, Enum): + def wowser(self): pass + @classmethod + def classmethod_wowser(cls): pass + @staticmethod + def staticmethod_wowser(): pass + self.FloatWowserNoMembers = FloatWowserNoMembers + + class EnumWithInit(Enum): + def __init__(self, greeting, farewell): + self.greeting = greeting + self.farewell = farewell + ENGLISH = 'hello', 'goodbye' + GERMAN = 'Guten Morgen', 'Auf Wiedersehen' + def some_method(self): pass + self.EnumWithInit = EnumWithInit + + # see issue22506 + class SuperEnum1(Enum): + def invisible(self): + return "did you see me?" + class SubEnum1(SuperEnum1): + sample = 5 + self.SubEnum1 = SubEnum1 + + class SuperEnum2(IntEnum): + def __new__(cls, value, description=""): + obj = int.__new__(cls, value) + obj._value_ = value + obj.description = description + return obj + class SubEnum2(SuperEnum2): + sample = 5 + self.SubEnum2 = SubEnum2 + + def test_dir_basics_for_all_enums(self): + enums_for_tests = ( + # Generic enums in enum.py + Enum, + IntEnum, + StrEnum, + # Generic enums defined outside of enum.py + self.DateEnum, + self.FloatEnum, + # Concrete enums derived from enum.py generics + self.Grades, + self.Season, + # Concrete enums derived from generics defined outside of enum.py + self.Konstants, + self.Holiday, + # Standard enum with added behaviour & members + self.Wowser, + # Mixin-enum-from-enum.py with added behaviour & members + self.IntWowser, + # Mixin-enum-from-oustide-enum.py with added behaviour & members + self.FloatWowser, + # Equivalents of the three immediately above, but with no members + self.WowserNoMembers, + self.IntWowserNoMembers, + self.FloatWowserNoMembers, + # Enum with members and an __init__ method + self.EnumWithInit, + # Special cases to test + self.SubEnum1, + self.SubEnum2 + ) + + for cls in enums_for_tests: + with self.subTest(cls=cls): + cls_dir = dir(cls) + # test that dir is deterministic + self.assertEqual(cls_dir, dir(cls)) + # test that dir is sorted + self.assertEqual(list(cls_dir), sorted(cls_dir)) + # test that there are no dupes in dir + self.assertEqual(len(cls_dir), len(set(cls_dir))) + # test that there are no sunders in dir + self.assertFalse(any(enum._is_sunder(attr) for attr in cls_dir)) + self.assertNotIn('__new__', cls_dir) + + for attr in ('__class__', '__doc__', '__members__', '__module__'): + with self.subTest(attr=attr): + self.assertIn(attr, cls_dir) + + def test_dir_for_enum_with_members(self): + enums_for_test = ( + # Enum with members + self.Season, + # IntEnum with members + self.Grades, + # Two custom-mixin enums with members + self.Konstants, + self.Holiday, + # several enums-with-added-behaviour and members + self.Wowser, + self.IntWowser, + self.FloatWowser, + # An enum with an __init__ method and members + self.EnumWithInit, + # Special cases to test + self.SubEnum1, + self.SubEnum2 + ) + + for cls in enums_for_test: + cls_dir = dir(cls) + member_names = cls._member_names_ + with self.subTest(cls=cls): + self.assertTrue(all(member_name in cls_dir for member_name in member_names)) + for member in cls: + member_dir = dir(member) + # test that dir is deterministic + self.assertEqual(member_dir, dir(member)) + # test that dir is sorted + self.assertEqual(list(member_dir), sorted(member_dir)) + # test that there are no dupes in dir + self.assertEqual(len(member_dir), len(set(member_dir))) + + for attr_name in cls_dir: + with self.subTest(attr_name=attr_name): + if attr_name in {'__members__', '__init__', '__new__', *member_names}: + self.assertNotIn(attr_name, member_dir) + else: + self.assertIn(attr_name, member_dir) + + self.assertFalse(any(enum._is_sunder(attr) for attr in member_dir)) + + def test_dir_for_enums_with_added_behaviour(self): + enums_for_test = ( + self.Wowser, + self.IntWowser, + self.FloatWowser, + self.WowserNoMembers, + self.SubclassOfWowserNoMembers, + self.IntWowserNoMembers, + self.FloatWowserNoMembers + ) + + for cls in enums_for_test: + with self.subTest(cls=cls): + self.assertIn('wowser', dir(cls)) + self.assertIn('classmethod_wowser', dir(cls)) + self.assertIn('staticmethod_wowser', dir(cls)) + self.assertTrue(all( + all(attr in dir(member) for attr in ('wowser', 'classmethod_wowser', 'staticmethod_wowser')) + for member in cls + )) + + self.assertEqual(dir(self.WowserNoMembers), dir(self.SubclassOfWowserNoMembers)) + # Check classmethods are present + self.assertIn('from_bytes', dir(self.IntWowser)) + self.assertIn('from_bytes', dir(self.IntWowserNoMembers)) + + def test_help_output_on_enum_members(self): + added_behaviour_enums = ( + self.Wowser, + self.IntWowser, + self.FloatWowser + ) + + for cls in added_behaviour_enums: + with self.subTest(cls=cls): + rendered_doc = pydoc.render_doc(cls.this) + self.assertIn('Wowser docstring', rendered_doc) + if cls in {self.IntWowser, self.FloatWowser}: + self.assertIn('float(self)', rendered_doc) + + def test_dir_for_enum_with_init(self): + EnumWithInit = self.EnumWithInit + + cls_dir = dir(EnumWithInit) + self.assertIn('__init__', cls_dir) + self.assertIn('some_method', cls_dir) + self.assertNotIn('greeting', cls_dir) + self.assertNotIn('farewell', cls_dir) + + member_dir = dir(EnumWithInit.ENGLISH) + self.assertNotIn('__init__', member_dir) + self.assertIn('some_method', member_dir) + self.assertIn('greeting', member_dir) + self.assertIn('farewell', member_dir) + + def test_mixin_dirs(self): + from datetime import date + + enums_for_test = ( + # generic mixins from enum.py + (IntEnum, int), + (StrEnum, str), + # generic mixins from outside enum.py + (self.FloatEnum, float), + (self.DateEnum, date), + # concrete mixin from enum.py + (self.Grades, int), + # concrete mixin from outside enum.py + (self.Holiday, date), + # concrete mixin from enum.py with added behaviour + (self.IntWowser, int), + # concrete mixin from outside enum.py with added behaviour + (self.FloatWowser, float) + ) + + enum_dict = Enum.__dict__ + enum_dir = dir(Enum) + enum_module_names = enum.__all__ + is_from_enum_module = lambda cls: cls.__name__ in enum_module_names + is_enum_dunder = lambda attr: enum._is_dunder(attr) and attr in enum_dict + + def attr_is_inherited_from_object(cls, attr_name): + for base in cls.__mro__: + if attr_name in base.__dict__: + return base is object + return False + + # General tests + for enum_cls, mixin_cls in enums_for_test: + with self.subTest(enum_cls=enum_cls): + cls_dir = dir(enum_cls) + cls_dict = enum_cls.__dict__ + + mixin_attrs = [ + x for x in dir(mixin_cls) + if not attr_is_inherited_from_object(cls=mixin_cls, attr_name=x) + ] + + first_enum_base = next( + base for base in enum_cls.__mro__ + if is_from_enum_module(base) + ) + + for attr in mixin_attrs: + with self.subTest(attr=attr): + if enum._is_sunder(attr): + # Unlikely, but no harm in testing + self.assertNotIn(attr, cls_dir) + elif attr in {'__class__', '__doc__', '__members__', '__module__'}: + self.assertIn(attr, cls_dir) + elif is_enum_dunder(attr): + if is_from_enum_module(enum_cls): + self.assertNotIn(attr, cls_dir) + elif getattr(enum_cls, attr) is getattr(first_enum_base, attr): + self.assertNotIn(attr, cls_dir) + else: + self.assertIn(attr, cls_dir) + else: + self.assertIn(attr, cls_dir) + + # Some specific examples + int_enum_dir = dir(IntEnum) + self.assertIn('imag', int_enum_dir) + self.assertIn('__rfloordiv__', int_enum_dir) + self.assertNotIn('__format__', int_enum_dir) + self.assertNotIn('__hash__', int_enum_dir) + self.assertNotIn('__init_subclass__', int_enum_dir) + self.assertNotIn('__subclasshook__', int_enum_dir) + + class OverridesFormatOutsideEnumModule(Enum): + def __format__(self, *args, **kwargs): + return super().__format__(*args, **kwargs) + SOME_MEMBER = 1 + + self.assertIn('__format__', dir(OverridesFormatOutsideEnumModule)) + self.assertIn('__format__', dir(OverridesFormatOutsideEnumModule.SOME_MEMBER)) + + def test_dir_on_sub_with_behavior_on_super(self): + # see issue22506 + self.assertEqual( + set(dir(self.SubEnum1.sample)), + set(['__class__', '__doc__', '__module__', 'name', 'value', 'invisible']), + ) + + def test_dir_on_sub_with_behavior_including_instance_dict_on_super(self): + # see issue40084 + self.assertTrue({'description'} <= set(dir(self.SubEnum2.sample))) + + def test_enum_in_enum_out(self): + Season = self.Season + self.assertIs(Season(Season.WINTER), Season.WINTER) + + def test_enum_value(self): + Season = self.Season + self.assertEqual(Season.SPRING.value, 1) + + def test_intenum_value(self): + self.assertEqual(IntStooges.CURLY.value, 2) + + def test_enum(self): + Season = self.Season + lst = list(Season) + self.assertEqual(len(lst), len(Season)) + self.assertEqual(len(Season), 4, Season) + self.assertEqual( + [Season.SPRING, Season.SUMMER, Season.AUTUMN, Season.WINTER], lst) + + for i, season in enumerate('SPRING SUMMER AUTUMN WINTER'.split(), 1): + e = Season(i) + self.assertEqual(e, getattr(Season, season)) + self.assertEqual(e.value, i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, season) + self.assertIn(e, Season) + self.assertIs(type(e), Season) + self.assertIsInstance(e, Season) + self.assertEqual(str(e), season) + self.assertEqual(repr(e), 'Season.{0}'.format(season)) + + def test_value_name(self): + Season = self.Season + self.assertEqual(Season.SPRING.name, 'SPRING') + self.assertEqual(Season.SPRING.value, 1) + with self.assertRaises(AttributeError): + Season.SPRING.name = 'invierno' + with self.assertRaises(AttributeError): + Season.SPRING.value = 2 + + def test_changing_member(self): + Season = self.Season + with self.assertRaises(AttributeError): + Season.WINTER = 'really cold' + + def test_attribute_deletion(self): + class Season(Enum): + SPRING = 1 + SUMMER = 2 + AUTUMN = 3 + WINTER = 4 + + def spam(cls): + pass + + self.assertTrue(hasattr(Season, 'spam')) + del Season.spam + self.assertFalse(hasattr(Season, 'spam')) + + with self.assertRaises(AttributeError): + del Season.SPRING + with self.assertRaises(AttributeError): + del Season.DRY + with self.assertRaises(AttributeError): + del Season.SPRING.name + + def test_bool_of_class(self): + class Empty(Enum): + pass + self.assertTrue(bool(Empty)) + + def test_bool_of_member(self): + class Count(Enum): + zero = 0 + one = 1 + two = 2 + for member in Count: + self.assertTrue(bool(member)) + + def test_invalid_names(self): + with self.assertRaises(ValueError): + class Wrong(Enum): + mro = 9 + with self.assertRaises(ValueError): + class Wrong(Enum): + _create_= 11 + with self.assertRaises(ValueError): + class Wrong(Enum): + _get_mixins_ = 9 + with self.assertRaises(ValueError): + class Wrong(Enum): + _find_new_ = 1 + with self.assertRaises(ValueError): + class Wrong(Enum): + _any_name_ = 9 + def test_bool(self): # plain Enum members are always True class Logic(Enum): @@ -865,56 +656,92 @@ class TestSpecial(unittest.TestCase): self.assertTrue(IntLogic.true) self.assertFalse(IntLogic.false) + @unittest.skipIf( + python_version >= (3, 12), + '__contains__ now returns True/False for all inputs', + ) + def test_contains_er(self): + Season = self.Season + self.assertIn(Season.AUTUMN, Season) + with self.assertRaises(TypeError): + with self.assertWarns(DeprecationWarning): + 3 in Season + with self.assertRaises(TypeError): + with self.assertWarns(DeprecationWarning): + 'AUTUMN' in Season + val = Season(3) + self.assertIn(val, Season) + # + class OtherEnum(Enum): + one = 1; two = 2 + self.assertNotIn(OtherEnum.two, Season) + + @unittest.skipIf( + python_version < (3, 12), + '__contains__ only works with enum memmbers before 3.12', + ) + def test_contains_tf(self): + Season = self.Season + self.assertIn(Season.AUTUMN, Season) + self.assertTrue(3 in Season) + self.assertFalse('AUTUMN' in Season) + val = Season(3) + self.assertIn(val, Season) + # + class OtherEnum(Enum): + one = 1; two = 2 + self.assertNotIn(OtherEnum.two, Season) + def test_comparisons(self): Season = self.Season with self.assertRaises(TypeError): Season.SPRING < Season.WINTER with self.assertRaises(TypeError): Season.SPRING > 4 - # + self.assertNotEqual(Season.SPRING, 1) - # + class Part(Enum): SPRING = 1 CLIP = 2 BARREL = 3 - # + self.assertNotEqual(Season.SPRING, Part.SPRING) with self.assertRaises(TypeError): Season.SPRING < Part.CLIP - def test_dir_with_custom_dunders(self): - class PlainEnum(Enum): - pass - cls_dir = dir(PlainEnum) - self.assertNotIn('__repr__', cls_dir) - self.assertNotIn('__str__', cls_dir) - self.assertNotIn('__repr__', cls_dir) - self.assertNotIn('__repr__', cls_dir) - # - class MyEnum(Enum): - def __repr__(self): - return object.__repr__(self) - def __str__(self): - return object.__repr__(self) - def __format__(self): - return object.__repr__(self) - def __init__(self): - pass - cls_dir = dir(MyEnum) - self.assertIn('__repr__', cls_dir) - self.assertIn('__str__', cls_dir) - self.assertIn('__repr__', cls_dir) - self.assertIn('__repr__', cls_dir) + def test_enum_duplicates(self): + class Season(Enum): + SPRING = 1 + SUMMER = 2 + AUTUMN = FALL = 3 + WINTER = 4 + ANOTHER_SPRING = 1 + lst = list(Season) + self.assertEqual( + lst, + [Season.SPRING, Season.SUMMER, + Season.AUTUMN, Season.WINTER, + ]) + self.assertIs(Season.FALL, Season.AUTUMN) + self.assertEqual(Season.FALL.value, 3) + self.assertEqual(Season.AUTUMN.value, 3) + self.assertIs(Season(3), Season.AUTUMN) + self.assertIs(Season(1), Season.SPRING) + self.assertEqual(Season.FALL.name, 'AUTUMN') + self.assertEqual( + [k for k,v in Season.__members__.items() if v.name != k], + ['FALL', 'ANOTHER_SPRING'], + ) - def test_duplicate_name_error(self): + def test_duplicate_name(self): with self.assertRaises(TypeError): class Color(Enum): red = 1 green = 2 blue = 3 red = 4 - # + with self.assertRaises(TypeError): class Color(Enum): red = 1 @@ -922,45 +749,232 @@ class TestSpecial(unittest.TestCase): blue = 3 def red(self): return 'red' - # + with self.assertRaises(TypeError): class Color(Enum): - @enum.property + @property def red(self): return 'redder' red = 1 green = 2 blue = 3 - def test_enum_function_with_qualname(self): - if isinstance(Theory, Exception): - raise Theory - self.assertEqual(Theory.__qualname__, 'spanish_inquisition') + def test_reserved__sunder_(self): + with self.assertRaisesRegex( + ValueError, + '_sunder_ names, such as ._bad_., are reserved', + ): + class Bad(Enum): + _bad_ = 1 def test_enum_with_value_name(self): class Huh(Enum): name = 1 value = 2 - self.assertEqual(list(Huh), [Huh.name, Huh.value]) + self.assertEqual( + list(Huh), + [Huh.name, Huh.value], + ) self.assertIs(type(Huh.name), Huh) self.assertEqual(Huh.name.name, 'name') self.assertEqual(Huh.name.value, 1) + def test_format_enum(self): + Season = self.Season + self.assertEqual('{}'.format(Season.SPRING), + '{}'.format(str(Season.SPRING))) + self.assertEqual( '{:}'.format(Season.SPRING), + '{:}'.format(str(Season.SPRING))) + self.assertEqual('{:20}'.format(Season.SPRING), + '{:20}'.format(str(Season.SPRING))) + self.assertEqual('{:^20}'.format(Season.SPRING), + '{:^20}'.format(str(Season.SPRING))) + self.assertEqual('{:>20}'.format(Season.SPRING), + '{:>20}'.format(str(Season.SPRING))) + self.assertEqual('{:<20}'.format(Season.SPRING), + '{:<20}'.format(str(Season.SPRING))) + + def test_str_override_enum(self): + class EnumWithStrOverrides(Enum): + one = auto() + two = auto() + + def __str__(self): + return 'Str!' + self.assertEqual(str(EnumWithStrOverrides.one), 'Str!') + self.assertEqual('{}'.format(EnumWithStrOverrides.one), 'Str!') + + def test_format_override_enum(self): + class EnumWithFormatOverride(Enum): + one = 1.0 + two = 2.0 + def __format__(self, spec): + return 'Format!!' + self.assertEqual(str(EnumWithFormatOverride.one), 'one') + self.assertEqual('{}'.format(EnumWithFormatOverride.one), 'Format!!') + + def test_str_and_format_override_enum(self): + class EnumWithStrFormatOverrides(Enum): + one = auto() + two = auto() + def __str__(self): + return 'Str!' + def __format__(self, spec): + return 'Format!' + self.assertEqual(str(EnumWithStrFormatOverrides.one), 'Str!') + self.assertEqual('{}'.format(EnumWithStrFormatOverrides.one), 'Format!') + + def test_str_override_mixin(self): + class MixinEnumWithStrOverride(float, Enum): + one = 1.0 + two = 2.0 + def __str__(self): + return 'Overridden!' + self.assertEqual(str(MixinEnumWithStrOverride.one), 'Overridden!') + self.assertEqual('{}'.format(MixinEnumWithStrOverride.one), 'Overridden!') + + def test_str_and_format_override_mixin(self): + class MixinWithStrFormatOverrides(float, Enum): + one = 1.0 + two = 2.0 + def __str__(self): + return 'Str!' + def __format__(self, spec): + return 'Format!' + self.assertEqual(str(MixinWithStrFormatOverrides.one), 'Str!') + self.assertEqual('{}'.format(MixinWithStrFormatOverrides.one), 'Format!') + + def test_format_override_mixin(self): + class TestFloat(float, Enum): + one = 1.0 + two = 2.0 + def __format__(self, spec): + return 'TestFloat success!' + self.assertEqual(str(TestFloat.one), 'one') + self.assertEqual('{}'.format(TestFloat.one), 'TestFloat success!') + + @unittest.skipIf( + python_version < (3, 12), + 'mixin-format is still using member.value', + ) + def test_mixin_format_warning(self): + class Grades(int, Enum): + A = 5 + B = 4 + C = 3 + D = 2 + F = 0 + self.assertEqual(f'{self.Grades.B}', 'B') + + @unittest.skipIf( + python_version >= (3, 12), + 'mixin-format now uses member instead of member.value', + ) + def test_mixin_format_warning(self): + class Grades(int, Enum): + A = 5 + B = 4 + C = 3 + D = 2 + F = 0 + with self.assertWarns(DeprecationWarning): + self.assertEqual(f'{Grades.B}', '4') + + def assertFormatIsValue(self, spec, member): + if python_version < (3, 12) and (not spec or spec in ('{}','{:}')): + with self.assertWarns(DeprecationWarning): + self.assertEqual(spec.format(member), spec.format(member.value)) + else: + self.assertEqual(spec.format(member), spec.format(member.value)) + + def test_format_enum_date(self): + Holiday = self.Holiday + self.assertFormatIsValue('{}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{:}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{:20}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{:^20}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{:>20}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{:<20}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{:%Y %m}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{:%Y %m %M:00}', Holiday.IDES_OF_MARCH) + + def test_format_enum_float(self): + Konstants = self.Konstants + self.assertFormatIsValue('{}', Konstants.TAU) + self.assertFormatIsValue('{:}', Konstants.TAU) + self.assertFormatIsValue('{:20}', Konstants.TAU) + self.assertFormatIsValue('{:^20}', Konstants.TAU) + self.assertFormatIsValue('{:>20}', Konstants.TAU) + self.assertFormatIsValue('{:<20}', Konstants.TAU) + self.assertFormatIsValue('{:n}', Konstants.TAU) + self.assertFormatIsValue('{:5.2}', Konstants.TAU) + self.assertFormatIsValue('{:f}', Konstants.TAU) + + def test_format_enum_int(self): + class Grades(int, Enum): + A = 5 + B = 4 + C = 3 + D = 2 + F = 0 + self.assertFormatIsValue('{}', Grades.C) + self.assertFormatIsValue('{:}', Grades.C) + self.assertFormatIsValue('{:20}', Grades.C) + self.assertFormatIsValue('{:^20}', Grades.C) + self.assertFormatIsValue('{:>20}', Grades.C) + self.assertFormatIsValue('{:<20}', Grades.C) + self.assertFormatIsValue('{:+}', Grades.C) + self.assertFormatIsValue('{:08X}', Grades.C) + self.assertFormatIsValue('{:b}', Grades.C) + + def test_format_enum_str(self): + Directional = self.Directional + self.assertFormatIsValue('{}', Directional.WEST) + self.assertFormatIsValue('{:}', Directional.WEST) + self.assertFormatIsValue('{:20}', Directional.WEST) + self.assertFormatIsValue('{:^20}', Directional.WEST) + self.assertFormatIsValue('{:>20}', Directional.WEST) + self.assertFormatIsValue('{:<20}', Directional.WEST) + + def test_object_str_override(self): + class Colors(Enum): + RED, GREEN, BLUE = 1, 2, 3 + def __repr__(self): + return "test.%s" % (self._name_, ) + __str__ = object.__str__ + self.assertEqual(str(Colors.RED), 'test.RED') + + def test_enum_str_override(self): + class MyStrEnum(Enum): + def __str__(self): + return 'MyStr' + class MyMethodEnum(Enum): + def hello(self): + return 'Hello! My name is %s' % self.name + class Test1Enum(MyMethodEnum, int, MyStrEnum): + One = 1 + Two = 2 + self.assertTrue(Test1Enum._member_type_ is int) + self.assertEqual(str(Test1Enum.One), 'MyStr') + self.assertEqual(format(Test1Enum.One, ''), 'MyStr') + # + class Test2Enum(MyStrEnum, MyMethodEnum): + One = 1 + Two = 2 + self.assertEqual(str(Test2Enum.One), 'MyStr') + self.assertEqual(format(Test1Enum.One, ''), 'MyStr') + def test_inherited_data_type(self): class HexInt(int): - __qualname__ = 'HexInt' def __repr__(self): return hex(self) class MyEnum(HexInt, enum.Enum): - __qualname__ = 'MyEnum' A = 1 B = 2 C = 3 + def __repr__(self): + return '<%s.%s: %r>' % (self.__class__.__name__, self._name_, self._value_) self.assertEqual(repr(MyEnum.A), '') - globals()['HexInt'] = HexInt - globals()['MyEnum'] = MyEnum - test_pickle_dump_load(self.assertIs, MyEnum.A) - test_pickle_dump_load(self.assertIs, MyEnum) # class SillyInt(HexInt): __qualname__ = 'SillyInt' @@ -976,7 +990,7 @@ class TestSpecial(unittest.TestCase): test_pickle_dump_load(self.assertIs, MyOtherEnum.E) test_pickle_dump_load(self.assertIs, MyOtherEnum) # - # This did not work in 3.10, but does now with pickling by name + # This did not work in 3.9, but does now with pickling by name class UnBrokenInt(int): __qualname__ = 'UnBrokenInt' def __new__(cls, value): @@ -993,124 +1007,6 @@ class TestSpecial(unittest.TestCase): test_pickle_dump_load(self.assertIs, MyUnBrokenEnum.I) test_pickle_dump_load(self.assertIs, MyUnBrokenEnum) - def test_floatenum_fromhex(self): - h = float.hex(FloatStooges.MOE.value) - self.assertIs(FloatStooges.fromhex(h), FloatStooges.MOE) - h = float.hex(FloatStooges.MOE.value + 0.01) - with self.assertRaises(ValueError): - FloatStooges.fromhex(h) - - def test_programmatic_function_type(self): - MinorEnum = Enum('MinorEnum', 'june july august', type=int) - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 1): - e = MinorEnum(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - - def test_programmatic_function_string_with_start(self): - MinorEnum = Enum('MinorEnum', 'june july august', start=10) - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 10): - e = MinorEnum(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - - def test_programmatic_function_type_with_start(self): - MinorEnum = Enum('MinorEnum', 'june july august', type=int, start=30) - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 30): - e = MinorEnum(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - - def test_programmatic_function_string_list_with_start(self): - MinorEnum = Enum('MinorEnum', ['june', 'july', 'august'], start=20) - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 20): - e = MinorEnum(i) - self.assertEqual(int(e.value), i) - self.assertNotEqual(e, i) - self.assertEqual(e.name, month) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - - def test_programmatic_function_type_from_subclass(self): - MinorEnum = IntEnum('MinorEnum', 'june july august') - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 1): - e = MinorEnum(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - - def test_programmatic_function_type_from_subclass_with_start(self): - MinorEnum = IntEnum('MinorEnum', 'june july august', start=40) - lst = list(MinorEnum) - self.assertEqual(len(lst), len(MinorEnum)) - self.assertEqual(len(MinorEnum), 3, MinorEnum) - self.assertEqual( - [MinorEnum.june, MinorEnum.july, MinorEnum.august], - lst, - ) - for i, month in enumerate('june july august'.split(), 40): - e = MinorEnum(i) - self.assertEqual(e, i) - self.assertEqual(e.name, month) - self.assertIn(e, MinorEnum) - self.assertIs(type(e), MinorEnum) - - def test_intenum_from_bytes(self): - self.assertIs(IntStooges.from_bytes(b'\x00\x03', 'big'), IntStooges.MOE) - with self.assertRaises(ValueError): - IntStooges.from_bytes(b'\x00\x05', 'big') - - def test_reserved_sunder_error(self): - with self.assertRaisesRegex( - ValueError, - '_sunder_ names, such as ._bad_., are reserved', - ): - class Bad(Enum): - _bad_ = 1 - def test_too_many_data_types(self): with self.assertRaisesRegex(TypeError, 'too many data types'): class Huh(str, int, Enum): @@ -1126,6 +1022,122 @@ class TestSpecial(unittest.TestCase): class Huh(MyStr, MyInt, Enum): One = 1 + def test_value_auto_assign(self): + class Some(Enum): + def __new__(cls, val): + return object.__new__(cls) + x = 1 + y = 2 + + self.assertEqual(Some.x.value, 1) + self.assertEqual(Some.y.value, 2) + + def test_hash(self): + Season = self.Season + dates = {} + dates[Season.WINTER] = '1225' + dates[Season.SPRING] = '0315' + dates[Season.SUMMER] = '0704' + dates[Season.AUTUMN] = '1031' + self.assertEqual(dates[Season.AUTUMN], '1031') + + def test_intenum_from_scratch(self): + class phy(int, Enum): + pi = 3 + tau = 2 * pi + self.assertTrue(phy.pi < phy.tau) + + def test_intenum_inherited(self): + class IntEnum(int, Enum): + pass + class phy(IntEnum): + pi = 3 + tau = 2 * pi + self.assertTrue(phy.pi < phy.tau) + + def test_floatenum_from_scratch(self): + class phy(float, Enum): + pi = 3.1415926 + tau = 2 * pi + self.assertTrue(phy.pi < phy.tau) + + def test_floatenum_inherited(self): + class FloatEnum(float, Enum): + pass + class phy(FloatEnum): + pi = 3.1415926 + tau = 2 * pi + self.assertTrue(phy.pi < phy.tau) + + def test_strenum_from_scratch(self): + class phy(str, Enum): + pi = 'Pi' + tau = 'Tau' + self.assertTrue(phy.pi < phy.tau) + + def test_strenum_inherited_methods(self): + class phy(StrEnum): + pi = 'Pi' + tau = 'Tau' + self.assertTrue(phy.pi < phy.tau) + self.assertEqual(phy.pi.upper(), 'PI') + self.assertEqual(phy.tau.count('a'), 1) + + def test_intenum(self): + class WeekDay(IntEnum): + SUNDAY = 1 + MONDAY = 2 + TUESDAY = 3 + WEDNESDAY = 4 + THURSDAY = 5 + FRIDAY = 6 + SATURDAY = 7 + + self.assertEqual(['a', 'b', 'c'][WeekDay.MONDAY], 'c') + self.assertEqual([i for i in range(WeekDay.TUESDAY)], [0, 1, 2]) + + lst = list(WeekDay) + self.assertEqual(len(lst), len(WeekDay)) + self.assertEqual(len(WeekDay), 7) + target = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY' + target = target.split() + for i, weekday in enumerate(target, 1): + e = WeekDay(i) + self.assertEqual(e, i) + self.assertEqual(int(e), i) + self.assertEqual(e.name, weekday) + self.assertIn(e, WeekDay) + self.assertEqual(lst.index(e)+1, i) + self.assertTrue(0 < e < 8) + self.assertIs(type(e), WeekDay) + self.assertIsInstance(e, int) + self.assertIsInstance(e, Enum) + + def test_intenum_duplicates(self): + class WeekDay(IntEnum): + SUNDAY = 1 + MONDAY = 2 + TUESDAY = TEUSDAY = 3 + WEDNESDAY = 4 + THURSDAY = 5 + FRIDAY = 6 + SATURDAY = 7 + self.assertIs(WeekDay.TEUSDAY, WeekDay.TUESDAY) + self.assertEqual(WeekDay(3).name, 'TUESDAY') + self.assertEqual([k for k,v in WeekDay.__members__.items() + if v.name != k], ['TEUSDAY', ]) + + def test_intenum_from_bytes(self): + self.assertIs(IntStooges.from_bytes(b'\x00\x03', 'big'), IntStooges.MOE) + with self.assertRaises(ValueError): + IntStooges.from_bytes(b'\x00\x05', 'big') + + def test_floatenum_fromhex(self): + h = float.hex(FloatStooges.MOE.value) + self.assertIs(FloatStooges.fromhex(h), FloatStooges.MOE) + h = float.hex(FloatStooges.MOE.value + 0.01) + with self.assertRaises(ValueError): + FloatStooges.fromhex(h) def test_pickle_enum(self): if isinstance(Stooges, Exception): @@ -1157,7 +1169,12 @@ class TestSpecial(unittest.TestCase): test_pickle_dump_load(self.assertIs, Question.who) test_pickle_dump_load(self.assertIs, Question) - def test_pickle_nested_class(self): + def test_enum_function_with_qualname(self): + if isinstance(Theory, Exception): + raise Theory + self.assertEqual(Theory.__qualname__, 'spanish_inquisition') + + def test_class_nested_enum_and_pickle_protocol_four(self): # would normally just have this directly in the class namespace class NestedEnum(Enum): twigs = 'common' @@ -1175,7 +1192,7 @@ class TestSpecial(unittest.TestCase): for proto in range(HIGHEST_PROTOCOL): self.assertEqual(ReplaceGlobalInt.TWO.__reduce_ex__(proto), 'TWO') - def test_pickle_explodes(self): + def test_exploding_pickle(self): BadPickle = Enum( 'BadPickle', 'dill sweet bread-n-butter', module=__name__) globals()['BadPickle'] = BadPickle @@ -1216,6 +1233,185 @@ class TestSpecial(unittest.TestCase): [Season.SUMMER, Season.WINTER, Season.AUTUMN, Season.SPRING], ) + def test_reversed_iteration_order(self): + self.assertEqual( + list(reversed(self.Season)), + [self.Season.WINTER, self.Season.AUTUMN, self.Season.SUMMER, + self.Season.SPRING] + ) + + def test_programmatic_function_string(self): + SummerMonth = Enum('SummerMonth', 'june july august') + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 1): + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_string_with_start(self): + SummerMonth = Enum('SummerMonth', 'june july august', start=10) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 10): + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_string_list(self): + SummerMonth = Enum('SummerMonth', ['june', 'july', 'august']) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 1): + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_string_list_with_start(self): + SummerMonth = Enum('SummerMonth', ['june', 'july', 'august'], start=20) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 20): + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_iterable(self): + SummerMonth = Enum( + 'SummerMonth', + (('june', 1), ('july', 2), ('august', 3)) + ) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 1): + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_from_dict(self): + SummerMonth = Enum( + 'SummerMonth', + OrderedDict((('june', 1), ('july', 2), ('august', 3))) + ) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 1): + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_type(self): + SummerMonth = Enum('SummerMonth', 'june july august', type=int) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 1): + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_type_with_start(self): + SummerMonth = Enum('SummerMonth', 'june july august', type=int, start=30) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 30): + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_type_from_subclass(self): + SummerMonth = IntEnum('SummerMonth', 'june july august') + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 1): + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + + def test_programmatic_function_type_from_subclass_with_start(self): + SummerMonth = IntEnum('SummerMonth', 'june july august', start=40) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 40): + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertIn(e, SummerMonth) + self.assertIs(type(e), SummerMonth) + def test_subclassing(self): if isinstance(Name, Exception): raise Name @@ -1229,18 +1425,15 @@ class TestSpecial(unittest.TestCase): red = 1 green = 2 blue = 3 - # with self.assertRaises(TypeError): class MoreColor(Color): cyan = 4 magenta = 5 yellow = 6 - # - with self.assertRaisesRegex(TypeError, " cannot extend "): + with self.assertRaisesRegex(TypeError, "EvenMoreColor: cannot extend enumeration 'Color'"): class EvenMoreColor(Color, IntEnum): chartruese = 7 - # - with self.assertRaisesRegex(TypeError, " cannot extend "): + with self.assertRaisesRegex(TypeError, "Foo: cannot extend enumeration 'Color'"): Color('Foo', ('pink', 'black')) def test_exclude_methods(self): @@ -1344,7 +1537,27 @@ class TestSpecial(unittest.TestCase): with self.assertRaises(KeyError): Color['chartreuse'] - # tests that need to be evalualted for moving + def test_new_repr(self): + class Color(Enum): + red = 1 + green = 2 + blue = 3 + def __repr__(self): + return "don't you just love shades of %s?" % self.name + self.assertEqual( + repr(Color.blue), + "don't you just love shades of blue?", + ) + + def test_inherited_repr(self): + class MyEnum(Enum): + def __repr__(self): + return "My name is %s." % self.name + class MyIntEnum(int, MyEnum): + this = 1 + that = 2 + theother = 3 + self.assertEqual(repr(MyIntEnum.that), "My name is that.") def test_multiple_mixin_mro(self): class auto_enum(type(Enum)): @@ -1397,7 +1610,7 @@ class TestSpecial(unittest.TestCase): return self def __getnewargs__(self): return self._args - @bltns.property + @property def __name__(self): return self._intname def __repr__(self): @@ -1457,7 +1670,7 @@ class TestSpecial(unittest.TestCase): return self def __getnewargs_ex__(self): return self._args, {} - @bltns.property + @property def __name__(self): return self._intname def __repr__(self): @@ -1517,7 +1730,7 @@ class TestSpecial(unittest.TestCase): return self def __reduce__(self): return self.__class__, self._args - @bltns.property + @property def __name__(self): return self._intname def __repr__(self): @@ -1577,7 +1790,7 @@ class TestSpecial(unittest.TestCase): return self def __reduce_ex__(self, proto): return self.__class__, self._args - @bltns.property + @property def __name__(self): return self._intname def __repr__(self): @@ -1634,7 +1847,7 @@ class TestSpecial(unittest.TestCase): self._intname = name self._args = _args return self - @bltns.property + @property def __name__(self): return self._intname def __repr__(self): @@ -1689,7 +1902,7 @@ class TestSpecial(unittest.TestCase): self._intname = name self._args = _args return self - @bltns.property + @property def __name__(self): return self._intname def __repr__(self): @@ -1878,7 +2091,6 @@ class TestSpecial(unittest.TestCase): class Test(Base): test = 1 self.assertEqual(Test.test.test, 'dynamic') - self.assertEqual(Test.test.value, 1) class Base2(Enum): @enum.property def flash(self): @@ -1886,7 +2098,6 @@ class TestSpecial(unittest.TestCase): class Test(Base2): flash = 1 self.assertEqual(Test.flash.flash, 'flashy dynamic') - self.assertEqual(Test.flash.value, 1) def test_no_duplicates(self): class UniqueEnum(Enum): @@ -1923,7 +2134,7 @@ class TestSpecial(unittest.TestCase): def __init__(self, mass, radius): self.mass = mass # in kilograms self.radius = radius # in meters - @enum.property + @property def surface_gravity(self): # universal gravitational constant (m3 kg-1 s-2) G = 6.67300E-11 @@ -1993,7 +2204,90 @@ class TestSpecial(unittest.TestCase): self.assertEqual(LabelledList.unprocessed, 1) self.assertEqual(LabelledList(1), LabelledList.unprocessed) - def test_default_missing_no_chained_exception(self): + def test_auto_number(self): + class Color(Enum): + red = auto() + blue = auto() + green = auto() + + self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) + self.assertEqual(Color.red.value, 1) + self.assertEqual(Color.blue.value, 2) + self.assertEqual(Color.green.value, 3) + + def test_auto_name(self): + class Color(Enum): + def _generate_next_value_(name, start, count, last): + return name + red = auto() + blue = auto() + green = auto() + + self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) + self.assertEqual(Color.red.value, 'red') + self.assertEqual(Color.blue.value, 'blue') + self.assertEqual(Color.green.value, 'green') + + def test_auto_name_inherit(self): + class AutoNameEnum(Enum): + def _generate_next_value_(name, start, count, last): + return name + class Color(AutoNameEnum): + red = auto() + blue = auto() + green = auto() + + self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) + self.assertEqual(Color.red.value, 'red') + self.assertEqual(Color.blue.value, 'blue') + self.assertEqual(Color.green.value, 'green') + + def test_auto_garbage(self): + class Color(Enum): + red = 'red' + blue = auto() + self.assertEqual(Color.blue.value, 1) + + def test_auto_garbage_corrected(self): + class Color(Enum): + red = 'red' + blue = 2 + green = auto() + + self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) + self.assertEqual(Color.red.value, 'red') + self.assertEqual(Color.blue.value, 2) + self.assertEqual(Color.green.value, 3) + + def test_auto_order(self): + with self.assertRaises(TypeError): + class Color(Enum): + red = auto() + green = auto() + blue = auto() + def _generate_next_value_(name, start, count, last): + return name + + def test_auto_order_wierd(self): + weird_auto = auto() + weird_auto.value = 'pathological case' + class Color(Enum): + red = weird_auto + def _generate_next_value_(name, start, count, last): + return name + blue = auto() + self.assertEqual(list(Color), [Color.red, Color.blue]) + self.assertEqual(Color.red.value, 'pathological case') + self.assertEqual(Color.blue.value, 'blue') + + def test_duplicate_auto(self): + class Dupes(Enum): + first = primero = auto() + second = auto() + third = auto() + self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes)) + + def test_default_missing(self): class Color(Enum): RED = 1 GREEN = 2 @@ -2005,7 +2299,7 @@ class TestSpecial(unittest.TestCase): else: raise Exception('Exception not raised.') - def test_missing_override(self): + def test_missing(self): class Color(Enum): red = 1 green = 2 @@ -2069,9 +2363,9 @@ class TestSpecial(unittest.TestCase): class_1_ref = weakref.ref(Class1()) class_2_ref = weakref.ref(Class2()) # - # The exception raised by Enum used to create a reference loop and thus - # Class2 instances would stick around until the next garbage collection - # cycle, unlike Class1. Verify Class2 no longer does this. + # The exception raised by Enum creates a reference loop and thus + # Class2 instances will stick around until the next garbage collection + # cycle, unlike Class1. gc.collect() # For PyPy or other GCs. self.assertIs(class_1_ref(), None) self.assertIs(class_2_ref(), None) @@ -2102,12 +2396,11 @@ class TestSpecial(unittest.TestCase): self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 3) self.assertEqual(Color.MAX, 3) - self.assertEqual(str(Color.BLUE), 'Color.BLUE') + self.assertEqual(str(Color.BLUE), 'BLUE') class Color(MaxMixin, StrMixin, Enum): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ # needed as of 3.11 self.assertEqual(Color.RED.value, 1) self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 3) @@ -2117,7 +2410,6 @@ class TestSpecial(unittest.TestCase): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ # needed as of 3.11 self.assertEqual(Color.RED.value, 1) self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 3) @@ -2127,7 +2419,6 @@ class TestSpecial(unittest.TestCase): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ # needed as of 3.11 self.assertEqual(CoolColor.RED.value, 1) self.assertEqual(CoolColor.GREEN.value, 2) self.assertEqual(CoolColor.BLUE.value, 3) @@ -2137,7 +2428,6 @@ class TestSpecial(unittest.TestCase): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ # needed as of 3.11 self.assertEqual(CoolerColor.RED.value, 1) self.assertEqual(CoolerColor.GREEN.value, 2) self.assertEqual(CoolerColor.BLUE.value, 3) @@ -2148,7 +2438,6 @@ class TestSpecial(unittest.TestCase): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ # needed as of 3.11 self.assertEqual(CoolestColor.RED.value, 1) self.assertEqual(CoolestColor.GREEN.value, 2) self.assertEqual(CoolestColor.BLUE.value, 3) @@ -2159,7 +2448,6 @@ class TestSpecial(unittest.TestCase): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ # needed as of 3.11 self.assertEqual(ConfusedColor.RED.value, 1) self.assertEqual(ConfusedColor.GREEN.value, 2) self.assertEqual(ConfusedColor.BLUE.value, 3) @@ -2170,7 +2458,6 @@ class TestSpecial(unittest.TestCase): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ # needed as of 3.11 self.assertEqual(ReformedColor.RED.value, 1) self.assertEqual(ReformedColor.GREEN.value, 2) self.assertEqual(ReformedColor.BLUE.value, 3) @@ -2203,12 +2490,11 @@ class TestSpecial(unittest.TestCase): return hex(self) class MyIntEnum(HexMixin, MyInt, enum.Enum): - __repr__ = HexMixin.__repr__ + pass class Foo(MyIntEnum): TEST = 1 self.assertTrue(isinstance(Foo.TEST, MyInt)) - self.assertEqual(Foo._member_type_, MyInt) self.assertEqual(repr(Foo.TEST), "0x1") class Fee(MyIntEnum): @@ -2220,7 +2506,7 @@ class TestSpecial(unittest.TestCase): return member self.assertEqual(Fee.TEST, 2) - def test_multiple_mixin_with_common_data_type(self): + def test_miltuple_mixin_with_common_data_type(self): class CaseInsensitiveStrEnum(str, Enum): @classmethod def _missing_(cls, value): @@ -2240,7 +2526,7 @@ class TestSpecial(unittest.TestCase): unknown._value_ = value cls._member_map_[value] = unknown return unknown - @enum.property + @property def valid(self): return self._valid # @@ -2284,7 +2570,7 @@ class TestSpecial(unittest.TestCase): self.assertEqual('{}'.format(GoodStrEnum.one), '1') self.assertEqual(GoodStrEnum.one, str(GoodStrEnum.one)) self.assertEqual(GoodStrEnum.one, '{}'.format(GoodStrEnum.one)) - self.assertEqual(repr(GoodStrEnum.one), "") + self.assertEqual(repr(GoodStrEnum.one), 'GoodStrEnum.one') # class DumbMixin: def __str__(self): @@ -2293,7 +2579,6 @@ class TestSpecial(unittest.TestCase): five = '5' six = '6' seven = '7' - __str__ = DumbMixin.__str__ # needed as of 3.11 self.assertEqual(DumbStrEnum.seven, '7') self.assertEqual(str(DumbStrEnum.seven), "don't do this") # @@ -2335,7 +2620,11 @@ class TestSpecial(unittest.TestCase): one = '1' two = b'2', 'ascii', 9 - def test_custom_strenum(self): + @unittest.skipIf( + python_version >= (3, 12), + 'mixin-format now uses member instead of member.value', + ) + def test_custom_strenum_with_warning(self): class CustomStrEnum(str, Enum): pass class OkayEnum(CustomStrEnum): @@ -2344,9 +2633,11 @@ class TestSpecial(unittest.TestCase): three = b'3', 'ascii' four = b'4', 'latin1', 'strict' self.assertEqual(OkayEnum.one, '1') - self.assertEqual(str(OkayEnum.one), 'OkayEnum.one') - self.assertEqual('{}'.format(OkayEnum.one), 'OkayEnum.one') - self.assertEqual(repr(OkayEnum.one), "") + self.assertEqual(str(OkayEnum.one), 'one') + with self.assertWarns(DeprecationWarning): + self.assertEqual('{}'.format(OkayEnum.one), '1') + self.assertEqual(OkayEnum.one, '{}'.format(OkayEnum.one)) + self.assertEqual(repr(OkayEnum.one), 'OkayEnum.one') # class DumbMixin: def __str__(self): @@ -2355,7 +2646,6 @@ class TestSpecial(unittest.TestCase): five = '5' six = '6' seven = '7' - __str__ = DumbMixin.__str__ # needed as of 3.11 self.assertEqual(DumbStrEnum.seven, '7') self.assertEqual(str(DumbStrEnum.seven), "don't do this") # @@ -2365,7 +2655,7 @@ class TestSpecial(unittest.TestCase): class HelloEnum(EnumMixin, CustomStrEnum): eight = '8' self.assertEqual(HelloEnum.eight, '8') - self.assertEqual(str(HelloEnum.eight), 'HelloEnum.eight') + self.assertEqual(str(HelloEnum.eight), 'eight') # class GoodbyeMixin: def goodbye(self): @@ -2373,7 +2663,69 @@ class TestSpecial(unittest.TestCase): class GoodbyeEnum(GoodbyeMixin, EnumMixin, CustomStrEnum): nine = '9' self.assertEqual(GoodbyeEnum.nine, '9') - self.assertEqual(str(GoodbyeEnum.nine), 'GoodbyeEnum.nine') + self.assertEqual(str(GoodbyeEnum.nine), 'nine') + # + class FirstFailedStrEnum(CustomStrEnum): + one = 1 # this will become '1' + two = '2' + class SecondFailedStrEnum(CustomStrEnum): + one = '1' + two = 2, # this will become '2' + three = '3' + class ThirdFailedStrEnum(CustomStrEnum): + one = '1' + two = 2 # this will become '2' + with self.assertRaisesRegex(TypeError, '.encoding. must be str, not '): + class ThirdFailedStrEnum(CustomStrEnum): + one = '1' + two = b'2', sys.getdefaultencoding + with self.assertRaisesRegex(TypeError, '.errors. must be str, not '): + class ThirdFailedStrEnum(CustomStrEnum): + one = '1' + two = b'2', 'ascii', 9 + + @unittest.skipIf( + python_version < (3, 12), + 'mixin-format currently uses member.value', + ) + def test_custom_strenum(self): + class CustomStrEnum(str, Enum): + pass + class OkayEnum(CustomStrEnum): + one = '1' + two = '2' + three = b'3', 'ascii' + four = b'4', 'latin1', 'strict' + self.assertEqual(OkayEnum.one, '1') + self.assertEqual(str(OkayEnum.one), 'one') + self.assertEqual('{}'.format(OkayEnum.one), 'one') + self.assertEqual(repr(OkayEnum.one), 'OkayEnum.one') + # + class DumbMixin: + def __str__(self): + return "don't do this" + class DumbStrEnum(DumbMixin, CustomStrEnum): + five = '5' + six = '6' + seven = '7' + self.assertEqual(DumbStrEnum.seven, '7') + self.assertEqual(str(DumbStrEnum.seven), "don't do this") + # + class EnumMixin(Enum): + def hello(self): + print('hello from %s' % (self, )) + class HelloEnum(EnumMixin, CustomStrEnum): + eight = '8' + self.assertEqual(HelloEnum.eight, '8') + self.assertEqual(str(HelloEnum.eight), 'eight') + # + class GoodbyeMixin: + def goodbye(self): + print('%s wishes you a fond farewell') + class GoodbyeEnum(GoodbyeMixin, EnumMixin, CustomStrEnum): + nine = '9' + self.assertEqual(GoodbyeEnum.nine, '9') + self.assertEqual(str(GoodbyeEnum.nine), 'nine') # class FirstFailedStrEnum(CustomStrEnum): one = 1 # this will become '1' @@ -2419,6 +2771,21 @@ class TestSpecial(unittest.TestCase): code = 'An$(5,1)', 2 description = 'Bn$', 3 + @unittest.skipUnless( + python_version == (3, 9), + 'private variables are now normal attributes', + ) + def test_warning_for_private_variables(self): + with self.assertWarns(DeprecationWarning): + class Private(Enum): + __corporal = 'Radar' + self.assertEqual(Private._Private__corporal.value, 'Radar') + try: + with self.assertWarns(DeprecationWarning): + class Private(Enum): + __major_ = 'Hoolihan' + except ValueError: + pass def test_private_variable_is_normal_attribute(self): class Private(Enum): @@ -2427,12 +2794,34 @@ class TestSpecial(unittest.TestCase): self.assertEqual(Private._Private__corporal, 'Radar') self.assertEqual(Private._Private__major_, 'Hoolihan') - def test_exception_for_member_from_member_access(self): - with self.assertRaisesRegex(AttributeError, " member has no attribute .NO."): + @unittest.skipUnless( + python_version < (3, 12), + 'member-member access now raises an exception', + ) + def test_warning_for_member_from_member_access(self): + with self.assertWarns(DeprecationWarning): class Di(Enum): YES = 1 NO = 0 nope = Di.YES.NO + self.assertIs(Di.NO, nope) + + @unittest.skipUnless( + python_version >= (3, 12), + 'member-member access currently issues a warning', + ) + def test_exception_for_member_from_member_access(self): + with self.assertRaisesRegex(AttributeError, "Di: no instance attribute .NO."): + class Di(Enum): + YES = 1 + NO = 0 + nope = Di.YES.NO + + def test_strenum_auto(self): + class Strings(StrEnum): + ONE = auto() + TWO = auto() + self.assertEqual([Strings.ONE, Strings.TWO], ['one', 'two']) def test_dynamic_members_with_static_methods(self): @@ -2450,7 +2839,7 @@ class TestSpecial(unittest.TestCase): self.assertEqual(Foo.FOO_CAT.value, 'aloof') self.assertEqual(Foo.FOO_HORSE.upper(), 'BIG') # - with self.assertRaisesRegex(TypeError, "'FOO_CAT' already defined as 'aloof'"): + with self.assertRaisesRegex(TypeError, "'FOO_CAT' already defined as: 'aloof'"): class FooBar(Enum): vars().update({ k: v @@ -2462,42 +2851,8 @@ class TestSpecial(unittest.TestCase): def upper(self): return self.value.upper() - def test_repr_with_dataclass(self): - "ensure dataclass-mixin has correct repr()" - from dataclasses import dataclass - @dataclass - class Foo: - __qualname__ = 'Foo' - a: int = 0 - class Entries(Foo, Enum): - ENTRY1 = Foo(1) - self.assertEqual(repr(Entries.ENTRY1), '') - - def test_repr_with_non_data_type_mixin(self): - # non-data_type is a mixin that doesn't define __new__ - class Foo: - def __init__(self, a): - self.a = a - def __repr__(self): - return f'Foo(a={self.a!r})' - class Entries(Foo, Enum): - ENTRY1 = Foo(1) - - self.assertEqual(repr(Entries.ENTRY1), '') - - def test_value_backup_assign(self): - # check that enum will add missing values when custom __new__ does not - class Some(Enum): - def __new__(cls, val): - return object.__new__(cls) - x = 1 - y = 2 - self.assertEqual(Some.x.value, 1) - self.assertEqual(Some.y.value, 2) - class TestOrder(unittest.TestCase): - "test usage of the `_order_` attribute" def test_same_members(self): class Color(Enum): @@ -2559,7 +2914,7 @@ class TestOrder(unittest.TestCase): verde = green -class OldTestFlag(unittest.TestCase): +class TestFlag(unittest.TestCase): """Tests of the Flags.""" class Perm(Flag): @@ -2582,6 +2937,65 @@ class OldTestFlag(unittest.TestCase): WHITE = RED|GREEN|BLUE BLANCO = RED|GREEN|BLUE + def test_str(self): + Perm = self.Perm + self.assertEqual(str(Perm.R), 'R') + self.assertEqual(str(Perm.W), 'W') + self.assertEqual(str(Perm.X), 'X') + self.assertEqual(str(Perm.R | Perm.W), 'R|W') + self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'R|W|X') + self.assertEqual(str(Perm(0)), 'Perm(0)') + self.assertEqual(str(~Perm.R), 'W|X') + self.assertEqual(str(~Perm.W), 'R|X') + self.assertEqual(str(~Perm.X), 'R|W') + self.assertEqual(str(~(Perm.R | Perm.W)), 'X') + self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm(0)') + self.assertEqual(str(Perm(~0)), 'R|W|X') + + Open = self.Open + self.assertEqual(str(Open.RO), 'RO') + self.assertEqual(str(Open.WO), 'WO') + self.assertEqual(str(Open.AC), 'AC') + self.assertEqual(str(Open.RO | Open.CE), 'CE') + self.assertEqual(str(Open.WO | Open.CE), 'WO|CE') + self.assertEqual(str(~Open.RO), 'WO|RW|CE') + self.assertEqual(str(~Open.WO), 'RW|CE') + self.assertEqual(str(~Open.AC), 'CE') + self.assertEqual(str(~(Open.RO | Open.CE)), 'AC') + self.assertEqual(str(~(Open.WO | Open.CE)), 'RW') + + def test_repr(self): + Perm = self.Perm + self.assertEqual(repr(Perm.R), 'Perm.R') + self.assertEqual(repr(Perm.W), 'Perm.W') + self.assertEqual(repr(Perm.X), 'Perm.X') + self.assertEqual(repr(Perm.R | Perm.W), 'Perm.R|Perm.W') + self.assertEqual(repr(Perm.R | Perm.W | Perm.X), 'Perm.R|Perm.W|Perm.X') + self.assertEqual(repr(Perm(0)), '0x0') + self.assertEqual(repr(~Perm.R), 'Perm.W|Perm.X') + self.assertEqual(repr(~Perm.W), 'Perm.R|Perm.X') + self.assertEqual(repr(~Perm.X), 'Perm.R|Perm.W') + self.assertEqual(repr(~(Perm.R | Perm.W)), 'Perm.X') + self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '0x0') + self.assertEqual(repr(Perm(~0)), 'Perm.R|Perm.W|Perm.X') + + Open = self.Open + self.assertEqual(repr(Open.RO), 'Open.RO') + self.assertEqual(repr(Open.WO), 'Open.WO') + self.assertEqual(repr(Open.AC), 'Open.AC') + self.assertEqual(repr(Open.RO | Open.CE), 'Open.CE') + self.assertEqual(repr(Open.WO | Open.CE), 'Open.WO|Open.CE') + self.assertEqual(repr(~Open.RO), 'Open.WO|Open.RW|Open.CE') + self.assertEqual(repr(~Open.WO), 'Open.RW|Open.CE') + self.assertEqual(repr(~Open.AC), 'Open.CE') + self.assertEqual(repr(~(Open.RO | Open.CE)), 'Open.AC') + self.assertEqual(repr(~(Open.WO | Open.CE)), 'Open.RW') + + def test_format(self): + Perm = self.Perm + self.assertEqual(format(Perm.R, ''), 'R') + self.assertEqual(format(Perm.R | Perm.X, ''), 'R|X') + def test_or(self): Perm = self.Perm for i in Perm: @@ -2674,7 +3088,7 @@ class OldTestFlag(unittest.TestCase): c = 4 d = 6 # - self.assertRaisesRegex(ValueError, 'invalid value 7', Iron, 7) + self.assertRaisesRegex(ValueError, 'invalid value: 7', Iron, 7) # self.assertIs(Water(7), Water.ONE|Water.TWO) self.assertIs(Water(~9), Water.TWO) @@ -2883,7 +3297,7 @@ class OldTestFlag(unittest.TestCase): self.assertEqual(Color.green.value, 4) def test_auto_number_garbage(self): - with self.assertRaisesRegex(TypeError, 'invalid flag value .not an int.'): + with self.assertRaisesRegex(TypeError, 'Invalid Flag value: .not an int.'): class Color(Flag): red = 'not an int' blue = auto() @@ -2918,12 +3332,11 @@ class OldTestFlag(unittest.TestCase): self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 4) self.assertEqual(Color.ALL.value, 7) - self.assertEqual(str(Color.BLUE), 'Color.BLUE') + self.assertEqual(str(Color.BLUE), 'BLUE') class Color(AllMixin, StrMixin, Flag): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ self.assertEqual(Color.RED.value, 1) self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 4) @@ -2933,7 +3346,6 @@ class OldTestFlag(unittest.TestCase): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ self.assertEqual(Color.RED.value, 1) self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 4) @@ -3014,8 +3426,21 @@ class OldTestFlag(unittest.TestCase): self.assertFalse(NeverEnum.__dict__.get('_test1', False)) self.assertFalse(NeverEnum.__dict__.get('_test2', False)) + def test_default_missing(self): + with self.assertRaisesRegex( + ValueError, + "'RED' is not a valid TestFlag.Color", + ) as ctx: + self.Color('RED') + self.assertIs(ctx.exception.__context__, None) -class OldTestIntFlag(unittest.TestCase): + P = Flag('P', 'X Y') + with self.assertRaisesRegex(ValueError, "'X' is not a valid P") as ctx: + P('X') + self.assertIs(ctx.exception.__context__, None) + + +class TestIntFlag(unittest.TestCase): """Tests of the IntFlags.""" class Perm(IntFlag): @@ -3060,6 +3485,73 @@ class OldTestIntFlag(unittest.TestCase): self.assertTrue(isinstance(Open.WO | Open.RW, Open)) self.assertEqual(Open.WO | Open.RW, 3) + + def test_str(self): + Perm = self.Perm + self.assertEqual(str(Perm.R), 'R') + self.assertEqual(str(Perm.W), 'W') + self.assertEqual(str(Perm.X), 'X') + self.assertEqual(str(Perm.R | Perm.W), 'R|W') + self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'R|W|X') + self.assertEqual(str(Perm.R | 8), '12') + self.assertEqual(str(Perm(0)), 'Perm(0)') + self.assertEqual(str(Perm(8)), '8') + self.assertEqual(str(~Perm.R), 'W|X') + self.assertEqual(str(~Perm.W), 'R|X') + self.assertEqual(str(~Perm.X), 'R|W') + self.assertEqual(str(~(Perm.R | Perm.W)), 'X') + self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm(0)') + self.assertEqual(str(~(Perm.R | 8)), '-13') + self.assertEqual(str(Perm(~0)), 'R|W|X') + self.assertEqual(str(Perm(~8)), '-9') + + Open = self.Open + self.assertEqual(str(Open.RO), 'RO') + self.assertEqual(str(Open.WO), 'WO') + self.assertEqual(str(Open.AC), 'AC') + self.assertEqual(str(Open.RO | Open.CE), 'CE') + self.assertEqual(str(Open.WO | Open.CE), 'WO|CE') + self.assertEqual(str(Open(4)), '4') + self.assertEqual(str(~Open.RO), 'WO|RW|CE') + self.assertEqual(str(~Open.WO), 'RW|CE') + self.assertEqual(str(~Open.AC), 'CE') + self.assertEqual(str(~(Open.RO | Open.CE)), 'AC') + self.assertEqual(str(~(Open.WO | Open.CE)), 'RW') + self.assertEqual(str(Open(~4)), '-5') + + def test_repr(self): + Perm = self.Perm + self.assertEqual(repr(Perm.R), 'Perm.R') + self.assertEqual(repr(Perm.W), 'Perm.W') + self.assertEqual(repr(Perm.X), 'Perm.X') + self.assertEqual(repr(Perm.R | Perm.W), 'Perm.R|Perm.W') + self.assertEqual(repr(Perm.R | Perm.W | Perm.X), 'Perm.R|Perm.W|Perm.X') + self.assertEqual(repr(Perm.R | 8), '12') + self.assertEqual(repr(Perm(0)), '0x0') + self.assertEqual(repr(Perm(8)), '8') + self.assertEqual(repr(~Perm.R), 'Perm.W|Perm.X') + self.assertEqual(repr(~Perm.W), 'Perm.R|Perm.X') + self.assertEqual(repr(~Perm.X), 'Perm.R|Perm.W') + self.assertEqual(repr(~(Perm.R | Perm.W)), 'Perm.X') + self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '0x0') + self.assertEqual(repr(~(Perm.R | 8)), '-13') + self.assertEqual(repr(Perm(~0)), 'Perm.R|Perm.W|Perm.X') + self.assertEqual(repr(Perm(~8)), '-9') + + Open = self.Open + self.assertEqual(repr(Open.RO), 'Open.RO') + self.assertEqual(repr(Open.WO), 'Open.WO') + self.assertEqual(repr(Open.AC), 'Open.AC') + self.assertEqual(repr(Open.RO | Open.CE), 'Open.CE') + self.assertEqual(repr(Open.WO | Open.CE), 'Open.WO|Open.CE') + self.assertEqual(repr(Open(4)), '4') + self.assertEqual(repr(~Open.RO), 'Open.WO|Open.RW|Open.CE') + self.assertEqual(repr(~Open.WO), 'Open.RW|Open.CE') + self.assertEqual(repr(~Open.AC), 'Open.CE') + self.assertEqual(repr(~(Open.RO | Open.CE)), 'Open.AC') + self.assertEqual(repr(~(Open.WO | Open.CE)), 'Open.RW') + self.assertEqual(repr(Open(~4)), '-5') + def test_global_repr_keep(self): self.assertEqual( repr(HeadlightsK(0)), @@ -3067,11 +3559,11 @@ class OldTestIntFlag(unittest.TestCase): ) self.assertEqual( repr(HeadlightsK(2**0 + 2**2 + 2**3)), - '%(m)s.LOW_BEAM_K|%(m)s.FOG_K|8' % {'m': SHORT_MODULE}, + '%(m)s.LOW_BEAM_K|%(m)s.FOG_K|0x8' % {'m': SHORT_MODULE}, ) self.assertEqual( repr(HeadlightsK(2**3)), - '%(m)s.HeadlightsK(8)' % {'m': SHORT_MODULE}, + '%(m)s.HeadlightsK(0x8)' % {'m': SHORT_MODULE}, ) def test_global_repr_conform1(self): @@ -3213,7 +3705,7 @@ class OldTestIntFlag(unittest.TestCase): c = 4 d = 6 # - self.assertRaisesRegex(ValueError, 'invalid value 5', Iron, 5) + self.assertRaisesRegex(ValueError, 'invalid value: 5', Iron, 5) # self.assertIs(Water(7), Water.ONE|Water.TWO) self.assertIs(Water(~9), Water.TWO) @@ -3450,12 +3942,11 @@ class OldTestIntFlag(unittest.TestCase): self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 4) self.assertEqual(Color.ALL.value, 7) - self.assertEqual(str(Color.BLUE), '4') + self.assertEqual(str(Color.BLUE), 'BLUE') class Color(AllMixin, StrMixin, IntFlag): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ self.assertEqual(Color.RED.value, 1) self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 4) @@ -3465,7 +3956,6 @@ class OldTestIntFlag(unittest.TestCase): RED = auto() GREEN = auto() BLUE = auto() - __str__ = StrMixin.__str__ self.assertEqual(Color.RED.value, 1) self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 4) @@ -3510,6 +4000,19 @@ class OldTestIntFlag(unittest.TestCase): 'at least one thread failed while creating composite members') self.assertEqual(256, len(seen), 'too many composite members created') + def test_default_missing(self): + with self.assertRaisesRegex( + ValueError, + "'RED' is not a valid TestIntFlag.Color", + ) as ctx: + self.Color('RED') + self.assertIs(ctx.exception.__context__, None) + + P = IntFlag('P', 'X Y') + with self.assertRaisesRegex(ValueError, "'X' is not a valid P") as ctx: + P('X') + self.assertIs(ctx.exception.__context__, None) + class TestEmptyAndNonLatinStrings(unittest.TestCase): @@ -3726,89 +4229,6 @@ class TestHelpers(unittest.TestCase): for name in self.sunder_names + self.dunder_names + self.random_names: self.assertFalse(enum._is_private('MyEnum', name), '%r is a private name?') - def test_auto_number(self): - class Color(Enum): - red = auto() - blue = auto() - green = auto() - - self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) - self.assertEqual(Color.red.value, 1) - self.assertEqual(Color.blue.value, 2) - self.assertEqual(Color.green.value, 3) - - def test_auto_name(self): - class Color(Enum): - def _generate_next_value_(name, start, count, last): - return name - red = auto() - blue = auto() - green = auto() - - self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) - self.assertEqual(Color.red.value, 'red') - self.assertEqual(Color.blue.value, 'blue') - self.assertEqual(Color.green.value, 'green') - - def test_auto_name_inherit(self): - class AutoNameEnum(Enum): - def _generate_next_value_(name, start, count, last): - return name - class Color(AutoNameEnum): - red = auto() - blue = auto() - green = auto() - - self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) - self.assertEqual(Color.red.value, 'red') - self.assertEqual(Color.blue.value, 'blue') - self.assertEqual(Color.green.value, 'green') - - def test_auto_garbage(self): - class Color(Enum): - red = 'red' - blue = auto() - self.assertEqual(Color.blue.value, 1) - - def test_auto_garbage_corrected(self): - class Color(Enum): - red = 'red' - blue = 2 - green = auto() - - self.assertEqual(list(Color), [Color.red, Color.blue, Color.green]) - self.assertEqual(Color.red.value, 'red') - self.assertEqual(Color.blue.value, 2) - self.assertEqual(Color.green.value, 3) - - def test_auto_order(self): - with self.assertRaises(TypeError): - class Color(Enum): - red = auto() - green = auto() - blue = auto() - def _generate_next_value_(name, start, count, last): - return name - - def test_auto_order_wierd(self): - weird_auto = auto() - weird_auto.value = 'pathological case' - class Color(Enum): - red = weird_auto - def _generate_next_value_(name, start, count, last): - return name - blue = auto() - self.assertEqual(list(Color), [Color.red, Color.blue]) - self.assertEqual(Color.red.value, 'pathological case') - self.assertEqual(Color.blue.value, 'blue') - - def test_duplicate_auto(self): - class Dupes(Enum): - first = primero = auto() - second = auto() - third = auto() - self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes)) - class TestEnumTypeSubclassing(unittest.TestCase): pass @@ -3818,35 +4238,7 @@ Help on class Color in module %s: class Color(enum.Enum) | Color(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None) |\x20\x20 - | A collection of name/value pairs. - |\x20\x20 - | Access them by: - |\x20\x20 - | - attribute access:: - |\x20\x20 - | >>> Color.CYAN - | - |\x20\x20 - | - value lookup: - |\x20\x20 - | >>> Color(1) - | - |\x20\x20 - | - name lookup: - |\x20\x20 - | >>> Color['CYAN'] - | - |\x20\x20 - | Enumerations can be iterated over, and know how many members they have: - |\x20\x20 - | >>> len(Color) - | 3 - |\x20\x20 - | >>> list(Color) - | [, , ] - |\x20\x20 - | Methods can be added to enumerations, and members can have their own - | attributes -- see the documentation for details. + | An enumeration. |\x20\x20 | Method resolution order: | Color @@ -3855,11 +4247,11 @@ class Color(enum.Enum) |\x20\x20 | Data and other attributes defined here: |\x20\x20 - | CYAN = + | blue = Color.blue |\x20\x20 - | MAGENTA = + | green = Color.green |\x20\x20 - | YELLOW = + | red = Color.red |\x20\x20 | ---------------------------------------------------------------------- | Data descriptors inherited from enum.Enum: @@ -3871,25 +4263,6 @@ class Color(enum.Enum) | The value of the Enum member. |\x20\x20 | ---------------------------------------------------------------------- - | Methods inherited from enum.EnumType: - |\x20\x20 - | __contains__(member) from enum.EnumType - | Return True if member is a member of this enum - | raises TypeError if member is not an enum member - |\x20\x20\x20\x20\x20\x20 - | note: in 3.12 TypeError will no longer be raised, and True will also be - | returned if member is the value of a member in this enum - |\x20\x20 - | __getitem__(name) from enum.EnumType - | Return the member matching `name`. - |\x20\x20 - | __iter__() from enum.EnumType - | Return members in definition order. - |\x20\x20 - | __len__() from enum.EnumType - | Return the number of members (no aliases) - |\x20\x20 - | ---------------------------------------------------------------------- | Readonly properties inherited from enum.EnumType: |\x20\x20 | __members__ @@ -3911,11 +4284,11 @@ class Color(enum.Enum) |\x20\x20 | Data and other attributes defined here: |\x20\x20 - | YELLOW = + | blue = Color.blue |\x20\x20 - | MAGENTA = + | green = Color.green |\x20\x20 - | CYAN = + | red = Color.red |\x20\x20 | ---------------------------------------------------------------------- | Data descriptors inherited from enum.Enum: @@ -3934,9 +4307,9 @@ class TestStdLib(unittest.TestCase): maxDiff = None class Color(Enum): - CYAN = 1 - MAGENTA = 2 - YELLOW = 3 + red = 1 + green = 2 + blue = 3 def test_pydoc(self): # indirectly test __objclass__ @@ -3948,34 +4321,24 @@ class TestStdLib(unittest.TestCase): helper = pydoc.Helper(output=output) helper(self.Color) result = output.getvalue().strip() - self.assertEqual(result, expected_text, result) + self.assertEqual(result, expected_text) def test_inspect_getmembers(self): values = dict(( ('__class__', EnumType), - ('__doc__', '...'), + ('__doc__', 'An enumeration.'), ('__members__', self.Color.__members__), ('__module__', __name__), - ('YELLOW', self.Color.YELLOW), - ('MAGENTA', self.Color.MAGENTA), - ('CYAN', self.Color.CYAN), + ('blue', self.Color.blue), + ('green', self.Color.green), ('name', Enum.__dict__['name']), + ('red', self.Color.red), ('value', Enum.__dict__['value']), - ('__len__', self.Color.__len__), - ('__contains__', self.Color.__contains__), - ('__name__', 'Color'), - ('__getitem__', self.Color.__getitem__), - ('__qualname__', 'TestStdLib.Color'), - ('__init_subclass__', getattr(self.Color, '__init_subclass__')), - ('__iter__', self.Color.__iter__), )) result = dict(inspect.getmembers(self.Color)) self.assertEqual(set(values.keys()), set(result.keys())) failed = False for k in values.keys(): - if k == '__doc__': - # __doc__ is huge, not comparing - continue if result[k] != values[k]: print() print('\n%s\n key: %s\n result: %s\nexpected: %s\n%s\n' % @@ -3990,42 +4353,23 @@ class TestStdLib(unittest.TestCase): values = [ Attribute(name='__class__', kind='data', defining_class=object, object=EnumType), - Attribute(name='__contains__', kind='method', - defining_class=EnumType, object=self.Color.__contains__), Attribute(name='__doc__', kind='data', - defining_class=self.Color, object='...'), - Attribute(name='__getitem__', kind='method', - defining_class=EnumType, object=self.Color.__getitem__), - Attribute(name='__iter__', kind='method', - defining_class=EnumType, object=self.Color.__iter__), - Attribute(name='__init_subclass__', kind='class method', - defining_class=object, object=getattr(self.Color, '__init_subclass__')), - Attribute(name='__len__', kind='method', - defining_class=EnumType, object=self.Color.__len__), + defining_class=self.Color, object='An enumeration.'), Attribute(name='__members__', kind='property', defining_class=EnumType, object=EnumType.__members__), Attribute(name='__module__', kind='data', defining_class=self.Color, object=__name__), - Attribute(name='__name__', kind='data', - defining_class=self.Color, object='Color'), - Attribute(name='__qualname__', kind='data', - defining_class=self.Color, object='TestStdLib.Color'), - Attribute(name='YELLOW', kind='data', - defining_class=self.Color, object=self.Color.YELLOW), - Attribute(name='MAGENTA', kind='data', - defining_class=self.Color, object=self.Color.MAGENTA), - Attribute(name='CYAN', kind='data', - defining_class=self.Color, object=self.Color.CYAN), + Attribute(name='blue', kind='data', + defining_class=self.Color, object=self.Color.blue), + Attribute(name='green', kind='data', + defining_class=self.Color, object=self.Color.green), + Attribute(name='red', kind='data', + defining_class=self.Color, object=self.Color.red), Attribute(name='name', kind='data', defining_class=Enum, object=Enum.__dict__['name']), Attribute(name='value', kind='data', defining_class=Enum, object=Enum.__dict__['value']), ] - for v in values: - try: - v.name - except AttributeError: - print(v) values.sort(key=lambda item: item.name) result = list(inspect.classify_class_attrs(self.Color)) result.sort(key=lambda item: item.name) @@ -4035,15 +4379,7 @@ class TestStdLib(unittest.TestCase): ) failed = False for v, r in zip(values, result): - if r.name in ('__init_subclass__', '__doc__'): - # not sure how to make the __init_subclass_ Attributes match - # so as long as there is one, call it good - # __doc__ is too big to check exactly, so treat the same as __init_subclass__ - for name in ('name','kind','defining_class'): - if getattr(v, name) != getattr(r, name): - print('\n%s\n%s\n%s\n%s\n' % ('=' * 75, r, v, '=' * 75), sep='') - failed = True - elif r != v: + if r != v: print('\n%s\n%s\n%s\n%s\n' % ('=' * 75, r, v, '=' * 75), sep='') failed = True if failed: @@ -4052,15 +4388,15 @@ class TestStdLib(unittest.TestCase): def test_test_simple_enum(self): @_simple_enum(Enum) class SimpleColor: - CYAN = 1 - MAGENTA = 2 - YELLOW = 3 + RED = 1 + GREEN = 2 + BLUE = 3 class CheckedColor(Enum): - CYAN = 1 - MAGENTA = 2 - YELLOW = 3 + RED = 1 + GREEN = 2 + BLUE = 3 self.assertTrue(_test_simple_enum(CheckedColor, SimpleColor) is None) - SimpleColor.MAGENTA._value_ = 9 + SimpleColor.GREEN._value_ = 9 self.assertRaisesRegex( TypeError, "enum mismatch", _test_simple_enum, CheckedColor, SimpleColor, @@ -4086,165 +4422,9 @@ class TestStdLib(unittest.TestCase): class MiscTestCase(unittest.TestCase): - def test__all__(self): support.check__all__(self, enum, not_exported={'bin', 'show_flag_values'}) - def test_doc_1(self): - class Single(Enum): - ONE = 1 - self.assertEqual( - Single.__doc__, - dedent("""\ - A collection of name/value pairs. - - Access them by: - - - attribute access:: - - >>> Single.ONE - - - - value lookup: - - >>> Single(1) - - - - name lookup: - - >>> Single['ONE'] - - - Enumerations can be iterated over, and know how many members they have: - - >>> len(Single) - 1 - - >>> list(Single) - [] - - Methods can be added to enumerations, and members can have their own - attributes -- see the documentation for details. - """)) - - def test_doc_2(self): - class Double(Enum): - ONE = 1 - TWO = 2 - self.assertEqual( - Double.__doc__, - dedent("""\ - A collection of name/value pairs. - - Access them by: - - - attribute access:: - - >>> Double.ONE - - - - value lookup: - - >>> Double(1) - - - - name lookup: - - >>> Double['ONE'] - - - Enumerations can be iterated over, and know how many members they have: - - >>> len(Double) - 2 - - >>> list(Double) - [, ] - - Methods can be added to enumerations, and members can have their own - attributes -- see the documentation for details. - """)) - - - def test_doc_1(self): - class Triple(Enum): - ONE = 1 - TWO = 2 - THREE = 3 - self.assertEqual( - Triple.__doc__, - dedent("""\ - A collection of name/value pairs. - - Access them by: - - - attribute access:: - - >>> Triple.ONE - - - - value lookup: - - >>> Triple(1) - - - - name lookup: - - >>> Triple['ONE'] - - - Enumerations can be iterated over, and know how many members they have: - - >>> len(Triple) - 3 - - >>> list(Triple) - [, , ] - - Methods can be added to enumerations, and members can have their own - attributes -- see the documentation for details. - """)) - - def test_doc_1(self): - class Quadruple(Enum): - ONE = 1 - TWO = 2 - THREE = 3 - FOUR = 4 - self.assertEqual( - Quadruple.__doc__, - dedent("""\ - A collection of name/value pairs. - - Access them by: - - - attribute access:: - - >>> Quadruple.ONE - - - - value lookup: - - >>> Quadruple(1) - - - - name lookup: - - >>> Quadruple['ONE'] - - - Enumerations can be iterated over, and know how many members they have: - - >>> len(Quadruple) - 4 - - >>> list(Quadruple)[:3] - [, , ] - - Methods can be added to enumerations, and members can have their own - attributes -- see the documentation for details. - """)) - # These are unordered here on purpose to ensure that declaration order # makes no difference. @@ -4262,10 +4442,6 @@ CONVERT_STRING_TEST_NAME_A = 5 # This one should sort first. CONVERT_STRING_TEST_NAME_E = 5 CONVERT_STRING_TEST_NAME_F = 5 -# global names for StrEnum._convert_ test -CONVERT_STR_TEST_2 = 'goodbye' -CONVERT_STR_TEST_1 = 'hello' - # We also need values that cannot be compared: UNCOMPARABLE_A = 5 UNCOMPARABLE_C = (9, 1) # naming order is broken on purpose @@ -4277,40 +4453,32 @@ COMPLEX_B = 3j class _ModuleWrapper: """We use this class as a namespace for swapping modules.""" + def __init__(self, module): self.__dict__.update(module.__dict__) -class TestConvert(unittest.TestCase): - def tearDown(self): - # Reset the module-level test variables to their original integer - # values, otherwise the already created enum values get converted - # instead. - g = globals() - for suffix in ['A', 'B', 'C', 'D', 'E', 'F']: - g['CONVERT_TEST_NAME_%s' % suffix] = 5 - g['CONVERT_STRING_TEST_NAME_%s' % suffix] = 5 - for suffix, value in (('A', 5), ('B', (9, 1)), ('C', 'value')): - g['UNCOMPARABLE_%s' % suffix] = value - for suffix, value in (('A', 2j), ('B', 3j), ('C', 1j)): - g['COMPLEX_%s' % suffix] = value - for suffix, value in (('1', 'hello'), ('2', 'goodbye')): - g['CONVERT_STR_TEST_%s' % suffix] = value - +class TestIntEnumConvert(unittest.TestCase): def test_convert_value_lookup_priority(self): - test_type = enum.IntEnum._convert_( - 'UnittestConvert', - MODULE, - filter=lambda x: x.startswith('CONVERT_TEST_')) + with support.swap_item( + sys.modules, MODULE, _ModuleWrapper(sys.modules[MODULE]), + ): + test_type = enum.IntEnum._convert_( + 'UnittestConvert', + MODULE, + filter=lambda x: x.startswith('CONVERT_TEST_')) # We don't want the reverse lookup value to vary when there are # multiple possible names for a given value. It should always # report the first lexigraphical name in that case. self.assertEqual(test_type(5).name, 'CONVERT_TEST_NAME_A') - def test_convert_int(self): - test_type = enum.IntEnum._convert_( - 'UnittestConvert', - MODULE, - filter=lambda x: x.startswith('CONVERT_TEST_')) + def test_convert(self): + with support.swap_item( + sys.modules, MODULE, _ModuleWrapper(sys.modules[MODULE]), + ): + test_type = enum.IntEnum._convert_( + 'UnittestConvert', + MODULE, + filter=lambda x: x.startswith('CONVERT_TEST_')) # Ensure that test_type has all of the desired names and values. self.assertEqual(test_type.CONVERT_TEST_NAME_F, test_type.CONVERT_TEST_NAME_A) @@ -4319,57 +4487,43 @@ class TestConvert(unittest.TestCase): self.assertEqual(test_type.CONVERT_TEST_NAME_D, 5) self.assertEqual(test_type.CONVERT_TEST_NAME_E, 5) # Ensure that test_type only picked up names matching the filter. - int_dir = dir(int) + [ - 'CONVERT_TEST_NAME_A', 'CONVERT_TEST_NAME_B', 'CONVERT_TEST_NAME_C', - 'CONVERT_TEST_NAME_D', 'CONVERT_TEST_NAME_E', 'CONVERT_TEST_NAME_F', - ] - self.assertEqual( - [name for name in dir(test_type) if name not in int_dir], - [], - msg='Names other than CONVERT_TEST_* found.', - ) + self.assertEqual([name for name in dir(test_type) + if name[0:2] not in ('CO', '__') + and name not in dir(IntEnum)], + [], msg='Names other than CONVERT_TEST_* found.') def test_convert_uncomparable(self): - uncomp = enum.Enum._convert_( - 'Uncomparable', - MODULE, - filter=lambda x: x.startswith('UNCOMPARABLE_')) + # We swap a module to some other object with `__dict__` + # because otherwise refleak is created. + # `_convert_` uses a module side effect that does this. See 30472 + with support.swap_item( + sys.modules, MODULE, _ModuleWrapper(sys.modules[MODULE]), + ): + uncomp = enum.Enum._convert_( + 'Uncomparable', + MODULE, + filter=lambda x: x.startswith('UNCOMPARABLE_')) + # Should be ordered by `name` only: self.assertEqual( list(uncomp), [uncomp.UNCOMPARABLE_A, uncomp.UNCOMPARABLE_B, uncomp.UNCOMPARABLE_C], - ) + ) def test_convert_complex(self): - uncomp = enum.Enum._convert_( - 'Uncomparable', - MODULE, - filter=lambda x: x.startswith('COMPLEX_')) + with support.swap_item( + sys.modules, MODULE, _ModuleWrapper(sys.modules[MODULE]), + ): + uncomp = enum.Enum._convert_( + 'Uncomparable', + MODULE, + filter=lambda x: x.startswith('COMPLEX_')) + # Should be ordered by `name` only: self.assertEqual( list(uncomp), [uncomp.COMPLEX_A, uncomp.COMPLEX_B, uncomp.COMPLEX_C], - ) - - def test_convert_str(self): - test_type = enum.StrEnum._convert_( - 'UnittestConvert', - MODULE, - filter=lambda x: x.startswith('CONVERT_STR_'), - as_global=True) - # Ensure that test_type has all of the desired names and values. - self.assertEqual(test_type.CONVERT_STR_TEST_1, 'hello') - self.assertEqual(test_type.CONVERT_STR_TEST_2, 'goodbye') - # Ensure that test_type only picked up names matching the filter. - str_dir = dir(str) + ['CONVERT_STR_TEST_1', 'CONVERT_STR_TEST_2'] - self.assertEqual( - [name for name in dir(test_type) if name not in str_dir], - [], - msg='Names other than CONVERT_STR_* found.', - ) - self.assertEqual(repr(test_type.CONVERT_STR_TEST_1), '%s.CONVERT_STR_TEST_1' % SHORT_MODULE) - self.assertEqual(str(test_type.CONVERT_STR_TEST_2), 'goodbye') - self.assertEqual(format(test_type.CONVERT_STR_TEST_1), 'hello') + ) def test_convert_raise(self): with self.assertRaises(AttributeError): @@ -4379,58 +4533,50 @@ class TestConvert(unittest.TestCase): filter=lambda x: x.startswith('CONVERT_TEST_')) def test_convert_repr_and_str(self): - test_type = enum.IntEnum._convert_( - 'UnittestConvert', - MODULE, - filter=lambda x: x.startswith('CONVERT_STRING_TEST_'), - as_global=True) + with support.swap_item( + sys.modules, MODULE, _ModuleWrapper(sys.modules[MODULE]), + ): + test_type = enum.IntEnum._convert_( + 'UnittestConvert', + MODULE, + filter=lambda x: x.startswith('CONVERT_STRING_TEST_')) self.assertEqual(repr(test_type.CONVERT_STRING_TEST_NAME_A), '%s.CONVERT_STRING_TEST_NAME_A' % SHORT_MODULE) - self.assertEqual(str(test_type.CONVERT_STRING_TEST_NAME_A), '5') + self.assertEqual(str(test_type.CONVERT_STRING_TEST_NAME_A), 'CONVERT_STRING_TEST_NAME_A') self.assertEqual(format(test_type.CONVERT_STRING_TEST_NAME_A), '5') +# global names for StrEnum._convert_ test +CONVERT_STR_TEST_2 = 'goodbye' +CONVERT_STR_TEST_1 = 'hello' -# helpers +class TestStrEnumConvert(unittest.TestCase): + def test_convert(self): + with support.swap_item( + sys.modules, MODULE, _ModuleWrapper(sys.modules[MODULE]), + ): + test_type = enum.StrEnum._convert_( + 'UnittestConvert', + MODULE, + filter=lambda x: x.startswith('CONVERT_STR_')) + # Ensure that test_type has all of the desired names and values. + self.assertEqual(test_type.CONVERT_STR_TEST_1, 'hello') + self.assertEqual(test_type.CONVERT_STR_TEST_2, 'goodbye') + # Ensure that test_type only picked up names matching the filter. + self.assertEqual([name for name in dir(test_type) + if name[0:2] not in ('CO', '__') + and name not in dir(StrEnum)], + [], msg='Names other than CONVERT_STR_* found.') -def enum_dir(cls): - # TODO: check for custom __init__, __new__, __format__, __repr__, __str__, __init_subclass__ - if cls._member_type_ is object: - interesting = set() - if cls.__init_subclass__ is not object.__init_subclass__: - interesting.add('__init_subclass__') - return sorted(set([ - '__class__', '__contains__', '__doc__', '__getitem__', - '__iter__', '__len__', '__members__', '__module__', - '__name__', '__qualname__', - ] - + cls._member_names_ - ) | interesting - ) - else: - # return whatever mixed-in data type has - return sorted(set( - dir(cls._member_type_) - + cls._member_names_ - )) - -def member_dir(member): - if member.__class__._member_type_ is object: - allowed = set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value']) - else: - allowed = set(dir(member)) - for cls in member.__class__.mro(): - for name, obj in cls.__dict__.items(): - if name[0] == '_': - continue - if isinstance(obj, enum.property): - if obj.fget is not None or name not in member._member_map_: - allowed.add(name) - else: - allowed.discard(name) - else: - allowed.add(name) - return sorted(allowed) - -missing = object() + def test_convert_repr_and_str(self): + with support.swap_item( + sys.modules, MODULE, _ModuleWrapper(sys.modules[MODULE]), + ): + test_type = enum.StrEnum._convert_( + 'UnittestConvert', + MODULE, + filter=lambda x: x.startswith('CONVERT_STR_')) + self.assertEqual(repr(test_type.CONVERT_STR_TEST_1), '%s.CONVERT_STR_TEST_1' % SHORT_MODULE) + self.assertEqual(str(test_type.CONVERT_STR_TEST_2), 'goodbye') + self.assertEqual(format(test_type.CONVERT_STR_TEST_1), 'hello') if __name__ == '__main__': diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index ac4626d0c45..3f0e7270eb2 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -908,7 +908,7 @@ class PendingSignalsTests(unittest.TestCase): %s - blocked = %s + blocked = %r signum = signal.SIGALRM # child: block and wait the signal diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 56cc23dbbbf..394d2942483 100755 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1517,11 +1517,9 @@ class GeneralModuleTests(unittest.TestCase): infos = socket.getaddrinfo(HOST, 80, socket.AF_INET, socket.SOCK_STREAM) for family, type, _, _, _ in infos: self.assertEqual(family, socket.AF_INET) - self.assertEqual(repr(family), '') - self.assertEqual(str(family), '2') + self.assertEqual(str(family), 'AF_INET') self.assertEqual(type, socket.SOCK_STREAM) - self.assertEqual(repr(type), '') - self.assertEqual(str(type), '1') + self.assertEqual(str(type), 'SOCK_STREAM') infos = socket.getaddrinfo(HOST, None, 0, socket.SOCK_STREAM) for _, socktype, _, _, _ in infos: self.assertEqual(socktype, socket.SOCK_STREAM) @@ -1795,10 +1793,8 @@ class GeneralModuleTests(unittest.TestCase): # Make sure that the AF_* and SOCK_* constants have enum-like string # reprs. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - self.assertEqual(repr(s.family), '') - self.assertEqual(repr(s.type), '') - self.assertEqual(str(s.family), '2') - self.assertEqual(str(s.type), '1') + self.assertEqual(str(s.family), 'AF_INET') + self.assertEqual(str(s.type), 'SOCK_STREAM') def test_socket_consistent_sock_type(self): SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 64f4bce7f77..f99a3e8da95 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -373,8 +373,7 @@ class BasicSocketTests(unittest.TestCase): # Make sure that the PROTOCOL_* constants have enum-like string # reprs. proto = ssl.PROTOCOL_TLS_CLIENT - self.assertEqual(repr(proto), '<_SSLMethod.PROTOCOL_TLS_CLIENT: 16>') - self.assertEqual(str(proto), '16') + self.assertEqual(str(proto), 'PROTOCOL_TLS_CLIENT') ctx = ssl.SSLContext(proto) self.assertIs(ctx.protocol, proto) @@ -623,7 +622,7 @@ class BasicSocketTests(unittest.TestCase): with self.assertWarns(DeprecationWarning) as cm: ssl.SSLContext(protocol) self.assertEqual( - f'ssl.{protocol.name} is deprecated', + f'{protocol!r} is deprecated', str(cm.warning) ) @@ -632,9 +631,8 @@ class BasicSocketTests(unittest.TestCase): ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) with self.assertWarns(DeprecationWarning) as cm: ctx.minimum_version = version - version_text = '%s.%s' % (version.__class__.__name__, version.name) self.assertEqual( - f'ssl.{version_text} is deprecated', + f'ssl.{version!r} is deprecated', str(cm.warning) ) diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index 8e4e64808b6..d5e2c5266aa 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -1490,10 +1490,8 @@ class UnicodeTest(string_tests.CommonTest, # issue18780 import enum class Float(float, enum.Enum): - # a mixed-in type will use the name for %s etc. PI = 3.1415926 class Int(enum.IntEnum): - # IntEnum uses the value and not the name for %s etc. IDES = 15 class Str(enum.StrEnum): # StrEnum uses the value and not the name for %s etc. @@ -1510,10 +1508,8 @@ class UnicodeTest(string_tests.CommonTest, # formatting jobs delegated from the string implementation: self.assertEqual('...%(foo)s...' % {'foo':Str.ABC}, '...abc...') - self.assertEqual('...%(foo)r...' % {'foo':Int.IDES}, - '......') self.assertEqual('...%(foo)s...' % {'foo':Int.IDES}, - '...15...') + '...IDES...') self.assertEqual('...%(foo)i...' % {'foo':Int.IDES}, '...15...') self.assertEqual('...%(foo)d...' % {'foo':Int.IDES}, diff --git a/Misc/NEWS.d/next/Library/2022-01-13-11-41-24.bpo-40066.1QuVli.rst b/Misc/NEWS.d/next/Library/2022-01-13-11-41-24.bpo-40066.1QuVli.rst deleted file mode 100644 index 2df48785578..00000000000 --- a/Misc/NEWS.d/next/Library/2022-01-13-11-41-24.bpo-40066.1QuVli.rst +++ /dev/null @@ -1,2 +0,0 @@ -``IntEnum``, ``IntFlag``, and ``StrEnum`` use the mixed-in type for their -``str()`` and ``format()`` output.