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.

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 github

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

Related resources

Last updated on