DEV Community

Cover image for ty: The Blazingly Fast Python Type Checker from Astral (Ruff & uv Creators)
Hiroshi Toyama
Hiroshi Toyama

Posted on

ty: The Blazingly Fast Python Type Checker from Astral (Ruff & uv Creators)

ty: The Blazingly Fast Python Type Checker from Astral

Astral, the company behind popular Python tools like Ruff and uv, has released a new type checker called ty. Built in Rust, it promises to revolutionize Python type checking with incredible speed and powerful features.

Key Features

  • ⚑ Blazingly Fast: 10-100x faster than mypy and Pyright
  • πŸ” Rich Diagnostics: Detailed error messages with multi-file context
  • 🧠 Smart Type Inference: Catches bugs even without type annotations
  • 🎯 Advanced Type System: Intersection types, advanced narrowing, reachability analysis
  • πŸ› οΈ Language Server: Built-in support for IDE integration (VS Code extension available)

Installation

# Using uv (recommended)
uv tool install ty@latest

# Using pip
pip install ty
Enter fullscreen mode Exit fullscreen mode

Basic Usage

# Check a file
ty check file.py

# Check directories
ty check src tests

# Verbose mode
ty check src --verbose
Enter fullscreen mode Exit fullscreen mode

What Makes ty Special?

1. Speed That Matters

ty can type-check the entire Home Assistant project in ~2.19 seconds, compared to mypy's 45.66 seconds. That's over 20x faster!

For large codebases, this speed difference transforms the developer experience:

  • No more waiting for CI to finish
  • Instant feedback in your IDE
  • Practical to run on every save

2. Powerful Type Inference

Here's the magic: ty can find bugs even without type annotations. Let's look at some examples.

Real-World Bug Detection

Example 1: Type Mismatches

def add(x: int, y: int) -> int:
    return x + y

result: str = add(1, 2)  # Oops! Assigning int to str
Enter fullscreen mode Exit fullscreen mode

ty's output:

error[invalid-assignment]: Object of type `int` is not assignable to `str`
 --> example.py:4:9
  |
4 | result: str = add(1, 2)
  |         ---   ^^^^^^^^^ Incompatible value of type `int`
  |         |
  |         Declared type
Enter fullscreen mode Exit fullscreen mode

Example 2: Missing Attributes

class User:
    def __init__(self, name: str):
        self.name = name

    def get_email(self):
        return self.email  # email was never defined!
Enter fullscreen mode Exit fullscreen mode

ty catches this:

error[unresolved-attribute]: Object of type `Self@get_email` has no attribute `email`
 --> example.py:6:16
  |
6 |         return self.email
  |                ^^^^^^^^^^
Enter fullscreen mode Exit fullscreen mode

This works even for complex cases:

class DatabaseClient:
    def __init__(self, connection_string: str):
        self.connection = connect(connection_string)

    def query(self, sql: str):
        # Typo: should be .execute(), not .exec()
        return self.connection.exec(sql)
Enter fullscreen mode Exit fullscreen mode

ty will warn you that .exec() doesn't exist on the connection object!

Example 3: Incorrect Function Arguments

numbers: list[int] = [1, 2, 3]
numbers.append("four")  # String in int list!
Enter fullscreen mode Exit fullscreen mode

ty's error:

error[invalid-argument-type]: Argument to bound method `append` is incorrect
 --> example.py:2:16
  |
2 | numbers.append("four")
  |                ^^^^^^ Expected `int`, found `Literal["four"]`
Enter fullscreen mode Exit fullscreen mode

Example 4: Null Safety

def process(value: str | None) -> int:
    return len(value)  # What if value is None?
Enter fullscreen mode Exit fullscreen mode

ty spots the issue:

error[invalid-argument-type]: Expected `Sized`, found `str | None`
 --> example.py:2:16
  |
2 |     return len(value)
  |                ^^^^^
  |
info: Element `None` of this union is not assignable to `Sized`
Enter fullscreen mode Exit fullscreen mode

Example 5: Implicit None Returns

def get_name(user_id: int) -> str:
    if user_id > 0:
        return "User"
    # Missing return statement - implicit None!
Enter fullscreen mode Exit fullscreen mode

ty catches this too:

error[invalid-return-type]: Function can implicitly return `None`,
which is not assignable to return type `str`
Enter fullscreen mode Exit fullscreen mode

Configuration

ty uses pyproject.toml for configuration:

[tool.ty]
[tool.ty.environment]
python-version = "3.10"

[tool.ty.src]
include = ["src/**/*.py", "tests/**/*.py"]
exclude = [".tox/**", "build/**", "dist/**"]

# Per-directory overrides
[[tool.ty.overrides]]
include = ["tests/**/*.py"]
[tool.ty.overrides.rules]
invalid-assignment = "ignore"  # More lenient for tests
Enter fullscreen mode Exit fullscreen mode

CI/CD Integration

GitHub Actions

name: Type Check
on: [push, pull_request]

jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      - name: Install dependencies
        run: pip install ty
      - name: Run type checker
        run: ty check src tests
Enter fullscreen mode Exit fullscreen mode

tox Integration

[testenv:ty]
deps =
    ty
    -e .
commands =
    ty check src tests
Enter fullscreen mode Exit fullscreen mode

pre-commit Hook

repos:
  - repo: local
    hooks:
      - id: ty
        name: ty type checker
        entry: ty check src tests
        language: system
        pass_filenames: false
        types: [python]
Enter fullscreen mode Exit fullscreen mode

Editor Integration

VS Code

  1. Install the "ty" extension from VS Code Marketplace
  2. Enjoy automatic:
    • Type checking as you type
    • Inlay hints for inferred types
    • Code actions and quick fixes
    • Jump to definition with type awareness

ty vs mypy vs Pyright

Feature ty mypy Pyright
Speed ⚑⚑⚑ Ultra-fast 🐒 Moderate πŸš€ Fast
Language Rust Python TypeScript
Error Messages πŸ“ Very detailed πŸ“„ Standard πŸ“ Detailed
Type Inference 🧠 Powerful πŸ“š Standard 🧠 Powerful
Maturity πŸ†• Beta βœ… Stable βœ… Stable
Diagnostics πŸ” Multi-file context πŸ“Š Single-file πŸ” Cross-file
Language Server βœ… Built-in ⚠️ Via dmypy βœ… Built-in

Current Limitations

As of v0.0.9 (Beta):

  • Limited rule set: Main rules implemented, more coming in 2026
  • No strict mode yet: Won't complain about missing type annotations
  • Beta status: API may change before stable release

However, even in beta, ty is already catching real bugs and providing value in production codebases.

Getting Started with ty

Here's a practical workflow for adopting ty in your project:

Step 1: Install and Run

uv tool install ty@latest
ty check src
Enter fullscreen mode Exit fullscreen mode

Step 2: Review Results

ty will show you actual bugs in your code, even without type annotations!

Step 3: Add to CI

# Add to your test script
tox -e ty

# Or run directly
ty check src tests
Enter fullscreen mode Exit fullscreen mode

Step 4: Gradual Type Annotation

As you maintain your code, gradually add type annotations where it makes sense:

# Before
def calculate_total(items):
    return sum(item.price for item in items)

# After
def calculate_total(items: list[Item]) -> Decimal:
    return sum(item.price for item in items)
Enter fullscreen mode Exit fullscreen mode

With annotations, ty becomes even more powerful!

Real-World Example: FastAPI Application

from fastapi import FastAPI, HTTPException

app = FastAPI()

class UserService:
    def __init__(self, db_url: str):
        self.db = connect_database(db_url)

    def get_user(self, user_id: int):
        user = self.db.query(User).filter(User.id == user_id).first()
        if user:
            return user
        # Bug: FastAPI expects HTTPException, but we're returning None!
        return None

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    service = UserService(DB_URL)
    user = service.get_user(user_id)
    # Bug: user could be None, but we're accessing .dict()
    return user.dict()
Enter fullscreen mode Exit fullscreen mode

ty will catch both bugs:

  1. get_user can return None, but FastAPI expects an exception or a valid user
  2. user.dict() could fail if user is None

Performance Tips

  1. Use file exclusion: Skip generated files and vendor directories
  2. Enable caching: ty caches results for unchanged files
  3. Parallel processing: ty automatically uses multiple cores
  4. Incremental mode: In your editor, only changed files are re-checked

Roadmap

According to Astral, ty is targeting a stable 1.0 release in 2026, with plans for:

  • βœ… More comprehensive rule coverage
  • βœ… Strict mode (enforce type annotations)
  • βœ… Better integration with popular frameworks
  • βœ… Plugin system for custom rules
  • βœ… Performance optimizations

Conclusion

ty represents the next evolution in Python type checking. Its combination of blazing speed, powerful inference, and excellent diagnostics makes it a compelling choice for Python developers.

While still in beta, ty is already proving valuable for catching real bugs and improving code quality. If you're using mypy or Pyright and frustrated by slow check times, ty is worth trying.

The Astral team has an excellent track record with Ruff and uv, and ty looks set to be another game-changer in the Python tooling ecosystem.

Resources


Have you tried ty yet? What's your experience with Python type checkers? Let me know in the comments! πŸ‘‡

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.