From acf7403f9baea3ae1119fc6b4a3298522188bf96 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Sat, 15 Jan 2022 22:41:43 -0800 Subject: bpo-40066: [Enum] update str() and format() output (GH-30582) Undo rejected PEP-663 changes: - restore `repr()` to its 3.10 status - restore `str()` to its 3.10 status New changes: - `IntEnum` and `IntFlag` now leave `__str__` as the original `int.__str__` so that str() and format() return the same result - zero-valued flags without a name have a slightly changed repr(), e.g. `repr(Color(0)) == ''` - update `dir()` for mixed-in types to return all the methods and attributes of the mixed-in type - added `_numeric_repr_` to `Flag` to control display of unnamed values - enums without doc strings have a more comprehensive doc string added - `ReprEnum` added -- inheriting from this makes it so only `__repr__` is replaced, not `__str__` nor `__format__`; `IntEnum`, `IntFlag`, and `StrEnum` all inherit from `ReprEnum` --- Doc/howto/enum.rst | 272 +- Doc/library/enum.rst | 265 +- Doc/library/ssl.rst | 4 +- Lib/enum.py | 603 ++-- Lib/inspect.py | 30 +- Lib/plistlib.py | 3 +- Lib/re.py | 2 + Lib/ssl.py | 1 - Lib/test/test_enum.py | 2932 ++++++++++---------- 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, 2104 insertions(+), 2038 deletions(-) create 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 6c09b99..fa0e228 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -2,15 +2,10 @@ 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. @@ -28,6 +23,14 @@ 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. @@ -41,13 +44,14 @@ 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 and the -member name. The ``str()`` on a member shows only its name:: +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:: >>> print(Weekday.THURSDAY) - THURSDAY + Weekday.THURSDAY The *type* of an enumeration member is the enum it belongs to:: @@ -97,8 +101,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()) - Weekday.TUESDAY + >>> Weekday.from_date(date.today()) # doctest: +SKIP + Of course, if you're reading this on some other day, you'll see that day instead. @@ -124,21 +128,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) - SATURDAY - SUNDAY + Weekday.SATURDAY + Weekday.SUNDAY Okay, let's get some chores set up:: @@ -173,6 +177,7 @@ yourself some work and use :func:`auto()` for the values:: .. _enum-advanced-tutorial: + Programmatic access to enumeration members and their attributes --------------------------------------------------------------- @@ -181,16 +186,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`:: @@ -212,7 +217,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`` @@ -227,11 +232,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:: @@ -299,7 +304,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 @@ -308,10 +313,10 @@ aliases:: >>> for name, member in Shape.__members__.items(): ... name, member ... - ('SQUARE', Shape.SQUARE) - ('DIAMOND', Shape.DIAMOND) - ('CIRCLE', Shape.CIRCLE) - ('ALIAS_FOR_SQUARE', Shape.SQUARE) + ('SQUARE', ) + ('DIAMOND', ) + ('CIRCLE', ) + ('ALIAS_FOR_SQUARE', ) The ``__members__`` attribute can be used for detailed programmatic access to the enumeration members. For example, finding all the aliases:: @@ -360,8 +365,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. @@ -389,7 +394,7 @@ usual. If we have this enumeration:: Then:: >>> Mood.favorite_mood() - Mood.HAPPY + >>> Mood.HAPPY.describe() ('HAPPY', 3) >>> str(Mood.FUNKY) @@ -425,7 +430,7 @@ any members. So this is forbidden:: ... Traceback (most recent call last): ... - TypeError: MoreColor: cannot extend enumeration 'Color' + TypeError: cannot extend But this is allowed:: @@ -476,11 +481,9 @@ 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. @@ -625,16 +628,7 @@ 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. :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',)``. +to each other. .. versionadded:: 3.11 @@ -645,9 +639,8 @@ 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. However, as the name implies, :class:`IntFlag` -members also subclass :class:`int` and can be used wherever an :class:`int` is -used. +:class:`IntFlag` member, if possible. Like :class:`IntEnum`, :class:`IntFlag` +members are also integers and can be used wherever an :class:`int` is used. .. note:: @@ -670,7 +663,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 @@ -685,11 +678,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:: @@ -702,7 +695,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 @@ -710,7 +703,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 @@ -726,7 +719,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 @@ -753,7 +746,7 @@ flags being set, the boolean evaluation is :data:`False`:: ... GREEN = auto() ... >>> Color.RED & Color.GREEN - Color(0) + >>> bool(Color.RED & Color.GREEN) False @@ -767,7 +760,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:: @@ -779,7 +772,7 @@ value:: ... GREEN = auto() ... >>> Color.BLACK - Color.BLACK + >>> bool(Color.BLACK) False @@ -787,7 +780,7 @@ value:: >>> purple = Color.RED | Color.BLUE >>> list(purple) - [Color.RED, Color.BLUE] + [, ] .. versionadded:: 3.11 @@ -812,16 +805,16 @@ simple to implement independently:: pass This demonstrates how similar derived enumerations can be defined; for example -a :class:`StrEnum` that mixes in :class:`str` instead of :class:`int`. +a :class:`FloatEnum` that mixes in :class:`float` 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 @@ -829,15 +822,18 @@ 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 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. + 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. When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------ @@ -866,10 +862,10 @@ want one of them to be the value:: ... >>> print(Coordinate['PY']) - PY + Coordinate.PY >>> print(Coordinate(3)) - VY + Coordinate.VY Finer Points @@ -927,8 +923,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:: @@ -949,35 +945,36 @@ 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.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`:: +``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:: >>> class FieldTypes(Enum): ... name = 0 ... value = 1 ... size = 2 ... - >>> FieldTypes.value.size # doctest: +SKIP - DeprecationWarning: accessing one member from another is not supported, - and will be disabled in 3.12 - + >>> FieldTypes.value.size + Traceback (most recent call last): + ... + AttributeError: member has no attribute 'size' + .. 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): - ... example = '11', 16 # '11' will be interpreted as a hexadecimal - ... # number - >>> MyEnum.example.value + >>> 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... 17 @@ -1000,13 +997,12 @@ 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 and the -class. Attributes defined in an :func:`__init__` method will only show up in a -:func:`dir` of the member:: +class below, those methods will show up in a :func:`dir` of the member, +but not of the class:: - >>> dir(Planet) - ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__init__', '__members__', '__module__', 'surface_gravity'] - >>> dir(Planet.EARTH) + >>> dir(Planet) # doctest: +SKIP + ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__'] + >>> dir(Planet.EARTH) # doctest: +SKIP ['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value'] @@ -1025,19 +1021,10 @@ 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 """""""""""""""""""""""""""""""" @@ -1060,16 +1047,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:: @@ -1079,25 +1066,29 @@ 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 has changed slightly -- zero-valued flags - are never considered to be contained:: +- membership / containment checking: zero-valued flags are always considered + to be contained:: >>> Color.BLACK in Color.WHITE - False + True - otherwise, if all bits of one flag are in the other flag, True is returned:: + otherwise, only if all bits of one flag are in the other flag will True + be 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``: @@ -1181,7 +1172,7 @@ Using :class:`auto` would look like:: ... GREEN = auto() ... >>> Color.GREEN - + Using :class:`object` @@ -1194,10 +1185,24 @@ 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 """""""""""""""""""""""""" @@ -1209,9 +1214,7 @@ Using a string as the value would look like:: ... BLUE = 'too fast!' ... >>> Color.GREEN - - >>> Color.GREEN.value - 'go' + Using a custom :meth:`__new__` @@ -1232,9 +1235,7 @@ 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:: @@ -1257,7 +1258,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 @@ -1384,30 +1385,9 @@ 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 8bb19dc..906c60b 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 the :keyword:`class` syntax, or by +Enumerations are created either by using :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 the :keyword:`class` syntax to create Enums, Enums +Even though we can use :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 *enum members*) and are functionally constants. + *enumeration members* (or *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,15 +110,10 @@ 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:`global_enum` - - :class:`Enum` class decorator to apply the appropriate global `__repr__`, - and export its members into the global name space. - - :func:`.property` + :func:`property` Allows :class:`Enum` members to have attributes without conflicting with - other members' names. + member names. :func:`unique` @@ -131,7 +126,7 @@ Module Contents .. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto`` -.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``FlagBoundary`` +.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``FlagBoundary``, ``property`` --------------- @@ -145,6 +140,11 @@ 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,32 +162,31 @@ Data Types .. method:: EnumType.__dir__(cls) Returns ``['__class__', '__doc__', '__members__', '__module__']`` and the - names of the members in ``cls``. User-defined methods and methods from - mixin classes will also be included:: + names of the members in *cls*:: >>> dir(Color) - ['BLUE', 'GREEN', 'RED', '__class__', '__doc__', '__members__', '__module__'] + ['BLUE', 'GREEN', 'RED', '__class__', '__contains__', '__doc__', '__getitem__', '__init_subclass__', '__iter__', '__len__', '__members__', '__module__', '__name__', '__qualname__'] .. 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 a :exc:`KeyError`:: + Returns the Enum member in *cls* matching *name*, or raises an :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) @@ -201,7 +200,7 @@ Data Types Returns each member in *cls* in reverse definition order:: >>> list(reversed(Color)) - [Color.BLUE, Color.GREEN, Color.RED] + [, , ] .. class:: Enum @@ -232,7 +231,7 @@ Data Types .. attribute:: Enum._ignore_ ``_ignore_`` is only used during creation and is removed from the - enumeration once that is complete. + enumeration once creation 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 @@ -261,7 +260,7 @@ Data Types .. method:: Enum.__dir__(self) Returns ``['__class__', '__doc__', '__module__', 'name', 'value']`` and - any public methods defined on ``self.__class__`` or a mixin class:: + any public methods defined on *self.__class__*:: >>> from datetime import date >>> class Weekday(Enum): @@ -276,7 +275,7 @@ Data Types ... def today(cls): ... print('today is %s' % cls(date.today().isoweekday()).name) >>> dir(Weekday.SATURDAY) - ['__class__', '__doc__', '__module__', 'name', 'today', 'value'] + ['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'today', 'value'] .. method:: Enum._generate_next_value_(name, start, count, last_values) @@ -298,6 +297,11 @@ 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 @@ -317,42 +321,55 @@ 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 and the member name, but can be overridden:: + *Enum* name, member name, and value, but can be overridden:: - >>> class OldStyle(Enum): - ... RETRO = auto() - ... OLD_SCHOOl = auto() - ... YESTERYEAR = auto() + >>> class OtherStyle(Enum): + ... ALTERNATE = auto() + ... OTHER = auto() + ... SOMETHING_ELSE = auto() ... def __repr__(self): ... cls_name = self.__class__.__name__ - ... return f'<{cls_name}.{self.name}: {self.value}>' - >>> OldStyle.RETRO - + ... return f'{cls_name}.{self.name}' + >>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}" + (OtherStyle.ALTERNATE, 'OtherStyle.ALTERNATE', 'OtherStyle.ALTERNATE') .. method:: Enum.__str__(self) Returns the string used for *str()* calls. By default, returns the - member name, but can be overridden:: + *Enum* name and member name, but can be overridden:: - >>> class OldStyle(Enum): - ... RETRO = auto() - ... OLD_SCHOOl = auto() - ... YESTERYEAR = auto() + >>> class OtherStyle(Enum): + ... ALTERNATE = auto() + ... OTHER = auto() + ... SOMETHING_ELSE = auto() ... def __str__(self): - ... cls_name = self.__class__.__name__ - ... return f'{cls_name}.{self.name}' - >>> OldStyle.RETRO - OldStyle.RETRO + ... return f'{self.name}' + >>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}" + (, 'ALTERNATE', 'ALTERNATE') + + .. method:: Enum.__format__(self) + + 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:: + .. 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 @@ -367,7 +384,7 @@ Data Types ... TWO = 2 ... THREE = 3 >>> Numbers.THREE - Numbers.THREE + >>> Numbers.ONE + Numbers.TWO 3 >>> Numbers.THREE + 5 @@ -375,10 +392,14 @@ Data Types >>> Numbers.THREE == 3 True -.. note:: + .. note:: - Using :class:`auto` with :class:`IntEnum` results in integers of increasing value, - starting with ``1``. + 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. .. class:: StrEnum @@ -392,13 +413,16 @@ Data Types instead of ``isinstance(str, unknown)``), and in those locations you will need to use ``str(StrEnum.member)``. + .. note:: -.. note:: + Using :class:`auto` with :class:`StrEnum` results in the lower-cased member + name as the value. - Using :class:`auto` with :class:`StrEnum` results in values of the member name, - lower-cased. + .. 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. -.. versionadded:: 3.11 + .. versionadded:: 3.11 .. class:: Flag @@ -431,9 +455,9 @@ Data Types Returns all contained members:: >>> list(Color.RED) - [Color.RED] + [] >>> list(purple) - [Color.RED, Color.BLUE] + [, ] .. method:: __len__(self): @@ -461,42 +485,52 @@ 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_ + + Function used to format any remaining unnamed numeric values. Default is + the value's repr; common choices are :func:`hex` and :func:`oct`. + + .. note:: -.. note:: + Using :class:`auto` with :class:`Flag` results in integers that are powers + of two, starting with ``1``. - 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 @@ -509,9 +543,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*:: @@ -524,15 +558,25 @@ 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 -.. note:: + 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. - 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:`TypeError`. + various constraints; failed constraints result in a :exc:`ValueError`. .. attribute:: UNIQUE @@ -582,11 +626,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 @@ -606,7 +650,7 @@ Data Types >>> StrictFlag(2**2 + 2**4) Traceback (most recent call last): ... - ValueError: StrictFlag: invalid value: 20 + ValueError: invalid value 20 given 0b0 10100 allowed 0b0 00111 @@ -621,7 +665,7 @@ Data Types ... GREEN = auto() ... BLUE = auto() >>> ConformFlag(2**2 + 2**4) - ConformFlag.BLUE + .. attribute:: EJECT @@ -647,12 +691,52 @@ 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 ------------------------ @@ -668,15 +752,6 @@ 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 @@ -688,7 +763,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 @@ -714,7 +789,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 --------------- @@ -726,14 +801,20 @@ 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: - - ``format()`` will use the value of the enum member, unless ``__str__`` - has been overridden + - ``__str__`` uses the value and not the name of the enum member - - ``StrEnum.__str__`` uses the value and not the name of the enum member + - ``__format__``, because it uses ``__str__``, will also use the value of + the enum member instead of its name - If you do not need/want those limitations, you can create your own base - class by mixing in the ``int`` or ``str`` type yourself:: + 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:: >>> 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 eb33d7e..4d8488a 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 93ea1be..772e1ea 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', + 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum', 'auto', 'unique', 'property', 'verify', 'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP', - 'global_flag_repr', 'global_enum_repr', 'global_enum', + 'global_flag_repr', 'global_enum_repr', 'global_str', '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 = None +Enum = Flag = EJECT = _stdlib_enums = ReprEnum = None def _is_descriptor(obj): """ @@ -116,9 +116,9 @@ def bin(num, max_bits=None): ceiling = 2 ** (num).bit_length() if num >= 0: - s = _bltin_bin(num + ceiling).replace('1', '0', 1) + s = bltns.bin(num + ceiling).replace('1', '0', 1) else: - s = _bltin_bin(~num ^ (ceiling - 1) + ceiling) + s = bltns.bin(~num ^ (ceiling - 1) + ceiling) sign = s[:3] digits = s[3:] if max_bits is not None: @@ -126,6 +126,19 @@ 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: @@ -149,22 +162,12 @@ class property(DynamicClassAttribute): return ownerclass._member_map_[self.name] except KeyError: raise AttributeError( - '%s: no class attribute %r' % (ownerclass.__name__, self.name) + '%r has no attribute %r' % (ownerclass, 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( - '%s: no instance attribute %r' % (ownerclass.__name__, self.name) + '%r member has no attribute %r' % (ownerclass, self.name) ) else: return self.fget(instance) @@ -172,7 +175,7 @@ class property(DynamicClassAttribute): def __set__(self, instance, value): if self.fset is None: raise AttributeError( - "%s: cannot set instance attribute %r" % (self.clsname, self.name) + " cannot set attribute %r" % (self.clsname, self.name) ) else: return self.fset(instance, value) @@ -180,7 +183,7 @@ class property(DynamicClassAttribute): def __delete__(self, instance): if self.fdel is None: raise AttributeError( - "%s: cannot delete instance attribute %r" % (self.clsname, self.name) + " cannot delete attribute %r" % (self.clsname, self.name) ) else: return self.fdel(instance) @@ -328,7 +331,7 @@ class _EnumDict(dict): elif _is_sunder(key): if key not in ( '_order_', - '_generate_next_value_', '_missing_', '_ignore_', + '_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_', '_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_', ): raise ValueError( @@ -358,13 +361,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( @@ -395,7 +398,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 @@ -413,9 +416,10 @@ 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: @@ -427,8 +431,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: {0}'.format( - ','.join(invalid_names))) + raise ValueError('invalid enum member name(s) '.format( + ','.join(repr(n) for n in invalid_names))) # # adjust the sunders _order_ = classdict.pop('_order_', None) @@ -458,6 +462,8 @@ 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_'] = ( @@ -467,10 +473,6 @@ 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) @@ -481,18 +483,140 @@ 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 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) + if name not in classdict: + setattr(enum_class, name, getattr(first_enum, name)) # # replace any other __new__ with our own (as long as Enum is not None, # anyway) -- again, this is to support pickle @@ -563,13 +687,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__(self): + def __bool__(cls): """ classes/types should always be True. """ @@ -614,6 +738,13 @@ 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( @@ -631,60 +762,33 @@ class EnumType(type): # nicer error message when someone tries to delete an attribute # (see issue19025). if attr in cls._member_map_: - raise AttributeError("%s: cannot delete Enum member %r." % (cls.__name__, attr)) + raise AttributeError("%r cannot delete member %r." % (cls.__name__, attr)) super().__delattr__(attr) - 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 __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 __getattr__(cls, name): """ @@ -703,18 +807,24 @@ 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): """ - Returns members in definition order. + Return 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_) - @_bltin_property + @bltns.property def __members__(cls): """ Returns a mapping of member name->value. @@ -732,7 +842,7 @@ class EnumType(type): def __reversed__(cls): """ - Returns members in reverse definition order. + Return members in reverse definition order. """ return (cls._member_map_[name] for name in reversed(cls._member_names_)) @@ -746,7 +856,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): @@ -801,8 +911,7 @@ class EnumType(type): return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary) - def _convert_(cls, name, module, filter, source=None, *, boundary=None): - + def _convert_(cls, name, module, filter, source=None, *, boundary=None, as_global=False): """ Create a new Enum subclass that replaces a collection of global constants """ @@ -834,22 +943,25 @@ 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 - global_enum(cls) + if as_global: + global_enum(cls) + else: + sys.modules[cls.__module__].__dict__.update(cls.__members__) module_globals[name] = cls return cls - @staticmethod - def _check_for_existing_members(class_name, bases): + @classmethod + def _check_for_existing_members_(mcls, class_name, bases): for chain in bases: for base in chain.__mro__: if issubclass(base, Enum) and base._member_names_: raise TypeError( - "%s: cannot extend enumeration %r" - % (class_name, base.__name__) + " cannot extend %r" + % (class_name, base) ) @classmethod - def _get_mixins_(cls, class_name, bases): + def _get_mixins_(mcls, class_name, bases): """ Returns the type for creating enum members, and the first inherited enum class. @@ -859,30 +971,7 @@ class EnumType(type): if not bases: return object, Enum - 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 + mcls._check_for_existing_members_(class_name, bases) # ensure final parent class is an Enum derivative, find any concrete # data type, and check that Enum has no members @@ -890,12 +979,51 @@ class EnumType(type): if not issubclass(first_enum, Enum): raise TypeError("new enumerations should be created as " "`EnumName([mixin_type, ...] [data_type,] enum_type)`") - cls._check_for_existing_members(class_name, bases) - member_type = _find_data_type(bases) or object + member_type = mcls._find_data_type_(class_name, bases) or object return member_type, first_enum - @staticmethod - def _find_new_(classdict, 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): """ Returns the __new__ to be used for creating the enum members. @@ -943,9 +1071,42 @@ EnumMeta = EnumType class Enum(metaclass=EnumType): """ - Generic enumeration. + 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) + - Derive from this class to define new enumerations. + - 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. """ def __new__(cls, value): @@ -999,6 +1160,9 @@ 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. @@ -1021,47 +1185,44 @@ class Enum(metaclass=EnumType): return None def __repr__(self): - return "%s.%s" % ( self.__class__.__name__, self._name_) + v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__ + return "<%s.%s: %s>" % (self.__class__.__name__, self._name_, v_repr(self._value_)) def __str__(self): - return "%s" % (self._name_, ) + return "%s.%s" % (self.__class__.__name__, self._name_, ) def __dir__(self): """ Returns all members and all public methods """ - 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) + 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 def __format__(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) + return str.__format__(str(self), format_spec) def __hash__(self): return hash(self._name_) @@ -1088,34 +1249,25 @@ class Enum(metaclass=EnumType): return self._value_ -class IntEnum(int, Enum): +class ReprEnum(Enum): """ - Enum where members are also (and must be) ints + Only changes the repr(), leaving str() and format() to the mixed-in type. """ - def __str__(self): - return "%s" % (self._name_, ) - 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 IntEnum(int, ReprEnum): + """ + Enum where members are also (and must be) ints + """ -class StrEnum(str, Enum): +class StrEnum(str, ReprEnum): """ 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: @@ -1135,10 +1287,6 @@ class StrEnum(str, Enum): 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. @@ -1169,6 +1317,8 @@ 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. @@ -1184,7 +1334,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 @@ -1232,8 +1382,8 @@ class Flag(Enum, boundary=STRICT): if cls._boundary_ is STRICT: max_bits = max(value.bit_length(), flag_mask.bit_length()) raise ValueError( - "%s: invalid value: %r\n given %s\n allowed %s" % ( - cls.__name__, value, bin(value, max_bits), bin(flag_mask, max_bits), + "%r invalid value %r\n given %s\n allowed %s" % ( + cls, value, bin(value, max_bits), bin(flag_mask, max_bits), )) elif cls._boundary_ is CONFORM: value = value & flag_mask @@ -1247,7 +1397,7 @@ class Flag(Enum, boundary=STRICT): ) else: raise ValueError( - 'unknown flag boundary: %r' % (cls._boundary_, ) + '%r unknown flag boundary %r' % (cls, cls._boundary_, ) ) if value < 0: neg_value = value @@ -1274,7 +1424,7 @@ class Flag(Enum, boundary=STRICT): m._name_ for m in cls._iter_member_(member_value) ]) if unknown: - pseudo_member._name_ += '|0x%x' % unknown + pseudo_member._name_ += '|%s' % cls._numeric_repr_(unknown) else: pseudo_member._name_ = None # use setdefault in case another thread already created a composite @@ -1292,10 +1442,8 @@ class Flag(Enum, boundary=STRICT): """ if not isinstance(other, self.__class__): raise TypeError( - "unsupported operand type(s) for 'in': '%s' and '%s'" % ( + "unsupported operand type(s) for 'in': %r and %r" % ( 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): @@ -1309,27 +1457,18 @@ 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 "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('|')) + return "<%s: %s>" % (cls_name, v_repr(self._value_)) else: - 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) + return "<%s.%s: %s>" % (cls_name, self._name_, v_repr(self._value_)) def __str__(self): - cls = self.__class__ + cls_name = self.__class__.__name__ if self._name_ is None: - return '%s(%x)' % (cls.__name__, self._value_) + return '%s(%r)' % (cls_name, self._value_) else: - return self._name_ + return "%s.%s" % (cls_name, self._name_) def __bool__(self): return bool(self._value_) @@ -1362,20 +1501,11 @@ class Flag(Enum, boundary=STRICT): return self._inverted_ -class IntFlag(int, Flag, boundary=EJECT): +class IntFlag(int, ReprEnum, 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__): @@ -1412,6 +1542,7 @@ class IntFlag(int, 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 @@ -1456,7 +1587,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(0x%x)" % (module, cls_name, self._value_) + return "%s.%s(%r)" % (module, cls_name, self._value_) if _is_single_bit(self): return '%s.%s' % (module, self._name_) if self._boundary_ is not FlagBoundary.KEEP: @@ -1464,14 +1595,22 @@ def global_flag_repr(self): else: name = [] for n in self._name_.split('|'): - if n.startswith('0'): + if n[0].isdigit(): 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): +def global_enum(cls, update_str=False): """ 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 @@ -1481,6 +1620,8 @@ def global_enum(cls): 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 @@ -1522,6 +1663,7 @@ 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 @@ -1543,13 +1685,8 @@ 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 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) + if name not in body: + setattr(enum_class, name, getattr(etype, name)) gnv_last_values = [] if issubclass(enum_class, Flag): # Flag / IntFlag @@ -1760,8 +1897,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_'): - # keys known to be different + if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__'): + # keys known to be different, or very long continue elif key in member_names: # members are checked below @@ -1882,3 +2019,5 @@ 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 5d33f0d..8236698 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2567,15 +2567,21 @@ class _empty: class _ParameterKind(enum.IntEnum): - POSITIONAL_ONLY = 0 - POSITIONAL_OR_KEYWORD = 1 - VAR_POSITIONAL = 2 - KEYWORD_ONLY = 3 - VAR_KEYWORD = 4 + POSITIONAL_ONLY = 'positional-only' + POSITIONAL_OR_KEYWORD = 'positional or keyword' + VAR_POSITIONAL = 'variadic positional' + KEYWORD_ONLY = 'keyword-only' + VAR_KEYWORD = 'variadic keyword' + + def __new__(cls, description): + value = len(cls.__members__) + member = int.__new__(cls, value) + member._value_ = value + member.description = description + return member - @property - def description(self): - return _PARAM_NAME_MAPPING[self] + def __str__(self): + return self.name _POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY _POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD @@ -2583,14 +2589,6 @@ _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 3ab71ed..4862355 100644 --- a/Lib/plistlib.py +++ b/Lib/plistlib.py @@ -61,7 +61,8 @@ import struct from xml.parsers.expat import ParserCreate -PlistFormat = enum.global_enum(enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)) +PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__) +globals().update(PlistFormat.__members__) class UID: diff --git a/Lib/re.py b/Lib/re.py index ea41217..a7ab9b3 100644 --- a/Lib/re.py +++ b/Lib/re.py @@ -155,6 +155,8 @@ 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 2079251..dafb70a 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -119,7 +119,6 @@ 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 43f98c1..a0953fb 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -6,15 +6,18 @@ 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 +from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum 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] @@ -107,6 +110,12 @@ 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 @@ -116,21 +125,36 @@ class TestHelpers(unittest.TestCase): setattr(obj, attr, 1) self.assertTrue(enum._is_descriptor(obj)) - def test_is_sunder(self): + 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) 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_is_dunder(self): + 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) 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: @@ -166,473 +190,658 @@ class HeadlightsC(IntFlag, boundary=enum.CONFORM): # tests -class TestEnum(unittest.TestCase): +class _EnumTests: + """ + Test for behavior that is the same across the different types of enumerations. + """ + + values = None def setUp(self): - class Season(Enum): - SPRING = 1 - SUMMER = 2 - AUTUMN = 3 - WINTER = 4 - self.Season = Season + 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 - class Konstants(float, Enum): - E = 2.7182818 - PI = 3.1415926 - TAU = 2 * PI - self.Konstants = Konstants + def assertFormatIsValue(self, spec, member): + self.assertEqual(spec.format(member), spec.format(member.value)) - class Grades(IntEnum): - A = 5 - B = 4 - C = 3 - D = 2 - F = 0 - self.Grades = Grades + def assertFormatIsStr(self, spec, member): + self.assertEqual(spec.format(member), spec.format(str(member))) - class Directional(str, Enum): - EAST = 'east' - WEST = 'west' - NORTH = 'north' - SOUTH = 'south' - self.Directional = Directional + 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 - from datetime import date - class Holiday(date, Enum): - NEW_YEAR = 2013, 1, 1 - IDES_OF_MARCH = 2013, 3, 15 - self.Holiday = Holiday + 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']) - class DateEnum(date, Enum): pass - self.DateEnum = DateEnum + 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) - class FloatEnum(float, Enum): pass - self.FloatEnum = FloatEnum + def test_changing_member_fails(self): + MainEnum = self.MainEnum + with self.assertRaises(AttributeError): + self.MainEnum.second = 'really first' - 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 + @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) - 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 + @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) - 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 + 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 SuperEnum1(Enum): + class SuperEnum(self.enum_type): def invisible(self): return "did you see me?" - class SubEnum1(SuperEnum1): - sample = 5 - self.SubEnum1 = SubEnum1 + class SubEnum(SuperEnum): + sample = auto() + self.assertTrue('invisible' not in dir(SubEnum)) + self.assertTrue('invisible' in dir(SubEnum.sample)) - class SuperEnum2(IntEnum): - def __new__(cls, value, description=""): - obj = int.__new__(cls, value) - obj._value_ = value - obj.description = description + 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 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 - )) + 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)) - 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 + def test_enum_in_enum_out(self): + Main = self.MainEnum + self.assertIs(Main(Main.first), Main.first) - 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) - ] + 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()) - first_enum_base = next( - base for base in enum_cls.__mro__ - if is_from_enum_module(base) + 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)) - 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_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_dir_on_sub_with_behavior_on_super(self): - # see issue22506 + 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( - set(dir(self.SubEnum1.sample)), - set(['__class__', '__doc__', '__module__', 'name', 'value', 'invisible']), + [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_dir_on_sub_with_behavior_including_instance_dict_on_super(self): - # see issue40084 - self.assertTrue({'description'} <= set(dir(self.SubEnum2.sample))) + 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_enum_in_enum_out(self): - Season = self.Season - self.assertIs(Season(Season.WINTER), Season.WINTER) + 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_enum_value(self): - Season = self.Season - self.assertEqual(Season.SPRING.value, 1) + 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_intenum_value(self): - self.assertEqual(IntStooges.CURLY.value, 2) + 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_enum(self): - Season = self.Season - lst = list(Season) - self.assertEqual(len(lst), len(Season)) - self.assertEqual(len(Season), 4, Season) + def test_reversed_iteration_order(self): self.assertEqual( - [Season.SPRING, Season.SUMMER, Season.AUTUMN, Season.WINTER], lst) + list(reversed(self.MainEnum)), + [self.MainEnum.third, self.MainEnum.second, self.MainEnum.first], + ) - 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 +class _PlainOutputTests: - def test_changing_member(self): - Season = self.Season - with self.assertRaises(AttributeError): - Season.WINTER = 'really cold' + 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_attribute_deletion(self): - class Season(Enum): - SPRING = 1 - SUMMER = 2 - AUTUMN = 3 - WINTER = 4 + 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 spam(cls): - pass + 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") - self.assertTrue(hasattr(Season, 'spam')) - del Season.spam - self.assertFalse(hasattr(Season, 'spam')) + 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) - 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)) +class _MixedOutputTests: - 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_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] - 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 + +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 + """ + + def setUp(self): + class Season(Enum): + SPRING = 1 + SUMMER = 2 + AUTUMN = 3 + WINTER = 4 + self.Season = Season + # + class Grades(IntEnum): + A = 5 + B = 4 + C = 3 + 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 def test_bool(self): # plain Enum members are always True @@ -656,92 +865,56 @@ class TestEnum(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_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_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_duplicate_name(self): + def test_duplicate_name_error(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 @@ -749,232 +922,45 @@ class TestEnum(unittest.TestCase): blue = 3 def red(self): return 'red' - + # with self.assertRaises(TypeError): class Color(Enum): - @property + @enum.property def red(self): return 'redder' - red = 1 - green = 2 - blue = 3 - - def test_reserved__sunder_(self): - with self.assertRaisesRegex( - ValueError, - '_sunder_ names, such as ._bad_., are reserved', - ): - class Bad(Enum): - _bad_ = 1 + 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_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' @@ -990,7 +976,7 @@ class TestEnum(unittest.TestCase): test_pickle_dump_load(self.assertIs, MyOtherEnum.E) test_pickle_dump_load(self.assertIs, MyOtherEnum) # - # This did not work in 3.9, but does now with pickling by name + # This did not work in 3.10, but does now with pickling by name class UnBrokenInt(int): __qualname__ = 'UnBrokenInt' def __new__(cls, value): @@ -1007,6 +993,124 @@ class TestEnum(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): @@ -1022,122 +1126,6 @@ class TestEnum(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): @@ -1169,12 +1157,7 @@ class TestEnum(unittest.TestCase): test_pickle_dump_load(self.assertIs, Question.who) test_pickle_dump_load(self.assertIs, Question) - 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): + def test_pickle_nested_class(self): # would normally just have this directly in the class namespace class NestedEnum(Enum): twigs = 'common' @@ -1192,225 +1175,46 @@ class TestEnum(unittest.TestCase): for proto in range(HIGHEST_PROTOCOL): self.assertEqual(ReplaceGlobalInt.TWO.__reduce_ex__(proto), 'TWO') - def test_exploding_pickle(self): + def test_pickle_explodes(self): BadPickle = Enum( 'BadPickle', 'dill sweet bread-n-butter', module=__name__) globals()['BadPickle'] = BadPickle # now break BadPickle to test exception raising enum._make_class_unpicklable(BadPickle) - test_pickle_exception(self.assertRaises, TypeError, BadPickle.dill) - test_pickle_exception(self.assertRaises, PicklingError, BadPickle) - - def test_string_enum(self): - class SkillLevel(str, Enum): - master = 'what is the sound of one hand clapping?' - journeyman = 'why did the chicken cross the road?' - apprentice = 'knock, knock!' - self.assertEqual(SkillLevel.apprentice, 'knock, knock!') - - def test_getattr_getitem(self): - class Period(Enum): - morning = 1 - noon = 2 - evening = 3 - night = 4 - self.assertIs(Period(2), Period.noon) - self.assertIs(getattr(Period, 'night'), Period.night) - self.assertIs(Period['morning'], Period.morning) - - def test_getattr_dunder(self): - Season = self.Season - self.assertTrue(getattr(Season, '__eq__')) - - def test_iteration_order(self): - class Season(Enum): - SUMMER = 2 - WINTER = 4 - AUTUMN = 3 - SPRING = 1 - self.assertEqual( - list(Season), - [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) + test_pickle_exception(self.assertRaises, TypeError, BadPickle.dill) + test_pickle_exception(self.assertRaises, PicklingError, BadPickle) - 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_string_enum(self): + class SkillLevel(str, Enum): + master = 'what is the sound of one hand clapping?' + journeyman = 'why did the chicken cross the road?' + apprentice = 'knock, knock!' + self.assertEqual(SkillLevel.apprentice, 'knock, knock!') - 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_getattr_getitem(self): + class Period(Enum): + morning = 1 + noon = 2 + evening = 3 + night = 4 + self.assertIs(Period(2), Period.noon) + self.assertIs(getattr(Period, 'night'), Period.night) + self.assertIs(Period['morning'], Period.morning) - 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_getattr_dunder(self): + Season = self.Season + self.assertTrue(getattr(Season, '__eq__')) - 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) + def test_iteration_order(self): + class Season(Enum): + SUMMER = 2 + WINTER = 4 + AUTUMN = 3 + SPRING = 1 self.assertEqual( - [SummerMonth.june, SummerMonth.july, SummerMonth.august], - lst, + list(Season), + [Season.SUMMER, Season.WINTER, Season.AUTUMN, Season.SPRING], ) - 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): @@ -1425,15 +1229,18 @@ class TestEnum(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, "EvenMoreColor: cannot extend enumeration 'Color'"): + # + with self.assertRaisesRegex(TypeError, " cannot extend "): class EvenMoreColor(Color, IntEnum): chartruese = 7 - with self.assertRaisesRegex(TypeError, "Foo: cannot extend enumeration 'Color'"): + # + with self.assertRaisesRegex(TypeError, " cannot extend "): Color('Foo', ('pink', 'black')) def test_exclude_methods(self): @@ -1537,27 +1344,7 @@ class TestEnum(unittest.TestCase): with self.assertRaises(KeyError): Color['chartreuse'] - 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.") + # tests that need to be evalualted for moving def test_multiple_mixin_mro(self): class auto_enum(type(Enum)): @@ -1610,7 +1397,7 @@ class TestEnum(unittest.TestCase): return self def __getnewargs__(self): return self._args - @property + @bltns.property def __name__(self): return self._intname def __repr__(self): @@ -1670,7 +1457,7 @@ class TestEnum(unittest.TestCase): return self def __getnewargs_ex__(self): return self._args, {} - @property + @bltns.property def __name__(self): return self._intname def __repr__(self): @@ -1730,7 +1517,7 @@ class TestEnum(unittest.TestCase): return self def __reduce__(self): return self.__class__, self._args - @property + @bltns.property def __name__(self): return self._intname def __repr__(self): @@ -1790,7 +1577,7 @@ class TestEnum(unittest.TestCase): return self def __reduce_ex__(self, proto): return self.__class__, self._args - @property + @bltns.property def __name__(self): return self._intname def __repr__(self): @@ -1847,7 +1634,7 @@ class TestEnum(unittest.TestCase): self._intname = name self._args = _args return self - @property + @bltns.property def __name__(self): return self._intname def __repr__(self): @@ -1902,7 +1689,7 @@ class TestEnum(unittest.TestCase): self._intname = name self._args = _args return self - @property + @bltns.property def __name__(self): return self._intname def __repr__(self): @@ -2091,6 +1878,7 @@ class TestEnum(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): @@ -2098,6 +1886,7 @@ class TestEnum(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): @@ -2134,7 +1923,7 @@ class TestEnum(unittest.TestCase): def __init__(self, mass, radius): self.mass = mass # in kilograms self.radius = radius # in meters - @property + @enum.property def surface_gravity(self): # universal gravitational constant (m3 kg-1 s-2) G = 6.67300E-11 @@ -2204,90 +1993,7 @@ class TestEnum(unittest.TestCase): self.assertEqual(LabelledList.unprocessed, 1) self.assertEqual(LabelledList(1), LabelledList.unprocessed) - 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): + def test_default_missing_no_chained_exception(self): class Color(Enum): RED = 1 GREEN = 2 @@ -2299,7 +2005,7 @@ class TestEnum(unittest.TestCase): else: raise Exception('Exception not raised.') - def test_missing(self): + def test_missing_override(self): class Color(Enum): red = 1 green = 2 @@ -2363,9 +2069,9 @@ class TestEnum(unittest.TestCase): class_1_ref = weakref.ref(Class1()) class_2_ref = weakref.ref(Class2()) # - # The exception raised by Enum creates a reference loop and thus - # Class2 instances will stick around until the next garbage collection - # cycle, unlike Class1. + # 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. gc.collect() # For PyPy or other GCs. self.assertIs(class_1_ref(), None) self.assertIs(class_2_ref(), None) @@ -2396,11 +2102,12 @@ class TestEnum(unittest.TestCase): self.assertEqual(Color.GREEN.value, 2) self.assertEqual(Color.BLUE.value, 3) self.assertEqual(Color.MAX, 3) - self.assertEqual(str(Color.BLUE), 'BLUE') + self.assertEqual(str(Color.BLUE), 'Color.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) @@ -2410,6 +2117,7 @@ class TestEnum(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) @@ -2419,6 +2127,7 @@ class TestEnum(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) @@ -2428,6 +2137,7 @@ class TestEnum(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) @@ -2438,6 +2148,7 @@ class TestEnum(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) @@ -2448,6 +2159,7 @@ class TestEnum(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) @@ -2458,6 +2170,7 @@ class TestEnum(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) @@ -2490,11 +2203,12 @@ class TestEnum(unittest.TestCase): return hex(self) class MyIntEnum(HexMixin, MyInt, enum.Enum): - pass + __repr__ = HexMixin.__repr__ 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): @@ -2506,7 +2220,7 @@ class TestEnum(unittest.TestCase): return member self.assertEqual(Fee.TEST, 2) - def test_miltuple_mixin_with_common_data_type(self): + def test_multiple_mixin_with_common_data_type(self): class CaseInsensitiveStrEnum(str, Enum): @classmethod def _missing_(cls, value): @@ -2526,7 +2240,7 @@ class TestEnum(unittest.TestCase): unknown._value_ = value cls._member_map_[value] = unknown return unknown - @property + @enum.property def valid(self): return self._valid # @@ -2570,7 +2284,7 @@ class TestEnum(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), 'GoodStrEnum.one') + self.assertEqual(repr(GoodStrEnum.one), "") # class DumbMixin: def __str__(self): @@ -2579,6 +2293,7 @@ class TestEnum(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") # @@ -2620,74 +2335,6 @@ class TestEnum(unittest.TestCase): one = '1' two = b'2', 'ascii', 9 - @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): - one = '1' - two = '2' - three = b'3', 'ascii' - four = b'4', 'latin1', 'strict' - self.assertEqual(OkayEnum.one, '1') - 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): - 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' - 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 @@ -2697,9 +2344,9 @@ class TestEnum(unittest.TestCase): 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') + self.assertEqual(str(OkayEnum.one), 'OkayEnum.one') + self.assertEqual('{}'.format(OkayEnum.one), 'OkayEnum.one') + self.assertEqual(repr(OkayEnum.one), "") # class DumbMixin: def __str__(self): @@ -2708,6 +2355,7 @@ class TestEnum(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") # @@ -2717,7 +2365,7 @@ class TestEnum(unittest.TestCase): class HelloEnum(EnumMixin, CustomStrEnum): eight = '8' self.assertEqual(HelloEnum.eight, '8') - self.assertEqual(str(HelloEnum.eight), 'eight') + self.assertEqual(str(HelloEnum.eight), 'HelloEnum.eight') # class GoodbyeMixin: def goodbye(self): @@ -2725,7 +2373,7 @@ class TestEnum(unittest.TestCase): class GoodbyeEnum(GoodbyeMixin, EnumMixin, CustomStrEnum): nine = '9' self.assertEqual(GoodbyeEnum.nine, '9') - self.assertEqual(str(GoodbyeEnum.nine), 'nine') + self.assertEqual(str(GoodbyeEnum.nine), 'GoodbyeEnum.nine') # class FirstFailedStrEnum(CustomStrEnum): one = 1 # this will become '1' @@ -2771,21 +2419,6 @@ class TestEnum(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): @@ -2794,35 +2427,13 @@ class TestEnum(unittest.TestCase): self.assertEqual(Private._Private__corporal, 'Radar') self.assertEqual(Private._Private__major_, 'Hoolihan') - @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."): + with self.assertRaisesRegex(AttributeError, " member has no 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): # @@ -2839,7 +2450,7 @@ class TestEnum(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 @@ -2851,8 +2462,42 @@ class TestEnum(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): @@ -2914,7 +2559,7 @@ class TestOrder(unittest.TestCase): verde = green -class TestFlag(unittest.TestCase): +class OldTestFlag(unittest.TestCase): """Tests of the Flags.""" class Perm(Flag): @@ -2934,67 +2579,8 @@ class TestFlag(unittest.TestCase): GREEN = 2 BLUE = 4 PURPLE = RED|BLUE - 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') + WHITE = RED|GREEN|BLUE + BLANCO = RED|GREEN|BLUE def test_or(self): Perm = self.Perm @@ -3088,7 +2674,7 @@ class TestFlag(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) @@ -3297,7 +2883,7 @@ class TestFlag(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() @@ -3332,11 +2918,12 @@ class TestFlag(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), 'BLUE') + self.assertEqual(str(Color.BLUE), 'Color.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) @@ -3346,6 +2933,7 @@ class TestFlag(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) @@ -3426,21 +3014,8 @@ class TestFlag(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) - - 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): +class OldTestIntFlag(unittest.TestCase): """Tests of the IntFlags.""" class Perm(IntFlag): @@ -3485,73 +3060,6 @@ class TestIntFlag(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)), @@ -3559,11 +3067,11 @@ class TestIntFlag(unittest.TestCase): ) self.assertEqual( repr(HeadlightsK(2**0 + 2**2 + 2**3)), - '%(m)s.LOW_BEAM_K|%(m)s.FOG_K|0x8' % {'m': SHORT_MODULE}, + '%(m)s.LOW_BEAM_K|%(m)s.FOG_K|8' % {'m': SHORT_MODULE}, ) self.assertEqual( repr(HeadlightsK(2**3)), - '%(m)s.HeadlightsK(0x8)' % {'m': SHORT_MODULE}, + '%(m)s.HeadlightsK(8)' % {'m': SHORT_MODULE}, ) def test_global_repr_conform1(self): @@ -3705,7 +3213,7 @@ class TestIntFlag(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) @@ -3942,11 +3450,12 @@ class TestIntFlag(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), 'BLUE') + self.assertEqual(str(Color.BLUE), '4') 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) @@ -3956,6 +3465,7 @@ class TestIntFlag(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) @@ -4000,19 +3510,6 @@ class TestIntFlag(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): @@ -4229,6 +3726,89 @@ 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 @@ -4238,7 +3818,35 @@ 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 - | An enumeration. + | 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. |\x20\x20 | Method resolution order: | Color @@ -4247,11 +3855,11 @@ class Color(enum.Enum) |\x20\x20 | Data and other attributes defined here: |\x20\x20 - | blue = Color.blue + | CYAN = |\x20\x20 - | green = Color.green + | MAGENTA = |\x20\x20 - | red = Color.red + | YELLOW = |\x20\x20 | ---------------------------------------------------------------------- | Data descriptors inherited from enum.Enum: @@ -4263,6 +3871,25 @@ 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__ @@ -4284,11 +3911,11 @@ class Color(enum.Enum) |\x20\x20 | Data and other attributes defined here: |\x20\x20 - | blue = Color.blue + | YELLOW = |\x20\x20 - | green = Color.green + | MAGENTA = |\x20\x20 - | red = Color.red + | CYAN = |\x20\x20 | ---------------------------------------------------------------------- | Data descriptors inherited from enum.Enum: @@ -4307,9 +3934,9 @@ class TestStdLib(unittest.TestCase): maxDiff = None class Color(Enum): - red = 1 - green = 2 - blue = 3 + CYAN = 1 + MAGENTA = 2 + YELLOW = 3 def test_pydoc(self): # indirectly test __objclass__ @@ -4321,24 +3948,34 @@ class TestStdLib(unittest.TestCase): helper = pydoc.Helper(output=output) helper(self.Color) result = output.getvalue().strip() - self.assertEqual(result, expected_text) + self.assertEqual(result, expected_text, result) def test_inspect_getmembers(self): values = dict(( ('__class__', EnumType), - ('__doc__', 'An enumeration.'), + ('__doc__', '...'), ('__members__', self.Color.__members__), ('__module__', __name__), - ('blue', self.Color.blue), - ('green', self.Color.green), + ('YELLOW', self.Color.YELLOW), + ('MAGENTA', self.Color.MAGENTA), + ('CYAN', self.Color.CYAN), ('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' % @@ -4353,23 +3990,42 @@ 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='An enumeration.'), + 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__), Attribute(name='__members__', kind='property', defining_class=EnumType, object=EnumType.__members__), Attribute(name='__module__', kind='data', defining_class=self.Color, object=__name__), - 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=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='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) @@ -4379,7 +4035,15 @@ class TestStdLib(unittest.TestCase): ) failed = False for v, r in zip(values, result): - if r != v: + 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: print('\n%s\n%s\n%s\n%s\n' % ('=' * 75, r, v, '=' * 75), sep='') failed = True if failed: @@ -4388,15 +4052,15 @@ class TestStdLib(unittest.TestCase): def test_test_simple_enum(self): @_simple_enum(Enum) class SimpleColor: - RED = 1 - GREEN = 2 - BLUE = 3 + CYAN = 1 + MAGENTA = 2 + YELLOW = 3 class CheckedColor(Enum): - RED = 1 - GREEN = 2 - BLUE = 3 + CYAN = 1 + MAGENTA = 2 + YELLOW = 3 self.assertTrue(_test_simple_enum(CheckedColor, SimpleColor) is None) - SimpleColor.GREEN._value_ = 9 + SimpleColor.MAGENTA._value_ = 9 self.assertRaisesRegex( TypeError, "enum mismatch", _test_simple_enum, CheckedColor, SimpleColor, @@ -4422,9 +4086,165 @@ 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. @@ -4442,6 +4262,10 @@ 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 @@ -4453,32 +4277,40 @@ 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 TestIntEnumConvert(unittest.TestCase): +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 + def test_convert_value_lookup_priority(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_')) + 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(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_')) + def test_convert_int(self): + 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) @@ -4487,43 +4319,57 @@ class TestIntEnumConvert(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. - 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.') + 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.', + ) def test_convert_uncomparable(self): - # 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_')) - + 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): - with support.swap_item( - sys.modules, MODULE, _ModuleWrapper(sys.modules[MODULE]), - ): - uncomp = enum.Enum._convert_( - 'Uncomparable', - MODULE, - filter=lambda x: x.startswith('COMPLEX_')) - + 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): @@ -4533,50 +4379,58 @@ class TestIntEnumConvert(unittest.TestCase): filter=lambda x: x.startswith('CONVERT_TEST_')) def test_convert_repr_and_str(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_STRING_TEST_')) + test_type = enum.IntEnum._convert_( + 'UnittestConvert', + MODULE, + filter=lambda x: x.startswith('CONVERT_STRING_TEST_'), + as_global=True) 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), 'CONVERT_STRING_TEST_NAME_A') + self.assertEqual(str(test_type.CONVERT_STRING_TEST_NAME_A), '5') 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' -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.') +# helpers - 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') +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() if __name__ == '__main__': diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 3f0e727..ac4626d 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -908,7 +908,7 @@ class PendingSignalsTests(unittest.TestCase): %s - blocked = %r + blocked = %s signum = signal.SIGALRM # child: block and wait the signal diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 394d294..56cc23d 100755 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1517,9 +1517,11 @@ 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(str(family), 'AF_INET') + self.assertEqual(repr(family), '') + self.assertEqual(str(family), '2') self.assertEqual(type, socket.SOCK_STREAM) - self.assertEqual(str(type), 'SOCK_STREAM') + self.assertEqual(repr(type), '') + self.assertEqual(str(type), '1') infos = socket.getaddrinfo(HOST, None, 0, socket.SOCK_STREAM) for _, socktype, _, _, _ in infos: self.assertEqual(socktype, socket.SOCK_STREAM) @@ -1793,8 +1795,10 @@ 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(str(s.family), 'AF_INET') - self.assertEqual(str(s.type), 'SOCK_STREAM') + self.assertEqual(repr(s.family), '') + self.assertEqual(repr(s.type), '') + self.assertEqual(str(s.family), '2') + self.assertEqual(str(s.type), '1') 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 f99a3e8..64f4bce 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -373,7 +373,8 @@ class BasicSocketTests(unittest.TestCase): # Make sure that the PROTOCOL_* constants have enum-like string # reprs. proto = ssl.PROTOCOL_TLS_CLIENT - self.assertEqual(str(proto), 'PROTOCOL_TLS_CLIENT') + self.assertEqual(repr(proto), '<_SSLMethod.PROTOCOL_TLS_CLIENT: 16>') + self.assertEqual(str(proto), '16') ctx = ssl.SSLContext(proto) self.assertIs(ctx.protocol, proto) @@ -622,7 +623,7 @@ class BasicSocketTests(unittest.TestCase): with self.assertWarns(DeprecationWarning) as cm: ssl.SSLContext(protocol) self.assertEqual( - f'{protocol!r} is deprecated', + f'ssl.{protocol.name} is deprecated', str(cm.warning) ) @@ -631,8 +632,9 @@ 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!r} is deprecated', + f'ssl.{version_text} is deprecated', str(cm.warning) ) diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index d5e2c52..8e4e648 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -1490,8 +1490,10 @@ 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. @@ -1508,8 +1510,10 @@ 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}, - '...IDES...') + '...15...') 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 new file mode 100644 index 0000000..2df4878 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-13-11-41-24.bpo-40066.1QuVli.rst @@ -0,0 +1,2 @@ +``IntEnum``, ``IntFlag``, and ``StrEnum`` use the mixed-in type for their +``str()`` and ``format()`` output. -- cgit v0.12