2025, Dec 31 21:00

How to Apply Numeric Constraints to a Specific Type in Pydantic Unions with Annotated[int, Field(ge=2)]

Learn how to scope numeric validation in Pydantic union fields: apply ge=2 only to int using typing.Annotated and Field. Avoid TypeError and keep models clear.

When a pydantic model field accepts multiple types, attaching a numeric constraint globally to that field can backfire. A typical case is a field that may be an int, a str, or even a list, while you want ge=2 to apply only when the value is actually an integer. If the constraint is attached at the field level, it will be attempted against non-numeric values too, which leads to a runtime error instead of selective validation.

Problem

The intent is simple: validate integers with ge=2, allow other types to pass untouched. The straightforward code below does not achieve that.

from pydantic import BaseModel, Field

class Parcel(BaseModel):
    payload: str | list | int = Field('some string', ge=2)

obj = Parcel(payload='asdf')  # should not apply ge
obj = Parcel(payload=4)       # should apply ge

Instead of conditional validation, the model attempts to apply the constraint to the provided value regardless of its type and fails with:

TypeError: Unable to apply constraint 'ge' to supplied value asdf

Why this happens

The constraint ge=2 is attached to the entire field, not to a specific type within the union. As a result, it is evaluated for non-numeric inputs such as a str, which cannot satisfy the numeric comparison and triggers the error. The root cause is that the constraint is not scoped to the int branch of the union.

Solution

Scope the constraint to the integer variant only. Use typing.Annotated to bind Field(ge=2) to the int type within the union. This way the comparison is attempted exclusively when the value is an integer, and other types bypass the numeric rule.

from pydantic import BaseModel, Field
from typing import Annotated

class Parcel(BaseModel):
    payload: str | list | Annotated[int, Field(ge=2)] = 'some string'

obj = Parcel(payload='asdf')  # ok, no numeric constraint for str
obj = Parcel(payload=4)       # ok, ge=2 applied to int

Why this matters

Multi-type fields are common in real-world models, and unconditional constraints create fragile validation flows. Scoping rules to the correct variant avoids unnecessary TypeError, keeps the intent explicit, and makes models easier to evolve. It also improves readability: anyone scanning the type declaration immediately sees that the integer branch carries the numeric requirement while other branches do not.

Takeaways

Attach constraints exactly where they belong. For union types in pydantic, apply Field-based rules to the specific variant using Annotated. This preserves flexible input while enforcing precise validation only when it makes sense.