Skip to content

Commit 8cb5412

Browse files
committed
Decode enums that mix in str or int and int enum dict keys
1 parent d558995 commit 8cb5412

4 files changed

Lines changed: 40 additions & 15 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ to include examples, links to docs, or any other relevant information.
5858

5959
- Restore `frozenset` values when decoding JSON payloads with a `frozenset` type hint,
6060
including nested frozen sets.
61+
- Decode enums that mix in `str` or `int` (for example `class Color(str, Enum)`) to the enum
62+
instead of a list of characters or an error, and decode `dict` keys typed as an `int` enum.
6163

6264
- Ordinary absolute imports of already-loaded modules in sandboxed workflows no longer go through
6365
importlib's module locks, fixing intermittent `Failed validating workflow` errors on Python 3.10

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ The default data converter supports converting multiple types including:
330330
* Anything that [`json.dump`](https://docs.python.org/3/library/json.html#json.dump) supports natively
331331
* [dataclasses](https://docs.python.org/3/library/dataclasses.html)
332332
* Iterables including ones JSON dump may not support by default, e.g. `set`
333-
* [IntEnum, StrEnum](https://docs.python.org/3/library/enum.html) based enumerates
333+
* [IntEnum, StrEnum](https://docs.python.org/3/library/enum.html) based enumerates, including enums that mix in `int` or `str`
334334
* [UUID](https://docs.python.org/3/library/uuid.html)
335335
* `datetime.datetime`
336336

‎temporalio/converter/_payload_converter.py‎

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from abc import ABC, abstractmethod
1616
from collections.abc import Callable, Mapping, Sequence
1717
from datetime import datetime
18-
from enum import IntEnum
18+
from enum import Enum
1919
from itertools import zip_longest
2020
from types import UnionType
2121
from typing import (
@@ -42,9 +42,6 @@
4242
if sys.version_info < (3, 11):
4343
# Python's datetime.fromisoformat doesn't support certain formats pre-3.11
4444
from dateutil import parser # type: ignore
45-
# StrEnum is available in 3.11+
46-
if sys.version_info >= (3, 11):
47-
from enum import StrEnum # type: ignore[reportUnreachable]
4845

4946
from temporalio.converter._serialization_context import (
5047
SerializationContext,
@@ -961,6 +958,13 @@ def value_to_type(
961958
if isinstance(key, str):
962959
if key_type is int or key_type is float:
963960
key = key_type(key)
961+
elif (
962+
inspect.isclass(key_type)
963+
and issubclass(key_type, Enum)
964+
and issubclass(key_type, int)
965+
):
966+
# JSON serializes int enum keys as their number
967+
key = int(key)
964968
elif key_type is bool:
965969
key = {"true": True, "false": False}[key]
966970
elif key_type is type(None):
@@ -1034,22 +1038,21 @@ def value_to_type(
10341038
)
10351039
return getattr(hint, "parse_obj")(value)
10361040

1037-
# IntEnum
1038-
if inspect.isclass(hint) and issubclass(hint, IntEnum):
1041+
# IntEnum, or any enum that mixes in int
1042+
if inspect.isclass(hint) and issubclass(hint, Enum) and issubclass(hint, int):
10391043
if not isinstance(value, int):
10401044
raise TypeError(
10411045
f"Cannot convert to enum {hint}, value not an integer, value is {type(value)}"
10421046
)
10431047
return hint(value)
10441048

1045-
# StrEnum, available in 3.11+
1046-
if sys.version_info >= (3, 11):
1047-
if inspect.isclass(hint) and issubclass(hint, StrEnum): # type:ignore[reportUnreachable]
1048-
if not isinstance(value, str):
1049-
raise TypeError(
1050-
f"Cannot convert to enum {hint}, value not a string, value is {type(value)}"
1051-
)
1052-
return hint(value)
1049+
# StrEnum, or any enum that mixes in str
1050+
if inspect.isclass(hint) and issubclass(hint, Enum) and issubclass(hint, str):
1051+
if not isinstance(value, str):
1052+
raise TypeError(
1053+
f"Cannot convert to enum {hint}, value not a string, value is {type(value)}"
1054+
)
1055+
return hint(value)
10531056

10541057
# UUID
10551058
if inspect.isclass(hint) and issubclass(hint, uuid.UUID):

‎tests/test_converter.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ class SerializableEnum(IntEnum):
7272
FOO = 1
7373

7474

75+
class SerializableMixinStrEnum(str, Enum):
76+
FOO = "foo"
77+
78+
79+
class SerializableMixinIntEnum(int, Enum):
80+
FOO = 1
81+
82+
7583
if sys.version_info >= (3, 11):
7684

7785
class SerializableStrEnum(StrEnum): # type:ignore[reportUnreachable]
@@ -498,6 +506,18 @@ def fail(hint: Any, value: Any) -> None:
498506
# IntEnum
499507
ok(SerializableEnum, SerializableEnum.FOO)
500508
ok(list[SerializableEnum], [SerializableEnum.FOO, SerializableEnum.FOO])
509+
ok(dict[SerializableEnum, str], {SerializableEnum.FOO: "foo"})
510+
511+
# Enums that mix in int or str
512+
ok(SerializableMixinIntEnum, SerializableMixinIntEnum.FOO)
513+
ok(dict[SerializableMixinIntEnum, str], {SerializableMixinIntEnum.FOO: "foo"})
514+
ok(SerializableMixinStrEnum, SerializableMixinStrEnum.FOO)
515+
ok(
516+
list[SerializableMixinStrEnum],
517+
[SerializableMixinStrEnum.FOO, SerializableMixinStrEnum.FOO],
518+
)
519+
ok(dict[SerializableMixinStrEnum, int], {SerializableMixinStrEnum.FOO: 1})
520+
fail(SerializableMixinStrEnum, 5)
501521

502522
# UUID
503523
ok(UUID, uuid4())

0 commit comments

Comments
 (0)