msgspec icon indicating copy to clipboard operation
msgspec copied to clipboard

Better enum support

Open cibere opened this issue 1 year ago • 4 comments

Description

I would like to suggest allowing enums to be passed in literals and to the tag kwarg in msgspec.Structs.

cibere avatar Jul 17 '23 03:07 cibere

Thanks for opening this. Can you please add minimal complete examples of code you'd want to work that hits each of these issues?

jcrist avatar Jul 17 '23 14:07 jcrist

Hello, just chiming in here.

Allowing enum values in literals is useful for making specialised versions of a struct. e.g. I have something like this at the moment

class InteractionCallbackType(IntEnum):
    PONG = 1
    CHANNEL_MESSAGE_WITH_SOURCE = 4

class InteractionResponse(msgspec.Struct, kw_only=True):
    type: InteractionCallbackType
    data: Optional[Any]

class PongInteractionResponse(InteractionResponse, kw_only=True):
    type: Literal[1] = 1
    data: None = None

class InteractionMessage(msgspec.Struct):
    content: str
    flags: int

class MessageInteractionResponse(InteractionResponse, kw_only=True):
    type: Literal[4] = 4
    data: InteractionMessage

where PongInteractionResponse and MessageInteractionResponse are specialised versions of InteractionResponse with literal values for the type field.

It would be nice for MessageInteractionResponse to be defined like this instead, using the enum value:

class MessageInteractionResponse(InteractionResponse, kw_only=True):
    type: Literal[InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE] = InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE
    data: InteractionMessage

auburnsummer avatar Jul 19 '23 03:07 auburnsummer

Sorry for responding so late, but here you go.

Enums in Literals

auburnsummer convered it pretty well, but I want to add onto it. The ideal solution would be:

class SomeEnum(Enum):
    video = 1
    audio = 2

class VideoPayload(msgspec.Struct):
    type: Literal[SomeEnum.video]

The reason I think this should be supported is because you have to put the enum's value in the literal, which could change. Technically you could do this:

Literal[SomeEnum.video.value]

Though, typecheckers dislike it:

Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum valuePylance

Enums in the tag kwarg

This one is a little petty, but I just want to remove the need to pass SomeEnum.video.value to the tag kwarg opposed to SomeEnum.video. The ideal solution would be:

class SomeEnum(Enum):
    audio = 2
    tts = 3

class TTSPayload(msgspec.Struct, tag=SomeEnum.tts):
    text: str
    speed: int

class AudioPayload(msgspec.Struct, tag=SomeEnum.audio):
    url: str

Rather than

class SomeEnum(Enum):
    audio = 2
    tts = 3

class TTSPayload(msgspec.Struct, tag=SomeEnum.tts.value):
    text: str
    speed: int

class AudioPayload(msgspec.Struct, tag=SomeEnum.audio.value):
    url: str

cibere avatar Jul 20 '23 02:07 cibere

I'd also benefit from enum members in Literals. Somewhat useful for reused flags that not all flags are valid for all fields, comes up with a few external APIs I've had to deal with.

On the typing side, Literals may include slightly more than msgspec currently allows, that being

literal ints, byte and unicode strings, bools, Enum values and None

If this is something you're willing to accept into msgspec, I can make time to PR support for it, but I understand that there may be reasons not to fully support all of the things typing does without first considering the implications of it on serialization and potential ambiguities.

mikeshardmind avatar Nov 19 '23 01:11 mikeshardmind