Skip to content

Fix Union with Literals#69

Merged
NiklasRosenstein merged 6 commits intoNiklasRosenstein:developfrom
rhaps0dy:fix-literal-union
Mar 25, 2026
Merged

Fix Union with Literals#69
NiklasRosenstein merged 6 commits intoNiklasRosenstein:developfrom
rhaps0dy:fix-literal-union

Conversation

@rhaps0dy
Copy link
Copy Markdown
Contributor

@rhaps0dy rhaps0dy commented May 10, 2024

NOTE: currently this PR has a test that fails: serializing a Keyed union with literals. Fixing this requires changing the signature of get_type_id for UnionMembers for a bunch of classes, which I don't feel like doing now. Do I need to do it?

Partially Fixes #47

Unions with Literal types on them could not be serialized or deserialized. They now can.

@rhaps0dy rhaps0dy force-pushed the fix-literal-union branch from 0c5094b to d70350f Compare May 10, 2024 03:35
@rhaps0dy
Copy link
Copy Markdown
Contributor Author

Thoughts on this PR? IS this something you'd like to address?

@NiklasRosenstein
Copy link
Copy Markdown
Owner

Hey @rhaps0dy, while I'm failing to see much practical applications 😉 this is a legit type of type hint that seems like it should deserializable by Databind. I think we could just say that the nested union serialization format is not supported when a Literal is involved in the union members and error out.

@rhaps0dy
Copy link
Copy Markdown
Contributor Author

Oh, I can give you practical applications for this one!

Here's my configuration for describing a convolution layer, pay attention to the padding type:

@dataclasses.dataclass(frozen=True)
class ConvConfig:
    features: int
    kernel_size: Tuple[int, ...]
    strides: Tuple[int, ...]
    padding: Literal["SAME", "VALID"] | Sequence[Tuple[int, ...]] = "SAME"
    use_bias: bool = True
    initialization: Literal["torch", "lecun"] = "lecun"

from e.g. the Torch docs (https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html):

padding controls the amount of padding applied to the input. It can be either a string {‘valid’, ‘same’} or an int / a tuple of ints giving the amount of implicit padding applied on both sides.

this would imply a type of Literal["same", "valid"] | Sequence[int | Tuple[int, ...]], which I haven't used because this is for Jax code, but you see the use.

--

The actual reason I fixed it is that I thought there were very few [bug] issues in the tracker and maybe I could make PRs for all of them. But now I'm making use of it.

rhaps0dy and others added 4 commits March 25, 2026 13:50
Add get_type_id_for_value(value) to UnionMembers so that Literal type
members can be resolved by value rather than by type (e.g. type("hi")
is str, which can't distinguish Literal["hi"] from Literal["bye"]).

- UnionMembers: add non-abstract get_type_id_for_value with default
  fallback to get_type_id(type(value))
- StaticUnionMembers: override to check LiteralTypeHint members first,
  handling both raw typing annotations and TypeHint instances
- ChainUnionMembers: override to delegate to children
- UnionConverter: include Literal members in the auto-generated members
  dict, remove separate literal_types loop, use get_type_id_for_value
  in the serialize path, wrap ValueError as ConversionError
- Tests: remove xfail from keyed literal test, simplify best_match
  literal test, fix test_union_literal to include LiteralConverter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@NiklasRosenstein
Copy link
Copy Markdown
Owner

This comment was generated by Claude Code (claude-opus-4-6).

Implementation: Integrate Literal types into UnionMembers

This commit properly integrates Literal type support into the UnionMembers abstraction so that all union styles (KEYED, NESTED, FLAT) work with Literal members — not just BEST_MATCH.

Problem

StaticUnionMembers.get_type_id(type_) does type-based lookup, but type("hi") is str, which can't distinguish Literal["hi"] from Literal["bye"]. This caused KEYED/NESTED/FLAT union styles to fail when members included Literal types.

Solution

Added get_type_id_for_value(value) to the UnionMembers hierarchy:

  • UnionMembers (base): non-abstract method with default fallback to get_type_id(type(value)), preserving backward compatibility for third-party implementations
  • StaticUnionMembers: checks LiteralTypeHint members first (matching value in hint.values), handles both raw t.Literal[...] annotations and TypeHint instances, then falls back to type-based matching
  • ChainUnionMembers: delegates to each child

In UnionConverter:

  • Literal members are now included in the auto-generated members dict for plain Union types (no more separate literal_types side-channel)
  • Serialization path uses get_type_id_for_value(ctx.value) instead of get_type_id(type(ctx.value))
  • ValueError from member lookup is wrapped as ConversionError

Tests

  • Removed @pytest.mark.xfail from test_union_converter_keyed_literal — it now passes
  • Simplified test_union_converter_best_match_literal (identical branches collapsed)
  • Fixed pre-existing bug in test_union_literal (was missing LiteralConverter in mapper)
  • All 55 tests pass

NiklasRosenstein and others added 2 commits March 25, 2026 14:22
- Add -> None return type annotation
- Use t.Union[...] syntax instead of | operator for type aliases
  with Literal members (fixes valid-type and operator errors)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses issue #47 by enabling JSON serialization/deserialization for typing.Union types that include typing.Literal members, which previously failed due to union-member constraints and type-only member resolution.

Changes:

  • Extend UnionConverter to accept LiteralTypeHint members in “plain” (non-annotated) unions and to resolve union members for serialization based on the actual value (to support Literals).
  • Add UnionMembers.get_type_id_for_value() with implementations for StaticUnionMembers and ChainUnionMembers to support value-based matching (especially for Literals).
  • Add regression tests covering best-match and keyed union behavior with Literal members, and document the fix in the changelog.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
databind/src/databind/json/tests/converters_test.py Adds regression tests for unions containing Literal members (best-match and keyed styles).
databind/src/databind/json/converters.py Updates UnionConverter to allow Literal members in plain unions and to select union members by value for serialization.
databind/src/databind/core/union.py Adds value-based union member resolution API and implements Literal-aware matching in StaticUnionMembers (and delegation in ChainUnionMembers).
.changelog/_unreleased.toml Adds an unreleased changelog entry describing the fix for #47.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@NiklasRosenstein NiklasRosenstein merged commit 5204149 into NiklasRosenstein:develop Mar 25, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

no deserializer for Union[Literal[...], str] and payload of type str

3 participants