Type-Check Python Fast with Astral ty (2026)

Efficient Code Sep 26, 2026

Watch on YouTube

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

  • ty is Astral's extremely fast Python type checker and language server (Rust).
  • Try it immediately with uvx ty check, or pin it with uv add --dev ty.
  • Configure rules and Python version under [tool.ty] in pyproject.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.

ToolJobKeep it for
uvenvs, lockfile, runinstall + CI sync
rufflint + formatstyle, imports, many bugs
tystatic typeswrong args, Optional holes, narrowing

Versions tested (2026-09-26)

  • Python 3.13.5
  • ty 0.0.84 (via uvx and uv add --dev ty)
  • uv 0.12.19
  • Cold ty check buggy.py wall time ≈ 0.046s on 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. Prefer uv run ty check so 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

  1. Run uvx ty check on your hottest package today.
  2. Add uv add --dev ty and a [tool.ty] block.
  3. Wire uv run ty check into the same CI job as Ruff.
  4. 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.

Share:
Last updated: September 2026
Built-in Functions in Python 2026 str() in Python 2026: String Conversion + Modern Formatting & Best Practices memoryview with JAX in Python 2026: Zero-Copy NumPy → JAX Array Interop + Efficient ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + GPU Pinning & ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + ML Examples memoryview with NumPy & PyTorch in Python 2026: Zero-Copy Views, Efficient Slicing & ML Interop Examples memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples memoryview() in Python 2026: Zero-Copy Magic for Large Binary Data + Real Examples import() in Python 2026: How It Works, Security Risks & Modern Alternatives (importlib) zip() in Python 2026: How to Use It, strict=True Behavior & Real-World Examples vars() in Python 2026: Accessing Object Namespace + Modern Introspection Patterns type() in Python 2026: Dynamic Type Inspection & Object Creation + Modern Patterns tuple() in Python 2026: Immutable Sequences + Modern Patterns & Best Practices super() in Python 2026: Method Resolution & Modern Inheritance Patterns sum() in Python 2026: Summing Iterables + Modern Numeric Patterns & Best Practices staticmethod() in Python 2026: Static Methods, Utility Functions & Modern Best Practices sorted() in Python 2026: Sorting Iterables + Modern Patterns & Best Practices slice() in Python 2026: Creating Slice Objects + Modern Patterns & Best Practices setattr() in Python 2026: Dynamic Attribute Setting + Modern Patterns & Safety set() in Python 2026: Mutable Sets Creation + Modern Patterns & Best Practices