From 9bf7c2d638a582af2444bc864feba13ab8957b68 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Sat, 3 Jul 2021 21:08:42 -0700 Subject: [3.10] bpo-44559: [Enum] revert enum module to 3.9 (GH-27010) * [Enum] revert enum module to 3.9 --- Doc/howto/enum.rst | 1416 ------------------------- Doc/howto/index.rst | 1 - Doc/library/enum.rst | 1566 ++++++++++++++++++---------- Doc/library/http.rst | 4 +- Doc/library/socket.rst | 4 +- Doc/library/ssl.rst | 4 +- Doc/whatsnew/3.10.rst | 11 - Lib/ast.py | 5 +- Lib/enum.py | 1465 ++++++-------------------- Lib/http/__init__.py | 5 +- Lib/inspect.py | 3 + Lib/plistlib.py | 3 +- Lib/pstats.py | 5 +- Lib/re.py | 28 +- Lib/ssl.py | 13 +- Lib/test/test_ast.py | 30 - Lib/test/test_enum.py | 1360 +++++------------------- Lib/test/test_httplib.py | 145 --- Lib/test/test_pstats.py | 27 - Lib/test/test_pydoc.py | 2 +- Lib/test/test_re.py | 8 +- Lib/test/test_signal.py | 29 +- Lib/test/test_socket.py | 43 +- Lib/test/test_ssl.py | 156 +-- Lib/test/test_unicode.py | 11 +- Lib/test/test_uuid.py | 8 - Lib/tkinter/__init__.py | 5 +- Lib/tkinter/test/test_tkinter/test_misc.py | 44 - Lib/uuid.py | 5 +- 29 files changed, 1662 insertions(+), 4744 deletions(-) delete mode 100644 Doc/howto/enum.rst diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst deleted file mode 100644 index b0eea55..0000000 --- a/Doc/howto/enum.rst +++ /dev/null @@ -1,1416 +0,0 @@ -========== -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. - -They are most useful when you have a variable that can take one of a limited -selection of values. For example, the days of the week:: - - >>> from enum import Enum - >>> class Weekday(Enum): - ... MONDAY = 1 - ... TUESDAY = 2 - ... WEDNESDAY = 3 - ... THURSDAY = 4 - ... FRIDAY = 5 - ... SATURDAY = 6 - ... SUNDAY = 7 - -As you can see, creating an :class:`Enum` is as simple as writing a class that -inherits from :class:`Enum` itself. - -.. note:: Case of Enum Members - - Because Enums are used to represent constants we recommend using - UPPER_CASE names for members, and will be using that style in our examples. - -Depending on the nature of the enum a member's value may or may not be -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:: - - >>> print(Weekday.THURSDAY) - THURSDAY - -The *type* of an enumeration member is the enum it belongs to:: - - >>> type(Weekday.MONDAY) - - >>> isinstance(Weekday.FRIDAY, Weekday) - True - -Enum members have an attribute that contains just their :attr:`name`:: - - >>> print(Weekday.TUESDAY.name) - TUESDAY - -Likewise, they have an attribute for their :attr:`value`:: - - - >>> Weekday.WEDNESDAY.value - 3 - -Unlike many languages that treat enumerations solely as name/value pairs, -Python Enums can have behavior added. For example, :class:`datetime.date` -has two methods for returning the weekday: :meth:`weekday` and :meth:`isoweekday`. -The difference is that one of them counts from 0-6 and the other from 1-7. -Rather than keep track of that ourselves we can add a method to the :class:`Weekday` -enum to extract the day from the :class:`date` instance and return the matching -enum member:: - - @classmethod - def from_date(cls, date): - return cls(date.isoweekday()) - -The complete :class:`Weekday` enum now looks like this:: - - >>> class Weekday(Enum): - ... MONDAY = 1 - ... TUESDAY = 2 - ... WEDNESDAY = 3 - ... THURSDAY = 4 - ... FRIDAY = 5 - ... SATURDAY = 6 - ... SUNDAY = 7 - ... # - ... @classmethod - ... def from_date(cls, date): - ... return cls(date.isoweekday()) - -Now we can find out what today is! Observe:: - - >>> from datetime import date - >>> Weekday.from_date(date.today()) - Weekday.TUESDAY - -Of course, if you're reading this on some other day, you'll see that day instead. - -This :class:`Weekday` enum is great if our variable only needs one day, but -what if we need several? Maybe we're writing a function to plot chores during -a week, and don't want to use a :class:`list` -- we could use a different type -of :class:`Enum`:: - - >>> from enum import Flag - >>> class Weekday(Flag): - ... MONDAY = 1 - ... TUESDAY = 2 - ... WEDNESDAY = 4 - ... THURSDAY = 8 - ... FRIDAY = 16 - ... SATURDAY = 32 - ... SUNDAY = 64 - -We've changed two things: we're inherited from :class:`Flag`, and the values are -all powers of 2. - -Just like the original :class:`Weekday` enum above, we can have a single selection:: - - >>> 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 - -Okay, let's get some chores set up:: - - >>> chores_for_ethan = { - ... 'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY, - ... 'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY, - ... 'answer SO questions': Weekday.SATURDAY, - ... } - -And a function to display the chores for a given day:: - - >>> def show_chores(chores, day): - ... for chore, days in chores.items(): - ... if day in days: - ... print(chore) - >>> show_chores(chores_for_ethan, Weekday.SATURDAY) - answer SO questions - -In cases where the actual values of the members do not matter, you can save -yourself some work and use :func:`auto()` for the values:: - - >>> from enum import auto - >>> class Weekday(Flag): - ... MONDAY = auto() - ... TUESDAY = auto() - ... WEDNESDAY = auto() - ... THURSDAY = auto() - ... FRIDAY = auto() - ... SATURDAY = auto() - ... SUNDAY = auto() - - -.. _enum-advanced-tutorial: - -Programmatic access to enumeration members and their attributes ---------------------------------------------------------------- - -Sometimes it's useful to access members in enumerations programmatically (i.e. -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`:: - - >>> member = Color.RED - >>> member.name - 'RED' - >>> member.value - 1 - - -Duplicating enum members and values ------------------------------------ - -Having two enum members with the same name is invalid:: - - >>> class Shape(Enum): - ... SQUARE = 2 - ... SQUARE = 3 - ... - Traceback (most recent call last): - ... - 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`` -is an alias for the member ``A``. By-value lookup of the value of ``A`` will -return the member ``A``. By-name lookup of ``A`` will return the member ``A``. -By-name lookup of ``B`` will also return the member ``A``:: - - >>> class Shape(Enum): - ... SQUARE = 2 - ... DIAMOND = 1 - ... CIRCLE = 3 - ... ALIAS_FOR_SQUARE = 2 - ... - >>> Shape.SQUARE - Shape.SQUARE - >>> Shape.ALIAS_FOR_SQUARE - Shape.SQUARE - >>> Shape(2) - Shape.SQUARE - -.. note:: - - Attempting to create a member with the same name as an already - defined attribute (another member, a method, etc.) or attempting to create - an attribute with the same name as a member is not allowed. - - -Ensuring unique enumeration values ----------------------------------- - -By default, enumerations allow multiple names as aliases for the same value. -When this behavior isn't desired, you can use the :func:`unique` decorator:: - - >>> from enum import Enum, unique - >>> @unique - ... class Mistake(Enum): - ... ONE = 1 - ... TWO = 2 - ... THREE = 3 - ... FOUR = 3 - ... - Traceback (most recent call last): - ... - ValueError: duplicate values found in : FOUR -> THREE - - -Using automatic values ----------------------- - -If the exact value is unimportant you can use :class:`auto`:: - - >>> from enum import Enum, auto - >>> class Color(Enum): - ... RED = auto() - ... BLUE = auto() - ... GREEN = auto() - ... - >>> [member.value for member in Color] - [1, 2, 3] - -The values are chosen by :func:`_generate_next_value_`, which can be -overridden:: - - >>> class AutoName(Enum): - ... def _generate_next_value_(name, start, count, last_values): - ... return name - ... - >>> class Ordinal(AutoName): - ... NORTH = auto() - ... SOUTH = auto() - ... EAST = auto() - ... WEST = auto() - ... - >>> [member.value for member in Ordinal] - ['NORTH', 'SOUTH', 'EAST', 'WEST'] - -.. note:: - - The :meth:`_generate_next_value_` method must be defined before any members. - -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 -aliases:: - - >>> for name, member in Shape.__members__.items(): - ... name, member - ... - ('SQUARE', Shape.SQUARE) - ('DIAMOND', Shape.DIAMOND) - ('CIRCLE', Shape.CIRCLE) - ('ALIAS_FOR_SQUARE', Shape.SQUARE) - -The ``__members__`` attribute can be used for detailed programmatic access to -the enumeration members. For example, finding all the aliases:: - - >>> [name for name, member in Shape.__members__.items() if member.name != name] - ['ALIAS_FOR_SQUARE'] - - -Comparisons ------------ - -Enumeration members are compared by identity:: - - >>> Color.RED is Color.RED - True - >>> Color.RED is Color.BLUE - False - >>> Color.RED is not Color.BLUE - True - -Ordered comparisons between enumeration values are *not* supported. Enum -members are not integers (but see `IntEnum`_ below):: - - >>> Color.RED < Color.BLUE - Traceback (most recent call last): - File "", line 1, in - TypeError: '<' not supported between instances of 'Color' and 'Color' - -Equality comparisons are defined though:: - - >>> Color.BLUE == Color.RED - False - >>> Color.BLUE != Color.RED - True - >>> Color.BLUE == Color.BLUE - True - -Comparisons against non-enumeration values will always compare not equal -(again, :class:`IntEnum` was explicitly designed to behave differently, see -below):: - - >>> Color.BLUE == 2 - False - - -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 -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. - -Enumerations are Python classes, and can have methods and special methods as -usual. If we have this enumeration:: - - >>> class Mood(Enum): - ... FUNKY = 1 - ... HAPPY = 3 - ... - ... def describe(self): - ... # self is the member here - ... return self.name, self.value - ... - ... def __str__(self): - ... return 'my custom str! {0}'.format(self.value) - ... - ... @classmethod - ... def favorite_mood(cls): - ... # cls here is the enumeration - ... return cls.HAPPY - ... - -Then:: - - >>> Mood.favorite_mood() - Mood.HAPPY - >>> Mood.HAPPY.describe() - ('HAPPY', 3) - >>> str(Mood.FUNKY) - 'my custom str! 1' - -The rules for what is allowed are as follows: names that start and end with -a single underscore are reserved by enum and cannot be used; all other -attributes defined within an enumeration will become members of this -enumeration, with the exception of special methods (:meth:`__str__`, -:meth:`__add__`, etc.), descriptors (methods are also descriptors), and -variable names listed in :attr:`_ignore_`. - -Note: if your enumeration defines :meth:`__new__` and/or :meth:`__init__` then -any value(s) given to the enum member will be passed into those methods. -See `Planet`_ for an example. - - -Restricted Enum subclassing ---------------------------- - -A new :class:`Enum` class must have one base enum class, up to one concrete -data type, and as many :class:`object`-based mixin classes as needed. The -order of these base classes is:: - - class EnumName([mix-in, ...,] [data-type,] base-enum): - pass - -Also, subclassing an enumeration is allowed only if the enumeration does not define -any members. So this is forbidden:: - - >>> class MoreColor(Color): - ... PINK = 17 - ... - Traceback (most recent call last): - ... - TypeError: MoreColor: cannot extend enumeration 'Color' - -But this is allowed:: - - >>> class Foo(Enum): - ... def some_behavior(self): - ... pass - ... - >>> class Bar(Foo): - ... HAPPY = 1 - ... SAD = 2 - ... - -Allowing subclassing of enums that define members would lead to a violation of -some important invariants of types and instances. On the other hand, it makes -sense to allow sharing some common behavior between a group of enumerations. -(See `OrderedEnum`_ for an example.) - - -Pickling --------- - -Enumerations can be pickled and unpickled:: - - >>> from test.test_enum import Fruit - >>> from pickle import dumps, loads - >>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO)) - True - -The usual restrictions for pickling apply: picklable enums must be defined in -the top level of a module, since unpickling requires them to be importable -from that module. - -.. note:: - - With pickle protocol version 4 it is possible to easily pickle enums - nested in other classes. - -It is possible to modify how enum members are pickled/unpickled by defining -:meth:`__reduce_ex__` in the enumeration class. - - -Functional API --------------- - -The :class:`Enum` class is callable, providing the following functional API:: - - >>> Animal = Enum('Animal', 'ANT BEE CAT DOG') - >>> 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. - -The second argument is the *source* of enumeration member names. It can be a -whitespace-separated string of names, a sequence of names, a sequence of -2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to -values. The last two options enable assigning arbitrary values to -enumerations; the others auto-assign increasing integers starting with 1 (use -the ``start`` parameter to specify a different starting value). A -new class derived from :class:`Enum` is returned. In other words, the above -assignment to :class:`Animal` is equivalent to:: - - >>> class Animal(Enum): - ... ANT = 1 - ... BEE = 2 - ... CAT = 3 - ... DOG = 4 - ... - -The reason for defaulting to ``1`` as the starting number and not ``0`` is -that ``0`` is ``False`` in a boolean sense, but by default enum members all -evaluate to ``True``. - -Pickling enums created with the functional API can be tricky as frame stack -implementation details are used to try and figure out which module the -enumeration is being created in (e.g. it will fail if you use a utility -function in separate module, and also may not work on IronPython or Jython). -The solution is to specify the module name explicitly as follows:: - - >>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__) - -.. warning:: - - If ``module`` is not supplied, and Enum cannot determine what it is, - the new Enum members will not be unpicklable; to keep errors closer to - the source, pickling will be disabled. - -The new pickle protocol 4 also, in some circumstances, relies on -:attr:`~definition.__qualname__` being set to the location where pickle will be able -to find the class. For example, if the class was made available in class -SomeData in the global scope:: - - >>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal') - -The complete signature is:: - - Enum( - value='NewEnumName', - names=<...>, - *, - module='...', - qualname='...', - type=, - start=1, - ) - -:value: What the new enum class will record as its name. - -:names: The enum members. This can be a whitespace or comma separated string - (values will start at 1 unless otherwise specified):: - - 'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE' - - or an iterator of names:: - - ['RED', 'GREEN', 'BLUE'] - - or an iterator of (name, value) pairs:: - - [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)] - - or a mapping:: - - {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42} - -:module: name of module where new enum class can be found. - -:qualname: where in module new enum class can be found. - -:type: type to mix in to new enum class. - -:start: number to start counting at if only names are passed in. - -.. versionchanged:: 3.5 - The *start* parameter was added. - - -Derived Enumerations --------------------- - -IntEnum -^^^^^^^ - -The first variation of :class:`Enum` that is provided is also a subclass of -:class:`int`. Members of an :class:`IntEnum` can be compared to integers; -by extension, integer enumerations of different types can also be compared -to each other:: - - >>> from enum import IntEnum - >>> class Shape(IntEnum): - ... CIRCLE = 1 - ... SQUARE = 2 - ... - >>> class Request(IntEnum): - ... POST = 1 - ... GET = 2 - ... - >>> Shape == 1 - False - >>> Shape.CIRCLE == 1 - True - >>> Shape.CIRCLE == Request.POST - True - -However, they still can't be compared to standard :class:`Enum` enumerations:: - - >>> class Shape(IntEnum): - ... CIRCLE = 1 - ... SQUARE = 2 - ... - >>> class Color(Enum): - ... RED = 1 - ... GREEN = 2 - ... - >>> Shape.CIRCLE == Color.RED - False - -:class:`IntEnum` values behave like integers in other ways you'd expect:: - - >>> int(Shape.CIRCLE) - 1 - >>> ['a', 'b', 'c'][Shape.CIRCLE] - 'b' - >>> [i for i in range(Shape.SQUARE)] - [0, 1] - - -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',)``. - -.. versionadded:: 3.10 - - -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. - -.. note:: - - Any operation on an :class:`IntFlag` member besides the bit-wise operations will - lose the :class:`IntFlag` membership. - - Bit-wise operations that result in invalid :class:`IntFlag` values will lose the - :class:`IntFlag` membership. See :class:`FlagBoundary` for - details. - -.. versionadded:: 3.6 -.. versionchanged:: 3.10 - -Sample :class:`IntFlag` class:: - - >>> from enum import IntFlag - >>> class Perm(IntFlag): - ... R = 4 - ... W = 2 - ... X = 1 - ... - >>> Perm.R | Perm.W - Perm.R|Perm.W - >>> Perm.R + Perm.W - 6 - >>> RW = Perm.R | Perm.W - >>> Perm.R in RW - True - -It is also possible to name the combinations:: - - >>> class Perm(IntFlag): - ... R = 4 - ... W = 2 - ... X = 1 - ... RWX = 7 - >>> Perm.RWX - Perm.RWX - >>> ~Perm.RWX - Perm(0) - >>> Perm(7) - Perm.RWX - -.. note:: - - Named combinations are considered aliases. Aliases do not show up during - iteration, but can be returned from by-value lookups. - -.. versionchanged:: 3.10 - -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 - -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 - -.. note:: - - The negation operator, ``~``, always returns an :class:`IntFlag` member with a - positive value:: - - >>> (~Perm.X).value == (Perm.R|Perm.W).value == 6 - True - -:class:`IntFlag` members can also be iterated over:: - - >>> list(RW) - [Perm.R, Perm.W] - -.. versionadded:: 3.10 - - -Flag -^^^^ - -The last variation is :class:`Flag`. Like :class:`IntFlag`, :class:`Flag` -members can be combined using the bitwise operators (&, \|, ^, ~). Unlike -:class:`IntFlag`, they cannot be combined with, nor compared against, any -other :class:`Flag` enumeration, nor :class:`int`. While it is possible to -specify the values directly it is recommended to use :class:`auto` as the -value and let :class:`Flag` select an appropriate value. - -.. versionadded:: 3.6 - -Like :class:`IntFlag`, if a combination of :class:`Flag` members results in no -flags being set, the boolean evaluation is :data:`False`:: - - >>> from enum import Flag, auto - >>> class Color(Flag): - ... RED = auto() - ... BLUE = auto() - ... GREEN = auto() - ... - >>> Color.RED & Color.GREEN - Color(0) - >>> bool(Color.RED & Color.GREEN) - False - -Individual flags should have values that are powers of two (1, 2, 4, 8, ...), -while combinations of flags won't:: - - >>> class Color(Flag): - ... RED = auto() - ... BLUE = auto() - ... GREEN = auto() - ... WHITE = RED | BLUE | GREEN - ... - >>> Color.WHITE - Color.WHITE - -Giving a name to the "no flags set" condition does not change its boolean -value:: - - >>> class Color(Flag): - ... BLACK = 0 - ... RED = auto() - ... BLUE = auto() - ... GREEN = auto() - ... - >>> Color.BLACK - Color.BLACK - >>> bool(Color.BLACK) - False - -:class:`Flag` members can also be iterated over:: - - >>> purple = Color.RED | Color.BLUE - >>> list(purple) - [Color.RED, Color.BLUE] - -.. versionadded:: 3.10 - -.. note:: - - For the majority of new code, :class:`Enum` and :class:`Flag` are strongly - recommended, since :class:`IntEnum` and :class:`IntFlag` break some - semantic promises of an enumeration (by being comparable to integers, and - thus by transitivity to other unrelated enumerations). :class:`IntEnum` - and :class:`IntFlag` should be used only in cases where :class:`Enum` and - :class:`Flag` will not do; for example, when integer constants are replaced - with enumerations, or for interoperability with other systems. - - -Others -^^^^^^ - -While :class:`IntEnum` is part of the :mod:`enum` module, it would be very -simple to implement independently:: - - class IntEnum(int, Enum): - pass - -This demonstrates how similar derived enumerations can be defined; for example -a :class:`StrEnum` that mixes in :class:`str` instead of :class:`int`. - -Some rules: - -1. When subclassing :class:`Enum`, mix-in types must appear before - :class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum` - example above. -2. 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 - add methods and don't specify another type. -3. 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. -4. %-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. -5. :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. - -When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------- - -:meth:`__new__` must be used whenever you want to customize the actual value of -the :class:`Enum` member. Any other modifications may go in either -:meth:`__new__` or :meth:`__init__`, with :meth:`__init__` being preferred. - -For example, if you want to pass several items to the constructor, but only -want one of them to be the value:: - - >>> class Coordinate(bytes, Enum): - ... """ - ... Coordinate with binary codes that can be indexed by the int code. - ... """ - ... def __new__(cls, value, label, unit): - ... obj = bytes.__new__(cls, [value]) - ... obj._value_ = value - ... obj.label = label - ... obj.unit = unit - ... return obj - ... PX = (0, 'P.X', 'km') - ... PY = (1, 'P.Y', 'km') - ... VX = (2, 'V.X', 'km/s') - ... VY = (3, 'V.Y', 'km/s') - ... - - >>> print(Coordinate['PY']) - PY - - >>> print(Coordinate(3)) - VY - - -Finer Points -^^^^^^^^^^^^ - -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 by the `Functional API`_ and by - :class:`auto` 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_`` - -To help keep Python 2 / Python 3 code in sync an :attr:`_order_` attribute can -be provided. It will be checked against the actual order of the enumeration -and raise an error if the two do not match:: - - >>> class Color(Enum): - ... _order_ = 'RED GREEN BLUE' - ... RED = 1 - ... BLUE = 3 - ... GREEN = 2 - ... - Traceback (most recent call last): - ... - TypeError: member order does not match _order_: - ['RED', 'BLUE', 'GREEN'] - ['RED', 'GREEN', 'BLUE'] - -.. note:: - - In Python 2 code the :attr:`_order_` attribute is necessary as definition - order is lost before it can be recorded. - - -_Private__names -""""""""""""""" - -Private names are not converted to enum members, but remain normal attributes. - -.. versionchanged:: 3.10 - - -``Enum`` member type -"""""""""""""""""""" - -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`:: - - >>> 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 - - -.. versionchanged:: 3.5 -.. versionchanged:: 3.10 - - -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 -constructor. For example:: - - >>> class MyEnum(IntEnum): - ... example = '11', 16 # '11' will be interpreted as a hexadecimal - ... # number - >>> MyEnum.example.value - 17 - - -Boolean value of ``Enum`` classes and members -""""""""""""""""""""""""""""""""""""""""""""" - -Enum classes that are mixed with non-:class:`Enum` types (such as -:class:`int`, :class:`str`, etc.) are evaluated according to the mixed-in -type's rules; otherwise, all members evaluate as :data:`True`. To make your -own enum's boolean evaluation depend on the member's value add the following to -your class:: - - def __bool__(self): - return bool(self.value) - -Plain :class:`Enum` classes always evaluate as :data:`True`. - - -``Enum`` classes with methods -""""""""""""""""""""""""""""" - -If you give your enum subclass extra methods, like the `Planet`_ -class above, 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__', '__members__', '__module__'] - >>> dir(Planet.EARTH) - ['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value'] - - -Combining members of ``Flag`` -""""""""""""""""""""""""""""" - -Iterating over a combination of :class:`Flag` members will only return the members that -are comprised of a single bit:: - - >>> class Color(Flag): - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - ... MAGENTA = RED | BLUE - ... YELLOW = RED | GREEN - ... 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 -"""""""""""""""""""""""""""""""" - -Using the following snippet for our examples:: - - >>> class Color(IntFlag): - ... BLACK = 0 - ... RED = 1 - ... GREEN = 2 - ... BLUE = 4 - ... PURPLE = RED | BLUE - ... WHITE = RED | GREEN | BLUE - ... - -the following are true: - -- single-bit flags are canonical -- multi-bit and zero-bit flags are aliases -- 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:: - - >>> (Color.RED | Color.GREEN).name - 'RED|GREEN' - -- 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:: - - >>> Color.BLACK in Color.WHITE - False - - otherwise, if all bits of one flag are in the other flag, True is returned:: - - >>> Color.PURPLE in Color.WHITE - True - -There is a new boundary mechanism that controls how out-of-range / invalid -bits are handled: ``STRICT``, ``CONFORM``, ``EJECT``, and ``KEEP``: - - * STRICT --> raises an exception when presented with invalid values - * CONFORM --> discards any invalid bits - * EJECT --> lose Flag status and become a normal int with the given value - * KEEP --> keep the extra bits - - keeps Flag status and extra bits - - extra bits do not show up in iteration - - extra bits do show up in repr() and str() - -The default for Flag is ``STRICT``, the default for ``IntFlag`` is ``EJECT``, -and the default for ``_convert_`` is ``KEEP`` (see ``ssl.Options`` for an -example of when ``KEEP`` is needed). - - -.. _enum-class-differences: - -How are Enums different? ------------------------- - -Enums have a custom metaclass that affects many aspects of both derived :class:`Enum` -classes and their instances (members). - - -Enum Classes -^^^^^^^^^^^^ - -The :class:`EnumType` metaclass is responsible for providing the -:meth:`__contains__`, :meth:`__dir__`, :meth:`__iter__` and other methods that -allow one to do things with an :class:`Enum` class that fail on a typical -class, such as `list(Color)` or `some_enum_var in Color`. :class:`EnumType` is -responsible for ensuring that various other methods on the final :class:`Enum` -class are correct (such as :meth:`__new__`, :meth:`__getnewargs__`, -:meth:`__str__` and :meth:`__repr__`). - - -Enum Members (aka instances) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The most interesting thing about enum members is that they are singletons. -:class:`EnumType` creates them all while it is creating the enum class itself, -and then puts a custom :meth:`__new__` in place to ensure that no new ones are -ever instantiated by returning only the existing member instances. - - -.. _enum-cookbook: - - -While :class:`Enum`, :class:`IntEnum`, :class:`StrEnum`, :class:`Flag`, and -:class:`IntFlag` are expected to cover the majority of use-cases, they cannot -cover them all. Here are recipes for some different types of enumerations -that can be used directly, or as examples for creating one's own. - - -Omitting values -^^^^^^^^^^^^^^^ - -In many use-cases one doesn't care what the actual value of an enumeration -is. There are several ways to define this type of simple enumeration: - -- use instances of :class:`auto` for the value -- use instances of :class:`object` as the value -- use a descriptive string as the value -- use a tuple as the value and a custom :meth:`__new__` to replace the - tuple with an :class:`int` value - -Using any of these methods signifies to the user that these values are not -important, and also enables one to add, remove, or reorder members without -having to renumber the remaining members. - - -Using :class:`auto` -""""""""""""""""""" - -Using :class:`auto` would look like:: - - >>> class Color(Enum): - ... RED = auto() - ... BLUE = auto() - ... GREEN = auto() - ... - >>> Color.GREEN - - - -Using :class:`object` -""""""""""""""""""""" - -Using :class:`object` would look like:: - - >>> class Color(Enum): - ... RED = object() - ... GREEN = object() - ... BLUE = object() - ... - >>> Color.GREEN - - - -Using a descriptive string -"""""""""""""""""""""""""" - -Using a string as the value would look like:: - - >>> class Color(Enum): - ... RED = 'stop' - ... GREEN = 'go' - ... BLUE = 'too fast!' - ... - >>> Color.GREEN - - >>> Color.GREEN.value - 'go' - - -Using a custom :meth:`__new__` -"""""""""""""""""""""""""""""" - -Using an auto-numbering :meth:`__new__` would look like:: - - >>> class AutoNumber(Enum): - ... def __new__(cls): - ... value = len(cls.__members__) + 1 - ... obj = object.__new__(cls) - ... obj._value_ = value - ... return obj - ... - >>> class Color(AutoNumber): - ... RED = () - ... GREEN = () - ... BLUE = () - ... - >>> Color.GREEN - - >>> Color.GREEN.value - 2 - -To make a more general purpose ``AutoNumber``, add ``*args`` to the signature:: - - >>> class AutoNumber(Enum): - ... def __new__(cls, *args): # this is the only change from above - ... value = len(cls.__members__) + 1 - ... obj = object.__new__(cls) - ... obj._value_ = value - ... return obj - ... - -Then when you inherit from ``AutoNumber`` you can write your own ``__init__`` -to handle any extra arguments:: - - >>> class Swatch(AutoNumber): - ... def __init__(self, pantone='unknown'): - ... self.pantone = pantone - ... AUBURN = '3497' - ... SEA_GREEN = '1246' - ... BLEACHED_CORAL = () # New color, no Pantone code yet! - ... - >>> Swatch.SEA_GREEN - - >>> Swatch.SEA_GREEN.pantone - '1246' - >>> Swatch.BLEACHED_CORAL.pantone - 'unknown' - -.. note:: - - The :meth:`__new__` method, if defined, is used during creation of the Enum - members; it is then replaced by Enum's :meth:`__new__` which is used after - class creation for lookup of existing members. - - -OrderedEnum -^^^^^^^^^^^ - -An ordered enumeration that is not based on :class:`IntEnum` and so maintains -the normal :class:`Enum` invariants (such as not being comparable to other -enumerations):: - - >>> class OrderedEnum(Enum): - ... def __ge__(self, other): - ... if self.__class__ is other.__class__: - ... return self.value >= other.value - ... return NotImplemented - ... def __gt__(self, other): - ... if self.__class__ is other.__class__: - ... return self.value > other.value - ... return NotImplemented - ... def __le__(self, other): - ... if self.__class__ is other.__class__: - ... return self.value <= other.value - ... return NotImplemented - ... def __lt__(self, other): - ... if self.__class__ is other.__class__: - ... return self.value < other.value - ... return NotImplemented - ... - >>> class Grade(OrderedEnum): - ... A = 5 - ... B = 4 - ... C = 3 - ... D = 2 - ... F = 1 - ... - >>> Grade.C < Grade.A - True - - -DuplicateFreeEnum -^^^^^^^^^^^^^^^^^ - -Raises an error if a duplicate member name is found instead of creating an -alias:: - - >>> class DuplicateFreeEnum(Enum): - ... def __init__(self, *args): - ... cls = self.__class__ - ... if any(self.value == e.value for e in cls): - ... a = self.name - ... e = cls(self.value).name - ... raise ValueError( - ... "aliases not allowed in DuplicateFreeEnum: %r --> %r" - ... % (a, e)) - ... - >>> class Color(DuplicateFreeEnum): - ... RED = 1 - ... GREEN = 2 - ... BLUE = 3 - ... GRENE = 2 - ... - Traceback (most recent call last): - ... - ValueError: aliases not allowed in DuplicateFreeEnum: 'GRENE' --> 'GREEN' - -.. note:: - - This is a useful example for subclassing Enum to add or change other - behaviors as well as disallowing aliases. If the only desired change is - disallowing aliases, the :func:`unique` decorator can be used instead. - - -Planet -^^^^^^ - -If :meth:`__new__` or :meth:`__init__` is defined the value of the enum member -will be passed to those methods:: - - >>> class Planet(Enum): - ... MERCURY = (3.303e+23, 2.4397e6) - ... VENUS = (4.869e+24, 6.0518e6) - ... EARTH = (5.976e+24, 6.37814e6) - ... MARS = (6.421e+23, 3.3972e6) - ... JUPITER = (1.9e+27, 7.1492e7) - ... SATURN = (5.688e+26, 6.0268e7) - ... URANUS = (8.686e+25, 2.5559e7) - ... NEPTUNE = (1.024e+26, 2.4746e7) - ... def __init__(self, mass, radius): - ... self.mass = mass # in kilograms - ... self.radius = radius # in meters - ... @property - ... def surface_gravity(self): - ... # universal gravitational constant (m3 kg-1 s-2) - ... G = 6.67300E-11 - ... return G * self.mass / (self.radius * self.radius) - ... - >>> Planet.EARTH.value - (5.976e+24, 6378140.0) - >>> Planet.EARTH.surface_gravity - 9.802652743337129 - -.. _enum-time-period: - -TimePeriod -^^^^^^^^^^ - -An example to show the :attr:`_ignore_` attribute in use:: - - >>> from datetime import timedelta - >>> class Period(timedelta, Enum): - ... "different lengths of time" - ... _ignore_ = 'Period i' - ... Period = vars() - ... for i in range(367): - ... 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 -^^^^^^^^^^^^^^^^^^^^^^^^ - -Creating a :class:`Flag` enum that is more resilient out-of-bounds results to -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: - -Subclassing EnumType --------------------- - -While most enum needs can be met by customizing :class:`Enum` subclasses, -either with class decorators or custom functions, :class:`EnumType` can be -subclassed to provide a different Enum experience. - diff --git a/Doc/howto/index.rst b/Doc/howto/index.rst index eae8f14..01a78a5 100644 --- a/Doc/howto/index.rst +++ b/Doc/howto/index.rst @@ -17,7 +17,6 @@ Currently, the HOWTOs are: cporting.rst curses.rst descriptor.rst - enum.rst functional.rst logging.rst logging-cookbook.rst diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index d3df151b..e8e4942 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -13,722 +13,1190 @@ **Source code:** :source:`Lib/enum.py` -.. sidebar:: Important +---------------- - This page contains the API reference information. For tutorial - information and discussion of more advanced topics, see +An enumeration is a set of symbolic names (members) bound to unique, +constant values. Within an enumeration, the members can be compared +by identity, and the enumeration itself can be iterated over. - * :ref:`Basic Tutorial ` - * :ref:`Advanced Tutorial ` - * :ref:`Enum Cookbook ` +.. note:: Case of Enum Members ---------------- - -An enumeration: - -* is a set of symbolic names (members) bound to unique values -* can be iterated over to return its members in definition order -* 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 -using function-call syntax:: - - >>> from enum import Enum - - >>> # class syntax - >>> class Color(Enum): - ... RED = 1 - ... GREEN = 2 - ... BLUE = 3 - - >>> # functional syntax - >>> Color = Enum('Color', ['RED', 'GREEN', 'BLUE']) - -Even though we can use the :keyword:`class` syntax to create Enums, Enums -are not normal Python classes. See -:ref:`How are Enums different? ` for more details. + Because Enums are used to represent constants we recommend using + UPPER_CASE names for enum members, and will be using that style + in our examples. -.. note:: Nomenclature - - - 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. - - The enum members have *names* and *values* (the name of - :attr:`Color.RED` is ``RED``, the value of :attr:`Color.BLUE` is - ``3``, etc.) - ---------------- Module Contents --------------- - :class:`EnumType` - - The ``type`` for Enum and its subclasses. - - :class:`Enum` - - Base class for creating enumerated constants. - - :class:`IntEnum` +This module defines four enumeration classes that can be used to define unique +sets of names and values: :class:`Enum`, :class:`IntEnum`, :class:`Flag`, and +:class:`IntFlag`. It also defines one decorator, :func:`unique`, and one +helper, :class:`auto`. - Base class for creating enumerated constants that are also - subclasses of :class:`int`. (`Notes`_) - - :class:`StrEnum` - - Base class for creating enumerated constants that are also - subclasses of :class:`str`. (`Notes`_) - - :class:`Flag` - - Base class for creating enumerated constants that can be combined using - the bitwise operations without losing their :class:`Flag` membership. - - :class:`IntFlag` - - Base class for creating enumerated constants that can be combined using - the bitwise operators without losing their :class:`IntFlag` membership. - :class:`IntFlag` members are also subclasses of :class:`int`. (`Notes`_) - - :class:`EnumCheck` - - An enumeration with the values ``CONTINUOUS``, ``NAMED_FLAGS``, and - ``UNIQUE``, for use with :func:`verify` to ensure various constraints - are met by a given enumeration. - - :class:`FlagBoundary` - - An enumeration with the values ``STRICT``, ``CONFORM``, ``EJECT``, and - ``KEEP`` which allows for more fine-grained control over how invalid values - are dealt with in an enumeration. +.. class:: Enum - :class:`auto` + Base class for creating enumerated constants. See section + `Functional API`_ for an alternate construction syntax. - Instances are replaced with an appropriate value for Enum members. - :class:`StrEnum` defaults to the lower-cased version of the member name, - while other Enums default to 1 and increase from there. +.. class:: IntEnum - :func:`global_enum` + Base class for creating enumerated constants that are also + subclasses of :class:`int`. - :class:`Enum` class decorator to apply the appropriate global `__repr__`, - and export its members into the global name space. +.. class:: IntFlag - :func:`.property` + Base class for creating enumerated constants that can be combined using + the bitwise operators without losing their :class:`IntFlag` membership. + :class:`IntFlag` members are also subclasses of :class:`int`. - Allows :class:`Enum` members to have attributes without conflicting with - other members' names. +.. class:: Flag - :func:`unique` + Base class for creating enumerated constants that can be combined using + the bitwise operations without losing their :class:`Flag` membership. - Enum class decorator that ensures only one name is bound to any one value. +.. function:: unique + :noindex: - :func:`verify` + Enum class decorator that ensures only one name is bound to any one value. - Enum class decorator that checks user-selectable constraints on an - enumeration. +.. class:: auto + Instances are replaced with an appropriate value for Enum members. By default, the initial value starts at 1. .. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto`` -.. versionadded:: 3.10 ``StrEnum``, ``EnumCheck``, ``FlagBoundary`` ---------------- - -Data Types ----------- - - -.. class:: EnumType - *EnumType* is the :term:`metaclass` for *enum* enumerations. It is possible - to subclass *EnumType* -- see :ref:`Subclassing EnumType ` - for details. +Creating an Enum +---------------- - .. method:: EnumType.__contains__(cls, member) +Enumerations are created using the :keyword:`class` syntax, which makes them +easy to read and write. An alternative creation method is described in +`Functional API`_. To define an enumeration, subclass :class:`Enum` as +follows:: - Returns ``True`` if member belongs to the ``cls``:: + >>> from enum import Enum + >>> class Color(Enum): + ... RED = 1 + ... GREEN = 2 + ... BLUE = 3 + ... - >>> some_var = Color.RED - >>> some_var in Color - True +.. note:: Enum member values - .. note:: + Member values can be anything: :class:`int`, :class:`str`, etc.. If + the exact value is unimportant you may use :class:`auto` instances and an + appropriate value will be chosen for you. Care must be taken if you mix + :class:`auto` with other values. - In Python 3.12 it will be possible to check for member values and not - just members; until then, a ``TypeError`` will be raised if a - non-Enum-member is used in a containment check. +.. note:: Nomenclature - .. method:: EnumType.__dir__(cls) + - 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. + - The enum members have *names* and *values* (the name of + :attr:`Color.RED` is ``RED``, the value of :attr:`Color.BLUE` is + ``3``, etc.) - Returns ``['__class__', '__doc__', '__members__', '__module__']`` and the - names of the members in *cls*:: +.. note:: - >>> dir(Color) - ['BLUE', 'GREEN', 'RED', '__class__', '__doc__', '__members__', '__module__'] + Even though we use the :keyword:`class` syntax to create Enums, Enums + are not normal Python classes. See `How are Enums different?`_ for + more details. - .. method:: EnumType.__getattr__(cls, name) +Enumeration members have human readable string representations:: - Returns the Enum member in *cls* matching *name*, or raises an :exc:`AttributeError`:: + >>> print(Color.RED) + Color.RED - >>> Color.GREEN - Color.GREEN +...while their ``repr`` has more information:: - .. method:: EnumType.__getitem__(cls, name) + >>> print(repr(Color.RED)) + - Returns the Enum member in *cls* matching *name*, or raises an :exc:`KeyError`:: +The *type* of an enumeration member is the enumeration it belongs to:: - >>> Color['BLUE'] - Color.BLUE + >>> type(Color.RED) + + >>> isinstance(Color.GREEN, Color) + True + >>> - .. method:: EnumType.__iter__(cls) +Enum members also have a property that contains just their item name:: - Returns each member in *cls* in definition order:: + >>> print(Color.RED.name) + RED - >>> list(Color) - [Color.RED, Color.GREEN, Color.BLUE] +Enumerations support iteration, in definition order:: - .. method:: EnumType.__len__(cls) + >>> class Shake(Enum): + ... VANILLA = 7 + ... CHOCOLATE = 4 + ... COOKIES = 9 + ... MINT = 3 + ... + >>> for shake in Shake: + ... print(shake) + ... + Shake.VANILLA + Shake.CHOCOLATE + Shake.COOKIES + Shake.MINT - Returns the number of member in *cls*:: +Enumeration members are hashable, so they can be used in dictionaries and sets:: - >>> len(Color) - 3 + >>> apples = {} + >>> apples[Color.RED] = 'red delicious' + >>> apples[Color.GREEN] = 'granny smith' + >>> apples == {Color.RED: 'red delicious', Color.GREEN: 'granny smith'} + True - .. method:: EnumType.__reversed__(cls) - Returns each member in *cls* in reverse definition order:: +Programmatic access to enumeration members and their attributes +--------------------------------------------------------------- - >>> list(reversed(Color)) - [Color.BLUE, Color.GREEN, Color.RED] +Sometimes it's useful to access members in enumerations programmatically (i.e. +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(3) + -.. class:: Enum +If you want to access enum members by *name*, use item access:: - *Enum* is the base class for all *enum* enumerations. + >>> Color['RED'] + + >>> Color['GREEN'] + - .. attribute:: Enum.name +If you have an enum member and need its :attr:`name` or :attr:`value`:: - The name used to define the ``Enum`` member:: + >>> member = Color.RED + >>> member.name + 'RED' + >>> member.value + 1 - >>> Color.BLUE.name - 'BLUE' - .. attribute:: Enum.value +Duplicating enum members and values +----------------------------------- - The value given to the ``Enum`` member:: +Having two enum members with the same name is invalid:: - >>> Color.RED.value - 1 + >>> class Shape(Enum): + ... SQUARE = 2 + ... SQUARE = 3 + ... + Traceback (most recent call last): + ... + TypeError: Attempted to reuse key: 'SQUARE' - .. note:: Enum member values +However, two enum members are allowed to have the same value. Given two members +A and B with the same value (and A defined first), B is an alias to A. By-value +lookup of the value of A and B will return A. By-name lookup of B will also +return A:: - Member values can be anything: :class:`int`, :class:`str`, etc.. If - the exact value is unimportant you may use :class:`auto` instances and an - appropriate value will be chosen for you. Care must be taken if you mix - :class:`auto` with other values. + >>> class Shape(Enum): + ... SQUARE = 2 + ... DIAMOND = 1 + ... CIRCLE = 3 + ... ALIAS_FOR_SQUARE = 2 + ... + >>> Shape.SQUARE + + >>> Shape.ALIAS_FOR_SQUARE + + >>> Shape(2) + - .. attribute:: Enum._ignore_ +.. note:: - ``_ignore_`` is only used during creation and is removed from the - enumeration once that is complete. + Attempting to create a member with the same name as an already + defined attribute (another member, a method, etc.) or attempting to create + an attribute with the same name as a member is not allowed. - ``_ignore_`` is a list of names that will not become members, and whose - names will also be removed from the completed enumeration. See - :ref:`TimePeriod ` for an example. - .. method:: Enum.__call__(cls, value, names=None, \*, module=None, qualname=None, type=None, start=1, boundary=None) +Ensuring unique enumeration values +---------------------------------- - This method is called in two different ways: +By default, enumerations allow multiple names as aliases for the same value. +When this behavior isn't desired, the following decorator can be used to +ensure each value is used only once in the enumeration: - * to look up an existing member: +.. decorator:: unique - :cls: The enum class being called. - :value: The value to lookup. - - * to use the ``cls`` enum to create a new enum: - - :cls: The enum class being called. - :value: The name of the new Enum to create. - :names: The names/values of the members for the new Enum. - :module: The name of the module the new Enum is created in. - :qualname: The actual location in the module where this Enum can be found. - :type: A mix-in type for the new Enum. - :start: The first integer value for the Enum (used by :class:`auto`) - :boundary: How to handle out-of-range values from bit operations (:class:`Flag` only) - - .. method:: Enum.__dir__(self) - - Returns ``['__class__', '__doc__', '__module__', 'name', 'value']`` and - any public methods defined on *self.__class__*:: - - >>> from datetime import date - >>> class Weekday(Enum): - ... MONDAY = 1 - ... TUESDAY = 2 - ... WEDNESDAY = 3 - ... THURSDAY = 4 - ... FRIDAY = 5 - ... SATURDAY = 6 - ... SUNDAY = 7 - ... @classmethod - ... def today(cls): - ... print('today is %s' % cls(date.today.isoweekday).naem) - >>> dir(Weekday.SATURDAY) - ['__class__', '__doc__', '__module__', 'name', 'today', 'value'] - - .. method:: Enum._generate_next_value_(name, start, count, last_values) - - :name: The name of the member being defined (e.g. 'RED'). - :start: The start value for the Enum; the default is 1. - :count: The number of members currently defined, not including this one. - :last_values: A list of the previous values. - - A *staticmethod* that is used to determine the next value returned by - :class:`auto`:: - - >>> from enum import auto - >>> class PowersOfThree(Enum): - ... @staticmethod - ... def _generate_next_value_(name, start, count, last_values): - ... return (count + 1) * 3 - ... FIRST = auto() - ... SECOND = auto() - >>> PowersOfThree.SECOND.value - 6 - - .. method:: Enum._missing_(cls, value) - - A *classmethod* for looking up values not found in *cls*. By default it - does nothing, but can be overridden to implement custom search behavior:: - - >>> from enum import StrEnum - >>> class Build(StrEnum): - ... DEBUG = auto() - ... OPTIMIZED = auto() - ... @classmethod - ... def _missing_(cls, value): - ... value = value.lower() - ... for member in cls: - ... if member.value == value: - ... return member - ... return None - >>> 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:: - - >>> class OldStyle(Enum): - ... RETRO = auto() - ... OLD_SCHOOl = auto() - ... YESTERYEAR = auto() - ... def __repr__(self): - ... cls_name = self.__class__.__name__ - ... return f'<{cls_name}.{self.name}: {self.value}>' - >>> OldStyle.RETRO - - - .. method:: Enum.__str__(self) - - Returns the string used for *str()* calls. By default, returns the - member name, but can be overridden:: - - >>> class OldStyle(Enum): - ... RETRO = auto() - ... OLD_SCHOOl = auto() - ... YESTERYEAR = auto() - ... def __str__(self): - ... cls_name = self.__class__.__name__ - ... return f'{cls_name}.{self.name}' - >>> OldStyle.RETRO - OldStyle.RETRO +A :keyword:`class` decorator specifically for enumerations. It searches an +enumeration's :attr:`__members__` gathering any aliases it finds; if any are +found :exc:`ValueError` is raised with the details:: + + >>> from enum import Enum, unique + >>> @unique + ... class Mistake(Enum): + ... ONE = 1 + ... TWO = 2 + ... THREE = 3 + ... FOUR = 3 + ... + Traceback (most recent call last): + ... + ValueError: duplicate values found in : FOUR -> THREE + + +Using automatic values +---------------------- + +If the exact value is unimportant you can use :class:`auto`:: + + >>> from enum import Enum, auto + >>> class Color(Enum): + ... RED = auto() + ... BLUE = auto() + ... GREEN = auto() + ... + >>> list(Color) + [, , ] + +The values are chosen by :func:`_generate_next_value_`, which can be +overridden:: + + >>> class AutoName(Enum): + ... def _generate_next_value_(name, start, count, last_values): + ... return name + ... + >>> class Ordinal(AutoName): + ... NORTH = auto() + ... SOUTH = auto() + ... EAST = auto() + ... WEST = auto() + ... + >>> list(Ordinal) + [, , , ] .. note:: - Using :class:`auto` with :class:`Enum` results in integers of increasing value, - starting with ``1``. - - -.. class:: IntEnum - - *IntEnum* is the same as *Enum*, but its members are also integers and can be - used anywhere that an integer can be used. If any integer operation is performed - with an *IntEnum* member, the resulting value loses its enumeration status. - - >>> from enum import IntEnum - >>> class Numbers(IntEnum): - ... ONE = 1 - ... TWO = 2 - ... THREE = 3 - >>> Numbers.THREE - Numbers.THREE - >>> Numbers.ONE + Numbers.TWO - 3 - >>> Numbers.THREE + 5 - 8 - >>> Numbers.THREE == 3 - True + The goal of the default :meth:`_generate_next_value_` method is to provide + the next :class:`int` in sequence with the last :class:`int` provided, but + the way it does this is an implementation detail and may change. .. note:: - Using :class:`auto` with :class:`IntEnum` results in integers of increasing value, - starting with ``1``. - - -.. class:: StrEnum + The :meth:`_generate_next_value_` method must be defined before any members. - *StrEnum* is the same as *Enum*, but its members are also strings and can be used - in most of the same places that a string can be used. The result of any string - operation performed on or with a *StrEnum* member is not part of the enumeration. +Iteration +--------- - .. note:: There are places in the stdlib that check for an exact :class:`str` - instead of a :class:`str` subclass (i.e. ``type(unknown) == str`` - instead of ``isinstance(str, unknown)``), and in those locations you - will need to use ``str(StrEnum.member)``. +Iterating over the members of an enum does not provide the aliases:: + + >>> list(Shape) + [, , ] + +The special attribute ``__members__`` is a read-only ordered mapping of names +to members. It includes all names defined in the enumeration, including the +aliases:: + + >>> for name, member in Shape.__members__.items(): + ... name, member + ... + ('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:: -.. note:: + >>> [name for name, member in Shape.__members__.items() if member.name != name] + ['ALIAS_FOR_SQUARE'] - Using :class:`auto` with :class:`StrEnum` results in values of the member name, - lower-cased. +Comparisons +----------- -.. class:: Flag +Enumeration members are compared by identity:: - *Flag* members support the bitwise operators ``&`` (*AND*), ``|`` (*OR*), - ``^`` (*XOR*), and ``~`` (*INVERT*); the results of those operators are members - of the enumeration. + >>> Color.RED is Color.RED + True + >>> Color.RED is Color.BLUE + False + >>> Color.RED is not Color.BLUE + True - .. method:: __contains__(self, value) +Ordered comparisons between enumeration values are *not* supported. Enum +members are not integers (but see `IntEnum`_ below):: + + >>> Color.RED < Color.BLUE + Traceback (most recent call last): + File "", line 1, in + TypeError: '<' not supported between instances of 'Color' and 'Color' + +Equality comparisons are defined though:: + + >>> Color.BLUE == Color.RED + False + >>> Color.BLUE != Color.RED + True + >>> Color.BLUE == Color.BLUE + True - Returns *True* if value is in self:: +Comparisons against non-enumeration values will always compare not equal +(again, :class:`IntEnum` was explicitly designed to behave differently, see +below):: - >>> from enum import Flag, auto - >>> class Color(Flag): - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> purple = Color.RED | Color.BLUE - >>> white = Color.RED | Color.GREEN | Color.BLUE - >>> Color.GREEN in purple - False - >>> Color.GREEN in white - True - >>> purple in white - True - >>> white in purple - False + >>> Color.BLUE == 2 + False - .. method:: __iter__(self): - Returns all contained members:: +Allowed members and attributes of enumerations +---------------------------------------------- - >>> list(Color.RED) - [Color.RED] - >>> list(purple) - [Color.RED, Color.BLUE] +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. - .. method:: __len__(self): +Enumerations are Python classes, and can have methods and special methods as +usual. If we have this enumeration:: - Returns number of members in flag:: + >>> class Mood(Enum): + ... FUNKY = 1 + ... HAPPY = 3 + ... + ... def describe(self): + ... # self is the member here + ... return self.name, self.value + ... + ... def __str__(self): + ... return 'my custom str! {0}'.format(self.value) + ... + ... @classmethod + ... def favorite_mood(cls): + ... # cls here is the enumeration + ... return cls.HAPPY + ... - >>> len(Color.GREEN) - 1 - >>> len(white) - 3 +Then:: - .. method:: __bool__(self): + >>> Mood.favorite_mood() + + >>> Mood.HAPPY.describe() + ('HAPPY', 3) + >>> str(Mood.FUNKY) + 'my custom str! 1' + +The rules for what is allowed are as follows: names that start and end with +a single underscore are reserved by enum and cannot be used; all other +attributes defined within an enumeration will become members of this +enumeration, with the exception of special methods (:meth:`__str__`, +:meth:`__add__`, etc.), descriptors (methods are also descriptors), and +variable names listed in :attr:`_ignore_`. + +Note: if your enumeration defines :meth:`__new__` and/or :meth:`__init__` then +any value(s) given to the enum member will be passed into those methods. +See `Planet`_ for an example. + + +Restricted Enum subclassing +--------------------------- + +A new :class:`Enum` class must have one base Enum class, up to one concrete +data type, and as many :class:`object`-based mixin classes as needed. The +order of these base classes is:: + + class EnumName([mix-in, ...,] [data-type,] base-enum): + pass + +Also, subclassing an enumeration is allowed only if the enumeration does not define +any members. So this is forbidden:: + + >>> class MoreColor(Color): + ... PINK = 17 + ... + Traceback (most recent call last): + ... + TypeError: MoreColor: cannot extend enumeration 'Color' + +But this is allowed:: + + >>> class Foo(Enum): + ... def some_behavior(self): + ... pass + ... + >>> class Bar(Foo): + ... HAPPY = 1 + ... SAD = 2 + ... + +Allowing subclassing of enums that define members would lead to a violation of +some important invariants of types and instances. On the other hand, it makes +sense to allow sharing some common behavior between a group of enumerations. +(See `OrderedEnum`_ for an example.) + + +Pickling +-------- + +Enumerations can be pickled and unpickled:: + + >>> from test.test_enum import Fruit + >>> from pickle import dumps, loads + >>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO)) + True + +The usual restrictions for pickling apply: picklable enums must be defined in +the top level of a module, since unpickling requires them to be importable +from that module. - Returns *True* if any members in flag, *False* otherwise:: +.. note:: - >>> bool(Color.GREEN) - True - >>> bool(white) - True - >>> black = Color(0) - >>> bool(black) - False + With pickle protocol version 4 it is possible to easily pickle enums + nested in other classes. - .. method:: __or__(self, other) +It is possible to modify how Enum members are pickled/unpickled by defining +:meth:`__reduce_ex__` in the enumeration class. - Returns current flag binary or'ed with other:: - >>> Color.RED | Color.GREEN - Color.RED|Color.GREEN +Functional API +-------------- - .. method:: __and__(self, other) +The :class:`Enum` class is callable, providing the following functional API:: - Returns current flag binary and'ed with other:: + >>> Animal = Enum('Animal', 'ANT BEE CAT DOG') + >>> Animal + + >>> Animal.ANT + + >>> Animal.ANT.value + 1 + >>> list(Animal) + [, , , ] - >>> purple & white - Color.RED|Color.BLUE - >>> purple & Color.GREEN - 0x0 +The semantics of this API resemble :class:`~collections.namedtuple`. The first +argument of the call to :class:`Enum` is the name of the enumeration. - .. method:: __xor__(self, other) +The second argument is the *source* of enumeration member names. It can be a +whitespace-separated string of names, a sequence of names, a sequence of +2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to +values. The last two options enable assigning arbitrary values to +enumerations; the others auto-assign increasing integers starting with 1 (use +the ``start`` parameter to specify a different starting value). A +new class derived from :class:`Enum` is returned. In other words, the above +assignment to :class:`Animal` is equivalent to:: - Returns current flag binary xor'ed with other:: + >>> class Animal(Enum): + ... ANT = 1 + ... BEE = 2 + ... CAT = 3 + ... DOG = 4 + ... - >>> purple ^ white - Color.GREEN - >>> purple ^ Color.GREEN - Color.RED|Color.GREEN|Color.BLUE +The reason for defaulting to ``1`` as the starting number and not ``0`` is +that ``0`` is ``False`` in a boolean sense, but enum members all evaluate +to ``True``. - .. method:: __invert__(self): +Pickling enums created with the functional API can be tricky as frame stack +implementation details are used to try and figure out which module the +enumeration is being created in (e.g. it will fail if you use a utility +function in separate module, and also may not work on IronPython or Jython). +The solution is to specify the module name explicitly as follows:: - Returns all the flags in *type(self)* that are not in self:: + >>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__) - >>> ~white - 0x0 - >>> ~purple - Color.GREEN - >>> ~Color.RED - Color.GREEN|Color.BLUE +.. warning:: -.. note:: + If ``module`` is not supplied, and Enum cannot determine what it is, + the new Enum members will not be unpicklable; to keep errors closer to + the source, pickling will be disabled. - Using :class:`auto` with :class:`Flag` results in integers that are powers - of two, starting with ``1``. +The new pickle protocol 4 also, in some circumstances, relies on +:attr:`~definition.__qualname__` being set to the location where pickle will be able +to find the class. For example, if the class was made available in class +SomeData in the global scope:: + >>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal') -.. class:: IntFlag +The complete signature is:: - *IntFlag* is the same as *Flag*, but its members are also integers and can be - used anywhere that an integer can be used. + Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=, start=1) - >>> from enum import IntFlag, auto - >>> class Color(IntFlag): - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> Color.RED & 2 - 0x0 - >>> Color.RED | 2 - Color.RED|Color.GREEN +:value: What the new Enum class will record as its name. - If any integer operation is performed with an *IntFlag* member, the result is - not an *IntFlag*:: +:names: The Enum members. This can be a whitespace or comma separated string + (values will start at 1 unless otherwise specified):: - >>> Color.RED + 2 - 3 + 'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE' - If a *Flag* operation is performed with an *IntFlag* member and: + or an iterator of names:: - * the result is a valid *IntFlag*: an *IntFlag* is returned - * the result is not a valid *IntFlag*: the result depends on the *FlagBoundary* setting + ['RED', 'GREEN', 'BLUE'] -.. note:: + or an iterator of (name, value) pairs:: - Using :class:`auto` with :class:`IntFlag` results in integers that are powers - of two, starting with ``1``. + [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)] -.. class:: EnumCheck + or a mapping:: - *EnumCheck* contains the options used by the :func:`verify` decorator to ensure - various constraints; failed constraints result in a :exc:`TypeError`. + {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42} - .. attribute:: UNIQUE +:module: name of module where new Enum class can be found. - Ensure that each value has only one name:: +:qualname: where in module new Enum class can be found. - >>> from enum import Enum, verify, UNIQUE - >>> @verify(UNIQUE) - ... class Color(Enum): - ... RED = 1 - ... GREEN = 2 - ... BLUE = 3 - ... CRIMSON = 1 - Traceback (most recent call last): - ... - ValueError: aliases found in : CRIMSON -> RED +:type: type to mix in to new Enum class. +:start: number to start counting at if only names are passed in. - .. attribute:: CONTINUOUS +.. versionchanged:: 3.5 + The *start* parameter was added. - Ensure that there are no missing values between the lowest-valued member - and the highest-valued member:: - >>> from enum import Enum, verify, CONTINUOUS - >>> @verify(CONTINUOUS) - ... class Color(Enum): - ... RED = 1 - ... GREEN = 2 - ... BLUE = 5 - Traceback (most recent call last): - ... - ValueError: invalid enum 'Color': missing values 3, 4 +Derived Enumerations +-------------------- - .. attribute:: NAMED_FLAGS +IntEnum +^^^^^^^ - Ensure that any flag groups/masks contain only named flags -- useful when - values are specified instead of being generated by :func:`auto` +The first variation of :class:`Enum` that is provided is also a subclass of +:class:`int`. Members of an :class:`IntEnum` can be compared to integers; +by extension, integer enumerations of different types can also be compared +to each other:: - >>> from enum import Flag, verify, NAMED_FLAGS - >>> @verify(NAMED_FLAGS) - ... class Color(Flag): - ... RED = 1 - ... GREEN = 2 - ... BLUE = 4 - ... WHITE = 15 - ... NEON = 31 - Traceback (most recent call last): - ... - ValueError: invalid Flag 'Color': aliases WHITE and NEON are missing combined values of 0x18 [use enum.show_flag_values(value) for details] + >>> from enum import IntEnum + >>> class Shape(IntEnum): + ... CIRCLE = 1 + ... SQUARE = 2 + ... + >>> class Request(IntEnum): + ... POST = 1 + ... GET = 2 + ... + >>> Shape == 1 + False + >>> Shape.CIRCLE == 1 + True + >>> Shape.CIRCLE == Request.POST + True -.. note:: +However, they still can't be compared to standard :class:`Enum` enumerations:: - CONTINUOUS and NAMED_FLAGS are designed to work with integer-valued members. + >>> class Shape(IntEnum): + ... CIRCLE = 1 + ... SQUARE = 2 + ... + >>> class Color(Enum): + ... RED = 1 + ... GREEN = 2 + ... + >>> Shape.CIRCLE == Color.RED + False -.. versionadded:: 3.10 +:class:`IntEnum` values behave like integers in other ways you'd expect:: -.. class:: FlagBoundary + >>> int(Shape.CIRCLE) + 1 + >>> ['a', 'b', 'c'][Shape.CIRCLE] + 'b' + >>> [i for i in range(Shape.SQUARE)] + [0, 1] - *FlagBoundary* controls how out-of-range values are handled in *Flag* and its - subclasses. - .. attribute:: STRICT +IntFlag +^^^^^^^ - Out-of-range values cause a :exc:`ValueError` to be raised. This is the - default for :class:`Flag`:: +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. However, as the name implies, :class:`IntFlag` +members also subclass :class:`int` and can be used wherever an :class:`int` is +used. Any operation on an :class:`IntFlag` member besides the bit-wise +operations will lose the :class:`IntFlag` membership. - >>> from enum import Flag, STRICT - >>> class StrictFlag(Flag, boundary=STRICT): - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> StrictFlag(2**2 + 2**4) - Traceback (most recent call last): - ... - ValueError: StrictFlag: invalid value: 20 - given 0b0 10100 - allowed 0b0 00111 +.. versionadded:: 3.6 - .. attribute:: CONFORM +Sample :class:`IntFlag` class:: - Out-of-range values have invalid values removed, leaving a valid *Flag* - value:: + >>> from enum import IntFlag + >>> class Perm(IntFlag): + ... R = 4 + ... W = 2 + ... X = 1 + ... + >>> Perm.R | Perm.W + + >>> Perm.R + Perm.W + 6 + >>> RW = Perm.R | Perm.W + >>> Perm.R in RW + True + +It is also possible to name the combinations:: + + >>> class Perm(IntFlag): + ... R = 4 + ... W = 2 + ... X = 1 + ... RWX = 7 + >>> Perm.RWX + + >>> ~Perm.RWX + + +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 + + >>> bool(Perm.R & Perm.X) + False + +Because :class:`IntFlag` members are also subclasses of :class:`int` they can +be combined with them:: + + >>> Perm.X | 8 + + + +Flag +^^^^ + +The last variation is :class:`Flag`. Like :class:`IntFlag`, :class:`Flag` +members can be combined using the bitwise operators (&, \|, ^, ~). Unlike +:class:`IntFlag`, they cannot be combined with, nor compared against, any +other :class:`Flag` enumeration, nor :class:`int`. While it is possible to +specify the values directly it is recommended to use :class:`auto` as the +value and let :class:`Flag` select an appropriate value. + +.. versionadded:: 3.6 + +Like :class:`IntFlag`, if a combination of :class:`Flag` members results in no +flags being set, the boolean evaluation is :data:`False`:: + + >>> from enum import Flag, auto + >>> class Color(Flag): + ... RED = auto() + ... BLUE = auto() + ... GREEN = auto() + ... + >>> Color.RED & Color.GREEN + + >>> bool(Color.RED & Color.GREEN) + False + +Individual flags should have values that are powers of two (1, 2, 4, 8, ...), +while combinations of flags won't:: + + >>> class Color(Flag): + ... RED = auto() + ... BLUE = auto() + ... GREEN = auto() + ... WHITE = RED | BLUE | GREEN + ... + >>> Color.WHITE + + +Giving a name to the "no flags set" condition does not change its boolean +value:: + + >>> class Color(Flag): + ... BLACK = 0 + ... RED = auto() + ... BLUE = auto() + ... GREEN = auto() + ... + >>> Color.BLACK + + >>> bool(Color.BLACK) + False - >>> from enum import Flag, CONFORM - >>> class ConformFlag(Flag, boundary=CONFORM): - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> ConformFlag(2**2 + 2**4) - ConformFlag.BLUE +.. note:: - .. attribute:: EJECT + For the majority of new code, :class:`Enum` and :class:`Flag` are strongly + recommended, since :class:`IntEnum` and :class:`IntFlag` break some + semantic promises of an enumeration (by being comparable to integers, and + thus by transitivity to other unrelated enumerations). :class:`IntEnum` + and :class:`IntFlag` should be used only in cases where :class:`Enum` and + :class:`Flag` will not do; for example, when integer constants are replaced + with enumerations, or for interoperability with other systems. + + +Others +^^^^^^ + +While :class:`IntEnum` is part of the :mod:`enum` module, it would be very +simple to implement independently:: + + class IntEnum(int, Enum): + pass + +This demonstrates how similar derived enumerations can be defined; for example +a :class:`StrEnum` that mixes in :class:`str` instead of :class:`int`. + +Some rules: + +1. When subclassing :class:`Enum`, mix-in types must appear before + :class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum` + example above. +2. 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 + add methods and don't specify another type. +3. 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. +4. %-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. +5. :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. + +When to use :meth:`__new__` vs. :meth:`__init__` +------------------------------------------------ + +:meth:`__new__` must be used whenever you want to customize the actual value of +the :class:`Enum` member. Any other modifications may go in either +:meth:`__new__` or :meth:`__init__`, with :meth:`__init__` being preferred. + +For example, if you want to pass several items to the constructor, but only +want one of them to be the value:: + + >>> class Coordinate(bytes, Enum): + ... """ + ... Coordinate with binary codes that can be indexed by the int code. + ... """ + ... def __new__(cls, value, label, unit): + ... obj = bytes.__new__(cls, [value]) + ... obj._value_ = value + ... obj.label = label + ... obj.unit = unit + ... return obj + ... PX = (0, 'P.X', 'km') + ... PY = (1, 'P.Y', 'km') + ... VX = (2, 'V.X', 'km/s') + ... VY = (3, 'V.Y', 'km/s') + ... + + >>> print(Coordinate['PY']) + Coordinate.PY + + >>> print(Coordinate(3)) + Coordinate.VY + +Interesting examples +-------------------- + +While :class:`Enum`, :class:`IntEnum`, :class:`IntFlag`, and :class:`Flag` are +expected to cover the majority of use-cases, they cannot cover them all. Here +are recipes for some different types of enumerations that can be used directly, +or as examples for creating one's own. + + +Omitting values +^^^^^^^^^^^^^^^ + +In many use-cases one doesn't care what the actual value of an enumeration +is. There are several ways to define this type of simple enumeration: + +- use instances of :class:`auto` for the value +- use instances of :class:`object` as the value +- use a descriptive string as the value +- use a tuple as the value and a custom :meth:`__new__` to replace the + tuple with an :class:`int` value + +Using any of these methods signifies to the user that these values are not +important, and also enables one to add, remove, or reorder members without +having to renumber the remaining members. + +Whichever method you choose, you should provide a :meth:`repr` that also hides +the (unimportant) value:: + + >>> class NoValue(Enum): + ... def __repr__(self): + ... return '<%s.%s>' % (self.__class__.__name__, self.name) + ... + + +Using :class:`auto` +""""""""""""""""""" + +Using :class:`auto` would look like:: + + >>> class Color(NoValue): + ... RED = auto() + ... BLUE = auto() + ... GREEN = auto() + ... + >>> Color.GREEN + + + +Using :class:`object` +""""""""""""""""""""" + +Using :class:`object` would look like:: + + >>> class Color(NoValue): + ... RED = object() + ... GREEN = object() + ... BLUE = object() + ... + >>> Color.GREEN + + + +Using a descriptive string +"""""""""""""""""""""""""" + +Using a string as the value would look like:: + + >>> class Color(NoValue): + ... RED = 'stop' + ... GREEN = 'go' + ... BLUE = 'too fast!' + ... + >>> Color.GREEN + + >>> Color.GREEN.value + 'go' + + +Using a custom :meth:`__new__` +"""""""""""""""""""""""""""""" + +Using an auto-numbering :meth:`__new__` would look like:: + + >>> class AutoNumber(NoValue): + ... def __new__(cls): + ... value = len(cls.__members__) + 1 + ... obj = object.__new__(cls) + ... obj._value_ = value + ... return obj + ... + >>> class Color(AutoNumber): + ... RED = () + ... GREEN = () + ... BLUE = () + ... + >>> Color.GREEN + + >>> Color.GREEN.value + 2 + +To make a more general purpose ``AutoNumber``, add ``*args`` to the signature:: + + >>> class AutoNumber(NoValue): + ... def __new__(cls, *args): # this is the only change from above + ... value = len(cls.__members__) + 1 + ... obj = object.__new__(cls) + ... obj._value_ = value + ... return obj + ... + +Then when you inherit from ``AutoNumber`` you can write your own ``__init__`` +to handle any extra arguments:: + + >>> class Swatch(AutoNumber): + ... def __init__(self, pantone='unknown'): + ... self.pantone = pantone + ... AUBURN = '3497' + ... SEA_GREEN = '1246' + ... BLEACHED_CORAL = () # New color, no Pantone code yet! + ... + >>> Swatch.SEA_GREEN + + >>> Swatch.SEA_GREEN.pantone + '1246' + >>> Swatch.BLEACHED_CORAL.pantone + 'unknown' - Out-of-range values lose their *Flag* membership and revert to :class:`int`. - This is the default for :class:`IntFlag`:: +.. note:: - >>> from enum import Flag, EJECT - >>> class EjectFlag(Flag, boundary=EJECT): - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> EjectFlag(2**2 + 2**4) - 20 + The :meth:`__new__` method, if defined, is used during creation of the Enum + members; it is then replaced by Enum's :meth:`__new__` which is used after + class creation for lookup of existing members. + + +OrderedEnum +^^^^^^^^^^^ + +An ordered enumeration that is not based on :class:`IntEnum` and so maintains +the normal :class:`Enum` invariants (such as not being comparable to other +enumerations):: + + >>> class OrderedEnum(Enum): + ... def __ge__(self, other): + ... if self.__class__ is other.__class__: + ... return self.value >= other.value + ... return NotImplemented + ... def __gt__(self, other): + ... if self.__class__ is other.__class__: + ... return self.value > other.value + ... return NotImplemented + ... def __le__(self, other): + ... if self.__class__ is other.__class__: + ... return self.value <= other.value + ... return NotImplemented + ... def __lt__(self, other): + ... if self.__class__ is other.__class__: + ... return self.value < other.value + ... return NotImplemented + ... + >>> class Grade(OrderedEnum): + ... A = 5 + ... B = 4 + ... C = 3 + ... D = 2 + ... F = 1 + ... + >>> Grade.C < Grade.A + True + + +DuplicateFreeEnum +^^^^^^^^^^^^^^^^^ + +Raises an error if a duplicate member name is found instead of creating an +alias:: + + >>> class DuplicateFreeEnum(Enum): + ... def __init__(self, *args): + ... cls = self.__class__ + ... if any(self.value == e.value for e in cls): + ... a = self.name + ... e = cls(self.value).name + ... raise ValueError( + ... "aliases not allowed in DuplicateFreeEnum: %r --> %r" + ... % (a, e)) + ... + >>> class Color(DuplicateFreeEnum): + ... RED = 1 + ... GREEN = 2 + ... BLUE = 3 + ... GRENE = 2 + ... + Traceback (most recent call last): + ... + ValueError: aliases not allowed in DuplicateFreeEnum: 'GRENE' --> 'GREEN' - .. attribute:: KEEP +.. note:: - Out-of-range values are kept, and the *Flag* membership is kept. This is - used for some stdlib flags: + This is a useful example for subclassing Enum to add or change other + behaviors as well as disallowing aliases. If the only desired change is + disallowing aliases, the :func:`unique` decorator can be used instead. - >>> from enum import Flag, KEEP - >>> class KeepFlag(Flag, boundary=KEEP): - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> KeepFlag(2**2 + 2**4) - KeepFlag.BLUE|0x10 -.. versionadded:: 3.10 +Planet +^^^^^^ ---------------- +If :meth:`__new__` or :meth:`__init__` is defined the value of the enum member +will be passed to those methods:: + + >>> class Planet(Enum): + ... MERCURY = (3.303e+23, 2.4397e6) + ... VENUS = (4.869e+24, 6.0518e6) + ... EARTH = (5.976e+24, 6.37814e6) + ... MARS = (6.421e+23, 3.3972e6) + ... JUPITER = (1.9e+27, 7.1492e7) + ... SATURN = (5.688e+26, 6.0268e7) + ... URANUS = (8.686e+25, 2.5559e7) + ... NEPTUNE = (1.024e+26, 2.4746e7) + ... def __init__(self, mass, radius): + ... self.mass = mass # in kilograms + ... self.radius = radius # in meters + ... @property + ... def surface_gravity(self): + ... # universal gravitational constant (m3 kg-1 s-2) + ... G = 6.67300E-11 + ... return G * self.mass / (self.radius * self.radius) + ... + >>> Planet.EARTH.value + (5.976e+24, 6378140.0) + >>> Planet.EARTH.surface_gravity + 9.802652743337129 + + +TimePeriod +^^^^^^^^^^ + +An example to show the :attr:`_ignore_` attribute in use:: + + >>> from datetime import timedelta + >>> class Period(timedelta, Enum): + ... "different lengths of time" + ... _ignore_ = 'Period i' + ... Period = vars() + ... for i in range(367): + ... Period['day_%d' % i] = i + ... + >>> list(Period)[:2] + [, ] + >>> list(Period)[-2:] + [, ] + + +How are Enums different? +------------------------ + +Enums have a custom metaclass that affects many aspects of both derived Enum +classes and their instances (members). + + +Enum Classes +^^^^^^^^^^^^ + +The :class:`EnumMeta` metaclass is responsible for providing the +:meth:`__contains__`, :meth:`__dir__`, :meth:`__iter__` and other methods that +allow one to do things with an :class:`Enum` class that fail on a typical +class, such as `list(Color)` or `some_enum_var in Color`. :class:`EnumMeta` is +responsible for ensuring that various other methods on the final :class:`Enum` +class are correct (such as :meth:`__new__`, :meth:`__getnewargs__`, +:meth:`__str__` and :meth:`__repr__`). + + +Enum Members (aka instances) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The most interesting thing about Enum members is that they are singletons. +:class:`EnumMeta` creates them all while it is creating the :class:`Enum` +class itself, and then puts a custom :meth:`__new__` in place to ensure +that no new ones are ever instantiated by returning only the existing +member instances. + + +Finer Points +^^^^^^^^^^^^ + +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 by the `Functional API`_ and by + :class:`auto` to get an appropriate value for an enum member; may be + overridden + +.. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_`` +.. versionadded:: 3.7 ``_ignore_`` + +To help keep Python 2 / Python 3 code in sync an :attr:`_order_` attribute can +be provided. It will be checked against the actual order of the enumeration +and raise an error if the two do not match:: + + >>> class Color(Enum): + ... _order_ = 'RED GREEN BLUE' + ... RED = 1 + ... BLUE = 3 + ... GREEN = 2 + ... + Traceback (most recent call last): + ... + TypeError: member order does not match _order_ -Utilites and Decorators ------------------------ +.. note:: -.. class:: auto + In Python 2 code the :attr:`_order_` attribute is necessary as definition + order is lost before it can be recorded. - *auto* can be used in place of a value. If used, the *Enum* machinery will - call an *Enum*'s :meth:`_generate_next_value_` to get an appropriate value. - For *Enum* and *IntEnum* that appropriate value will be the last value plus - one; for *Flag* and *IntFlag* it will be the first power-of-two greater - than the last value; for *StrEnum* it will be the lower-cased version of the - member's name. - ``_generate_next_value_`` can be overridden to customize the values used by - *auto*. +_Private__names +""""""""""""""" -.. decorator:: global_enum +Private names will be normal attributes in Python 3.10 instead of either an error +or a member (depending on if the name ends with an underscore). Using these names +in 3.9 will issue a :exc:`DeprecationWarning`. - 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. +``Enum`` member type +"""""""""""""""""""" -.. decorator:: property +:class:`Enum` members are instances of their :class:`Enum` class, and are +normally accessed as ``EnumClass.member``. Under certain circumstances they +can also be accessed as ``EnumClass.member.member``, but you should never do +this as that lookup may fail or, worse, return something besides the +:class:`Enum` member you are looking for (this is another good reason to use +all-uppercase names for members):: - A decorator similar to the built-in *property*, but specifically for - enumerations. It allows member attributes to have the same names as members - themselves. + >>> class FieldTypes(Enum): + ... name = 0 + ... value = 1 + ... size = 2 + ... + >>> FieldTypes.value.size + + >>> FieldTypes.size.value + 2 - .. note:: the *property* and the member must be defined in separate classes; - for example, the *value* and *name* attributes are defined in the - *Enum* class, and *Enum* subclasses can define members with the - names ``value`` and ``name``. +.. versionchanged:: 3.5 -.. decorator:: unique - A :keyword:`class` decorator specifically for enumerations. It searches an - enumeration's :attr:`__members__`, gathering any aliases it finds; if any are - found :exc:`ValueError` is raised with the details:: +Boolean value of ``Enum`` classes and members +""""""""""""""""""""""""""""""""""""""""""""" - >>> from enum import Enum, unique - >>> @unique - ... class Mistake(Enum): - ... ONE = 1 - ... TWO = 2 - ... THREE = 3 - ... FOUR = 3 - ... - Traceback (most recent call last): - ... - ValueError: duplicate values found in : FOUR -> THREE +:class:`Enum` members that are mixed with non-:class:`Enum` types (such as +:class:`int`, :class:`str`, etc.) are evaluated according to the mixed-in +type's rules; otherwise, all members evaluate as :data:`True`. To make your +own Enum's boolean evaluation depend on the member's value add the following to +your class:: -.. decorator:: verify + def __bool__(self): + return bool(self.value) - A :keyword:`class` decorator specifically for enumerations. Members from - :class:`EnumCheck` are used to specify which constraints should be checked - on the decorated enumeration. +:class:`Enum` classes always evaluate as :data:`True`. -.. versionadded:: 3.10 ---------------- +``Enum`` classes with methods +""""""""""""""""""""""""""""" -Notes ------ +If you give your :class:`Enum` subclass extra methods, like the `Planet`_ +class above, those methods will show up in a :func:`dir` of the member, +but not of the class:: -:class:`IntEnum`, :class:`StrEnum`, and :class:`IntFlag` + >>> dir(Planet) + ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__'] + >>> dir(Planet.EARTH) + ['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value'] - 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 +Combining members of ``Flag`` +""""""""""""""""""""""""""""" - - ``StrEnum.__str__`` uses the value and not the name of the enum member +If a combination of Flag members is not named, the :func:`repr` will include +all named flags and all named combinations of flags that are in the value:: - If you do not need/want those limitations, you can create your own base - class by mixing in the ``int`` or ``str`` type yourself:: + >>> class Color(Flag): + ... RED = auto() + ... GREEN = auto() + ... BLUE = auto() + ... MAGENTA = RED | BLUE + ... YELLOW = RED | GREEN + ... CYAN = GREEN | BLUE + ... + >>> Color(3) # named combination + + >>> Color(7) # not named combination + - >>> from enum import Enum - >>> class MyIntEnum(int, Enum): - ... pass diff --git a/Doc/library/http.rst b/Doc/library/http.rst index 1569d50..2466c84 100644 --- a/Doc/library/http.rst +++ b/Doc/library/http.rst @@ -35,7 +35,7 @@ associated messages through the :class:`http.HTTPStatus` enum: >>> from http import HTTPStatus >>> HTTPStatus.OK - HTTPStatus.OK + >>> HTTPStatus.OK == 200 True >>> HTTPStatus.OK.value @@ -45,7 +45,7 @@ associated messages through the :class:`http.HTTPStatus` enum: >>> HTTPStatus.OK.description 'Request fulfilled, document follows' >>> list(HTTPStatus) - [HTTPStatus.CONTINUE, HTTPStatus.SWITCHING_PROTOCOLS, ...] + [, , ...] .. _http-status-codes: diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 914fdd7..ddda4d2 100755 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -788,9 +788,9 @@ The :mod:`socket` module also offers various network-related services: system if IPv6 isn't enabled):: >>> socket.getaddrinfo("example.org", 80, proto=socket.IPPROTO_TCP) - [(socket.AF_INET6, socket.SOCK_STREAM, + [(, , 6, '', ('2606:2800:220:1:248:1893:25c8:1946', 80, 0, 0)), - (socket.AF_INET, socket.SOCK_STREAM, + (, , 6, '', ('93.184.216.34', 80))] .. versionchanged:: 3.2 diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 4902d34..4b41d11 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/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 7ac627b..f74c7c7 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -987,17 +987,6 @@ encodings :func:`encodings.normalize_encoding` now ignores non-ASCII characters. (Contributed by Hai Shi in :issue:`39337`.) -enum ----- - -:class:`Enum` :func:`__repr__` now returns ``enum_name.member_name`` and -:func:`__str__` now returns ``member_name``. Stdlib enums available as -module constants have a :func:`repr` of ``module_name.member_name``. -(Contributed by Ethan Furman in :issue:`40066`.) - -Add :class:`enum.StrEnum` for enums where all members are strings. -(Contributed by Ethan Furman in :issue:`41816`.) - fileinput --------- diff --git a/Lib/ast.py b/Lib/ast.py index 66bcee8..f4d2f6e 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -27,7 +27,7 @@ import sys from _ast import * from contextlib import contextmanager, nullcontext -from enum import IntEnum, auto, _simple_enum +from enum import IntEnum, auto def parse(source, filename='', mode='exec', *, @@ -636,8 +636,7 @@ class Param(expr_context): # We unparse those infinities to INFSTR. _INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1) -@_simple_enum(IntEnum) -class _Precedence: +class _Precedence(IntEnum): """Precedence table that originated from python grammar.""" TUPLE = auto() diff --git a/Lib/enum.py b/Lib/enum.py index 84e3cc1..db79e66 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -1,25 +1,14 @@ import sys 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', - 'auto', 'unique', 'property', 'verify', - 'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP', - 'global_flag_repr', 'global_enum_repr', 'global_enum', - 'EnumCheck', 'CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE', + 'EnumMeta', + 'Enum', 'IntEnum', 'Flag', 'IntFlag', + 'auto', 'unique', ] -# 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 - def _is_descriptor(obj): """ Returns True if obj is a descriptor, False otherwise. @@ -55,77 +44,24 @@ def _is_sunder(name): def _is_private(cls_name, name): # do not use `re` as `re` imports `enum` pattern = '_%s__' % (cls_name, ) - pat_len = len(pattern) if ( - len(name) > pat_len + len(name) >= 5 and name.startswith(pattern) - and name[pat_len:pat_len+1] != ['_'] + and name[len(pattern)] != '_' and (name[-1] != '_' or name[-2] != '_') ): return True else: return False -def _is_single_bit(num): +def _make_class_unpicklable(cls): """ - True if only one bit set in num (should be an int) - """ - if num == 0: - return False - num &= num - 1 - return num == 0 - -def _make_class_unpicklable(obj): - """ - Make the given obj un-picklable. - - obj should be either a dictionary, or an Enum + Make the given class un-picklable. """ def _break_on_call_reduce(self, proto): raise TypeError('%r cannot be pickled' % self) - if isinstance(obj, dict): - obj['__reduce_ex__'] = _break_on_call_reduce - obj['__module__'] = '' - else: - setattr(obj, '__reduce_ex__', _break_on_call_reduce) - setattr(obj, '__module__', '') - -def _iter_bits_lsb(num): - # num must be an integer - if isinstance(num, Enum): - num = num.value - while num: - b = num & (~num + 1) - yield b - num ^= b - -def show_flag_values(value): - return list(_iter_bits_lsb(value)) - -def bin(num, max_bits=None): - """ - Like built-in bin(), except negative values are represented in - twos-compliment, and the leading bit always indicates sign - (0=positive, 1=negative). - - >>> bin(10) - '0b0 1010' - >>> bin(~10) # ~10 is -11 - '0b1 0101' - """ - - ceiling = 2 ** (num).bit_length() - if num >= 0: - s = _bltin_bin(num + ceiling).replace('1', '0', 1) - else: - s = _bltin_bin(~num ^ (ceiling - 1) + ceiling) - sign = s[:3] - digits = s[3:] - if max_bits is not None: - if len(digits) < max_bits: - digits = (sign[-1] * max_bits + digits)[-max_bits:] - return "%s %s" % (sign, digits) - + cls.__reduce_ex__ = _break_on_call_reduce + cls.__module__ = '' _auto_null = object() class auto: @@ -134,169 +70,12 @@ class auto: """ value = _auto_null -class property(DynamicClassAttribute): - """ - This is a descriptor, used to define attributes that act differently - when accessed through an enum member and through an enum class. - Instance access is the same as property(), but access to an attribute - through the enum class will instead look in the class' _member_map_ for - a corresponding enum member. - """ - - def __get__(self, instance, ownerclass=None): - if instance is None: - try: - return ownerclass._member_map_[self.name] - except KeyError: - raise AttributeError( - '%s: no class attribute %r' % (ownerclass.__name__, self.name) - ) - else: - if self.fget is None: - # check for member - if self.name in ownerclass._member_map_: - import warnings - warnings.warn( - "accessing one member from another is not supported, " - " and will be disabled in 3.12", - DeprecationWarning, - stacklevel=2, - ) - return ownerclass._member_map_[self.name] - raise AttributeError( - '%s: no instance attribute %r' % (ownerclass.__name__, self.name) - ) - else: - return self.fget(instance) - - def __set__(self, instance, value): - if self.fset is None: - raise AttributeError( - "%s: cannot set instance attribute %r" % (self.clsname, self.name) - ) - else: - return self.fset(instance, value) - - def __delete__(self, instance): - if self.fdel is None: - raise AttributeError( - "%s: cannot delete instance attribute %r" % (self.clsname, self.name) - ) - else: - return self.fdel(instance) - - def __set_name__(self, ownerclass, name): - self.name = name - self.clsname = ownerclass.__name__ - - -class _proto_member: - """ - intermediate step for enum members between class execution and final creation - """ - - def __init__(self, value): - self.value = value - - def __set_name__(self, enum_class, member_name): - """ - convert each quasi-member into an instance of the new enum class - """ - # first step: remove ourself from enum_class - delattr(enum_class, member_name) - # second step: create member based on enum_class - value = self.value - if not isinstance(value, tuple): - args = (value, ) - else: - args = value - if enum_class._member_type_ is tuple: # special case for tuple enums - args = (args, ) # wrap it one more time - if not enum_class._use_args_: - enum_member = enum_class._new_member_(enum_class) - if not hasattr(enum_member, '_value_'): - enum_member._value_ = value - else: - enum_member = enum_class._new_member_(enum_class, *args) - if not hasattr(enum_member, '_value_'): - if enum_class._member_type_ is object: - enum_member._value_ = value - else: - try: - enum_member._value_ = enum_class._member_type_(*args) - except Exception as exc: - raise TypeError( - '_value_ not set in __new__, unable to create it' - ) from None - value = enum_member._value_ - enum_member._name_ = member_name - enum_member.__objclass__ = enum_class - enum_member.__init__(*args) - enum_member._sort_order_ = len(enum_class._member_names_) - # If another member with the same value was already defined, the - # new member becomes an alias to the existing one. - for name, canonical_member in enum_class._member_map_.items(): - if canonical_member._value_ == enum_member._value_: - enum_member = canonical_member - break - else: - # this could still be an alias if the value is multi-bit and the - # class is a flag class - if ( - Flag is None - or not issubclass(enum_class, Flag) - ): - # no other instances found, record this member in _member_names_ - enum_class._member_names_.append(member_name) - elif ( - Flag is not None - and issubclass(enum_class, Flag) - and _is_single_bit(value) - ): - # no other instances found, record this member in _member_names_ - enum_class._member_names_.append(member_name) - # get redirect in place before adding to _member_map_ - # but check for other instances in parent classes first - need_override = False - descriptor = None - for base in enum_class.__mro__[1:]: - descriptor = base.__dict__.get(member_name) - if descriptor is not None: - if isinstance(descriptor, (property, DynamicClassAttribute)): - break - else: - need_override = True - # keep looking for an enum.property - if descriptor and not need_override: - # previous enum.property found, no further action needed - pass - else: - redirect = property() - redirect.__set_name__(enum_class, member_name) - if descriptor and need_override: - # previous enum.property found, but some other inherited attribute - # is in the way; copy fget, fset, fdel to this one - redirect.fget = descriptor.fget - redirect.fset = descriptor.fset - redirect.fdel = descriptor.fdel - setattr(enum_class, member_name, redirect) - # now add to _member_map_ (even aliases) - enum_class._member_map_[member_name] = enum_member - try: - # This may fail if value is not hashable. We can't add the value - # to the map, and by-value lookups for this value will be - # linear. - enum_class._value2member_map_.setdefault(value, enum_member) - except TypeError: - # keep track of the value in a list so containment checks are quick - enum_class._unhashable_values_.append(value) - class _EnumDict(dict): """ Track enum member order and ensure member names are not reused. - EnumType will use the names found in self._member_names as the + EnumMeta will use the names found in self._member_names as the enumeration member names. """ def __init__(self): @@ -316,24 +95,24 @@ class _EnumDict(dict): Single underscore (sunder) names are reserved. """ if _is_private(self._cls_name, key): - # do nothing, name will be a normal attribute - pass - elif _is_sunder(key): + import warnings + warnings.warn( + "private variables, such as %r, will be normal attributes in 3.11" + % (key, ), + DeprecationWarning, + stacklevel=2, + ) + if _is_sunder(key): if key not in ( - '_order_', + '_order_', '_create_pseudo_member_', '_generate_next_value_', '_missing_', '_ignore_', - '_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_', ): - raise ValueError( - '_sunder_ names, such as %r, are reserved for future Enum use' - % (key, ) - ) + raise ValueError('_names_ are reserved for future Enum use') if key == '_generate_next_value_': # check if members already defined as auto() if self._auto_called: raise TypeError("_generate_next_value_ must be defined before members") - _gnv = value.__func__ if isinstance(value, staticmethod) else value - setattr(self, '_generate_next_value', _gnv) + setattr(self, '_generate_next_value', value) elif key == '_ignore_': if isinstance(value, str): value = value.replace(',',' ').split() @@ -351,7 +130,7 @@ 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('Attempted to reuse key: %r' % key) elif key in self._ignore: pass elif not _is_descriptor(value): @@ -361,7 +140,10 @@ class _EnumDict(dict): if isinstance(value, auto): if value.value == _auto_null: value.value = self._generate_next_value( - key, 1, len(self._member_names), self._last_values[:], + key, + 1, + len(self._member_names), + self._last_values[:], ) self._auto_called = True value = value.value @@ -369,22 +151,16 @@ class _EnumDict(dict): self._last_values.append(value) super().__setitem__(key, value) - def update(self, members, **more_members): - try: - for name in members.keys(): - self[name] = members[name] - except AttributeError: - for name, value in members: - self[name] = value - for name, value in more_members.items(): - self[name] = value +# Dummy value for Enum as EnumMeta explicitly checks for it, but of course +# until EnumMeta finishes running the first time the Enum class doesn't exist. +# This is also why there are checks in EnumMeta like `if Enum is not None` +Enum = None -class EnumType(type): +class EnumMeta(type): """ Metaclass for Enum """ - @classmethod def __prepare__(metacls, cls, bases, **kwds): # check that previous enum members do not exist @@ -400,80 +176,148 @@ class EnumType(type): ) return enum_dict - def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **kwds): + def __new__(metacls, cls, bases, classdict, **kwds): # an Enum class is final once enumeration items have been defined; it # cannot be mixed with other types (int, float, etc.) if it has an # 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) classdict.setdefault('_ignore_', []).append('_ignore_') ignore = classdict['_ignore_'] for key in ignore: classdict.pop(key, None) - # - # grab member names - member_names = classdict._member_names - # - # 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))) - # - # adjust the sunders - _order_ = classdict.pop('_order_', None) - # convert to normal dict - classdict = dict(classdict.items()) - # - # data type of member and the controlling Enum class member_type, first_enum = metacls._get_mixins_(cls, bases) __new__, save_new, use_args = metacls._find_new_( classdict, member_type, first_enum, ) - classdict['_new_member_'] = __new__ - classdict['_use_args_'] = use_args - # - # convert future enum members into temporary _proto_members - # and record integer values in case this will be a Flag - flag_mask = 0 - for name in member_names: - value = classdict[name] - if isinstance(value, int): - flag_mask |= value - classdict[name] = _proto_member(value) - # - # house-keeping structures - classdict['_member_names_'] = [] - classdict['_member_map_'] = {} - classdict['_value2member_map_'] = {} - classdict['_unhashable_values_'] = [] - classdict['_member_type_'] = member_type - # - # Flag structures (will be removed if final class is not a Flag - classdict['_boundary_'] = ( - boundary - or getattr(first_enum, '_boundary_', None) - ) - classdict['_flag_mask_'] = flag_mask - classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1 - classdict['_inverted_'] = None - # + + # save enum items into separate mapping so they don't get baked into + # the new class + enum_members = {k: classdict[k] for k in classdict._member_names} + for name in classdict._member_names: + del classdict[name] + + # adjust the sunders + _order_ = classdict.pop('_order_', None) + + # check for illegal enum names (any others?) + invalid_names = set(enum_members) & {'mro', ''} + if invalid_names: + raise ValueError('Invalid enum member name: {0}'.format( + ','.join(invalid_names))) + # 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) - except RuntimeError as e: - # any exceptions raised by member.__new__ will get converted to a - # RuntimeError, so get that original exception back and raise it instead - exc = e.__cause__ or e - if exc is not None: - raise exc + + enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) + enum_class._member_names_ = [] # names in definition order + enum_class._member_map_ = {} # name->value map + enum_class._member_type_ = member_type + + # save DynamicClassAttribute attributes from super classes so we know + # if we can take the shortcut of storing members in the class dict + dynamic_attributes = { + k for c in enum_class.mro() + for k, v in c.__dict__.items() + if isinstance(v, DynamicClassAttribute) + } + + # Reverse value->name map for hashable values. + enum_class._value2member_map_ = {} + + # If a custom type is mixed into the Enum, and it does not know how + # to pickle itself, pickle.dumps will succeed but pickle.loads will + # fail. Rather than have the error show up later and possibly far + # from the source, sabotage the pickle protocol for this class so + # that pickle.dumps also fails. # + # However, if the new class implements its own __reduce_ex__, do not + # sabotage -- it's on them to make sure it works correctly. We use + # __reduce_ex__ instead of any of the others as it is preferred by + # pickle over __reduce__, and it handles all pickle protocols. + if '__reduce_ex__' not in classdict: + if member_type is not object: + methods = ('__getnewargs_ex__', '__getnewargs__', + '__reduce_ex__', '__reduce__') + if not any(m in member_type.__dict__ for m in methods): + if '__new__' in classdict: + # too late, sabotage + _make_class_unpicklable(enum_class) + else: + # final attempt to verify that pickling would work: + # travel mro until __new__ is found, checking for + # __reduce__ and friends along the way -- if any of them + # are found before/when __new__ is found, pickling should + # work + sabotage = None + for chain in bases: + for base in chain.__mro__: + if base is object: + continue + elif any(m in base.__dict__ for m in methods): + # found one, we're good + sabotage = False + break + elif '__new__' in base.__dict__: + # not good + sabotage = True + break + if sabotage is not None: + break + if sabotage: + _make_class_unpicklable(enum_class) + # instantiate them, checking for duplicates as we go + # we instantiate first instead of checking for duplicates first in case + # a custom __new__ is doing something funky with the values -- such as + # auto-numbering ;) + for member_name in classdict._member_names: + value = enum_members[member_name] + if not isinstance(value, tuple): + args = (value, ) + else: + args = value + if member_type is tuple: # special case for tuple enums + args = (args, ) # wrap it one more time + if not use_args: + enum_member = __new__(enum_class) + if not hasattr(enum_member, '_value_'): + enum_member._value_ = value + else: + enum_member = __new__(enum_class, *args) + if not hasattr(enum_member, '_value_'): + if member_type is object: + enum_member._value_ = value + else: + enum_member._value_ = member_type(*args) + value = enum_member._value_ + enum_member._name_ = member_name + enum_member.__objclass__ = enum_class + enum_member.__init__(*args) + # If another member with the same value was already defined, the + # new member becomes an alias to the existing one. + for name, canonical_member in enum_class._member_map_.items(): + if canonical_member._value_ == enum_member._value_: + enum_member = canonical_member + break + else: + # Aliases don't appear in member names (only in __members__). + enum_class._member_names_.append(member_name) + # performance boost for any member that would not shadow + # a DynamicClassAttribute + if member_name not in dynamic_attributes: + setattr(enum_class, member_name, enum_member) + # now add to _member_map_ + enum_class._member_map_[member_name] = enum_member + try: + # This may fail if value is not hashable. We can't add the value + # to the map, and by-value lookups for this value will be + # linear. + enum_class._value2member_map_[value] = enum_member + except TypeError: + pass + # 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 @@ -486,7 +330,7 @@ class EnumType(type): enum_method = getattr(first_enum, name, None) if obj_method is not None and obj_method is class_method: setattr(enum_class, name, enum_method) - # + # replace any other __new__ with our own (as long as Enum is not None, # anyway) -- again, this is to support pickle if Enum is not None: @@ -495,71 +339,14 @@ class EnumType(type): if save_new: enum_class.__new_member__ = __new__ enum_class.__new__ = Enum.__new__ - # + # py3 support for definition order (helps keep py2/py3 code in sync) - # - # _order_ checking is spread out into three/four steps - # - if enum_class is a Flag: - # - remove any non-single-bit flags from _order_ - # - remove any aliases from _order_ - # - check that _order_ and _member_names_ match - # - # step 1: ensure we have a list if _order_ is not None: if isinstance(_order_, str): _order_ = _order_.replace(',', ' ').split() - # - # remove Flag structures if final class is not a Flag - if ( - Flag is None and cls != 'Flag' - or Flag is not None and not issubclass(enum_class, Flag) - ): - delattr(enum_class, '_boundary_') - delattr(enum_class, '_flag_mask_') - delattr(enum_class, '_all_bits_') - delattr(enum_class, '_inverted_') - elif Flag is not None and issubclass(enum_class, Flag): - # ensure _all_bits_ is correct and there are no missing flags - single_bit_total = 0 - multi_bit_total = 0 - for flag in enum_class._member_map_.values(): - flag_value = flag._value_ - if _is_single_bit(flag_value): - single_bit_total |= flag_value - else: - # multi-bit flags are considered aliases - multi_bit_total |= flag_value - enum_class._flag_mask_ = single_bit_total - # - # set correct __iter__ - member_list = [m._value_ for m in enum_class] - if member_list != sorted(member_list): - enum_class._iter_member_ = enum_class._iter_member_by_def_ - if _order_: - # _order_ step 2: remove any items from _order_ that are not single-bit - _order_ = [ - o - for o in _order_ - if o not in enum_class._member_map_ or _is_single_bit(enum_class[o]._value_) - ] - # - if _order_: - # _order_ step 3: remove aliases from _order_ - _order_ = [ - o - for o in _order_ - if ( - o not in enum_class._member_map_ - or - (o in enum_class._member_map_ and o in enum_class._member_names_) - )] - # _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' - % (enum_class._member_names_, _order_) - ) - # + raise TypeError('member order does not match _order_') + return enum_class def __bool__(self): @@ -568,7 +355,7 @@ class EnumType(type): """ return True - def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None): + def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1): """ Either returns an existing member, or creates a new enum class. @@ -603,18 +390,10 @@ class EnumType(type): qualname=qualname, type=type, start=start, - boundary=boundary, ) def __contains__(cls, member): if not isinstance(member, Enum): - import warnings - warnings.warn( - "in 3.12 __contains__ will no longer raise TypeError, but will return True or\n" - "False depending on whether the value is a member or the value of a member", - DeprecationWarning, - stacklevel=2, - ) raise TypeError( "unsupported operand type(s) for 'in': '%s' and '%s'" % ( type(member).__qualname__, cls.__class__.__qualname__)) @@ -624,7 +403,7 @@ 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("%s: cannot delete Enum member." % cls.__name__) super().__delattr__(attr) def __dir__(self): @@ -661,7 +440,7 @@ class EnumType(type): def __len__(cls): return len(cls._member_names_) - @_bltin_property + @property def __members__(cls): """ Returns a mapping of member name->value. @@ -672,10 +451,7 @@ class EnumType(type): return MappingProxyType(cls._member_map_) def __repr__(cls): - if Flag is not None and issubclass(cls, Flag): - return "" % cls.__name__ - else: - return "" % cls.__name__ + return "" % cls.__name__ def __reversed__(cls): """ @@ -693,10 +469,10 @@ 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 members.') super().__setattr__(name, value) - def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1, boundary=None): + def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1): """ Convenience method to create a new Enum class. @@ -731,6 +507,7 @@ class EnumType(type): else: member_name, member_value = item classdict[member_name] = member_value + enum_class = metacls.__new__(metacls, class_name, bases, classdict) # TODO: replace the frame hack if a blessed way to know the calling # module is ever developed @@ -740,16 +517,15 @@ class EnumType(type): except (AttributeError, ValueError, KeyError): pass if module is None: - _make_class_unpicklable(classdict) + _make_class_unpicklable(enum_class) else: - classdict['__module__'] = module + enum_class.__module__ = module if qualname is not None: - classdict['__qualname__'] = qualname + enum_class.__qualname__ = qualname - return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary) - - def _convert_(cls, name, module, filter, source=None, *, boundary=None): + return enum_class + def _convert_(cls, name, module, filter, source=None): """ Create a new Enum subclass that replaces a collection of global constants """ @@ -758,9 +534,9 @@ class EnumType(type): # module; # also, replace the __reduce_ex__ method so unpickling works in # previous Python versions - module_globals = sys.modules[module].__dict__ + module_globals = vars(sys.modules[module]) if source: - source = source.__dict__ + source = vars(source) else: source = module_globals # _value2member_map_ is populated in the same order every time @@ -776,12 +552,9 @@ class EnumType(type): except TypeError: # unless some values aren't comparable, in which case sort by name members.sort(key=lambda t: t[0]) - body = {t[0]: t[1] for t in members} - body['__module__'] = module - 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) + cls = cls(name, members, module=module) + cls.__reduce_ex__ = _reduce_ex_by_name + module_globals.update(cls.__members__) module_globals[name] = cls return cls @@ -807,7 +580,7 @@ class EnumType(type): return object, Enum def _find_data_type(bases): - data_types = set() + data_types = [] for chain in bases: candidate = None for base in chain.__mro__: @@ -815,19 +588,19 @@ class EnumType(type): continue elif issubclass(base, Enum): if base._member_type_ is not object: - data_types.add(base._member_type_) + data_types.append(base._member_type_) break elif '__new__' in base.__dict__: if issubclass(base, Enum): continue - data_types.add(candidate or base) + data_types.append(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() + return data_types[0] else: return None @@ -857,7 +630,7 @@ class EnumType(type): __new__ = classdict.get('__new__', None) # should __new__ be saved as __new_member__ later? - save_new = first_enum is not None and __new__ is not None + save_new = __new__ is not None if __new__ is None: # check all possibles for __new_member__ before falling back to @@ -881,21 +654,19 @@ class EnumType(type): # if a non-object.__new__ is used then whatever value/tuple was # assigned to the enum member name will be passed to __new__ and to the # new enum member's __init__ - if first_enum is None or __new__ in (Enum.__new__, object.__new__): + if __new__ is object.__new__: use_args = False else: use_args = True return __new__, save_new, use_args -EnumMeta = EnumType -class Enum(metaclass=EnumType): +class Enum(metaclass=EnumMeta): """ Generic enumeration. Derive from this class to define new enumerations. """ - def __new__(cls, value): # all enum instances are actually created during class construction # without calling this method; this method is called by the metaclass' @@ -922,30 +693,19 @@ class Enum(metaclass=EnumType): except Exception as e: exc = e result = None - try: - if isinstance(result, cls): - return result - elif ( - Flag is not None and issubclass(cls, Flag) - and cls._boundary_ is EJECT and isinstance(result, int) - ): - return result - else: - ve_exc = ValueError("%r is not a valid %s" % (value, cls.__qualname__)) - if result is None and exc is None: - raise ve_exc - elif exc is None: - exc = TypeError( - 'error in %s._missing_: returned %r instead of None or a valid member' - % (cls.__name__, result) - ) - if not isinstance(exc, ValueError): - exc.__context__ = ve_exc - raise exc - finally: - # ensure all variables that could hold an exception are destroyed - exc = None - ve_exc = None + if isinstance(result, cls): + return result + else: + ve_exc = ValueError("%r is not a valid %s" % (value, cls.__qualname__)) + if result is None and exc is None: + raise ve_exc + elif exc is None: + exc = TypeError( + 'error in %s._missing_: returned %r instead of None or a valid member' + % (cls.__name__, result) + ) + exc.__context__ = ve_exc + raise exc def _generate_next_value_(name, start, count, last_values): """ @@ -969,10 +729,11 @@ class Enum(metaclass=EnumType): return None def __repr__(self): - return "%s.%s" % ( self.__class__.__name__, self._name_) + return "<%s.%s: %r>" % ( + self.__class__.__name__, self._name_, self._value_) def __str__(self): - return "%s" % (self._name_, ) + return "%s.%s" % (self.__class__.__name__, self._name_) def __dir__(self): """ @@ -993,23 +754,14 @@ class Enum(metaclass=EnumType): # 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__) + str_overridden = type(self).__str__ not in (Enum.__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) @@ -1018,104 +770,34 @@ class Enum(metaclass=EnumType): return hash(self._name_) def __reduce_ex__(self, proto): - return getattr, (self.__class__, self._name_) + return self.__class__, (self._value_, ) - # enum.property is used to provide access to the `name` and - # `value` attributes of enum members while keeping some measure of + # DynamicClassAttribute is used to provide access to the `name` and + # `value` properties of enum members while keeping some measure of # protection from modification, while still allowing for an enumeration # to have members named `name` and `value`. This works because enumeration - # members are not set directly on the enum class; they are kept in a - # separate structure, _member_map_, which is where enum.property looks for - # them + # members are not set directly on the enum class -- __getattr__ is + # used to look them up. - @property + @DynamicClassAttribute def name(self): """The name of the Enum member.""" return self._name_ - @property + @DynamicClassAttribute def value(self): """The value of the Enum member.""" return self._value_ class IntEnum(int, Enum): - """ - Enum where members are also (and must be) ints - """ - - 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) + """Enum where members are also (and must be) ints""" -class StrEnum(str, Enum): - """ - Enum where members are also (and must be) strings - """ - - def __new__(cls, *values): - if len(values) > 3: - raise TypeError('too many arguments for str(): %r' % (values, )) - if len(values) == 1: - # it must be a string - if not isinstance(values[0], str): - raise TypeError('%r is not a string' % (values[0], )) - if len(values) >= 2: - # check that encoding argument is a string - if not isinstance(values[1], str): - raise TypeError('encoding must be a string, not %r' % (values[1], )) - if len(values) == 3: - # check that errors argument is a string - if not isinstance(values[2], str): - raise TypeError('errors must be a string, not %r' % (values[2])) - value = str(*values) - member = str.__new__(cls, value) - 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. - """ - return name.lower() - - -def _reduce_ex_by_global_name(self, proto): +def _reduce_ex_by_name(self, proto): return self.name -class FlagBoundary(StrEnum): - """ - control how out of range values are handled - "strict" -> error is raised [default for Flag] - "conform" -> extra bits are discarded - "eject" -> lose flag status [default for IntFlag] - "keep" -> keep flag status and all bits - """ - STRICT = auto() - CONFORM = auto() - EJECT = auto() - KEEP = auto() -STRICT, CONFORM, EJECT, KEEP = FlagBoundary - - -class Flag(Enum, boundary=STRICT): +class Flag(Enum): """ Support for flags """ @@ -1131,110 +813,45 @@ class Flag(Enum, boundary=STRICT): """ if not count: return start if start is not None else 1 - last_value = max(last_values) - try: - high_bit = _high_bit(last_value) - except Exception: - raise TypeError('Invalid Flag value: %r' % last_value) from None + for last_value in reversed(last_values): + try: + high_bit = _high_bit(last_value) + break + except Exception: + raise TypeError('Invalid Flag value: %r' % last_value) from None return 2 ** (high_bit+1) @classmethod - def _iter_member_by_value_(cls, value): - """ - Extract all members from the value in definition (i.e. increasing value) order. - """ - for val in _iter_bits_lsb(value & cls._flag_mask_): - yield cls._value2member_map_.get(val) - - _iter_member_ = _iter_member_by_value_ - - @classmethod - def _iter_member_by_def_(cls, value): + def _missing_(cls, value): """ - Extract all members from the value in definition order. + Returns member (possibly creating it) if one can be found for value. """ - yield from sorted( - cls._iter_member_by_value_(value), - key=lambda m: m._sort_order_, - ) + original_value = value + if value < 0: + value = ~value + possible_member = cls._create_pseudo_member_(value) + if original_value < 0: + possible_member = ~possible_member + return possible_member @classmethod - def _missing_(cls, value): + def _create_pseudo_member_(cls, value): """ - Create a composite member containing all canonical members present in `value`. - - If non-member values are present, result depends on `_boundary_` setting. + Create a composite member iff value contains only members. """ - if not isinstance(value, int): - raise ValueError( - "%r is not a valid %s" % (value, cls.__qualname__) - ) - # check boundaries - # - value must be in range (e.g. -16 <-> +15, i.e. ~15 <-> 15) - # - value must not include any skipped flags (e.g. if bit 2 is not - # defined, then 0d10 is invalid) - flag_mask = cls._flag_mask_ - all_bits = cls._all_bits_ - neg_value = None - if ( - not ~all_bits <= value <= all_bits - or value & (all_bits ^ flag_mask) - ): - 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), - )) - elif cls._boundary_ is CONFORM: - value = value & flag_mask - elif cls._boundary_ is EJECT: - return value - elif cls._boundary_ is KEEP: - if value < 0: - value = ( - max(all_bits+1, 2**(value.bit_length())) - + value - ) - else: - raise ValueError( - 'unknown flag boundary: %r' % (cls._boundary_, ) - ) - if value < 0: - neg_value = value - value = all_bits + 1 + value - # get members and unknown - unknown = value & ~flag_mask - member_value = value & flag_mask - if unknown and cls._boundary_ is not KEEP: - raise ValueError( - '%s(%r) --> unknown values %r [%s]' - % (cls.__name__, value, unknown, bin(unknown)) - ) - # normal Flag? - __new__ = getattr(cls, '__new_member__', None) - if cls._member_type_ is object and not __new__: + pseudo_member = cls._value2member_map_.get(value, None) + if pseudo_member is None: + # verify all bits are accounted for + _, extra_flags = _decompose(cls, value) + if extra_flags: + raise ValueError("%r is not a valid %s" % (value, cls.__qualname__)) # construct a singleton enum pseudo-member pseudo_member = object.__new__(cls) - else: - pseudo_member = (__new__ or cls._member_type_.__new__)(cls, value) - if not hasattr(pseudo_member, '_value_'): - pseudo_member._value_ = value - if member_value: - pseudo_member._name_ = '|'.join([ - m._name_ for m in cls._iter_member_(member_value) - ]) - if unknown: - pseudo_member._name_ += '|0x%x' % unknown - else: pseudo_member._name_ = None - # use setdefault in case another thread already created a composite - # with this value, but only if all members are known - # note: zero is a special case -- add it - if not unknown: + pseudo_member._value_ = value + # use setdefault in case another thread already created a composite + # with this value pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member) - if neg_value is not None: - cls._value2member_map_[neg_value] = pseudo_member return pseudo_member def __contains__(self, other): @@ -1245,42 +862,31 @@ class Flag(Enum, boundary=STRICT): raise TypeError( "unsupported operand type(s) for 'in': '%s' and '%s'" % ( type(other).__qualname__, self.__class__.__qualname__)) - if other._value_ == 0 or self._value_ == 0: - return False return other._value_ & self._value_ == other._value_ - def __iter__(self): - """ - Returns flags in definition order. - """ - yield from self._iter_member_(self._value_) - - def __len__(self): - return self._value_.bit_count() - def __repr__(self): - cls_name = self.__class__.__name__ - 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('|')) - 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) + cls = self.__class__ + if self._name_ is not None: + return '<%s.%s: %r>' % (cls.__name__, self._name_, self._value_) + members, uncovered = _decompose(cls, self._value_) + return '<%s.%s: %r>' % ( + cls.__name__, + '|'.join([str(m._name_ or m._value_) for m in members]), + self._value_, + ) def __str__(self): cls = self.__class__ - if self._name_ is None: - return '%s(%x)' % (cls.__name__, self._value_) + if self._name_ is not None: + return '%s.%s' % (cls.__name__, self._name_) + members, uncovered = _decompose(cls, self._value_) + if len(members) == 1 and members[0]._name_ is None: + return '%s.%r' % (cls.__name__, members[0]._value_) else: - return self._name_ + return '%s.%s' % ( + cls.__name__, + '|'.join([str(m._name_ or m._value_) for m in members]), + ) def __bool__(self): return bool(self._value_) @@ -1301,67 +907,86 @@ class Flag(Enum, boundary=STRICT): return self.__class__(self._value_ ^ other._value_) def __invert__(self): - if self._inverted_ is None: - if self._boundary_ is KEEP: - # use all bits - self._inverted_ = self.__class__(~self._value_) - else: - # calculate flags not in this member - self._inverted_ = self.__class__(self._flag_mask_ ^ self._value_) - if isinstance(self._inverted_, self.__class__): - self._inverted_._inverted_ = self - return self._inverted_ + members, uncovered = _decompose(self.__class__, self._value_) + inverted = self.__class__(0) + for m in self.__class__: + if m not in members and not (m._value_ & self._value_): + inverted = inverted | m + return self.__class__(inverted) -class IntFlag(int, Flag, boundary=EJECT): +class IntFlag(int, Flag): """ Support for integer-based Flags """ - def __format__(self, format_spec): + @classmethod + def _missing_(cls, value): + """ + Returns member (possibly creating it) if one can be found for value. + """ + if not isinstance(value, int): + raise ValueError("%r is not a valid %s" % (value, cls.__qualname__)) + new_member = cls._create_pseudo_member_(value) + return new_member + + @classmethod + def _create_pseudo_member_(cls, value): """ - Returns format using actual value unless __str__ has been overridden. + Create a composite member iff value contains only members. """ - str_overridden = type(self).__str__ != Flag.__str__ - value = self - if not str_overridden: - value = self._value_ - return int.__format__(value, format_spec) + pseudo_member = cls._value2member_map_.get(value, None) + if pseudo_member is None: + need_to_create = [value] + # get unaccounted for bits + _, extra_flags = _decompose(cls, value) + # timer = 10 + while extra_flags: + # timer -= 1 + bit = _high_bit(extra_flags) + flag_value = 2 ** bit + if (flag_value not in cls._value2member_map_ and + flag_value not in need_to_create + ): + need_to_create.append(flag_value) + if extra_flags == -flag_value: + extra_flags = 0 + else: + extra_flags ^= flag_value + for value in reversed(need_to_create): + # construct singleton pseudo-members + pseudo_member = int.__new__(cls, value) + pseudo_member._name_ = None + pseudo_member._value_ = value + # use setdefault in case another thread already created a composite + # with this value + pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member) + return pseudo_member def __or__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif isinstance(other, int): - other = other - else: + if not isinstance(other, (self.__class__, int)): return NotImplemented - value = self._value_ - return self.__class__(value | other) + result = self.__class__(self._value_ | self.__class__(other)._value_) + return result def __and__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif isinstance(other, int): - other = other - else: + if not isinstance(other, (self.__class__, int)): return NotImplemented - value = self._value_ - return self.__class__(value & other) + return self.__class__(self._value_ & self.__class__(other)._value_) def __xor__(self, other): - if isinstance(other, self.__class__): - other = other._value_ - elif isinstance(other, int): - other = other - else: + if not isinstance(other, (self.__class__, int)): return NotImplemented - value = self._value_ - return self.__class__(value ^ other) + return self.__class__(self._value_ ^ self.__class__(other)._value_) __ror__ = __or__ __rand__ = __and__ __rxor__ = __xor__ - __invert__ = Flag.__invert__ + + def __invert__(self): + result = self.__class__(~self._value_) + return result + def _high_bit(value): """ @@ -1384,441 +1009,31 @@ def unique(enumeration): (enumeration, alias_details)) return enumeration -def _power_of_two(value): - if value < 1: - return False - return value == 2 ** _high_bit(value) - -def global_enum_repr(self): - return '%s.%s' % (self.__class__.__module__, self._name_) - -def global_flag_repr(self): - module = self.__class__.__module__ - cls_name = self.__class__.__name__ - if self._name_ is None: - return "%x" % (module, cls_name, self._value_) - if _is_single_bit(self): - return '%s.%s' % (module, self._name_) - if self._boundary_ is not FlagBoundary.KEEP: - return module + module.join(self.name.split('|')) - else: - name = [] - for n in self._name_.split('|'): - if n.startswith('0'): - name.append(n) - else: - name.append('%s.%s' % (module, n)) - return '|'.join(name) - - -def global_enum(cls): +def _decompose(flag, value): """ - 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 - global namespace + Extract all members from the value. """ - if issubclass(cls, Flag): - cls.__repr__ = global_flag_repr - else: - cls.__repr__ = global_enum_repr - sys.modules[cls.__module__].__dict__.update(cls.__members__) - return cls - -def _simple_enum(etype=Enum, *, boundary=None, use_args=None): - """ - Class decorator that converts a normal class into an :class:`Enum`. No - safety checks are done, and some advanced behavior (such as - :func:`__init_subclass__`) is not available. Enum creation can be faster - using :func:`simple_enum`. - - >>> from enum import Enum, _simple_enum - >>> @_simple_enum(Enum) - ... class Color: - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> Color - - """ - def convert_class(cls): - nonlocal use_args - cls_name = cls.__name__ - if use_args is None: - use_args = etype._use_args_ - __new__ = cls.__dict__.get('__new__') - if __new__ is not None: - new_member = __new__.__func__ - else: - new_member = etype._member_type_.__new__ - attrs = {} - body = {} - if __new__ is not None: - body['__new_member__'] = new_member - body['_new_member_'] = new_member - body['_use_args_'] = use_args - body['_generate_next_value_'] = gnv = etype._generate_next_value_ - body['_member_names_'] = member_names = [] - body['_member_map_'] = member_map = {} - body['_value2member_map_'] = value2member_map = {} - body['_unhashable_values_'] = [] - body['_member_type_'] = member_type = etype._member_type_ - if issubclass(etype, Flag): - body['_boundary_'] = boundary or etype._boundary_ - body['_flag_mask_'] = None - body['_all_bits_'] = None - body['_inverted_'] = None - for name, obj in cls.__dict__.items(): - if name in ('__dict__', '__weakref__'): - continue - if _is_dunder(name) or _is_private(cls_name, name) or _is_sunder(name) or _is_descriptor(obj): - body[name] = obj - else: - attrs[name] = obj - if cls.__dict__.get('__doc__') is None: - body['__doc__'] = 'An enumeration.' - # - # 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 - 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) - gnv_last_values = [] - if issubclass(enum_class, Flag): - # Flag / IntFlag - single_bits = multi_bits = 0 - for name, value in attrs.items(): - if isinstance(value, auto) and auto.value is _auto_null: - value = gnv(name, 1, len(member_names), gnv_last_values) - if value in value2member_map: - # an alias to an existing member - redirect = property() - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = value2member_map[value] - else: - # create the member - if use_args: - if not isinstance(value, tuple): - value = (value, ) - member = new_member(enum_class, *value) - value = value[0] - else: - member = new_member(enum_class) - if __new__ is None: - member._value_ = value - member._name_ = name - member.__objclass__ = enum_class - member.__init__(value) - redirect = property() - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member - member._sort_order_ = len(member_names) - value2member_map[value] = member - if _is_single_bit(value): - # not a multi-bit alias, record in _member_names_ and _flag_mask_ - member_names.append(name) - single_bits |= value - else: - multi_bits |= value - gnv_last_values.append(value) - enum_class._flag_mask_ = single_bits - enum_class._all_bits_ = 2 ** ((single_bits|multi_bits).bit_length()) - 1 - # set correct __iter__ - member_list = [m._value_ for m in enum_class] - if member_list != sorted(member_list): - enum_class._iter_member_ = enum_class._iter_member_by_def_ - else: - # Enum / IntEnum / StrEnum - for name, value in attrs.items(): - if isinstance(value, auto): - if value.value is _auto_null: - value.value = gnv(name, 1, len(member_names), gnv_last_values) - value = value.value - if value in value2member_map: - # an alias to an existing member - redirect = property() - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = value2member_map[value] - else: - # create the member - if use_args: - if not isinstance(value, tuple): - value = (value, ) - member = new_member(enum_class, *value) - value = value[0] - else: - member = new_member(enum_class) - if __new__ is None: - member._value_ = value - member._name_ = name - member.__objclass__ = enum_class - member.__init__(value) - member._sort_order_ = len(member_names) - redirect = property() - redirect.__set_name__(enum_class, name) - setattr(enum_class, name, redirect) - member_map[name] = member - value2member_map[value] = member - member_names.append(name) - gnv_last_values.append(value) - if '__new__' in body: - enum_class.__new_member__ = enum_class.__new__ - enum_class.__new__ = Enum.__new__ - return enum_class - return convert_class - -@_simple_enum(StrEnum) -class EnumCheck: - """ - various conditions to check an enumeration for - """ - CONTINUOUS = "no skipped integer values" - NAMED_FLAGS = "multi-flag aliases may not contain unnamed flags" - UNIQUE = "one name per value" -CONTINUOUS, NAMED_FLAGS, UNIQUE = EnumCheck - - -class verify: - """ - Check an enumeration for various constraints. (see EnumCheck) - """ - def __init__(self, *checks): - self.checks = checks - def __call__(self, enumeration): - checks = self.checks - cls_name = enumeration.__name__ - if Flag is not None and issubclass(enumeration, Flag): - enum_type = 'flag' - elif issubclass(enumeration, Enum): - enum_type = 'enum' - else: - raise TypeError("the 'verify' decorator only works with Enum and Flag") - for check in checks: - if check is UNIQUE: - # check for duplicate names - duplicates = [] - for name, member in enumeration.__members__.items(): - if name != member.name: - duplicates.append((name, member.name)) - if duplicates: - alias_details = ', '.join( - ["%s -> %s" % (alias, name) for (alias, name) in duplicates]) - raise ValueError('aliases found in %r: %s' % - (enumeration, alias_details)) - elif check is CONTINUOUS: - values = set(e.value for e in enumeration) - if len(values) < 2: - continue - low, high = min(values), max(values) - missing = [] - if enum_type == 'flag': - # check for powers of two - for i in range(_high_bit(low)+1, _high_bit(high)): - if 2**i not in values: - missing.append(2**i) - elif enum_type == 'enum': - # check for powers of one - for i in range(low+1, high): - if i not in values: - missing.append(i) - else: - raise Exception('verify: unknown type %r' % enum_type) - if missing: - raise ValueError(('invalid %s %r: missing values %s' % ( - enum_type, cls_name, ', '.join((str(m) for m in missing))) - )[:256]) - # limit max length to protect against DOS attacks - elif check is NAMED_FLAGS: - # examine each alias and check for unnamed flags - member_names = enumeration._member_names_ - member_values = [m.value for m in enumeration] - missing_names = [] - missing_value = 0 - for name, alias in enumeration._member_map_.items(): - if name in member_names: - # not an alias - continue - values = list(_iter_bits_lsb(alias.value)) - missed = [v for v in values if v not in member_values] - if missed: - missing_names.append(name) - missing_value |= reduce(_or_, missed) - if missing_names: - if len(missing_names) == 1: - alias = 'alias %s is missing' % missing_names[0] - else: - alias = 'aliases %s and %s are missing' % ( - ', '.join(missing_names[:-1]), missing_names[-1] - ) - if _is_single_bit(missing_value): - value = 'value 0x%x' % missing_value - else: - value = 'combined values of 0x%x' % missing_value - raise ValueError( - 'invalid Flag %r: %s %s [use enum.show_flag_values(value) for details]' - % (cls_name, alias, value) - ) - return enumeration - -def _test_simple_enum(checked_enum, simple_enum): - """ - A function that can be used to test an enum created with :func:`_simple_enum` - against the version created by subclassing :class:`Enum`:: - - >>> from enum import Enum, _simple_enum, _test_simple_enum - >>> @_simple_enum(Enum) - ... class Color: - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> class CheckedColor(Enum): - ... RED = auto() - ... GREEN = auto() - ... BLUE = auto() - >>> _test_simple_enum(CheckedColor, Color) - - If differences are found, a :exc:`TypeError` is raised. - """ - failed = [] - if checked_enum.__dict__ != simple_enum.__dict__: - checked_dict = checked_enum.__dict__ - checked_keys = list(checked_dict.keys()) - simple_dict = simple_enum.__dict__ - simple_keys = list(simple_dict.keys()) - member_names = set( - list(checked_enum._member_map_.keys()) - + 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 - continue - elif key in member_names: - # members are checked below - continue - elif key not in simple_keys: - failed.append("missing key: %r" % (key, )) - elif key not in checked_keys: - failed.append("extra key: %r" % (key, )) - else: - checked_value = checked_dict[key] - simple_value = simple_dict[key] - if callable(checked_value): - continue - if key == '__doc__': - # remove all spaces/tabs - compressed_checked_value = checked_value.replace(' ','').replace('\t','') - compressed_simple_value = simple_value.replace(' ','').replace('\t','') - if compressed_checked_value != compressed_simple_value: - failed.append("%r:\n %s\n %s" % ( - key, - "checked -> %r" % (checked_value, ), - "simple -> %r" % (simple_value, ), - )) - elif checked_value != simple_value: - failed.append("%r:\n %s\n %s" % ( - key, - "checked -> %r" % (checked_value, ), - "simple -> %r" % (simple_value, ), - )) - failed.sort() - for name in member_names: - failed_member = [] - if name not in simple_keys: - failed.append('missing member from simple enum: %r' % name) - elif name not in checked_keys: - failed.append('extra member in simple enum: %r' % name) - else: - checked_member_dict = checked_enum[name].__dict__ - checked_member_keys = list(checked_member_dict.keys()) - simple_member_dict = simple_enum[name].__dict__ - simple_member_keys = list(simple_member_dict.keys()) - for key in set(checked_member_keys + simple_member_keys): - if key in ('__module__', '__objclass__', '_inverted_'): - # keys known to be different or absent - continue - elif key not in simple_member_keys: - failed_member.append("missing key %r not in the simple enum member %r" % (key, name)) - elif key not in checked_member_keys: - failed_member.append("extra key %r in simple enum member %r" % (key, name)) - else: - checked_value = checked_member_dict[key] - simple_value = simple_member_dict[key] - if checked_value != simple_value: - failed_member.append("%r:\n %s\n %s" % ( - key, - "checked member -> %r" % (checked_value, ), - "simple member -> %r" % (simple_value, ), - )) - if failed_member: - failed.append('%r member mismatch:\n %s' % ( - name, '\n '.join(failed_member), - )) - for method in ( - '__str__', '__repr__', '__reduce_ex__', '__format__', - '__getnewargs_ex__', '__getnewargs__', '__reduce_ex__', '__reduce__' - ): - if method in simple_keys and method in checked_keys: - # cannot compare functions, and it exists in both, so we're good - continue - elif method not in simple_keys and method not in checked_keys: - # method is inherited -- check it out - checked_method = getattr(checked_enum, method, None) - simple_method = getattr(simple_enum, method, None) - if hasattr(checked_method, '__func__'): - checked_method = checked_method.__func__ - simple_method = simple_method.__func__ - if checked_method != simple_method: - failed.append("%r: %-30s %s" % ( - method, - "checked -> %r" % (checked_method, ), - "simple -> %r" % (simple_method, ), - )) - else: - # if the method existed in only one of the enums, it will have been caught - # in the first checks above - pass - if failed: - raise TypeError('enum mismatch:\n %s' % '\n '.join(failed)) - -def _old_convert_(etype, name, module, filter, source=None, *, boundary=None): - """ - Create a new Enum subclass that replaces a collection of global constants - """ - # convert all constants from source (or module) that pass filter() to - # a new Enum called name, and export the enum and its members back to - # module; - # also, replace the __reduce_ex__ method so unpickling works in - # previous Python versions - module_globals = sys.modules[module].__dict__ - if source: - source = source.__dict__ - else: - source = module_globals - # _value2member_map_ is populated in the same order every time - # for a consistent reverse mapping of number to name when there - # are multiple names for the same number. - members = [ - (name, value) - for name, value in source.items() - if filter(name)] - try: - # sort by value - members.sort(key=lambda t: (t[1], t[0])) - except TypeError: - # unless some values aren't comparable, in which case sort by name - members.sort(key=lambda t: t[0]) - cls = etype(name, members, module=module, boundary=boundary or KEEP) - cls.__reduce_ex__ = _reduce_ex_by_global_name - cls.__repr__ = global_enum_repr - return cls + # _decompose is only called if the value is not named + not_covered = value + negative = value < 0 + members = [] + for member in flag: + member_value = member.value + if member_value and member_value & value == member_value: + members.append(member) + not_covered &= ~member_value + if not negative: + tmp = not_covered + while tmp: + flag_value = 2 ** _high_bit(tmp) + if flag_value in flag._value2member_map_: + members.append(flag._value2member_map_[flag_value]) + not_covered &= ~flag_value + tmp &= ~flag_value + if not members and value in flag._value2member_map_: + members.append(flag._value2member_map_[value]) + members.sort(key=lambda m: m._value_, reverse=True) + if len(members) > 1 and members[0].value == value: + # we have the breakdown, don't need the value member itself + members.pop(0) + return members, not_covered diff --git a/Lib/http/__init__.py b/Lib/http/__init__.py index 8b980e2..bf8d7d6 100644 --- a/Lib/http/__init__.py +++ b/Lib/http/__init__.py @@ -1,10 +1,9 @@ -from enum import IntEnum, _simple_enum +from enum import IntEnum __all__ = ['HTTPStatus'] -@_simple_enum(IntEnum) -class HTTPStatus: +class HTTPStatus(IntEnum): """HTTP status codes and reason phrases Status codes from the following RFCs are all observed: diff --git a/Lib/inspect.py b/Lib/inspect.py index 9f8cc01..0273ffa 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2561,6 +2561,9 @@ class _ParameterKind(enum.IntEnum): KEYWORD_ONLY = 3 VAR_KEYWORD = 4 + def __str__(self): + return self._name_ + @property def description(self): return _PARAM_NAME_MAPPING[self] diff --git a/Lib/plistlib.py b/Lib/plistlib.py index 5772efd..2eeebe4 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/pstats.py b/Lib/pstats.py index e77459d..0f93ae0 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -26,15 +26,14 @@ import time import marshal import re -from enum import StrEnum, _simple_enum +from enum import Enum from functools import cmp_to_key from dataclasses import dataclass from typing import Dict __all__ = ["Stats", "SortKey", "FunctionProfile", "StatsProfile"] -@_simple_enum(StrEnum) -class SortKey: +class SortKey(str, Enum): CALLS = 'calls', 'ncalls' CUMULATIVE = 'cumulative', 'cumtime' FILENAME = 'filename', 'module' diff --git a/Lib/re.py b/Lib/re.py index ea41217..1d82b50 100644 --- a/Lib/re.py +++ b/Lib/re.py @@ -142,9 +142,7 @@ __all__ = [ __version__ = "2.2.1" -@enum.global_enum -@enum._simple_enum(enum.IntFlag, boundary=enum.KEEP) -class RegexFlag: +class RegexFlag(enum.IntFlag): ASCII = A = sre_compile.SRE_FLAG_ASCII # assume ascii "locale" IGNORECASE = I = sre_compile.SRE_FLAG_IGNORECASE # ignore case LOCALE = L = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale @@ -156,6 +154,30 @@ class RegexFlag: TEMPLATE = T = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation + def __repr__(self): + if self._name_ is not None: + return f're.{self._name_}' + value = self._value_ + members = [] + negative = value < 0 + if negative: + value = ~value + for m in self.__class__: + if value & m._value_: + value &= ~m._value_ + members.append(f're.{m._name_}') + if value: + members.append(hex(value)) + res = '|'.join(members) + if negative: + if len(members) > 1: + res = f'~({res})' + else: + res = f'~{res}' + return res + __str__ = object.__str__ +globals().update(RegexFlag.__members__) + # sre exception error = sre_compile.error diff --git a/Lib/ssl.py b/Lib/ssl.py index a16ebd7..181065d 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -94,7 +94,6 @@ import sys import os from collections import namedtuple from enum import Enum as _Enum, IntEnum as _IntEnum, IntFlag as _IntFlag -from enum import _simple_enum import _ssl # if we can't import it, let the error propagate @@ -156,8 +155,7 @@ _PROTOCOL_NAMES = {value: name for name, value in _SSLMethod.__members__.items() _SSLv2_IF_EXISTS = getattr(_SSLMethod, 'PROTOCOL_SSLv2', None) -@_simple_enum(_IntEnum) -class TLSVersion: +class TLSVersion(_IntEnum): MINIMUM_SUPPORTED = _ssl.PROTO_MINIMUM_SUPPORTED SSLv3 = _ssl.PROTO_SSLv3 TLSv1 = _ssl.PROTO_TLSv1 @@ -167,8 +165,7 @@ class TLSVersion: MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED -@_simple_enum(_IntEnum) -class _TLSContentType: +class _TLSContentType(_IntEnum): """Content types (record layer) See RFC 8446, section B.1 @@ -182,8 +179,7 @@ class _TLSContentType: INNER_CONTENT_TYPE = 0x101 -@_simple_enum(_IntEnum) -class _TLSAlertType: +class _TLSAlertType(_IntEnum): """Alert types for TLSContentType.ALERT messages See RFC 8466, section B.2 @@ -224,8 +220,7 @@ class _TLSAlertType: NO_APPLICATION_PROTOCOL = 120 -@_simple_enum(_IntEnum) -class _TLSMessageType: +class _TLSMessageType(_IntEnum): """Message types (handshake protocol) See RFC 8446, section B.3 diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index a44f8f5..3fac03d 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -1,7 +1,6 @@ import ast import builtins import dis -import enum import os import sys import types @@ -699,35 +698,6 @@ class AST_Tests(unittest.TestCase): with self.assertRaisesRegex(ValueError, f"Name node can't be used with '{constant}' constant"): compile(expr, "", "eval") - def test_precedence_enum(self): - class _Precedence(enum.IntEnum): - """Precedence table that originated from python grammar.""" - TUPLE = enum.auto() - YIELD = enum.auto() # 'yield', 'yield from' - TEST = enum.auto() # 'if'-'else', 'lambda' - OR = enum.auto() # 'or' - AND = enum.auto() # 'and' - NOT = enum.auto() # 'not' - CMP = enum.auto() # '<', '>', '==', '>=', '<=', '!=', - # 'in', 'not in', 'is', 'is not' - EXPR = enum.auto() - BOR = EXPR # '|' - BXOR = enum.auto() # '^' - BAND = enum.auto() # '&' - SHIFT = enum.auto() # '<<', '>>' - ARITH = enum.auto() # '+', '-' - TERM = enum.auto() # '*', '@', '/', '%', '//' - FACTOR = enum.auto() # unary '+', '-', '~' - POWER = enum.auto() # '**' - AWAIT = enum.auto() # 'await' - ATOM = enum.auto() - def next(self): - try: - return self.__class__(self + 1) - except ValueError: - return self - enum._test_simple_enum(_Precedence, ast._Precedence) - class ASTHelpers_Test(unittest.TestCase): maxDiff = None diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 0267ff5..eb1266b 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -1,32 +1,16 @@ import enum -import doctest import inspect -import os import pydoc import sys import unittest import threading from collections import OrderedDict -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 Enum, IntEnum, EnumMeta, Flag, IntFlag, unique, auto 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 test.support import ALWAYS_EQ, check__all__, threading_helper from datetime import timedelta -python_version = sys.version_info[:2] - -def load_tests(loader, tests, ignore): - tests.addTests(doctest.DocTestSuite(enum)) - if os.path.exists('Doc/library/enum.rst'): - tests.addTests(doctest.DocFileSuite( - '../../Doc/library/enum.rst', - optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE, - )) - return tests # for pickle tests try: @@ -62,9 +46,14 @@ except Exception as exc: FlagStooges = exc # for pickle test and subclass tests -class Name(StrEnum): - BDFL = 'Guido van Rossum' - FLUFL = 'Barry Warsaw' +try: + class StrEnum(str, Enum): + 'accepts only string values' + class Name(StrEnum): + BDFL = 'Guido van Rossum' + FLUFL = 'Barry Warsaw' +except Exception as exc: + Name = exc try: Question = Enum('Question', 'who what when where why', module=__name__) @@ -265,8 +254,11 @@ class TestEnum(unittest.TestCase): 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)) + self.assertEqual(str(e), 'Season.' + season) + self.assertEqual( + repr(e), + ''.format(season, i), + ) def test_value_name(self): Season = self.Season @@ -355,38 +347,17 @@ 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): + def test_contains(self): Season = self.Season self.assertIn(Season.AUTUMN, Season) with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 3 in Season + 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) + 'AUTUMN' in 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) @@ -458,13 +429,6 @@ class TestEnum(unittest.TestCase): 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 def test_enum_with_value_name(self): class Huh(Enum): @@ -509,7 +473,7 @@ class TestEnum(unittest.TestCase): two = 2.0 def __format__(self, spec): return 'Format!!' - self.assertEqual(str(EnumWithFormatOverride.one), 'one') + self.assertEqual(str(EnumWithFormatOverride.one), 'EnumWithFormatOverride.one') self.assertEqual('{}'.format(EnumWithFormatOverride.one), 'Format!!') def test_str_and_format_override_enum(self): @@ -549,42 +513,11 @@ class TestEnum(unittest.TestCase): two = 2.0 def __format__(self, spec): return 'TestFloat success!' - self.assertEqual(str(TestFloat.one), 'one') + self.assertEqual(str(TestFloat.one), 'TestFloat.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)) + self.assertEqual(spec.format(member), spec.format(member.value)) def test_format_enum_date(self): Holiday = self.Holiday @@ -610,12 +543,7 @@ class TestEnum(unittest.TestCase): 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 + Grades = self.Grades self.assertFormatIsValue('{}', Grades.C) self.assertFormatIsValue('{:}', Grades.C) self.assertFormatIsValue('{:20}', Grades.C) @@ -665,15 +593,19 @@ class TestEnum(unittest.TestCase): 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' @@ -689,22 +621,21 @@ 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 - class UnBrokenInt(int): - __qualname__ = 'UnBrokenInt' + class BrokenInt(int): + __qualname__ = 'BrokenInt' def __new__(cls, value): return int.__new__(cls, value) - class MyUnBrokenEnum(UnBrokenInt, Enum): - __qualname__ = 'MyUnBrokenEnum' + class MyBrokenEnum(BrokenInt, Enum): + __qualname__ = 'MyBrokenEnum' G = 7 H = 8 I = 9 - self.assertIs(MyUnBrokenEnum._member_type_, UnBrokenInt) - self.assertIs(MyUnBrokenEnum(7), MyUnBrokenEnum.G) - globals()['UnBrokenInt'] = UnBrokenInt - globals()['MyUnBrokenEnum'] = MyUnBrokenEnum - test_pickle_dump_load(self.assertIs, MyUnBrokenEnum.I) - test_pickle_dump_load(self.assertIs, MyUnBrokenEnum) + self.assertIs(MyBrokenEnum._member_type_, BrokenInt) + self.assertIs(MyBrokenEnum(7), MyBrokenEnum.G) + globals()['BrokenInt'] = BrokenInt + globals()['MyBrokenEnum'] = MyBrokenEnum + test_pickle_exception(self.assertRaises, TypeError, MyBrokenEnum.G) + test_pickle_exception(self.assertRaises, PicklingError, MyBrokenEnum) def test_too_many_data_types(self): with self.assertRaisesRegex(TypeError, 'too many data types'): @@ -764,13 +695,14 @@ class TestEnum(unittest.TestCase): tau = 'Tau' self.assertTrue(phy.pi < phy.tau) - def test_strenum_inherited_methods(self): + def test_strenum_inherited(self): + class StrEnum(str, Enum): + pass 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): @@ -877,7 +809,7 @@ class TestEnum(unittest.TestCase): class ReplaceGlobalInt(IntEnum): ONE = 1 TWO = 2 - ReplaceGlobalInt.__reduce_ex__ = enum._reduce_ex_by_global_name + ReplaceGlobalInt.__reduce_ex__ = enum._reduce_ex_by_name for proto in range(HIGHEST_PROTOCOL): self.assertEqual(ReplaceGlobalInt.TWO.__reduce_ex__(proto), 'TWO') @@ -1302,11 +1234,9 @@ class TestEnum(unittest.TestCase): return self._intname def __repr__(self): # repr() is updated to include the name and type info - return "{}({!r}, {})".format( - type(self).__name__, - self.__name__, - int.__repr__(self), - ) + return "{}({!r}, {})".format(type(self).__name__, + self.__name__, + int.__repr__(self)) def __str__(self): # str() is unchanged, even if it relies on the repr() fallback base = int @@ -1321,8 +1251,7 @@ class TestEnum(unittest.TestCase): if isinstance(self, NamedInt) and isinstance(other, NamedInt): return NamedInt( '({0} + {1})'.format(self.__name__, other.__name__), - temp, - ) + temp ) else: return temp @@ -1362,11 +1291,9 @@ class TestEnum(unittest.TestCase): return self._intname def __repr__(self): # repr() is updated to include the name and type info - return "{}({!r}, {})".format( - type(self).__name__, - self.__name__, - int.__repr__(self), - ) + return "{}({!r}, {})".format(type(self).__name__, + self.__name__, + int.__repr__(self)) def __str__(self): # str() is unchanged, even if it relies on the repr() fallback base = int @@ -1381,8 +1308,7 @@ class TestEnum(unittest.TestCase): if isinstance(self, NamedInt) and isinstance(other, NamedInt): return NamedInt( '({0} + {1})'.format(self.__name__, other.__name__), - temp, - ) + temp ) else: return temp @@ -1422,11 +1348,9 @@ class TestEnum(unittest.TestCase): return self._intname def __repr__(self): # repr() is updated to include the name and type info - return "{}({!r}, {})".format( - type(self).__name__, - self.__name__, - int.__repr__(self), - ) + return "{}({!r}, {})".format(type(self).__name__, + self.__name__, + int.__repr__(self)) def __str__(self): # str() is unchanged, even if it relies on the repr() fallback base = int @@ -1441,8 +1365,7 @@ class TestEnum(unittest.TestCase): if isinstance(self, NamedInt) and isinstance(other, NamedInt): return NamedInt( '({0} + {1})'.format(self.__name__, other.__name__), - temp, - ) + temp ) else: return temp @@ -1482,11 +1405,9 @@ class TestEnum(unittest.TestCase): return self._intname def __repr__(self): # repr() is updated to include the name and type info - return "{}({!r}, {})".format( - type(self).__name__, - self.__name__, - int.__repr__(self), - ) + return "{}({!r}, {})".format(type(self).__name__, + self.__name__, + int.__repr__(self)) def __str__(self): # str() is unchanged, even if it relies on the repr() fallback base = int @@ -1501,8 +1422,7 @@ class TestEnum(unittest.TestCase): if isinstance(self, NamedInt) and isinstance(other, NamedInt): return NamedInt( '({0} + {1})'.format(self.__name__, other.__name__), - temp, - ) + temp ) else: return temp @@ -1511,6 +1431,7 @@ class TestEnum(unittest.TestCase): x = ('the-x', 1) y = ('the-y', 2) + self.assertIs(NEI.__new__, Enum.__new__) self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") globals()['NamedInt'] = NamedInt @@ -1539,11 +1460,9 @@ class TestEnum(unittest.TestCase): return self._intname def __repr__(self): # repr() is updated to include the name and type info - return "{}({!r}, {})".format( - type(self).__name__, - self.__name__, - int.__repr__(self), - ) + return "{}({!r}, {})".format(type(self).__name__, + self.__name__, + int.__repr__(self)) def __str__(self): # str() is unchanged, even if it relies on the repr() fallback base = int @@ -1574,10 +1493,10 @@ class TestEnum(unittest.TestCase): NI5 = NamedInt('test', 5) self.assertEqual(NI5, 5) self.assertEqual(NEI.y.value, 2) - test_pickle_dump_load(self.assertIs, NEI.y) - test_pickle_dump_load(self.assertIs, NEI) + test_pickle_exception(self.assertRaises, TypeError, NEI.x) + test_pickle_exception(self.assertRaises, PicklingError, NEI) - def test_subclasses_with_direct_pickle_support(self): + def test_subclasses_without_direct_pickle_support_using_name(self): class NamedInt(int): __qualname__ = 'NamedInt' def __new__(cls, *args): @@ -1594,11 +1513,9 @@ class TestEnum(unittest.TestCase): return self._intname def __repr__(self): # repr() is updated to include the name and type info - return "{}({!r}, {})".format( - type(self).__name__, - self.__name__, - int.__repr__(self), - ) + return "{}({!r}, {})".format(type(self).__name__, + self.__name__, + int.__repr__(self)) def __str__(self): # str() is unchanged, even if it relies on the repr() fallback base = int @@ -1613,8 +1530,7 @@ class TestEnum(unittest.TestCase): if isinstance(self, NamedInt) and isinstance(other, NamedInt): return NamedInt( '({0} + {1})'.format(self.__name__, other.__name__), - temp, - ) + temp ) else: return temp @@ -1778,13 +1694,6 @@ class TestEnum(unittest.TestCase): class Test(Base): test = 1 self.assertEqual(Test.test.test, 'dynamic') - class Base2(Enum): - @enum.property - def flash(self): - return 'flashy dynamic' - class Test(Base2): - flash = 1 - self.assertEqual(Test.flash.flash, 'flashy dynamic') def test_no_duplicates(self): class UniqueEnum(Enum): @@ -2023,38 +1932,6 @@ class TestEnum(unittest.TestCase): else: raise Exception('Exception not raised.') - def test_missing_exceptions_reset(self): - import weakref - # - class TestEnum(enum.Enum): - VAL1 = 'val1' - VAL2 = 'val2' - # - class Class1: - def __init__(self): - # Gracefully handle an exception of our own making - try: - raise ValueError() - except ValueError: - pass - # - class Class2: - def __init__(self): - # Gracefully handle an exception of Enum's making - try: - TestEnum('invalid_value') - except ValueError: - pass - # No strong refs here so these are free to die. - 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 gargage collection - # cycle, unlike Class1. - self.assertIs(class_1_ref(), None) - self.assertIs(class_2_ref(), None) - def test_multiple_mixin(self): class MaxMixin: @classproperty @@ -2081,7 +1958,7 @@ 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() @@ -2152,6 +2029,13 @@ class TestEnum(unittest.TestCase): self.assertTrue(issubclass(ReformedColor, int)) def test_multiple_inherited_mixin(self): + class StrEnum(str, Enum): + def __new__(cls, *args, **kwargs): + for a in args: + if not isinstance(a, str): + raise TypeError("Enumeration '%s' (%s) is not" + " a string" % (a, type(a).__name__)) + return str.__new__(cls, *args, **kwargs) @unique class Decision1(StrEnum): REVERT = "REVERT" @@ -2191,50 +2075,6 @@ class TestEnum(unittest.TestCase): return member self.assertEqual(Fee.TEST, 2) - def test_miltuple_mixin_with_common_data_type(self): - class CaseInsensitiveStrEnum(str, Enum): - @classmethod - def _missing_(cls, value): - for member in cls._member_map_.values(): - if member._value_.lower() == value.lower(): - return member - return super()._missing_(value) - # - class LenientStrEnum(str, Enum): - def __init__(self, *args): - self._valid = True - @classmethod - def _missing_(cls, value): - unknown = cls._member_type_.__new__(cls, value) - unknown._valid = False - unknown._name_ = value.upper() - unknown._value_ = value - cls._member_map_[value] = unknown - return unknown - @property - def valid(self): - return self._valid - # - class JobStatus(CaseInsensitiveStrEnum, LenientStrEnum): - ACTIVE = "active" - PENDING = "pending" - TERMINATED = "terminated" - # - JS = JobStatus - self.assertEqual(list(JobStatus), [JS.ACTIVE, JS.PENDING, JS.TERMINATED]) - self.assertEqual(JS.ACTIVE, 'active') - self.assertEqual(JS.ACTIVE.value, 'active') - self.assertIs(JS('Active'), JS.ACTIVE) - self.assertTrue(JS.ACTIVE.valid) - missing = JS('missing') - self.assertEqual(list(JobStatus), [JS.ACTIVE, JS.PENDING, JS.TERMINATED]) - self.assertEqual(JS.ACTIVE, 'active') - self.assertEqual(JS.ACTIVE.value, 'active') - self.assertIs(JS('Active'), JS.ACTIVE) - self.assertTrue(JS.ACTIVE.valid) - self.assertTrue(isinstance(missing, JS)) - self.assertFalse(missing.valid) - def test_empty_globals(self): # bpo-35717: sys._getframe(2).f_globals['__name__'] fails with KeyError # when using compile and exec because f_globals is empty @@ -2244,220 +2084,8 @@ class TestEnum(unittest.TestCase): local_ls = {} exec(code, global_ns, local_ls) - def test_strenum(self): - class GoodStrEnum(StrEnum): - one = '1' - two = '2' - three = b'3', 'ascii' - four = b'4', 'latin1', 'strict' - self.assertEqual(GoodStrEnum.one, '1') - self.assertEqual(str(GoodStrEnum.one), '1') - 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') - # - class DumbMixin: - def __str__(self): - return "don't do this" - class DumbStrEnum(DumbMixin, StrEnum): - 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, StrEnum): - eight = '8' - self.assertEqual(HelloEnum.eight, '8') - self.assertEqual(HelloEnum.eight, str(HelloEnum.eight)) - # - class GoodbyeMixin: - def goodbye(self): - print('%s wishes you a fond farewell') - class GoodbyeEnum(GoodbyeMixin, EnumMixin, StrEnum): - nine = '9' - self.assertEqual(GoodbyeEnum.nine, '9') - self.assertEqual(GoodbyeEnum.nine, str(GoodbyeEnum.nine)) - # - with self.assertRaisesRegex(TypeError, '1 is not a string'): - class FirstFailedStrEnum(StrEnum): - one = 1 - two = '2' - with self.assertRaisesRegex(TypeError, "2 is not a string"): - class SecondFailedStrEnum(StrEnum): - one = '1' - two = 2, - three = '3' - with self.assertRaisesRegex(TypeError, '2 is not a string'): - class ThirdFailedStrEnum(StrEnum): - one = '1' - two = 2 - with self.assertRaisesRegex(TypeError, 'encoding must be a string, not %r' % (sys.getdefaultencoding, )): - class ThirdFailedStrEnum(StrEnum): - one = '1' - two = b'2', sys.getdefaultencoding - with self.assertRaisesRegex(TypeError, 'errors must be a string, not 9'): - class ThirdFailedStrEnum(StrEnum): - 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 - class OkayEnum(CustomStrEnum): - one = '1' - two = '2' - three = b'3', 'ascii' - four = b'4', 'latin1', 'strict' - self.assertEqual(OkayEnum.one, '1') - self.assertEqual(str(OkayEnum.one), 'one') - self.assertEqual('{}'.format(OkayEnum.one), 'one') - self.assertEqual(repr(OkayEnum.one), 'OkayEnum.one') - # - class DumbMixin: - def __str__(self): - return "don't do this" - class DumbStrEnum(DumbMixin, CustomStrEnum): - five = '5' - six = '6' - seven = '7' - self.assertEqual(DumbStrEnum.seven, '7') - self.assertEqual(str(DumbStrEnum.seven), "don't do this") - # - class EnumMixin(Enum): - def hello(self): - print('hello from %s' % (self, )) - class HelloEnum(EnumMixin, CustomStrEnum): - eight = '8' - self.assertEqual(HelloEnum.eight, '8') - self.assertEqual(str(HelloEnum.eight), 'eight') - # - class GoodbyeMixin: - def goodbye(self): - print('%s wishes you a fond farewell') - class GoodbyeEnum(GoodbyeMixin, EnumMixin, CustomStrEnum): - nine = '9' - self.assertEqual(GoodbyeEnum.nine, '9') - self.assertEqual(str(GoodbyeEnum.nine), 'nine') - # - class FirstFailedStrEnum(CustomStrEnum): - one = 1 # this will become '1' - 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 - - def test_missing_value_error(self): - with self.assertRaisesRegex(TypeError, "_value_ not set in __new__"): - class Combined(str, Enum): - # - def __new__(cls, value, sequence): - enum = str.__new__(cls, value) - if '(' in value: - fis_name, segment = value.split('(', 1) - segment = segment.strip(' )') - else: - fis_name = value - segment = None - enum.fis_name = fis_name - enum.segment = segment - enum.sequence = sequence - return enum - # - def __repr__(self): - return "<%s.%s>" % (self.__class__.__name__, self._name_) - # - key_type = 'An$(1,2)', 0 - company_id = 'An$(3,2)', 1 - code = 'An$(5,1)', 2 - description = 'Bn$', 3 - @unittest.skipUnless( - python_version == (3, 9), + sys.version_info[:2] == (3, 9), 'private variables are now normal attributes', ) def test_warning_for_private_variables(self): @@ -2472,70 +2100,6 @@ class TestEnum(unittest.TestCase): except ValueError: pass - def test_private_variable_is_normal_attribute(self): - class Private(Enum): - __corporal = 'Radar' - __major_ = 'Hoolihan' - 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."): - 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): - # - foo_defines = {'FOO_CAT': 'aloof', 'BAR_DOG': 'friendly', 'FOO_HORSE': 'big'} - class Foo(Enum): - vars().update({ - k: v - for k, v in foo_defines.items() - if k.startswith('FOO_') - }) - def upper(self): - return self.value.upper() - self.assertEqual(list(Foo), [Foo.FOO_CAT, Foo.FOO_HORSE]) - self.assertEqual(Foo.FOO_CAT.value, 'aloof') - self.assertEqual(Foo.FOO_HORSE.upper(), 'BIG') - # - with self.assertRaisesRegex(TypeError, "'FOO_CAT' already defined as: 'aloof'"): - class FooBar(Enum): - vars().update({ - k: v - for k, v in foo_defines.items() - if k.startswith('FOO_') - }, - **{'FOO_CAT': 'small'}, - ) - def upper(self): - return self.value.upper() - class TestOrder(unittest.TestCase): @@ -2615,71 +2179,68 @@ class TestFlag(unittest.TestCase): class Color(Flag): BLACK = 0 RED = 1 - ROJO = 1 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') + self.assertEqual(str(Perm.R), 'Perm.R') + self.assertEqual(str(Perm.W), 'Perm.W') + self.assertEqual(str(Perm.X), 'Perm.X') + self.assertEqual(str(Perm.R | Perm.W), 'Perm.R|W') + self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|W|X') + self.assertEqual(str(Perm(0)), 'Perm.0') + self.assertEqual(str(~Perm.R), 'Perm.W|X') + self.assertEqual(str(~Perm.W), 'Perm.R|X') + self.assertEqual(str(~Perm.X), 'Perm.R|W') + self.assertEqual(str(~(Perm.R | Perm.W)), 'Perm.X') + self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm.0') + self.assertEqual(str(Perm(~0)), 'Perm.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') + self.assertEqual(str(Open.RO), 'Open.RO') + self.assertEqual(str(Open.WO), 'Open.WO') + self.assertEqual(str(Open.AC), 'Open.AC') + self.assertEqual(str(Open.RO | Open.CE), 'Open.CE') + self.assertEqual(str(Open.WO | Open.CE), 'Open.CE|WO') + self.assertEqual(str(~Open.RO), 'Open.CE|AC|RW|WO') + self.assertEqual(str(~Open.WO), 'Open.CE|RW') + self.assertEqual(str(~Open.AC), 'Open.CE') + self.assertEqual(str(~(Open.RO | Open.CE)), 'Open.AC') + self.assertEqual(str(~(Open.WO | Open.CE)), 'Open.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') + self.assertEqual(repr(Perm.R), '') + self.assertEqual(repr(Perm.W), '') + self.assertEqual(repr(Perm.X), '') + self.assertEqual(repr(Perm.R | Perm.W), '') + self.assertEqual(repr(Perm.R | Perm.W | Perm.X), '') + self.assertEqual(repr(Perm(0)), '') + self.assertEqual(repr(~Perm.R), '') + self.assertEqual(repr(~Perm.W), '') + self.assertEqual(repr(~Perm.X), '') + self.assertEqual(repr(~(Perm.R | Perm.W)), '') + self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '') + self.assertEqual(repr(Perm(~0)), '') 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') + self.assertEqual(repr(Open.RO), '') + self.assertEqual(repr(Open.WO), '') + self.assertEqual(repr(Open.AC), '') + self.assertEqual(repr(Open.RO | Open.CE), '') + self.assertEqual(repr(Open.WO | Open.CE), '') + self.assertEqual(repr(~Open.RO), '') + self.assertEqual(repr(~Open.WO), '') + self.assertEqual(repr(~Open.AC), '') + self.assertEqual(repr(~(Open.RO | Open.CE)), '') + self.assertEqual(repr(~(Open.WO | Open.CE)), '') def test_format(self): Perm = self.Perm - self.assertEqual(format(Perm.R, ''), 'R') - self.assertEqual(format(Perm.R | Perm.X, ''), 'R|X') + self.assertEqual(format(Perm.R, ''), 'Perm.R') + self.assertEqual(format(Perm.R | Perm.X, ''), 'Perm.R|X') def test_or(self): Perm = self.Perm @@ -2748,49 +2309,6 @@ class TestFlag(unittest.TestCase): for f in Open: self.assertEqual(bool(f.value), bool(f)) - def test_boundary(self): - self.assertIs(enum.Flag._boundary_, STRICT) - class Iron(Flag, boundary=STRICT): - ONE = 1 - TWO = 2 - EIGHT = 8 - self.assertIs(Iron._boundary_, STRICT) - # - class Water(Flag, boundary=CONFORM): - ONE = 1 - TWO = 2 - EIGHT = 8 - self.assertIs(Water._boundary_, CONFORM) - # - class Space(Flag, boundary=EJECT): - ONE = 1 - TWO = 2 - EIGHT = 8 - self.assertIs(Space._boundary_, EJECT) - # - class Bizarre(Flag, boundary=KEEP): - b = 3 - c = 4 - d = 6 - # - self.assertRaisesRegex(ValueError, 'invalid value: 7', Iron, 7) - # - self.assertIs(Water(7), Water.ONE|Water.TWO) - self.assertIs(Water(~9), Water.TWO) - # - self.assertEqual(Space(7), 7) - self.assertTrue(type(Space(7)) is int) - # - self.assertEqual(list(Bizarre), [Bizarre.c]) - self.assertIs(Bizarre(3), Bizarre.b) - self.assertIs(Bizarre(6), Bizarre.d) - - def test_iter(self): - Color = self.Color - Open = self.Open - self.assertEqual(list(Color), [Color.RED, Color.GREEN, Color.BLUE]) - self.assertEqual(list(Open), [Open.WO, Open.RW, Open.CE]) - def test_programatic_function_string(self): Perm = Flag('Perm', 'R W X') lst = list(Perm) @@ -2872,41 +2390,19 @@ class TestFlag(unittest.TestCase): test_pickle_dump_load(self.assertIs, FlagStooges.CURLY|FlagStooges.MOE) test_pickle_dump_load(self.assertIs, FlagStooges) - @unittest.skipIf( - python_version >= (3, 12), - '__contains__ now returns True/False for all inputs', - ) - def test_contains_er(self): + def test_contains(self): Open = self.Open Color = self.Color self.assertFalse(Color.BLACK in Open) self.assertFalse(Open.RO in Color) with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 'BLACK' in Color + 'BLACK' in Color with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 'RO' in Open + 'RO' in Open with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 1 in Color + 1 in Color with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 1 in Open - - @unittest.skipIf( - python_version < (3, 12), - '__contains__ only works with enum memmbers before 3.12', - ) - def test_contains_tf(self): - Open = self.Open - Color = self.Color - self.assertFalse(Color.BLACK in Open) - self.assertFalse(Open.RO in Color) - self.assertFalse('BLACK' in Color) - self.assertFalse('RO' in Open) - self.assertTrue(1 in Color) - self.assertTrue(1 in Open) + 1 in Open def test_member_contains(self): Perm = self.Perm @@ -2928,48 +2424,6 @@ class TestFlag(unittest.TestCase): self.assertFalse(W in RX) self.assertFalse(X in RW) - def test_member_iter(self): - Color = self.Color - self.assertEqual(list(Color.BLACK), []) - self.assertEqual(list(Color.PURPLE), [Color.RED, Color.BLUE]) - self.assertEqual(list(Color.BLUE), [Color.BLUE]) - self.assertEqual(list(Color.GREEN), [Color.GREEN]) - self.assertEqual(list(Color.WHITE), [Color.RED, Color.GREEN, Color.BLUE]) - self.assertEqual(list(Color.WHITE), [Color.RED, Color.GREEN, Color.BLUE]) - - def test_member_length(self): - self.assertEqual(self.Color.__len__(self.Color.BLACK), 0) - self.assertEqual(self.Color.__len__(self.Color.GREEN), 1) - self.assertEqual(self.Color.__len__(self.Color.PURPLE), 2) - self.assertEqual(self.Color.__len__(self.Color.BLANCO), 3) - - def test_number_reset_and_order_cleanup(self): - class Confused(Flag): - _order_ = 'ONE TWO FOUR DOS EIGHT SIXTEEN' - ONE = auto() - TWO = auto() - FOUR = auto() - DOS = 2 - EIGHT = auto() - SIXTEEN = auto() - self.assertEqual( - list(Confused), - [Confused.ONE, Confused.TWO, Confused.FOUR, Confused.EIGHT, Confused.SIXTEEN]) - self.assertIs(Confused.TWO, Confused.DOS) - self.assertEqual(Confused.DOS._value_, 2) - self.assertEqual(Confused.EIGHT._value_, 8) - self.assertEqual(Confused.SIXTEEN._value_, 16) - - def test_aliases(self): - Color = self.Color - self.assertEqual(Color(1).name, 'RED') - self.assertEqual(Color['ROJO'].name, 'RED') - self.assertEqual(Color(7).name, 'WHITE') - self.assertEqual(Color['BLANCO'].name, 'WHITE') - self.assertIs(Color.BLANCO, Color.WHITE) - Open = self.Open - self.assertIs(Open['AC'], Open.AC) - def test_auto_number(self): class Color(Flag): red = auto() @@ -2987,6 +2441,20 @@ class TestFlag(unittest.TestCase): red = 'not an int' blue = auto() + def test_cascading_failure(self): + class Bizarre(Flag): + c = 3 + d = 4 + f = 6 + # Bizarre.c | Bizarre.d + name = "TestFlag.test_cascading_failure..Bizarre" + self.assertRaisesRegex(ValueError, "5 is not a valid " + name, Bizarre, 5) + self.assertRaisesRegex(ValueError, "5 is not a valid " + name, Bizarre, 5) + self.assertRaisesRegex(ValueError, "2 is not a valid " + name, Bizarre, 2) + self.assertRaisesRegex(ValueError, "2 is not a valid " + name, Bizarre, 2) + self.assertRaisesRegex(ValueError, "1 is not a valid " + name, Bizarre, 1) + self.assertRaisesRegex(ValueError, "1 is not a valid " + name, Bizarre, 1) + def test_duplicate_auto(self): class Dupes(Enum): first = primero = auto() @@ -2994,6 +2462,13 @@ class TestFlag(unittest.TestCase): third = auto() self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes)) + def test_bizarre(self): + class Bizarre(Flag): + b = 3 + c = 4 + d = 6 + self.assertEqual(repr(Bizarre(7)), '') + def test_multiple_mixin(self): class AllMixin: @classproperty @@ -3017,7 +2492,7 @@ 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() @@ -3116,9 +2591,9 @@ class TestIntFlag(unittest.TestCase): """Tests of the IntFlags.""" class Perm(IntFlag): - R = 1 << 2 - W = 1 << 1 X = 1 << 0 + W = 1 << 1 + R = 1 << 2 class Open(IntFlag): RO = 0 @@ -3130,17 +2605,9 @@ class TestIntFlag(unittest.TestCase): class Color(IntFlag): BLACK = 0 RED = 1 - ROJO = 1 GREEN = 2 BLUE = 4 PURPLE = RED|BLUE - WHITE = RED|GREEN|BLUE - BLANCO = RED|GREEN|BLUE - - class Skip(IntFlag): - FIRST = 1 - SECOND = 2 - EIGHTH = 8 def test_type(self): Perm = self.Perm @@ -3160,83 +2627,74 @@ class TestIntFlag(unittest.TestCase): 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') + self.assertEqual(str(Perm.R), 'Perm.R') + self.assertEqual(str(Perm.W), 'Perm.W') + self.assertEqual(str(Perm.X), 'Perm.X') + self.assertEqual(str(Perm.R | Perm.W), 'Perm.R|W') + self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|W|X') + self.assertEqual(str(Perm.R | 8), 'Perm.8|R') + self.assertEqual(str(Perm(0)), 'Perm.0') + self.assertEqual(str(Perm(8)), 'Perm.8') + self.assertEqual(str(~Perm.R), 'Perm.W|X') + self.assertEqual(str(~Perm.W), 'Perm.R|X') + self.assertEqual(str(~Perm.X), 'Perm.R|W') + self.assertEqual(str(~(Perm.R | Perm.W)), 'Perm.X') + self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm.-8') + self.assertEqual(str(~(Perm.R | 8)), 'Perm.W|X') + self.assertEqual(str(Perm(~0)), 'Perm.R|W|X') + self.assertEqual(str(Perm(~8)), 'Perm.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(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') + self.assertEqual(str(Open.RO), 'Open.RO') + self.assertEqual(str(Open.WO), 'Open.WO') + self.assertEqual(str(Open.AC), 'Open.AC') + self.assertEqual(str(Open.RO | Open.CE), 'Open.CE') + self.assertEqual(str(Open.WO | Open.CE), 'Open.CE|WO') + self.assertEqual(str(Open(4)), 'Open.4') + self.assertEqual(str(~Open.RO), 'Open.CE|AC|RW|WO') + self.assertEqual(str(~Open.WO), 'Open.CE|RW') + self.assertEqual(str(~Open.AC), 'Open.CE') + self.assertEqual(str(~(Open.RO | Open.CE)), 'Open.AC|RW|WO') + self.assertEqual(str(~(Open.WO | Open.CE)), 'Open.RW') + self.assertEqual(str(Open(~4)), 'Open.CE|AC|RW|WO') 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') + self.assertEqual(repr(Perm.R), '') + self.assertEqual(repr(Perm.W), '') + self.assertEqual(repr(Perm.X), '') + self.assertEqual(repr(Perm.R | Perm.W), '') + self.assertEqual(repr(Perm.R | Perm.W | Perm.X), '') + self.assertEqual(repr(Perm.R | 8), '') + self.assertEqual(repr(Perm(0)), '') + self.assertEqual(repr(Perm(8)), '') + self.assertEqual(repr(~Perm.R), '') + self.assertEqual(repr(~Perm.W), '') + self.assertEqual(repr(~Perm.X), '') + self.assertEqual(repr(~(Perm.R | Perm.W)), '') + self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '') + self.assertEqual(repr(~(Perm.R | 8)), '') + self.assertEqual(repr(Perm(~0)), '') + self.assertEqual(repr(Perm(~8)), '') 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') + self.assertEqual(repr(Open.RO), '') + self.assertEqual(repr(Open.WO), '') + self.assertEqual(repr(Open.AC), '') + self.assertEqual(repr(Open.RO | Open.CE), '') + self.assertEqual(repr(Open.WO | Open.CE), '') + self.assertEqual(repr(Open(4)), '') + self.assertEqual(repr(~Open.RO), '') + self.assertEqual(repr(~Open.WO), '') + self.assertEqual(repr(~Open.AC), '') + self.assertEqual(repr(~(Open.RO | Open.CE)), '') + self.assertEqual(repr(~(Open.WO | Open.CE)), '') + self.assertEqual(repr(Open(~4)), '') def test_format(self): Perm = self.Perm self.assertEqual(format(Perm.R, ''), '4') self.assertEqual(format(Perm.R | Perm.X, ''), '5') - # - class NewPerm(IntFlag): - R = 1 << 2 - W = 1 << 1 - X = 1 << 0 - def __str__(self): - return self._name_ - self.assertEqual(format(NewPerm.R, ''), 'R') - self.assertEqual(format(NewPerm.R | Perm.X, ''), 'R|X') def test_or(self): Perm = self.Perm @@ -3314,7 +2772,8 @@ class TestIntFlag(unittest.TestCase): RWX = Perm.R | Perm.W | Perm.X values = list(Perm) + [RW, RX, WX, RWX, Perm(0)] for i in values: - self.assertEqual(~i, (~i).value) + self.assertEqual(~i, ~i.value) + self.assertEqual((~i).value, ~i.value) self.assertIs(type(~i), Perm) self.assertEqual(~~i, i) for i in Perm: @@ -3323,50 +2782,6 @@ class TestIntFlag(unittest.TestCase): self.assertIs(Open.WO & ~Open.WO, Open.RO) self.assertIs((Open.WO|Open.CE) & ~Open.WO, Open.CE) - def test_boundary(self): - self.assertIs(enum.IntFlag._boundary_, EJECT) - class Iron(IntFlag, boundary=STRICT): - ONE = 1 - TWO = 2 - EIGHT = 8 - self.assertIs(Iron._boundary_, STRICT) - # - class Water(IntFlag, boundary=CONFORM): - ONE = 1 - TWO = 2 - EIGHT = 8 - self.assertIs(Water._boundary_, CONFORM) - # - class Space(IntFlag, boundary=EJECT): - ONE = 1 - TWO = 2 - EIGHT = 8 - self.assertIs(Space._boundary_, EJECT) - # - # - class Bizarre(IntFlag, boundary=KEEP): - b = 3 - c = 4 - d = 6 - # - self.assertRaisesRegex(ValueError, 'invalid value: 5', Iron, 5) - # - self.assertIs(Water(7), Water.ONE|Water.TWO) - self.assertIs(Water(~9), Water.TWO) - # - self.assertEqual(Space(7), 7) - self.assertTrue(type(Space(7)) is int) - # - self.assertEqual(list(Bizarre), [Bizarre.c]) - self.assertIs(Bizarre(3), Bizarre.b) - self.assertIs(Bizarre(6), Bizarre.d) - - def test_iter(self): - Color = self.Color - Open = self.Open - self.assertEqual(list(Color), [Color.RED, Color.GREEN, Color.BLUE]) - self.assertEqual(list(Open), [Open.WO, Open.RW, Open.CE]) - def test_programatic_function_string(self): Perm = IntFlag('Perm', 'R W X') lst = list(Perm) @@ -3468,11 +2883,7 @@ class TestIntFlag(unittest.TestCase): self.assertEqual(len(lst), len(Thing)) self.assertEqual(len(Thing), 0, Thing) - @unittest.skipIf( - python_version >= (3, 12), - '__contains__ now returns True/False for all inputs', - ) - def test_contains_er(self): + def test_contains(self): Open = self.Open Color = self.Color self.assertTrue(Color.GREEN in Color) @@ -3480,33 +2891,13 @@ class TestIntFlag(unittest.TestCase): self.assertFalse(Color.GREEN in Open) self.assertFalse(Open.RW in Color) with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 'GREEN' in Color + 'GREEN' in Color with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 'RW' in Open + 'RW' in Open with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 2 in Color + 2 in Color with self.assertRaises(TypeError): - with self.assertWarns(DeprecationWarning): - 2 in Open - - @unittest.skipIf( - python_version < (3, 12), - '__contains__ only works with enum memmbers before 3.12', - ) - def test_contains_tf(self): - Open = self.Open - Color = self.Color - self.assertTrue(Color.GREEN in Color) - self.assertTrue(Open.RW in Open) - self.assertTrue(Color.GREEN in Open) - self.assertTrue(Open.RW in Color) - self.assertFalse('GREEN' in Color) - self.assertFalse('RW' in Open) - self.assertTrue(2 in Color) - self.assertTrue(2 in Open) + 2 in Open def test_member_contains(self): Perm = self.Perm @@ -3530,30 +2921,6 @@ class TestIntFlag(unittest.TestCase): with self.assertRaises(TypeError): self.assertFalse('test' in RW) - def test_member_iter(self): - Color = self.Color - self.assertEqual(list(Color.BLACK), []) - self.assertEqual(list(Color.PURPLE), [Color.RED, Color.BLUE]) - self.assertEqual(list(Color.BLUE), [Color.BLUE]) - self.assertEqual(list(Color.GREEN), [Color.GREEN]) - self.assertEqual(list(Color.WHITE), [Color.RED, Color.GREEN, Color.BLUE]) - - def test_member_length(self): - self.assertEqual(self.Color.__len__(self.Color.BLACK), 0) - self.assertEqual(self.Color.__len__(self.Color.GREEN), 1) - self.assertEqual(self.Color.__len__(self.Color.PURPLE), 2) - self.assertEqual(self.Color.__len__(self.Color.BLANCO), 3) - - def test_aliases(self): - Color = self.Color - self.assertEqual(Color(1).name, 'RED') - self.assertEqual(Color['ROJO'].name, 'RED') - self.assertEqual(Color(7).name, 'WHITE') - self.assertEqual(Color['BLANCO'].name, 'WHITE') - self.assertIs(Color.BLANCO, Color.WHITE) - Open = self.Open - self.assertIs(Open['AC'], Open.AC) - def test_bool(self): Perm = self.Perm for f in Perm: @@ -3562,7 +2929,6 @@ class TestIntFlag(unittest.TestCase): for f in Open: self.assertEqual(bool(f.value), bool(f)) - def test_multiple_mixin(self): class AllMixin: @classproperty @@ -3586,7 +2952,7 @@ 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), 'Color.BLUE') class Color(AllMixin, StrMixin, IntFlag): RED = auto() GREEN = auto() @@ -3670,7 +3036,6 @@ class TestUnique(unittest.TestCase): one = 1 two = 'dos' tres = 4.0 - # @unique class Cleaner(IntEnum): single = 1 @@ -3696,178 +3061,25 @@ class TestUnique(unittest.TestCase): turkey = 3 def test_unique_with_name(self): - @verify(UNIQUE) - class Silly(Enum): - one = 1 - two = 'dos' - name = 3 - # - @verify(UNIQUE) - class Sillier(IntEnum): - single = 1 - name = 2 - triple = 3 - value = 4 - -class TestVerify(unittest.TestCase): - - def test_continuous(self): - @verify(CONTINUOUS) - class Auto(Enum): - FIRST = auto() - SECOND = auto() - THIRD = auto() - FORTH = auto() - # - @verify(CONTINUOUS) - class Manual(Enum): - FIRST = 3 - SECOND = 4 - THIRD = 5 - FORTH = 6 - # - with self.assertRaisesRegex(ValueError, 'invalid enum .Missing.: missing values 5, 6, 7, 8, 9, 10, 12'): - @verify(CONTINUOUS) - class Missing(Enum): - FIRST = 3 - SECOND = 4 - THIRD = 11 - FORTH = 13 - # - with self.assertRaisesRegex(ValueError, 'invalid flag .Incomplete.: missing values 32'): - @verify(CONTINUOUS) - class Incomplete(Flag): - FIRST = 4 - SECOND = 8 - THIRD = 16 - FORTH = 64 - # - with self.assertRaisesRegex(ValueError, 'invalid flag .StillIncomplete.: missing values 16'): - @verify(CONTINUOUS) - class StillIncomplete(Flag): - FIRST = 4 - SECOND = 8 - THIRD = 11 - FORTH = 32 - - - def test_composite(self): - class Bizarre(Flag): - b = 3 - c = 4 - d = 6 - self.assertEqual(list(Bizarre), [Bizarre.c]) - self.assertEqual(Bizarre.b.value, 3) - self.assertEqual(Bizarre.c.value, 4) - self.assertEqual(Bizarre.d.value, 6) - with self.assertRaisesRegex( - ValueError, - "invalid Flag 'Bizarre': aliases b and d are missing combined values of 0x3 .use enum.show_flag_values.value. for details.", - ): - @verify(NAMED_FLAGS) - class Bizarre(Flag): - b = 3 - c = 4 - d = 6 - # - self.assertEqual(enum.show_flag_values(3), [1, 2]) - class Bizarre(IntFlag): - b = 3 - c = 4 - d = 6 - self.assertEqual(list(Bizarre), [Bizarre.c]) - self.assertEqual(Bizarre.b.value, 3) - self.assertEqual(Bizarre.c.value, 4) - self.assertEqual(Bizarre.d.value, 6) - with self.assertRaisesRegex( - ValueError, - "invalid Flag 'Bizarre': alias d is missing value 0x2 .use enum.show_flag_values.value. for details.", - ): - @verify(NAMED_FLAGS) - class Bizarre(IntFlag): - c = 4 - d = 6 - self.assertEqual(enum.show_flag_values(2), [2]) - - def test_unique_clean(self): - @verify(UNIQUE) - class Clean(Enum): - one = 1 - two = 'dos' - tres = 4.0 - # - @verify(UNIQUE) - class Cleaner(IntEnum): - single = 1 - double = 2 - triple = 3 - - def test_unique_dirty(self): - with self.assertRaisesRegex(ValueError, 'tres.*one'): - @verify(UNIQUE) - class Dirty(Enum): - one = 1 - two = 'dos' - tres = 1 - with self.assertRaisesRegex( - ValueError, - 'double.*single.*turkey.*triple', - ): - @verify(UNIQUE) - class Dirtier(IntEnum): - single = 1 - double = 1 - triple = 3 - turkey = 3 - - def test_unique_with_name(self): - @verify(UNIQUE) + @unique class Silly(Enum): one = 1 two = 'dos' name = 3 - # - @verify(UNIQUE) + @unique class Sillier(IntEnum): single = 1 name = 2 triple = 3 value = 4 -class TestHelpers(unittest.TestCase): - 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_sunder(self): - for name in self.sunder_names + self.private_and_sunder_names: - self.assertTrue(enum._is_sunder(name), '%r is a not sunder name?' % name) - for name in self.dunder_names + self.private_names + self.random_names: - self.assertFalse(enum._is_sunder(name), '%r is a sunder name?' % name) - - def test_dunder(self): - for name in self.dunder_names: - self.assertTrue(enum._is_dunder(name), '%r is a not dunder name?' % name) - for name in self.sunder_names + self.private_names + self.private_and_sunder_names + self.random_names: - self.assertFalse(enum._is_dunder(name), '%r is a dunder name?' % name) - - def test_is_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?') - -class TestEnumTypeSubclassing(unittest.TestCase): - pass expected_help_output_with_docs = """\ 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) + | Color(value, names=None, *, module=None, qualname=None, type=None, start=1) |\x20\x20 | An enumeration. |\x20\x20 @@ -3878,11 +3090,11 @@ class Color(enum.Enum) |\x20\x20 | Data and other attributes defined here: |\x20\x20 - | blue = Color.blue + | blue = |\x20\x20 - | green = Color.green + | green = |\x20\x20 - | red = Color.red + | red = |\x20\x20 | ---------------------------------------------------------------------- | Data descriptors inherited from enum.Enum: @@ -3894,7 +3106,7 @@ class Color(enum.Enum) | The value of the Enum member. |\x20\x20 | ---------------------------------------------------------------------- - | Readonly properties inherited from enum.EnumType: + | Readonly properties inherited from enum.EnumMeta: |\x20\x20 | __members__ | Returns a mapping of member name->value. @@ -3915,11 +3127,11 @@ class Color(enum.Enum) |\x20\x20 | Data and other attributes defined here: |\x20\x20 - | blue = Color.blue + | blue = |\x20\x20 - | green = Color.green + | green = |\x20\x20 - | red = Color.red + | red = |\x20\x20 | ---------------------------------------------------------------------- | Data descriptors inherited from enum.Enum: @@ -3929,7 +3141,7 @@ class Color(enum.Enum) | value |\x20\x20 | ---------------------------------------------------------------------- - | Data descriptors inherited from enum.EnumType: + | Data descriptors inherited from enum.EnumMeta: |\x20\x20 | __members__""" @@ -3956,7 +3168,7 @@ class TestStdLib(unittest.TestCase): def test_inspect_getmembers(self): values = dict(( - ('__class__', EnumType), + ('__class__', EnumMeta), ('__doc__', 'An enumeration.'), ('__members__', self.Color.__members__), ('__module__', __name__), @@ -3967,7 +3179,7 @@ class TestStdLib(unittest.TestCase): ('value', Enum.__dict__['value']), )) result = dict(inspect.getmembers(self.Color)) - self.assertEqual(set(values.keys()), set(result.keys())) + self.assertEqual(values.keys(), result.keys()) failed = False for k in values.keys(): if result[k] != values[k]: @@ -3983,11 +3195,11 @@ class TestStdLib(unittest.TestCase): from inspect import Attribute values = [ Attribute(name='__class__', kind='data', - defining_class=object, object=EnumType), + defining_class=object, object=EnumMeta), Attribute(name='__doc__', kind='data', defining_class=self.Color, object='An enumeration.'), Attribute(name='__members__', kind='property', - defining_class=EnumType, object=EnumType.__members__), + defining_class=EnumMeta, object=EnumMeta.__members__), Attribute(name='__module__', kind='data', defining_class=self.Color, object=__name__), Attribute(name='blue', kind='data', @@ -4004,10 +3216,6 @@ class TestStdLib(unittest.TestCase): values.sort(key=lambda item: item.name) result = list(inspect.classify_class_attrs(self.Color)) result.sort(key=lambda item: item.name) - self.assertEqual( - len(values), len(result), - "%s != %s" % ([a.name for a in values], [a.name for a in result]) - ) failed = False for v, r in zip(values, result): if r != v: @@ -4016,45 +3224,10 @@ class TestStdLib(unittest.TestCase): if failed: self.fail("result does not equal expected, see print above") - def test_test_simple_enum(self): - @_simple_enum(Enum) - class SimpleColor: - RED = 1 - GREEN = 2 - BLUE = 3 - class CheckedColor(Enum): - RED = 1 - GREEN = 2 - BLUE = 3 - self.assertTrue(_test_simple_enum(CheckedColor, SimpleColor) is None) - SimpleColor.GREEN._value_ = 9 - self.assertRaisesRegex( - TypeError, "enum mismatch", - _test_simple_enum, CheckedColor, SimpleColor, - ) - class CheckedMissing(IntFlag, boundary=KEEP): - SIXTY_FOUR = 64 - ONE_TWENTY_EIGHT = 128 - TWENTY_FORTY_EIGHT = 2048 - ALL = 2048 + 128 + 64 + 12 - CM = CheckedMissing - self.assertEqual(list(CheckedMissing), [CM.SIXTY_FOUR, CM.ONE_TWENTY_EIGHT, CM.TWENTY_FORTY_EIGHT]) - # - @_simple_enum(IntFlag, boundary=KEEP) - class Missing: - SIXTY_FOUR = 64 - ONE_TWENTY_EIGHT = 128 - TWENTY_FORTY_EIGHT = 2048 - ALL = 2048 + 128 + 64 + 12 - M = Missing - self.assertEqual(list(CheckedMissing), [M.SIXTY_FOUR, M.ONE_TWENTY_EIGHT, M.TWENTY_FORTY_EIGHT]) - # - _test_simple_enum(CheckedMissing, Missing) - class MiscTestCase(unittest.TestCase): def test__all__(self): - support.check__all__(self, enum, not_exported={'bin', 'show_flag_values'}) + check__all__(self, enum) # These are unordered here on purpose to ensure that declaration order @@ -4066,22 +3239,7 @@ CONVERT_TEST_NAME_A = 5 # This one should sort first. CONVERT_TEST_NAME_E = 5 CONVERT_TEST_NAME_F = 5 -CONVERT_STRING_TEST_NAME_D = 5 -CONVERT_STRING_TEST_NAME_C = 5 -CONVERT_STRING_TEST_NAME_B = 5 -CONVERT_STRING_TEST_NAME_A = 5 # This one should sort first. -CONVERT_STRING_TEST_NAME_E = 5 -CONVERT_STRING_TEST_NAME_F = 5 - class TestIntEnumConvert(unittest.TestCase): - def setUp(self): - # Reset the module-level test variables to their original integer - # values, otherwise the already created enum values get converted - # instead. - for suffix in ['A', 'B', 'C', 'D', 'E', 'F']: - globals()[f'CONVERT_TEST_NAME_{suffix}'] = 5 - globals()[f'CONVERT_STRING_TEST_NAME_{suffix}'] = 5 - def test_convert_value_lookup_priority(self): test_type = enum.IntEnum._convert_( 'UnittestConvert', @@ -4109,7 +3267,7 @@ class TestIntEnumConvert(unittest.TestCase): if name[0:2] not in ('CO', '__')], [], msg='Names other than CONVERT_TEST_* found.') - @unittest.skipUnless(python_version == (3, 8), + @unittest.skipUnless(sys.version_info[:2] == (3, 8), '_convert was deprecated in 3.8') def test_convert_warn(self): with self.assertWarns(DeprecationWarning): @@ -4118,7 +3276,7 @@ class TestIntEnumConvert(unittest.TestCase): ('test.test_enum', '__main__')[__name__=='__main__'], filter=lambda x: x.startswith('CONVERT_TEST_')) - @unittest.skipUnless(python_version >= (3, 9), + @unittest.skipUnless(sys.version_info >= (3, 9), '_convert was removed in 3.9') def test_convert_raise(self): with self.assertRaises(AttributeError): @@ -4127,50 +3285,6 @@ class TestIntEnumConvert(unittest.TestCase): ('test.test_enum', '__main__')[__name__=='__main__'], filter=lambda x: x.startswith('CONVERT_TEST_')) - def test_convert_repr_and_str(self): - module = ('test.test_enum', '__main__')[__name__=='__main__'] - test_type = enum.IntEnum._convert_( - 'UnittestConvert', - module, - filter=lambda x: x.startswith('CONVERT_STRING_TEST_')) - self.assertEqual(repr(test_type.CONVERT_STRING_TEST_NAME_A), '%s.CONVERT_STRING_TEST_NAME_A' % module) - self.assertEqual(str(test_type.CONVERT_STRING_TEST_NAME_A), 'CONVERT_STRING_TEST_NAME_A') - self.assertEqual(format(test_type.CONVERT_STRING_TEST_NAME_A), '5') - -# global names for StrEnum._convert_ test -CONVERT_STR_TEST_2 = 'goodbye' -CONVERT_STR_TEST_1 = 'hello' - -class TestStrEnumConvert(unittest.TestCase): - def setUp(self): - global CONVERT_STR_TEST_1 - global CONVERT_STR_TEST_2 - CONVERT_STR_TEST_2 = 'goodbye' - CONVERT_STR_TEST_1 = 'hello' - - def test_convert(self): - test_type = enum.StrEnum._convert_( - 'UnittestConvert', - ('test.test_enum', '__main__')[__name__=='__main__'], - 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', '__')], - [], msg='Names other than CONVERT_STR_* found.') - - def test_convert_repr_and_str(self): - module = ('test.test_enum', '__main__')[__name__=='__main__'] - 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' % module) - self.assertEqual(str(test_type.CONVERT_STR_TEST_2), 'goodbye') - self.assertEqual(format(test_type.CONVERT_STR_TEST_1), 'hello') - if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py index 8265b8d..77152cf 100644 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@ -1,4 +1,3 @@ -import enum import errno from http import client, HTTPStatus import io @@ -525,150 +524,6 @@ class BasicTest(TestCase): # see issue40084 self.assertTrue({'description', 'name', 'phrase', 'value'} <= set(dir(HTTPStatus(404)))) - def test_simple_httpstatus(self): - class CheckedHTTPStatus(enum.IntEnum): - """HTTP status codes and reason phrases - - Status codes from the following RFCs are all observed: - - * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616 - * RFC 6585: Additional HTTP Status Codes - * RFC 3229: Delta encoding in HTTP - * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518 - * RFC 5842: Binding Extensions to WebDAV - * RFC 7238: Permanent Redirect - * RFC 2295: Transparent Content Negotiation in HTTP - * RFC 2774: An HTTP Extension Framework - * RFC 7725: An HTTP Status Code to Report Legal Obstacles - * RFC 7540: Hypertext Transfer Protocol Version 2 (HTTP/2) - * RFC 2324: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0) - * RFC 8297: An HTTP Status Code for Indicating Hints - * RFC 8470: Using Early Data in HTTP - """ - def __new__(cls, value, phrase, description=''): - obj = int.__new__(cls, value) - obj._value_ = value - - obj.phrase = phrase - obj.description = description - return obj - # informational - CONTINUE = 100, 'Continue', 'Request received, please continue' - SWITCHING_PROTOCOLS = (101, 'Switching Protocols', - 'Switching to new protocol; obey Upgrade header') - PROCESSING = 102, 'Processing' - EARLY_HINTS = 103, 'Early Hints' - # success - OK = 200, 'OK', 'Request fulfilled, document follows' - CREATED = 201, 'Created', 'Document created, URL follows' - ACCEPTED = (202, 'Accepted', - 'Request accepted, processing continues off-line') - NON_AUTHORITATIVE_INFORMATION = (203, - 'Non-Authoritative Information', 'Request fulfilled from cache') - NO_CONTENT = 204, 'No Content', 'Request fulfilled, nothing follows' - RESET_CONTENT = 205, 'Reset Content', 'Clear input form for further input' - PARTIAL_CONTENT = 206, 'Partial Content', 'Partial content follows' - MULTI_STATUS = 207, 'Multi-Status' - ALREADY_REPORTED = 208, 'Already Reported' - IM_USED = 226, 'IM Used' - # redirection - MULTIPLE_CHOICES = (300, 'Multiple Choices', - 'Object has several resources -- see URI list') - MOVED_PERMANENTLY = (301, 'Moved Permanently', - 'Object moved permanently -- see URI list') - FOUND = 302, 'Found', 'Object moved temporarily -- see URI list' - SEE_OTHER = 303, 'See Other', 'Object moved -- see Method and URL list' - NOT_MODIFIED = (304, 'Not Modified', - 'Document has not changed since given time') - USE_PROXY = (305, 'Use Proxy', - 'You must use proxy specified in Location to access this resource') - TEMPORARY_REDIRECT = (307, 'Temporary Redirect', - 'Object moved temporarily -- see URI list') - PERMANENT_REDIRECT = (308, 'Permanent Redirect', - 'Object moved permanently -- see URI list') - # client error - BAD_REQUEST = (400, 'Bad Request', - 'Bad request syntax or unsupported method') - UNAUTHORIZED = (401, 'Unauthorized', - 'No permission -- see authorization schemes') - PAYMENT_REQUIRED = (402, 'Payment Required', - 'No payment -- see charging schemes') - FORBIDDEN = (403, 'Forbidden', - 'Request forbidden -- authorization will not help') - NOT_FOUND = (404, 'Not Found', - 'Nothing matches the given URI') - METHOD_NOT_ALLOWED = (405, 'Method Not Allowed', - 'Specified method is invalid for this resource') - NOT_ACCEPTABLE = (406, 'Not Acceptable', - 'URI not available in preferred format') - PROXY_AUTHENTICATION_REQUIRED = (407, - 'Proxy Authentication Required', - 'You must authenticate with this proxy before proceeding') - REQUEST_TIMEOUT = (408, 'Request Timeout', - 'Request timed out; try again later') - CONFLICT = 409, 'Conflict', 'Request conflict' - GONE = (410, 'Gone', - 'URI no longer exists and has been permanently removed') - LENGTH_REQUIRED = (411, 'Length Required', - 'Client must specify Content-Length') - PRECONDITION_FAILED = (412, 'Precondition Failed', - 'Precondition in headers is false') - REQUEST_ENTITY_TOO_LARGE = (413, 'Request Entity Too Large', - 'Entity is too large') - REQUEST_URI_TOO_LONG = (414, 'Request-URI Too Long', - 'URI is too long') - UNSUPPORTED_MEDIA_TYPE = (415, 'Unsupported Media Type', - 'Entity body in unsupported format') - REQUESTED_RANGE_NOT_SATISFIABLE = (416, - 'Requested Range Not Satisfiable', - 'Cannot satisfy request range') - EXPECTATION_FAILED = (417, 'Expectation Failed', - 'Expect condition could not be satisfied') - IM_A_TEAPOT = (418, 'I\'m a Teapot', - 'Server refuses to brew coffee because it is a teapot.') - MISDIRECTED_REQUEST = (421, 'Misdirected Request', - 'Server is not able to produce a response') - UNPROCESSABLE_ENTITY = 422, 'Unprocessable Entity' - LOCKED = 423, 'Locked' - FAILED_DEPENDENCY = 424, 'Failed Dependency' - TOO_EARLY = 425, 'Too Early' - UPGRADE_REQUIRED = 426, 'Upgrade Required' - PRECONDITION_REQUIRED = (428, 'Precondition Required', - 'The origin server requires the request to be conditional') - TOO_MANY_REQUESTS = (429, 'Too Many Requests', - 'The user has sent too many requests in ' - 'a given amount of time ("rate limiting")') - REQUEST_HEADER_FIELDS_TOO_LARGE = (431, - 'Request Header Fields Too Large', - 'The server is unwilling to process the request because its header ' - 'fields are too large') - UNAVAILABLE_FOR_LEGAL_REASONS = (451, - 'Unavailable For Legal Reasons', - 'The server is denying access to the ' - 'resource as a consequence of a legal demand') - # server errors - INTERNAL_SERVER_ERROR = (500, 'Internal Server Error', - 'Server got itself in trouble') - NOT_IMPLEMENTED = (501, 'Not Implemented', - 'Server does not support this operation') - BAD_GATEWAY = (502, 'Bad Gateway', - 'Invalid responses from another server/proxy') - SERVICE_UNAVAILABLE = (503, 'Service Unavailable', - 'The server cannot process the request due to a high load') - GATEWAY_TIMEOUT = (504, 'Gateway Timeout', - 'The gateway server did not receive a timely response') - HTTP_VERSION_NOT_SUPPORTED = (505, 'HTTP Version Not Supported', - 'Cannot fulfill request') - VARIANT_ALSO_NEGOTIATES = 506, 'Variant Also Negotiates' - INSUFFICIENT_STORAGE = 507, 'Insufficient Storage' - LOOP_DETECTED = 508, 'Loop Detected' - NOT_EXTENDED = 510, 'Not Extended' - NETWORK_AUTHENTICATION_REQUIRED = (511, - 'Network Authentication Required', - 'The client needs to authenticate to gain network access') - enum._test_simple_enum(CheckedHTTPStatus, HTTPStatus) - - def test_status_lines(self): # Test HTTP status lines diff --git a/Lib/test/test_pstats.py b/Lib/test/test_pstats.py index acc2fa5..c9a5fd5 100644 --- a/Lib/test/test_pstats.py +++ b/Lib/test/test_pstats.py @@ -3,7 +3,6 @@ import unittest from test import support from io import StringIO from pstats import SortKey -from enum import StrEnum, _test_simple_enum import pstats import cProfile @@ -62,32 +61,6 @@ class StatsTestCase(unittest.TestCase): self.assertEqual(self.stats.sort_type, self.stats.sort_arg_dict_default[sortkey][-1]) - def test_sort_stats_enum(self): - for member in SortKey: - self.stats.sort_stats(member) - self.assertEqual( - self.stats.sort_type, - self.stats.sort_arg_dict_default[member.value][-1]) - class CheckedSortKey(StrEnum): - CALLS = 'calls', 'ncalls' - CUMULATIVE = 'cumulative', 'cumtime' - FILENAME = 'filename', 'module' - LINE = 'line' - NAME = 'name' - NFL = 'nfl' - PCALLS = 'pcalls' - STDNAME = 'stdname' - TIME = 'time', 'tottime' - def __new__(cls, *values): - value = values[0] - obj = str.__new__(cls, value) - obj._value_ = value - for other_value in values[1:]: - cls._value2member_map_[other_value] = obj - obj._all_values = values - return obj - _test_simple_enum(CheckedSortKey, SortKey) - def test_sort_starts_mix(self): self.assertRaises(TypeError, self.stats.sort_stats, 'calls', diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py index c862a80..0afa3e6 100644 --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -453,7 +453,7 @@ class PydocDocTest(unittest.TestCase): zero = 0 one = 1 doc = pydoc.render_doc(BinaryInteger) - self.assertIn('BinaryInteger.zero', doc) + self.assertIn('', doc) def test_mixed_case_module_names_are_lower_cased(self): # issue16484 diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 4231e96..588cc79 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -2177,13 +2177,11 @@ class PatternReprTests(unittest.TestCase): "re.IGNORECASE|re.DOTALL|re.VERBOSE") self.assertEqual(repr(re.I|re.S|re.X|(1<<20)), "re.IGNORECASE|re.DOTALL|re.VERBOSE|0x100000") - self.assertEqual( - repr(~re.I), - "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DOTALL|re.VERBOSE|re.TEMPLATE|re.DEBUG") + self.assertEqual(repr(~re.I), "~re.IGNORECASE") self.assertEqual(repr(~(re.I|re.S|re.X)), - "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.TEMPLATE|re.DEBUG") + "~(re.IGNORECASE|re.DOTALL|re.VERBOSE)") self.assertEqual(repr(~(re.I|re.S|re.X|(1<<20))), - "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.TEMPLATE|re.DEBUG|0xffe00") + "~(re.IGNORECASE|re.DOTALL|re.VERBOSE|0x100000)") class ImplementationTest(unittest.TestCase): diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index daecf19..2144d61 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -1,4 +1,3 @@ -import enum import errno import os import random @@ -34,32 +33,6 @@ class GenericTests(unittest.TestCase): self.assertIsInstance(sig, signal.Signals) self.assertEqual(sys.platform, "win32") - CheckedSignals = enum._old_convert_( - enum.IntEnum, 'Signals', 'signal', - lambda name: - name.isupper() - and (name.startswith('SIG') and not name.startswith('SIG_')) - or name.startswith('CTRL_'), - source=signal, - ) - enum._test_simple_enum(CheckedSignals, signal.Signals) - - CheckedHandlers = enum._old_convert_( - enum.IntEnum, 'Handlers', 'signal', - lambda name: name in ('SIG_DFL', 'SIG_IGN'), - source=signal, - ) - enum._test_simple_enum(CheckedHandlers, signal.Handlers) - - Sigmasks = getattr(signal, 'Sigmasks', None) - if Sigmasks is not None: - CheckedSigmasks = enum._old_convert_( - enum.IntEnum, 'Sigmasks', 'signal', - lambda name: name in ('SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK'), - source=signal, - ) - enum._test_simple_enum(CheckedSigmasks, Sigmasks) - @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") class PosixTests(unittest.TestCase): @@ -899,7 +872,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 828d1f3..a59afd4 100755 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1518,9 +1518,9 @@ class GeneralModuleTests(unittest.TestCase): infos = socket.getaddrinfo(HOST, 80, socket.AF_INET, socket.SOCK_STREAM) for family, type, _, _, _ in infos: self.assertEqual(family, socket.AF_INET) - self.assertEqual(str(family), 'AF_INET') + self.assertEqual(str(family), 'AddressFamily.AF_INET') self.assertEqual(type, socket.SOCK_STREAM) - self.assertEqual(str(type), 'SOCK_STREAM') + self.assertEqual(str(type), 'SocketKind.SOCK_STREAM') infos = socket.getaddrinfo(HOST, None, 0, socket.SOCK_STREAM) for _, socktype, _, _, _ in infos: self.assertEqual(socktype, socket.SOCK_STREAM) @@ -1794,8 +1794,8 @@ class GeneralModuleTests(unittest.TestCase): # Make sure that the AF_* and SOCK_* constants have enum-like string # reprs. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - self.assertEqual(str(s.family), 'AF_INET') - self.assertEqual(str(s.type), 'SOCK_STREAM') + self.assertEqual(str(s.family), 'AddressFamily.AF_INET') + self.assertEqual(str(s.type), 'SocketKind.SOCK_STREAM') def test_socket_consistent_sock_type(self): SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0) @@ -1942,41 +1942,6 @@ class GeneralModuleTests(unittest.TestCase): fileno=afile.fileno()) self.assertEqual(cm.exception.errno, errno.ENOTSOCK) - def test_addressfamily_enum(self): - import _socket, enum - CheckedAddressFamily = enum._old_convert_( - enum.IntEnum, 'AddressFamily', 'socket', - lambda C: C.isupper() and C.startswith('AF_'), - source=_socket, - ) - enum._test_simple_enum(CheckedAddressFamily, socket.AddressFamily) - - def test_socketkind_enum(self): - import _socket, enum - CheckedSocketKind = enum._old_convert_( - enum.IntEnum, 'SocketKind', 'socket', - lambda C: C.isupper() and C.startswith('SOCK_'), - source=_socket, - ) - enum._test_simple_enum(CheckedSocketKind, socket.SocketKind) - - def test_msgflag_enum(self): - import _socket, enum - CheckedMsgFlag = enum._old_convert_( - enum.IntFlag, 'MsgFlag', 'socket', - lambda C: C.isupper() and C.startswith('MSG_'), - source=_socket, - ) - enum._test_simple_enum(CheckedMsgFlag, socket.MsgFlag) - - def test_addressinfo_enum(self): - import _socket, enum - CheckedAddressInfo = enum._old_convert_( - enum.IntFlag, 'AddressInfo', 'socket', - lambda C: C.isupper() and C.startswith('AI_'), - source=_socket) - enum._test_simple_enum(CheckedAddressInfo, socket.AddressInfo) - @unittest.skipUnless(HAVE_SOCKET_CAN, 'SocketCan required for this test.') class BasicCANTest(unittest.TestCase): diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index fab382f..e523634 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -13,7 +13,6 @@ import socket import select import time import datetime -import enum import gc import os import errno @@ -373,7 +372,7 @@ 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(str(proto), '_SSLMethod.PROTOCOL_TLS_CLIENT') ctx = ssl.SSLContext(proto) self.assertIs(ctx.protocol, proto) @@ -618,7 +617,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) ) @@ -628,7 +627,7 @@ class BasicSocketTests(unittest.TestCase): with self.assertWarns(DeprecationWarning) as cm: ctx.minimum_version = version self.assertEqual( - f'ssl.{version!r} is deprecated', + f'ssl.{version!s} is deprecated', str(cm.warning) ) @@ -4870,155 +4869,6 @@ class TestSSLDebug(unittest.TestCase): s.connect((HOST, server.port)) -class TestEnumerations(unittest.TestCase): - - def test_tlsversion(self): - class CheckedTLSVersion(enum.IntEnum): - MINIMUM_SUPPORTED = _ssl.PROTO_MINIMUM_SUPPORTED - SSLv3 = _ssl.PROTO_SSLv3 - TLSv1 = _ssl.PROTO_TLSv1 - TLSv1_1 = _ssl.PROTO_TLSv1_1 - TLSv1_2 = _ssl.PROTO_TLSv1_2 - TLSv1_3 = _ssl.PROTO_TLSv1_3 - MAXIMUM_SUPPORTED = _ssl.PROTO_MAXIMUM_SUPPORTED - enum._test_simple_enum(CheckedTLSVersion, TLSVersion) - - def test_tlscontenttype(self): - class Checked_TLSContentType(enum.IntEnum): - """Content types (record layer) - - See RFC 8446, section B.1 - """ - CHANGE_CIPHER_SPEC = 20 - ALERT = 21 - HANDSHAKE = 22 - APPLICATION_DATA = 23 - # pseudo content types - HEADER = 0x100 - INNER_CONTENT_TYPE = 0x101 - enum._test_simple_enum(Checked_TLSContentType, _TLSContentType) - - def test_tlsalerttype(self): - class Checked_TLSAlertType(enum.IntEnum): - """Alert types for TLSContentType.ALERT messages - - See RFC 8466, section B.2 - """ - CLOSE_NOTIFY = 0 - UNEXPECTED_MESSAGE = 10 - BAD_RECORD_MAC = 20 - DECRYPTION_FAILED = 21 - RECORD_OVERFLOW = 22 - DECOMPRESSION_FAILURE = 30 - HANDSHAKE_FAILURE = 40 - NO_CERTIFICATE = 41 - BAD_CERTIFICATE = 42 - UNSUPPORTED_CERTIFICATE = 43 - CERTIFICATE_REVOKED = 44 - CERTIFICATE_EXPIRED = 45 - CERTIFICATE_UNKNOWN = 46 - ILLEGAL_PARAMETER = 47 - UNKNOWN_CA = 48 - ACCESS_DENIED = 49 - DECODE_ERROR = 50 - DECRYPT_ERROR = 51 - EXPORT_RESTRICTION = 60 - PROTOCOL_VERSION = 70 - INSUFFICIENT_SECURITY = 71 - INTERNAL_ERROR = 80 - INAPPROPRIATE_FALLBACK = 86 - USER_CANCELED = 90 - NO_RENEGOTIATION = 100 - MISSING_EXTENSION = 109 - UNSUPPORTED_EXTENSION = 110 - CERTIFICATE_UNOBTAINABLE = 111 - UNRECOGNIZED_NAME = 112 - BAD_CERTIFICATE_STATUS_RESPONSE = 113 - BAD_CERTIFICATE_HASH_VALUE = 114 - UNKNOWN_PSK_IDENTITY = 115 - CERTIFICATE_REQUIRED = 116 - NO_APPLICATION_PROTOCOL = 120 - enum._test_simple_enum(Checked_TLSAlertType, _TLSAlertType) - - def test_tlsmessagetype(self): - class Checked_TLSMessageType(enum.IntEnum): - """Message types (handshake protocol) - - See RFC 8446, section B.3 - """ - HELLO_REQUEST = 0 - CLIENT_HELLO = 1 - SERVER_HELLO = 2 - HELLO_VERIFY_REQUEST = 3 - NEWSESSION_TICKET = 4 - END_OF_EARLY_DATA = 5 - HELLO_RETRY_REQUEST = 6 - ENCRYPTED_EXTENSIONS = 8 - CERTIFICATE = 11 - SERVER_KEY_EXCHANGE = 12 - CERTIFICATE_REQUEST = 13 - SERVER_DONE = 14 - CERTIFICATE_VERIFY = 15 - CLIENT_KEY_EXCHANGE = 16 - FINISHED = 20 - CERTIFICATE_URL = 21 - CERTIFICATE_STATUS = 22 - SUPPLEMENTAL_DATA = 23 - KEY_UPDATE = 24 - NEXT_PROTO = 67 - MESSAGE_HASH = 254 - CHANGE_CIPHER_SPEC = 0x0101 - enum._test_simple_enum(Checked_TLSMessageType, _TLSMessageType) - - def test_sslmethod(self): - Checked_SSLMethod = enum._old_convert_( - enum.IntEnum, '_SSLMethod', 'ssl', - lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23', - source=ssl._ssl, - ) - enum._test_simple_enum(Checked_SSLMethod, ssl._SSLMethod) - - def test_options(self): - CheckedOptions = enum._old_convert_( - enum.FlagEnum, 'Options', 'ssl', - lambda name: name.startswith('OP_'), - source=ssl._ssl, - ) - enum._test_simple_enum(CheckedOptions, ssl.Options) - - - def test_alertdescription(self): - CheckedAlertDescription = enum._old_convert_( - enum.IntEnum, 'AlertDescription', 'ssl', - lambda name: name.startswith('ALERT_DESCRIPTION_'), - source=ssl._ssl, - ) - enum._test_simple_enum(CheckedAlertDescription, ssl.AlertDescription) - - def test_sslerrornumber(self): - Checked_SSLMethod = enum._old_convert_( - enum.IntEnum, '_SSLMethod', 'ssl', - lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23', - source=ssl._ssl, - ) - enum._test_simple_enum(Checked_SSLMethod, ssl._SSLMethod) - - def test_verifyflags(self): - CheckedVerifyFlags = enum._old_convert_( - enum.FlagEnum, 'VerifyFlags', 'ssl', - lambda name: name.startswith('VERIFY_'), - source=ssl._ssl, - ) - enum._test_simple_enum(CheckedVerifyFlags, ssl.VerifyFlags) - - def test_verifymode(self): - CheckedVerifyMode = enum._old_convert_( - enum.IntEnum, 'VerifyMode', 'ssl', - lambda name: name.startswith('CERT_'), - source=ssl._ssl, - ) - enum._test_simple_enum(CheckedVerifyMode, ssl.VerifyMode) - def test_main(verbose=False): if support.verbose: plats = { diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index 5db7a9b..a6549c7 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -1466,23 +1466,22 @@ class UnicodeTest(string_tests.CommonTest, PI = 3.1415926 class Int(enum.IntEnum): IDES = 15 - class Str(enum.StrEnum): - # StrEnum uses the value and not the name for %s etc. + class Str(str, enum.Enum): ABC = 'abc' # Testing Unicode formatting strings... self.assertEqual("%s, %s" % (Str.ABC, Str.ABC), - 'abc, abc') + 'Str.ABC, Str.ABC') self.assertEqual("%s, %s, %d, %i, %u, %f, %5.2f" % (Str.ABC, Str.ABC, Int.IDES, Int.IDES, Int.IDES, Float.PI, Float.PI), - 'abc, abc, 15, 15, 15, 3.141593, 3.14') + 'Str.ABC, Str.ABC, 15, 15, 15, 3.141593, 3.14') # formatting jobs delegated from the string implementation: self.assertEqual('...%(foo)s...' % {'foo':Str.ABC}, - '...abc...') + '...Str.ABC...') self.assertEqual('...%(foo)s...' % {'foo':Int.IDES}, - '...IDES...') + '...Int.IDES...') self.assertEqual('...%(foo)i...' % {'foo':Int.IDES}, '...15...') self.assertEqual('...%(foo)d...' % {'foo':Int.IDES}, diff --git a/Lib/test/test_uuid.py b/Lib/test/test_uuid.py index 3f56192..d6a8333 100755 --- a/Lib/test/test_uuid.py +++ b/Lib/test/test_uuid.py @@ -4,7 +4,6 @@ from test.support import import_helper import builtins import contextlib import copy -import enum import io import os import pickle @@ -32,13 +31,6 @@ def mock_get_command_stdout(data): class BaseTestUUID: uuid = None - def test_safe_uuid_enum(self): - class CheckedSafeUUID(enum.Enum): - safe = 0 - unsafe = -1 - unknown = None - enum._test_simple_enum(CheckedSafeUUID, py_uuid.SafeUUID) - def test_UUID(self): equal = self.assertEqual ascending = [] diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 2513c97..552fc54 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -144,8 +144,7 @@ def _splitdict(tk, v, cut_minus=True, conv=None): return dict -@enum._simple_enum(enum.StrEnum) -class EventType: +class EventType(str, enum.Enum): KeyPress = '2' Key = KeyPress KeyRelease = '3' @@ -186,6 +185,8 @@ class EventType: Deactivate = '37' MouseWheel = '38' + __str__ = str.__str__ + class Event: """Container for the properties of an event. diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index ab8f647..7e78433 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -1,7 +1,6 @@ import functools import unittest import tkinter -import enum from test import support from tkinter.test.support import AbstractTkTest, AbstractDefaultRootTest @@ -269,49 +268,6 @@ class MiscTest(AbstractTkTest, unittest.TestCase): " num=3 delta=-1 focus=True" " x=10 y=20 width=300 height=200>") - def test_eventtype_enum(self): - class CheckedEventType(enum.StrEnum): - KeyPress = '2' - Key = KeyPress - KeyRelease = '3' - ButtonPress = '4' - Button = ButtonPress - ButtonRelease = '5' - Motion = '6' - Enter = '7' - Leave = '8' - FocusIn = '9' - FocusOut = '10' - Keymap = '11' # undocumented - Expose = '12' - GraphicsExpose = '13' # undocumented - NoExpose = '14' # undocumented - Visibility = '15' - Create = '16' - Destroy = '17' - Unmap = '18' - Map = '19' - MapRequest = '20' - Reparent = '21' - Configure = '22' - ConfigureRequest = '23' - Gravity = '24' - ResizeRequest = '25' - Circulate = '26' - CirculateRequest = '27' - Property = '28' - SelectionClear = '29' # undocumented - SelectionRequest = '30' # undocumented - Selection = '31' # undocumented - Colormap = '32' - ClientMessage = '33' # undocumented - Mapping = '34' # undocumented - VirtualEvent = '35' # undocumented - Activate = '36' - Deactivate = '37' - MouseWheel = '38' - enum._test_simple_enum(CheckedEventType, tkinter.EventType) - def test_getboolean(self): for v in 'true', 'yes', 'on', '1', 't', 'y', 1, True: self.assertIs(self.root.getboolean(v), True) diff --git a/Lib/uuid.py b/Lib/uuid.py index 67da885..5ae0a3e 100644 --- a/Lib/uuid.py +++ b/Lib/uuid.py @@ -47,7 +47,7 @@ Typical usage: import os import sys -from enum import Enum, _simple_enum +from enum import Enum __author__ = 'Ka-Ping Yee ' @@ -75,8 +75,7 @@ int_ = int # The built-in int type bytes_ = bytes # The built-in bytes type -@_simple_enum(Enum) -class SafeUUID: +class SafeUUID(Enum): safe = 0 unsafe = -1 unknown = None -- cgit v0.12