Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/msgspec/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,11 @@ class LiteralType(Type):
Parameters
----------
values: tuple
A tuple of possible values for this literal instance. Only `str` or
`int` literals are supported.
A tuple of possible values for this literal instance. Values may be
``None``, ``int``, or ``str`` literals.
"""

values: Union[Tuple[str, ...], Tuple[int, ...]]
values: Tuple[Union[None, int, str], ...]


class CustomType(Type):
Expand Down Expand Up @@ -885,7 +885,14 @@ def _translate_inner(
args = tuple(self.translate(a) for a in args if a is not _UnsetType)
return args[0] if len(args) == 1 else UnionType(args)
elif t is Literal:
return LiteralType(tuple(sorted(args)))
return LiteralType(
tuple(
sorted(
args,
key=lambda x: ("", 0) if x is None else (type(x).__name__, x),
)
Comment on lines +889 to +893
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

type_info() now returns mixed-type LiteralType.values by using a custom sort key, but msgspec.json.schema still does sorted(t.values) (see src/msgspec/_json_schema.py:367), which will continue to raise TypeError for mixed (None, int, str) enums. Consider either relying on the already-normalized ordering from type_info (no downstream re-sort), or factoring this sort key into a shared helper and reusing it in schema generation too.

Copilot uses AI. Check for mistakes.
)
)
elif _is_enum(t):
return EnumType(t)
elif is_struct_type(t):
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,25 @@ def test_str_literal():
assert mi.type_info(Literal["c", "a", "b"]) == mi.LiteralType(("a", "b", "c"))


def test_none_literal():
assert mi.type_info(Literal[None]) == mi.LiteralType((None,))


def test_mixed_int_none_literal():
result = mi.type_info(Literal[1, None])
assert result == mi.LiteralType((None, 1))


def test_mixed_str_int_literal():
result = mi.type_info(Literal[1, "a"])
assert result == mi.LiteralType((1, "a"))


def test_mixed_none_int_str_literal():
result = mi.type_info(Literal["b", 1, None, "a"])
assert result == mi.LiteralType((None, 1, "a", "b"))


def test_int_enum():
class Example(enum.IntEnum):
B = 3
Expand Down
Loading