ty now checks Pydantic like Pydantic does
ty shipped first-class Pydantic support across its July 2026 releases (0.0.57 through 0.0.61), and Pydantic author Sebastián Ramírez signed off on the work publicly. Before this, ty handled Pydantic models through the generic PEP 681 dataclass_transform marker, treating the field annotation as the source of truth. Now it models Pydantic’s actual runtime.
What ty checks now
ty distinguishes Pydantic’s lax and strict validation modes and follows Pydantic’s coercion rules. It reads model_config = ConfigDict(...) off the class, recognizing strict, frozen, extra, validate_by_name, and validate_by_alias. It reads per-field metadata from Field and Annotated, including aliases, default factories, and a per-field Strict() marker.
In lax mode, Pydantic’s default, ty accepts a coercible value and rejects one that isn’t:
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int = 0
User(name="ada", age="40") # ok: str coerces to int at runtime
User(name="ada", age=None) # error: None is not coercibleSet model_config = ConfigDict(strict=True) and ty switches to exact-type checking, flagging the same age="40". Set extra="forbid" and it rejects unknown keyword arguments in the constructor.
The Pyrefly gap just closed
The handbook’s ty vs Pyrefly page used to name Pydantic as the reason to pick ty: ty was strict where Pyrefly was lax, so ty flagged a User(age="forty") call that Pyrefly waved through. That contrast is gone. Both tools now model lax and strict modes, read ConfigDict, and honor extra="forbid", and they return the same verdict on the cases above.
One narrow divergence remains. ty reads a field-level Strict() marker that Pyrefly’s default preset does not:
from typing import Annotated
from pydantic import BaseModel, Strict
class Record(BaseModel):
id: Annotated[int, Strict()]
Record(id="1") # ty: error Pyrefly: no errorIf you keep some fields exact while the rest stay lax, ty catches the violations Pyrefly currently misses. The win is narrow, though: a bool passed to a strict int field stays silent in both tools, because bool is a static subtype of int, and neither replaces Pydantic’s runtime validation. For whole-model strictness through ConfigDict(strict=True), the two agree.
Both share one blind spot: in lax mode they accept any str for an int field, including "forty", which Pydantic rejects at runtime. The static check models the lax/strict distinction, not whether a specific string actually coerces.
(https://pydevtools.com/handbook/explanation/ty-vs-pyrefly/)