# How to migrate from pyright to ty


[ty](https://pydevtools.com/handbook/reference/ty.md) is a Python type checker developed by Astral, the creators of [uv](https://pydevtools.com/handbook/reference/uv.md) and [Ruff](https://pydevtools.com/handbook/reference/ruff.md), now part of OpenAI. It ships as a single binary with no runtime dependencies, which eliminates [pyright](https://pydevtools.com/handbook/reference/pyright.md)'s Node.js requirement.

{{< callout type="warning" >}}
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.
{{< /callout >}}

## Installation and basic usage

### Installing ty

ty is distributed as a standalone binary. The easiest way to use it is via [uvx](https://pydevtools.com/handbook/reference/uv.md):

```bash
# 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](https://docs.astral.sh/ty/installation/).

### 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](https://docs.astral.sh/ty/reference/cli/).

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

```bash
# 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](https://docs.astral.sh/ty/configuration/).

### 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):
```json
{
  "include": ["src"],
  "exclude": ["tests"],
  "pythonVersion": "3.11",
  "reportMissingImports": false,
  "reportMissingTypeStubs": false,
  "reportArgumentType": "warning",
  "reportReturnType": "warning",
  "reportOptionalMemberAccess": "warning",
  "typeCheckingMode": "basic"
}
```

ty ([pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md)):
```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`](https://docs.astral.sh/ty/reference/configuration/#error-on-warning) 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:

```toml
[[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:

```yaml
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:
```yaml
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:
```yaml
- 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:

```yaml
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`](https://docs.astral.sh/ty/reference/rules/#unused-ignore-comment), producing lines like:

```python
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:
```python
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:
```toml
[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:
```python
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:
```toml
[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:
```python
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):
```python
def generate_list_table(title: str | None = None) -> str:
```

Solution - Configuration:
```toml
[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:
```python
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:
```toml
[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:
```toml
[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:
```python
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:
```toml
[tool.ty.rules]
unsupported-operator = "warn"
non-subscriptable = "warn"
```

Or add proper None checks in code.

## Related resources

- [ty reference](https://pydevtools.com/handbook/reference/ty.md)
- [pyright reference](https://pydevtools.com/handbook/reference/pyright.md)
- [How to migrate from mypy to ty](https://pydevtools.com/handbook/how-to/how-to-migrate-from-mypy-to-ty.md)
- [How to try the ty type checker](https://pydevtools.com/handbook/how-to/how-to-try-the-ty-type-checker.md)
- [uv reference](https://pydevtools.com/handbook/reference/uv.md)
- [Ruff reference](https://pydevtools.com/handbook/reference/ruff.md)
