classification
Title: Multiple enum mixins not allowed even when they have the same datatype
Type: behavior Stage: resolved
Components: Versions: Python 3.11, Python 3.10, Python 3.9
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: ethan.furman Nosy List: JEphron, ethan.furman, miss-islington
Priority: normal Keywords: patch

Created on 2021-06-09 04:20 by JEphron, last changed 2021-06-11 08:27 by JEphron. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 26649 merged ethan.furman, 2021-06-10 19:24
PR 26652 merged miss-islington, 2021-06-10 20:32
PR 26653 merged miss-islington, 2021-06-10 20:32
PR 26654 closed miss-islington, 2021-06-10 20:32
Messages (11)
msg395378 - (view) Author: Jordan Ephron (JEphron) Date: 2021-06-09 04:20
Prior to 3.8 it was possible to create "abstract" enums (without members) and mix them together. To motivate with an example, perhaps we're modeling an API and want to be robust in the face of inconsistent casing


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)


and perhaps we also want to be robust in response to extensibility


class LenientStrEnum(str, Enum):
    @classmethod
    def _missing_(cls, value):
        logger.warning(
            f"[{cls.__name__}] encountered an unknown value!\n"
            f"Luckily I'm a LenientStrEnum, so I won't crash just yet.\n"
            f"You might want to add a new case though.\n"
            f"Value was: '{value}'"
        )
        return UnexpectedStr(value)


but we also want to model some known good set of values, so mixing together the abstract enums we'd get something like


class JobStatus(CaseInsensitiveStrEnum, LenientStrEnum):
    ACTIVE = "active"
    PENDING = "pending"
    TERMINATED = "terminated"

However, due to the resolution of https://bugs.python.org/issue39587 this no longer works, instead producing:

TypeError: 'JobStatus': too many data types: [<class 'str'>, <class 'str'>]


The relevant change is

https://github.com/ethanfurman/cpython/commit/bff01f3a3aac0c15fe8fbe8b2f561f7927d117a1

I believe that if we made `data_types` a set rather than a list then the example would become valid once again.
msg395380 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-06-09 06:20
Excellent bug report.

But what is an `UnexpectedString()` ?
msg395433 - (view) Author: Jordan Ephron (JEphron) Date: 2021-06-09 16:42
> But what is an `UnexpectedString()`

Sorry, that’s maybe less relevant to the example. It’s just a subclass of string with some marker to make it detectable later on, similar to schemes that taint user input to prevent sql injection or whatever

