Skip to content

Support constraints on Optional (union with None) types#994

Closed
Siyet wants to merge 1 commit intojcrist:mainfrom
Siyet:804-optional-constraints
Closed

Support constraints on Optional (union with None) types#994
Siyet wants to merge 1 commit intojcrist:mainfrom
Siyet:804-optional-constraints

Conversation

@Siyet
Copy link
Copy Markdown
Collaborator

@Siyet Siyet commented Mar 31, 2026

Summary

  • Derive constraint_kind from non-None union members so that Annotated[X | None, Meta(...)] constraints are validated correctly
  • Previously any constraint on an Optional[X] type raised TypeError because the union branch left kind as CK_OTHER
  • Supports all constraint kinds: numeric (gt/ge/lt/le/multiple_of), string (min_length/max_length/pattern), bytes, collections, datetime/time (tz)

Closes #804

Test plan

All 275 constraint tests pass (Ubuntu 22.04, Python 3.10):

tests/unit/test_constraints.py - 275 passed in 0.27s

New tests added:

  • test_optional_str_min_length - Annotated[Union[str, None], Meta(min_length=3)]
  • test_optional_int_gt - Annotated[Union[int, None], Meta(gt=0)]
  • test_optional_float_ge - Annotated[Union[float, None], Meta(ge=0.0)]

Each test verifies:

  • None values pass through without constraint checks

  • Valid values are accepted

  • Invalid values raise ValidationError

  • CI passes on all platforms

Derive constraint_kind from non-None union members so that
Annotated[X | None, Meta(...)] constraints are validated correctly.
Previously any constraint on an Optional[X] type raised TypeError
because the union branch left kind as CK_OTHER.

Closes #804
@Siyet
Copy link
Copy Markdown
Collaborator Author

Siyet commented Apr 1, 2026

CI failures are unrelated to this change:

"Profile Windows (ARM64)" - runner infra issue ("Install command runner" step)
"build" (documentation) - link checker fails on a pre-existing broken link (#976)
All build, test, and wheel jobs pass across all platforms.

@Siyet
Copy link
Copy Markdown
Collaborator Author

Siyet commented Apr 8, 2026

Resolved in the community fork msgspec-arise: PR #10, released in v0.20.2.

@jcrist
Copy link
Copy Markdown
Owner

jcrist commented Apr 8, 2026

Not supporting this was intentional.

We only support applying annotations directly to the type they apply to for clarity.

# This works, and makes it clear that the annotation applies to the `int`
Annotated[int, Meta(ge=1)] | None

# This doesn't work, but IMO it shouldn't. None isn't numeric and can't support >=
Annotated[int | None, Meta(ge=1)]

Since Optional[int] is the same as int | None, it's a slippery slope to applying annotations to a union and expecting them to apply to all union members. Annotated[int | str | None, Meta(ge=1)] for example. I'd rather just keep things simple and clear - annotations must be applied to the type directly, and not the union.

@jcrist jcrist closed this Apr 8, 2026
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.

Cannot set min_length for optional string filed

2 participants