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.
Understanding ty
When to consider migrating
- Projects wanting faster type checking on large codebases
- Teams already using other Astral tools (Ruff, uv)
- Projects wanting to eliminate Node.js dependency
- Projects without complex platform-specific dependencies lacking stubs
- Projects with simple Pyright configuration
When to wait
- Projects heavily dependent on Pyright-specific features like
--verifytypes - Projects requiring
typeCheckingModepresets (until ty adds equivalents) - Projects with complex platform-specific dependencies lacking stubs
- Projects where Pyright’s specific type inference is depended upon
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 --level error |
ty exits non-zero only on errors by default; use --error-on-warning to fail on warnings |
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.
Pointing to the Python environment
ty needs to find installed packages to resolve imports:
# Point to a virtualenv or interpreter
uvx ty check . --python .venv
# Or let ty auto-detect from an active virtualenv or a .venv in the project root
uvx ty check .For more details on how ty discovers and resolves modules, see the module discovery documentation.
Output formats
# Default verbose output
uvx ty check .
# Concise one-line-per-error output
uvx ty check . --output-format concise
# GitHub Actions annotations
uvx ty check . --output-format github
# GitLab Code Quality format
uvx ty check . --output-format gitlabAutomated 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 .This is useful when migrating large codebases where fixing all errors at once is not practical. It lets you prevent new type errors while gradually fixing existing ones.
Warning
Use --add-ignore thoughtfully. Suppressed errors should be addressed over time. Consider creating issues to track removing these suppressions.
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 doesn’t have per-path overrides in the same way mypy does, but uses include/exclude patterns.
ty supports path-based overrides:
[[tool.ty.overrides]]
include = ["legacy/**"]
[tool.ty.overrides.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 patterns
Common patterns found in production projects:
Pattern 1: pip-installed Pyright
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 .Pattern 2: npm-installed Pyright
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm install -g pyright
- run: pyrightPattern 3: Warning-tolerant CI
- run: pyright . --level error
# Only fails on errors, warnings are ignoredMigrating 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@v6
- 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. Warnings are reported but do not fail the run. Use --error-on-warning to fail on warnings (equivalent to Pyright’s --level error being the opposite default).
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 is stricter about Optional types. Pyright often allows implicit Optional.
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.
Error code mapping reference
This table maps common Pyright diagnostic rules to their ty rule equivalents.
| Pyright Rule | ty Rule | Description |
|---|---|---|
reportMissingImports |
unresolved-import | Module not found |
reportMissingTypeStubs |
unresolved-import | No separate missing-stubs rule; covered by unresolved-import |
reportArgumentType |
invalid-argument-type | Wrong argument type |
reportReturnType |
invalid-return-type | Wrong return type |
reportAssignmentType |
invalid-assignment | Wrong assignment type |
reportAttributeAccessIssue |
unresolved-attribute | Unknown attribute |
reportOptionalMemberAccess |
possibly-missing-attribute | Attribute may be None |
reportOptionalSubscript |
non-subscriptable | Subscript on None |
reportOptionalIterable |
not-iterable | Iterate over None |
reportOperatorIssue |
unsupported-operator | Invalid operator |
reportCallIssue |
call-non-callable / missing-argument / unknown-argument / invalid-argument-type | Call-related issues |
reportIndexIssue |
non-subscriptable | Index-related issues |
reportInvalidTypeArguments |
invalid-type-arguments | Generic type args |
reportGeneralTypeIssues |
(multiple rules) | Aggregate setting |
All Pyright diagnostic rules are documented in the Pyright configuration reference.
Step-by-step migration checklist
Phase 1: Assessment
-
Identify the current Pyright configuration
- Locate
pyrightconfig.jsonor[tool.pyright]in pyproject.toml - Note all options, especially
typeCheckingMode,include/exclude, and per-option settings
- Locate
-
Identify critical dependencies
- Platform-specific libraries without stubs
- Libraries with complex type behaviors (pandas, numpy)
-
Run baseline Pyright
- Document current error/warning counts
- Ensure Pyright passes in CI before starting migration
Phase 2: Initial ty setup
-
Create ty configuration file
- Create
ty.tomlor add[tool.ty]to pyproject.toml - Start minimal:
[environment] python = ".venv"
- Create
-
Run first ty check
uvx ty check your_package/- Document error count and types
- Compare to Pyright baseline
-
Consider using
--add-ignorefor baselineuvx ty check your_package/ --add-ignore- Automatically suppresses all current errors
- Allows gradual migration while preventing new errors
-
Add source configuration
- Match Pyright’s
includepatterns (convert to gitignore-style patterns) - Match Pyright’s
excludepatterns
[src] include = ["src"] exclude = ["tests"] - Match Pyright’s
Phase 3: Configuration tuning
-
Map Pyright options to ty
- Use the configuration mapping table above
- Accept that some options don’t have equivalents
-
Handle missing imports
- For platform-specific libraries:
[rules] unresolved-import = "ignore" unresolved-reference = "ignore"
- For platform-specific libraries:
-
Match warning levels
- If Pyright uses warnings, set ty rules to “warn”
- ty exits non-zero only on errors by default; use
--error-on-warningif you want warnings to fail
Phase 4: CI integration
-
Add ty to CI (non-blocking)
- run: uvx ty check src/ --python .venv || true- Collect data on ty findings
-
Run both checkers in parallel
- Keep Pyright as the source of truth
- Use ty for additional insights
-
Evaluate findings
- Are ty’s additional findings valuable?
- Are differences acceptable?
Phase 5: Transition decision
-
Compare checker behavior
- Review unique findings from each checker
- Assess false positive rates
-
Decide on transition strategy
- Option A: Replace Pyright with ty (if ty meets needs)
- Option B: Keep both checkers (different purposes)
- Option C: Stay with Pyright (if certain features are required)
-
Update documentation
- Document which checker is authoritative
- Update contributing guidelines
Phase 6: Finalization (if replacing Pyright)
-
Make ty blocking in CI
- run: uvx ty check src/ --python .venv -
Remove Pyright configuration
- Delete
pyrightconfig.json - Remove
[tool.pyright]from pyproject.toml - Remove Pyright from dependencies
- Delete
-
Update developer tooling
- IDE integrations (see editor integration guide)
- Pre-commit hooks