Skip to content

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.

ty is currently in beta. While actively developed, it’s still missing some features and may not be ready for full production adoption. This guide helps evaluate readiness and plan for migration.

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 typeCheckingMode presets (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 gitlab

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 .

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: pyright

Pattern 3: Warning-tolerant CI

- run: pyright . --level error
# Only fails on errors, warnings are ignored

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@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 github

ty 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 || true

Handling 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 imports

Or 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 differ

Why 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.json or [tool.pyright] in pyproject.toml
    • Note all options, especially typeCheckingMode, include/exclude, and per-option settings
  • 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.toml or add [tool.ty] to pyproject.toml
    • Start minimal:
      [environment]
      python = ".venv"
  • Run first ty check

    uvx ty check your_package/
    • Document error count and types
    • Compare to Pyright baseline
  • Consider using --add-ignore for baseline

    uvx ty check your_package/ --add-ignore
    • Automatically suppresses all current errors
    • Allows gradual migration while preventing new errors
  • Add source configuration

    • Match Pyright’s include patterns (convert to gitignore-style patterns)
    • Match Pyright’s exclude patterns
    [src]
    include = ["src"]
    exclude = ["tests"]

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"
  • Match warning levels

    • If Pyright uses warnings, set ty rules to “warn”
    • ty exits non-zero only on errors by default; use --error-on-warning if 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
  • Update developer tooling

Related resources

Last updated on

Please submit corrections and feedback...