Type-Check Python Fast with Astral ty (2026)
Type-Check Python Fast with Astral ty (2026) shows how to add Astral's new Rust type checker to a modern Python project. ty sits next to uv + Ruff: uv manages the environment, Ruff lints and formats, and ty catches type errors before they hit runtime. On cold checks it is routinely 10×–100× faster than mypy or Pyright, and it is designed for gradual adoption on partially typed codebases.
PyInns already covers type hints and static typing advances and modern project setup with uv + Ruff. This guide fills the missing piece of the Astral toolchain: a production-ready type checker you can run from the CLI, pin in CI, and grow into over time.
TL;DR
tyis Astral's extremely fast Python type checker and language server (Rust).- Try it immediately with
uvx ty check, or pin it withuv add --dev ty. - Configure rules and Python version under
[tool.ty]inpyproject.toml. - Pair it with Ruff: Ruff for lint/format, ty for types — do not treat Ruff as a full type checker.
- Pin the version in CI; ty is still on the 0.x beta line and moves quickly.
Why ty in 2026?
For years the default answers were mypy (ecosystem) or Pyright/Pylance (editor speed). ty targets both: CLI checks that finish in tens of milliseconds on small modules, fine-grained incremental analysis for editors, and a gradual guarantee that adding annotations to working code should not invent new errors. If you already standardized on uv and Ruff, ty is the natural next install.
| Tool | Job | Keep it for |
|---|---|---|
uv | envs, lockfile, run | install + CI sync |
ruff | lint + format | style, imports, many bugs |
ty | static types | wrong args, Optional holes, narrowing |
Versions tested (2026-09-26)
- Python
3.13.5 - ty
0.0.84(viauvxanduv add --dev ty) - uv
0.12.19 - Cold
ty check buggy.pywall time ≈0.046son this box (3 diagnostics)
# zero-install try
uvx ty check
# pin in a project
uv add --dev ty
uv run ty check
uv run ty --version # ty 0.0.84
1. A small file with three real type bugs
Save this as buggy.py. None of these fail at import time in the way you might expect — greet(42) and add_ids(1, "2") blow up or coerce only when executed, and maybe_len(None) raises TypeError only on the None path.
from typing import Optional
def greet(name: str) -> str:
return "Hello, " + name
def maybe_len(value: Optional[str]) -> int:
# Bug: value can be None
return len(value)
def add_ids(a: int, b: int) -> int:
return a + b
print(greet(42)) # Expected str, got int
print(maybe_len(None)) # len(None) is invalid
print(add_ids(1, "2")) # Expected int, got str
2. Run ty check — real output
From the project directory:
uvx ty check buggy.py
Actual diagnostics from ty 0.0.84 on this machine:
error[invalid-argument-type]: Argument to function `len` is incorrect
--> buggy.py:8:16
|
8 | return len(value)
| ^^^^^ Expected `Sized`, found `str | None`
info: element `None` of union `str | None` is not assignable to `Sized`
error[invalid-argument-type]: Argument to function `greet` is incorrect
--> buggy.py:14:13
|
14 | print(greet(42))
| ^^ Expected `str`, found `Literal[42]`
error[invalid-argument-type]: Argument to function `add_ids` is incorrect
--> buggy.py:20:18
|
20 | print(add_ids(1, "2"))
| ^^^ Expected `int`, found `Literal["2"]`
Found 3 diagnostics
Exit code is non-zero when diagnostics remain — perfect for failing a CI job.
3. Fix the code and confirm a clean check
from typing import Optional
def greet(name: str) -> str:
return f"Hello, {name}"
def maybe_len(value: Optional[str]) -> int:
if value is None:
return 0
return len(value)
def add_ids(a: int, b: int) -> int:
return a + b
print(greet("reader"))
print(maybe_len(None))
print(maybe_len("typed"))
print(add_ids(1, 2))
uvx ty check clean.py
# All checks passed!
python3 clean.py
# Hello, reader
# 0
# 5
# 3
Notice the narrowing: after if value is None: return 0, ty treats value as str on the remaining path, so len(value) is valid.
4. Project config in pyproject.toml
ty reads [tool.ty] (or a standalone ty.toml). A minimal, useful setup:
[project]
name = "my-app"
version = "0.1.0"
requires-python = ">=3.12"
[dependency-groups]
dev = ["ty>=0.0.84", "ruff"]
[tool.ty.environment]
python-version = "3.13"
[tool.ty.src]
include = ["src", "tests"]
exclude = ["src/generated"]
[tool.ty.rules]
possibly-unresolved-reference = "error"
uv sync
uv run ty check
uv run ruff check --fix .
uv run ruff format .
Command-line flags override the file. Use ty check --watch while editing for fine-grained incremental rechecks.
5. Drop ty into CI (GitHub Actions sketch)
name: typecheck
on: [push, pull_request]
jobs:
ty:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv sync --locked --group dev
- run: uv run ty check
- run: uv run ruff check .
Pin ty in the lockfile (uv lock) so every developer and CI runner see the same diagnostics. Because ty is still 0.x, treat major diagnostic shifts like a dependency upgrade: review them in a dedicated PR.
6. When to keep mypy or Pyright
- Keep mypy if you rely on a long tail of plugins (Django, SQLAlchemy custom plugins) that ty does not cover yet.
- Keep Pyright if your team already standardized on it in VS Code and you do not need Astral-toolchain coherence.
- Prefer ty when you want uv/Ruff-aligned tooling, very fast cold checks, and gradual adoption on mixed typed/untyped trees.
Many teams run ty in CI for speed and keep one legacy checker in a slower nightly job until plugin gaps close.
Common pitfalls
- Expecting Ruff to replace a type checker. Ruff catches many issues; it is not a substitute for ty/mypy/Pyright on argument and Optional bugs.
- Not activating / discovering the venv. ty looks for
.venv,VIRTUAL_ENV, or--python. Preferuv run ty checkso deps resolve correctly. - Unpinned ty in CI. Beta releases can change default severities. Commit
uv.lock. - Checking generated code. Exclude it under
[tool.ty.src] exclude.
What to do next
- Run
uvx ty checkon your hottest package today. - Add
uv add --dev tyand a[tool.ty]block. - Wire
uv run ty checkinto the same CI job as Ruff. - Deepen typing where ty reports
invalid-argument-type— those are the bugs that slip past tests.
With uv for installs, Ruff for lint/format, and ty for types, your 2026 Python toolchain stays fast from laptop to CI without juggling four legacy tools.