From 2354fbf02bce463f2dcb5673d752cb3e4ac25c55 Mon Sep 17 00:00:00 2001 From: Carl Kittelberger Date: Tue, 8 Sep 2026 06:54:39 +0200 Subject: [PATCH] fix: restore EventEnum(value) lookup on Python 3.12+ Python 3.12 changed enum.EnumMeta.__call__ to raise TypeError when called on an aggregate Enum subclass that defines zero members of its own, before _missing_ is ever invoked. EventEnum is intentionally such a class (real event IDs live only on its subclasses: ChannelID, PluginID, etc), so pyflp.parse() was broken on Python 3.12+. Override _EventEnumMeta.__call__ to detect exactly that case (no args/ kwds, integer value, zero members on cls) and route to _missing_ directly, restoring pre-3.12 behaviour. All other lookups fall through to the normal enum machinery unchanged. Fixes demberto/PyFLP#183, demberto/PyFLP#201. Verified against the sample files attached to #201 (FL Studio 25.1.3) as well as a small corpus of FL 11/12/24.2 projects. --- pyflp/_events.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/pyflp/_events.py b/pyflp/_events.py index 1606e26..b863f10 100644 --- a/pyflp/_events.py +++ b/pyflp/_events.py @@ -59,6 +59,34 @@ def __contains__(self, obj: object) -> bool: """ return obj in tuple(self) + def __call__(cls, value: object, *args: object, **kwds: object) -> object: + """Look up ``value`` even when ``cls`` (typically the base + ``EventEnum`` itself) defines no members of its own. + + ``EventEnum`` is intentionally empty -- real event IDs only exist on + its subclasses (``ChannelID``, ``PluginID``, etc) -- and callers are + expected to be able to do ``EventEnum(raw_id)`` and have + :meth:`EventEnum._missing_` dispatch to whichever subclass defines + that value. Since Python 3.12, ``enum.EnumMeta.__call__`` raises + ``TypeError`` before ever reaching ``_missing_`` when called on an + aggregate enum class with zero direct members, which breaks this + pattern. Detect exactly that case and route to ``_missing_`` + ourselves; everything else (name-based construction, lookups on + non-empty subclasses, etc.) is left to the normal enum machinery. + """ + if ( + not args + and not kwds + and not len(cls) + and isinstance(value, int) + and hasattr(cls, "_missing_") + ): + member = cls._missing_(value) + if member is not None: + return member + raise ValueError(f"{value!r} is not a valid {cls.__qualname__}") + return super().__call__(value, *args, **kwds) + class EventEnum(int, enum.Enum, metaclass=_EventEnumMeta): """IDs used by events.