How to migrate from pyright to ty
ty is a Python type checker developed by Astral (the creators of uv and Ruff). It ships as a single binary with no runtime dependencies, which eliminates pyright’s Node.js requirement.
Installation and basic usage
Installing ty
ty is distributed as a standalone binary. The easiest way to use it is via uvx:
# Run ty directly without installation
uvx ty check .
# Or install globally with uv
uv tool install ty
# Then run
ty check .For other installation methods, see the ty installation guide.
Basic command equivalents
| pyright Command | ty Equivalent |
|---|---|
pyright . |
uvx ty check . |
pyright src/ |
uvx ty check src/ |
pyright --pythonversion 3.11 . |
uvx ty check . --python-version 3.11 |
pyright --pythonpath .venv |
uvx ty check . --python .venv |
pyright --warnings |
uvx ty check . --error-on-warning (both flags treat warnings as errors; both tools otherwise exit non-zero only on errors) |
pyright --outputjson |
No direct equivalent; ty supports --output-format full, concise, github, gitlab |
pyright --verifytypes pkg |
No ty equivalent |
For all available CLI options, see the ty CLI reference.
Automated error suppression with –add-ignore
When migrating from pyright to ty, you may encounter many new type errors that you want to address gradually. The --add-ignore flag automates the process of suppressing these errors by adding ty: ignore[codes] comments throughout your codebase.
# Automatically add ignore comments for all current errors
uvx ty check . --add-ignore
# Then verify no new errors remain
uvx ty check .Migrating configuration
Configuration file locations
pyright supports:
pyrightconfig.json(most common)pyproject.toml→[tool.pyright]
ty supports:
ty.toml(takes precedence)pyproject.toml→[tool.ty]- User-level:
~/.config/ty/ty.toml
For complete configuration details, see the ty configuration guide.
Basic option mapping
| pyright Option | ty Equivalent | Notes |
|---|---|---|
"pythonVersion": "3.11" |
environment.python-version = "3.11" |
|
"pythonPath": ".venv" |
environment.python = ".venv" |
|
"extraPaths": ["stubs"] |
environment.extra-paths = ["stubs"] |
Additional module search paths |
"include": ["src"] |
src.include = ["src"] |
gitignore-style patterns, anchored to project root |
"exclude": ["tests"] |
src.exclude = ["tests"] |
gitignore-style patterns, anchored to project root |
"reportMissingImports": false |
rules.unresolved-import = "ignore" |
|
"reportMissingTypeStubs": false |
rules.unresolved-import = "ignore" |
No separate missing-stubs rule; unresolved-import covers both |
"reportGeneralTypeIssues": "warning" |
See individual rules | No aggregate setting |
"reportArgumentType": "warning" |
rules.invalid-argument-type = "warn" |
|
"reportReturnType": "warning" |
rules.invalid-return-type = "warn" |
|
"reportAssignmentType": "warning" |
rules.invalid-assignment = "warn" |
|
"reportAttributeAccessIssue": "warning" |
rules.unresolved-attribute = "warn" |
|
"reportOptionalMemberAccess": "warning" |
rules.possibly-missing-attribute = "warn" |
|
"reportOptionalSubscript": "warning" |
rules.non-subscriptable = "warn" |
|
"reportOptionalIterable": "warning" |
rules.not-iterable = "warn" |
|
"reportOperatorIssue": "warning" |
rules.unsupported-operator = "warn" |
|
"typeCheckingMode": "basic" |
No equivalent | ty has no preset modes |
"typeCheckingMode": "strict" |
No equivalent | Must set individual rules |
Example configuration migration
pyright (pyrightconfig.json):
{
"include": ["src"],
"exclude": ["tests"],
"pythonVersion": "3.11",
"reportMissingImports": false,
"reportMissingTypeStubs": false,
"reportArgumentType": "warning",
"reportReturnType": "warning",
"reportOptionalMemberAccess": "warning",
"typeCheckingMode": "basic"
}ty (pyproject.toml):
[tool.ty.environment]
python-version = "3.11"
python = ".venv"
[tool.ty.terminal]
error-on-warning = true
[tool.ty.src]
include = ["src"]
exclude = ["tests"]
[tool.ty.rules]
unresolved-import = "ignore"
invalid-argument-type = "warn"
invalid-return-type = "warn"
possibly-missing-attribute = "warn"Tip
error-on-warning = true makes ty exit non-zero on warnings, not just errors. This prevents warnings from accumulating silently and is recommended for CI setups.
If using ty.toml, drop the tool.ty prefix (e.g., [environment], [src], [rules]).
Per-path overrides
pyright supports per-directory configuration through executionEnvironments in pyrightconfig.json. ty replaces this with [[tool.ty.overrides]] blocks that match files using glob patterns:
[[tool.ty.overrides]]
include = ["legacy/**"]
rules = { unresolved-attribute = "ignore", invalid-assignment = "ignore" }Handling pyright-specific features
| pyright Feature | ty Support | Workaround |
|---|---|---|
typeCheckingMode presets |
Not supported | Configure individual rules |
--verifytypes |
Not supported | No equivalent |
reportUnknownMemberType |
No direct equivalent | ty doesn’t expose Unknown-specific toggles |
reportUnknownParameterType |
No direct equivalent | ty doesn’t expose Unknown-specific toggles |
extraPaths |
environment.extra-paths |
Add module search paths (or --extra-search-path) |
stubPath |
environment.extra-paths |
Point to custom stub dirs; environment.typeshed for stdlib |
CI integration
Current pyright CI setup
A typical pyright CI job:
name: pyright
on: [push, pull_request]
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install -e .[dev]
- run: pip install pyright
- run: pyright .Migrating to ty
Replace pyright with ty:
name: Type Check
on: [push, pull_request]
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: uv pip install -e .[dev]
# Use uvx to run ty without installation
- run: uvx ty check src/ --python .venv
# Or, if ty is a dev dependency:
# - run: uv run ty check src/With GitHub Actions annotations:
- run: uvx ty check src/ --output-format githubty exits non-zero only on errors by default. Use --error-on-warning to make warnings fail the run.
Running both type checkers during transition
During migration, run both checkers:
jobs:
pyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install -e .[dev] pyright
- run: pyright .
ty:
runs-on: ubuntu-latest
continue-on-error: true # Don't fail build yet
steps:
- uses: actions/checkout@v4
- run: pip install -e .[dev]
- run: uvx ty check . --python .venv || trueHandling dual-ignore comments
When running both checkers, you will encounter cases where pyright requires a # type: ignore comment that ty considers unnecessary. ty flags these with unused-ignore-comment, producing lines like:
result = func() # type: ignore[assignment] # ty: ignore[unused-ignore-comment]The first comment silences pyright. The second silences ty’s complaint about the first. This is an unavoidable cost of the dual-checker approach and a signal that the codebase is ready to drop pyright.
Common migration issues
Issue 1: Missing type stubs for platform-specific libraries
Symptom: Many unresolved-import and unresolved-reference errors for libraries like libcamera.
Example:
from libcamera import Transform
# ty: Cannot resolve imported module `libcamera`Why it happens: Platform-specific libraries (Linux-only, hardware-specific) often lack PyPI-distributed type stubs.
Solution:
[tool.ty.rules]
unresolved-import = "ignore"
unresolved-reference = "ignore" # Cascading effect of missing importsOr create stub files for the missing library.
Issue 2: pandas/numpy type inference differences
Symptom: possibly-missing-attribute warnings for DataFrame/Series methods.
Example:
df.index = df.index.tz_localize(tz)
# ty: Attribute `tz_localize` may be missing on object of type `Unknown | Index`Why it happens: ty may infer broader types for pandas operations than pyright.
Solution:
[tool.ty.rules]
possibly-missing-attribute = "warn" # or "ignore"Or add explicit type annotations to narrow types.
Issue 3: Parameter defaults with None
Symptom: invalid-parameter-default errors for param: str = None patterns.
Example:
def generate_list_table(title: str = None) -> str:
# ty: Default value of type `None` is not assignable to annotated parameter type `str`Why it happens: ty enforces this at the default rule level. pyright also flags it by default through strictParameterNoneValue = true, but projects that explicitly set strictParameterNoneValue = false or run on older pyright defaults may have accumulated str = None patterns that ty surfaces.
Solution - Code change (preferred):
def generate_list_table(title: str | None = None) -> str:Solution - Configuration:
[tool.ty.rules]
invalid-parameter-default = "warn"Issue 4: Method override parameter names
Symptom: invalid-method-override errors when subclass uses different parameter names.
Example:
class Allocator:
def allocate(self, config, use_case): pass
class DmaAllocator(Allocator):
def allocate(self, libcamera_config, _): pass
# ty: Invalid override - parameter names differWhy it happens: ty strictly enforces Liskov Substitution Principle, including parameter names.
Solution:
[tool.ty.rules]
invalid-method-override = "warn"Or rename parameters to match the base class.
Issue 5: No typeCheckingMode presets
Symptom: Difficulty matching pyright’s basic or strict modes.
Why it happens: ty doesn’t have aggregate strictness settings.
Solution: Configure individual rules. For a pyright “basic” equivalent:
[tool.ty.rules]
# ty is relatively strict by default
# Adjust specific rules as needed
invalid-argument-type = "warn"
invalid-return-type = "warn"
invalid-assignment = "warn"Issue 6: Different handling of Optional types
Symptom: More errors around nullable types than pyright reports.
Example:
if 'key' not in self._data else self._data['key']
# ty: Unsupported `not in` operation (self._data can be None)Why it happens: ty models Optional types more precisely.
Solution:
[tool.ty.rules]
unsupported-operator = "warn"
non-subscriptable = "warn"Or add proper None checks in code.