# ty now checks Pydantic like Pydantic does


[ty](https://pydevtools.com/handbook/reference/ty.md) 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](https://pydevtools.com/handbook/explanation/what-is-pep-681.md), 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:

```python
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 coercible
```

Set `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](https://pydevtools.com/handbook/explanation/ty-vs-pyrefly.md) 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:

```python
from typing import Annotated
from pydantic import BaseModel, Strict


class Record(BaseModel):
    id: Annotated[int, Strict()]


Record(id="1")   # ty: error   Pyrefly: no error
```

If 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.md)
- [ty reference](https://pydevtools.com/handbook/reference/ty.md)
- [How to migrate from mypy to ty](https://pydevtools.com/handbook/how-to/how-to-migrate-from-mypy-to-ty.md)
- [What is PEP 681 (`dataclass_transform`)?](https://pydevtools.com/handbook/explanation/what-is-pep-681.md)
- [ty 0.0.61 release notes](https://github.com/astral-sh/ty/releases)
