Repository: yukinarit/pyserde
Pull request #251 (2022-07-23 06:07:31)

Use str instead of repr in typename, fixing Union[Literal["str"], ...]
Hi, thank you for this package 😃 

- `str` is more similar to the behavior of format, which is used elsewhere in the function
-  this fixes `Union[something, Literal["str]]`
  - the current implementation tries to use something like `def union_de_something_literal_'str'` as a function name, which contains `'` and thus is invalid


Patch:
diff --git a/serde/compat.py b/serde/compat.py
index 1c33740..3909126 100644
--- a/serde/compat.py
+++ b/serde/compat.py
@@ -212,7 +212,7 @@ def typename(typ, with_typing_module: bool = False) -> str:
         args = type_args(typ)
         if not args:
             raise TypeError("Literal type requires at least one literal argument")
-        return f'Literal[{", ".join(repr(e) for e in args)}]'
+        return f'Literal[{", ".join(str(e) for e in args)}]'
     elif typ is Any:
         return f'{mod}Any'
     else:
diff --git a/serde/core.py b/serde/core.py
index ec22e35..2d50954 100644
--- a/serde/core.py
+++ b/serde/core.py
@@ -562,7 +562,7 @@ def union_func_name(prefix: str, union_args: List[Type]) -> str:
     >>> union_func_name("union_se", [int, List[str], IPv4Address])
     'union_se_int_List_str__IPv4Address'
     """
-    return re.sub(r"[ ,\[\]]+", "_", f"{prefix}_{'_'.join([typename(e) for e in union_args])}")
+    return re.sub(r"[^A-Za-z0-9]", "_", f"{prefix}_{'_'.join([typename(e) for e in union_args])}")
 
 
 def literal_func_name(literal_args: List[Any]) -> str:


Test patch:
diff --git a/tests/test_compat.py b/tests/test_compat.py
index faf3944..b96118a 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -5,6 +5,7 @@ from typing import Dict, Generic, List, NewType, Optional, Set, Tuple, TypeVar,
 
 import serde
 from serde.compat import (
+    Literal,
     is_dict,
     is_generic,
     is_list,
@@ -84,6 +85,7 @@ def test_typename():
     assert typename(Dict[str, Foo]) == "Dict[str, Foo]"
     assert typename(Set) == "Set"
     assert typename(Set[int]) == "Set[int]"
+    assert typename(Literal[1, 1.0, "Hey"]) == "Literal[1, 1.0, Hey]"
 
 
 def test_iter_types():
diff --git a/tests/test_union.py b/tests/test_union.py
index 9e42ffc..be7317a 100644
--- a/tests/test_union.py
+++ b/tests/test_union.py
@@ -10,6 +10,7 @@ import pytest
 from serde import SerdeError, from_dict, from_tuple
 from serde import init as serde_init
 from serde import logger, serde, to_dict, to_tuple
+from serde.compat import Literal
 from serde.json import from_json, to_json
 
 logging.basicConfig(level=logging.WARNING)
@@ -61,6 +62,16 @@ class ContUnion:
     v: Union[Dict[str, int], List[int], List[str]]
 
 
+@serde
+@dataclass(unsafe_hash=True)
+class LitUnion:
+    """
+    Union of literals
+    """
+
+    v: Union[int, Literal["foo", "bar", "*"]]
+
+
 def test_union():
     v = PriUnion(10)
     s = '{"v":10}'
@@ -128,6 +139,33 @@ def test_union_containers():
     assert v == from_json(ContUnion, s)
 
 
+def test_union_with_literal():
+    v = LitUnion(10)
+    s = '{"v":10}'
+    assert s == to_json(v)
+    assert v == from_json(LitUnion, s)
+
+    v = LitUnion("foo")
+    s = '{"v":"foo"}'
+    assert s == to_json(v)
+    assert v == from_json(LitUnion, s)
+
+    v = LitUnion("bar")
+    s = '{"v":"bar"}'
+    assert s == to_json(v)
+    assert v == from_json(LitUnion, s)
+
+    v = LitUnion("*")
+    s = '{"v":"*"}'
+    assert s == to_json(v)
+    assert v == from_json(LitUnion, s)
+
+    s = '{"v":"boo"}'
+
+    with pytest.raises(SerdeError):
+        from_json(LitUnion, s)
+
+
 def test_union_with_complex_types():
     @serde
     class A: