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
Basic Usage
# Check a file
ty check file.py
# Check directories
ty check src tests
# Verbose mode
ty check src --verbose
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
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
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!
ty catches this:
error[unresolved-attribute]: Object of type `Self@get_email` has no attribute `email`
--> example.py:6:16
|
6 | return self.email
| ^^^^^^^^^^
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)
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!
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"]`
Example 4: Null Safety
def process(value: str | None) -> int:
return len(value) # What if value is None?
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`
Example 5: Implicit None Returns
def get_name(user_id: int) -> str:
if user_id > 0:
return "User"
# Missing return statement - implicit None!
ty catches this too:
error[invalid-return-type]: Function can implicitly return `None`,
which is not assignable to return type `str`
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
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
tox Integration
[testenv:ty]
deps =
ty
-e .
commands =
ty check src tests
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]
Editor Integration
VS Code
- Install the "ty" extension from VS Code Marketplace
- 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
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
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)
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()
ty will catch both bugs:
-
get_usercan returnNone, but FastAPI expects an exception or a valid user -
user.dict()could fail if user isNone
Performance Tips
- Use file exclusion: Skip generated files and vendor directories
- Enable caching: ty caches results for unchanged files
- Parallel processing: ty automatically uses multiple cores
- 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
- π Official Website: astral.sh/ty
- π Documentation: docs.astral.sh/ty
- π» GitHub: github.com/astral-sh/ty
- π¬ Discord: Astral Discord
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.