> On Jun 8, 2021, at 23:20, Ethan Furman <report@bugs.python.org> wrote:
> 
> 
> Ethan Furman <ethan@stoneleaf.us> added the comment:
> 
> Excellent bug report.
> 
> But what is an `UnexpectedString()` ?
> 
> ----------
> 
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https://bugs.python.org/issue44356>
> _______________________________________
msg395446 - (view) Author: Jordan Ephron (JEphron) Date: 2021-06-09 17:27
Oh, on further investigation I see that the example wouldn't have worked on 3.8 anyway, due to issue34536 adding some checks to the type returned by _missing_, so maybe I'm pushing Enum too far in that case.
msg395543 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-06-10 15:33
Since I like puzzles, here is a working LenientStrEnum:

    class LenientStrEnum(str, Enum):
        #
        def __init__(self, *args):
            self._valid = True
        #
        @classmethod
        def _missing_(cls, value):
            logger.warning(
                f"[{cls.__name__}] encountered an unknown value!\n"
                f"Luckily I'm a LenientStrEnum, so I won't crash just yet.\n"
                f"You might want to add a new case though.\n"
                f"Value was: '{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

`_member_map_` is not guaranteed, but is unlikely to change.
msg395570 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-06-10 20:30
New changeset 8a4f0850d75747af8c96ca0e7eef1f5c1abfba25 by Ethan Furman in branch 'main':
bpo-44356: [Enum] allow multiple data-type mixins if they are all the same (GH-26649)
https://github.com/python/cpython/commit/8a4f0850d75747af8c96ca0e7eef1f5c1abfba25
msg395579 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-06-10 22:01
New changeset 01286017c3345e2b8a0af2bd48f6eb2087693a82 by Miss Islington (bot) in branch '3.10':
bpo-44356: [Enum] allow multiple data-type mixins if they are all the same (GH-26649) (GH-26653)
https://github.com/python/cpython/commit/01286017c3345e2b8a0af2bd48f6eb2087693a82
msg395580 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-06-10 22:03
New changeset 304ec53b53021ceddf62a38e66a06aed37e2ac41 by Miss Islington (bot) in branch '3.9':
bpo-44356: [Enum] allow multiple data-type mixins if they are all the same (GH-26649) (GH-26652)
https://github.com/python/cpython/commit/304ec53b53021ceddf62a38e66a06aed37e2ac41
msg395610 - (view) Author: Jordan Ephron (JEphron) Date: 2021-06-11 08:04
On 10.06.2021 15:33, Ethan Furman wrote:
>Since I like puzzles, here is a working LenientStrEnum:
>...

Oh indeed, that's really cool!
msg395611 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-06-11 08:19
Glad you like it!

Please don't change the title back.  :-)
msg395614 - (view) Author: Jordan Ephron (JEphron) Date: 2021-06-11 08:27
Oops, still getting used to the Python mailing list format. Learned
something new! Thanks for all your awesome work on Python.

On 11.06.2021 08:19, Ethan Furman wrote:
>
>Ethan Furman <ethan@stoneleaf.us> added the comment:
>
>Glad you like it!
>
>Please don't change the title back.  :-)
>
>----------
>resolution:  -> fixed
>stage: patch review -> resolved
>status: open -> closed
>title: Abstract enum mixins not allowed -> Multiple enum mixins not allowed even when they have the same datatype
>versions:  -Python 3.8
>
>_______________________________________
>Python tracker <report@bugs.python.org>
><https://bugs.python.org/issue44356>
>_______________________________________
History
Date User Action Args
2021-06-11 08:27:36JEphronsetmessages: + msg395614
2021-06-11 08:19:34ethan.furmansetstatus: open -> closed
versions: - Python 3.8
title: Abstract enum mixins not allowed -> Multiple enum mixins not allowed even when they have the same datatype
messages: + msg395611

resolution: fixed
stage: patch review -> resolved
2021-06-11 08:04:31JEphronsetmessages: + msg395610
title: Multiple enum mixins not allowed even when they have the same datatype -> Abstract enum mixins not allowed
2021-06-10 22:03:36ethan.furmansetmessages: + msg395580
2021-06-10 22:01:12ethan.furmansetmessages: + msg395579
2021-06-10 20:32:40miss-islingtonsetpull_requests: + pull_request25240
2021-06-10 20:32:33miss-islingtonsetpull_requests: + pull_request25239
2021-06-10 20:32:28miss-islingtonsetnosy: + miss-islington
pull_requests: + pull_request25238
2021-06-10 20:30:56ethan.furmansetmessages: + msg395570
2021-06-10 19:24:13ethan.furmansetkeywords: + patch
stage: patch review
pull_requests: + pull_request25235
2021-06-10 19:23:57ethan.furmansettitle: Abstract enum mixins not allowed -> Multiple enum mixins not allowed even when they have the same datatype
2021-06-10 15:33:58ethan.furmansetmessages: + msg395543
2021-06-09 17:27:13JEphronsetmessages: + msg395446
2021-06-09 16:42:03JEphronsetmessages: + msg395433
2021-06-09 06:20:20ethan.furmansetmessages: + msg395380
2021-06-09 04:25:12ethan.furmansetassignee: ethan.furman

nosy: + ethan.furman
versions: + Python 3.10, Python 3.11
2021-06-09 04:20:29JEphroncreate