Pyright: Microsoft's Fast Python Type Checker
Python's type system — introduced gradually via PEP 484 (2014) and subsequent PEPs — is optional. You can annotate types and get static checking, or ignore them entirely. For projects where you want static checking, two tools dominate: mypy (the reference implementation) and Pyright (Microsoft's type checker, written in TypeScript).
Pyright is faster, integrates natively with VS Code via Pylance, and supports stricter type checking modes. If you're starting a new project or frustrated with mypy's performance on large codebases, Pyright is worth evaluating.
Why Pyright vs. mypy
Speed: Pyright runs significantly faster than mypy, especially with incremental checking. On large codebases, this difference is noticeable during development.
VS Code integration: Pyright powers Pylance, the Python extension for VS Code. If you use VS Code, Pyright is already analyzing your code.
Strict mode: Pyright's strict mode is more aggressive than mypy's --strict, catching additional issues like missing return type annotations and untyped function calls.
TypedDict and dataclasses: Pyright's handling of TypedDict, dataclasses, and protocols is generally considered more accurate than mypy's.
Standard library stubs: Pyright uses its own type stubs for the standard library (typeshed-client) and updates them regularly.
The tradeoff: mypy has been the reference implementation for longer and is more widely used in CI pipelines. Third-party type stub packages sometimes target mypy specifically. For new projects, either is a good choice; Pyright is often preferred for VS Code users.
Installation
With pip:
pip install pyright
With npm (the original distribution):
npm install -g pyright
With uv:
uv tool install pyright
In a project (recommended for CI):
# pyproject.toml approach
[tool.pyright]
venvPath = "."
venv = ".venv"
pip install pyright # or uv add --dev pyright
Running Pyright
# Check all files in current directory
pyright
# Check a specific file
pyright src/main.py
# Use strict mode
pyright --strict
# Output as JSON (for CI parsing)
pyright --outputjson
Configuration: pyrightconfig.json
Create pyrightconfig.json in your project root:
{
"pythonVersion": "3.12",
"pythonPlatform": "Linux",
"venvPath": ".",
"venv": ".venv",
"include": ["src"],
"exclude": ["**/__pycache__", ".venv"],
"reportMissingImports": true,
"reportMissingTypeStubs": false
}
Or configure via pyproject.toml:
[tool.pyright]
pythonVersion = "3.12"
venvPath = "."
venv = ".venv"
include = ["src"]
Strictness Levels
Pyright has three built-in strictness settings:
off (default): Minimal checking. Only reports syntax errors and obvious issues.
basic: Standard checking. Catches type mismatches, undefined attributes, missing imports, and similar errors.
strict: Maximum checking. Adds:
- Require type annotations on all functions
- No
Unknowntypes allowed - No implicit
Optional(useT | Noneexplicitly) - Report all untyped function calls
For a new project, start with basic and migrate to strict over time:
{
"typeCheckingMode": "basic"
}
Or per-file in Python using # pyright: strict:
# pyright: strict
from typing import Optional
def greet(name: str) -> str: # annotation required in strict mode
return f"Hello, {name}"
Common Type Errors and Fixes
Missing return type annotation (strict mode):
# Error: Return type is unknown
def process(data: list[str]):
return [s.upper() for s in data]
# Fix:
def process(data: list[str]) -> list[str]:
return [s.upper() for s in data]
Optional not handled:
import os
# Error: "str | None" is not assignable to "str"
path: str = os.environ.get("PATH")
# Fix 1: assert not None
path = os.environ.get("PATH")
assert path is not None
# Fix 2: provide default
path = os.environ.get("PATH", "/usr/bin")
# Fix 3: use Optional
from typing import Optional
path: Optional[str] = os.environ.get("PATH")
TypedDict usage:
from typing import TypedDict
class UserConfig(TypedDict):
name: str
email: str
age: int
def process_user(config: UserConfig) -> None:
print(config["name"])
# Error: missing required key
bad_config: UserConfig = {"name": "Alice"} # missing "email" and "age"
Union type narrowing:
def handle(value: str | int) -> str:
# Error: int doesn't have .upper()
return value.upper()
# Fix: narrow the type first
def handle(value: str | int) -> str:
if isinstance(value, str):
return value.upper()
return str(value)
Protocol usage:
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
class Circle:
def draw(self) -> None:
print("Drawing circle")
class Square:
def draw(self) -> None:
print("Drawing square")
def render(shape: Drawable) -> None:
shape.draw()
# Works — Circle and Square satisfy Drawable protocol
render(Circle())
render(Square())
Pyright in VS Code (Pylance)
If you use the Python extension for VS Code, Pylance is powered by Pyright. Your pyrightconfig.json is respected automatically.
Configure Pylance's type checking mode in VS Code settings:
{
"python.analysis.typeCheckingMode": "basic"
}
Settings: off, basic, standard, or strict.
Pylance adds hover types, Go to Definition, and Find References on top of Pyright's type checking.
CI Integration
GitHub Actions:
- name: Type check
run: |
pip install pyright
pyright src/
Pre-commit hook:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.390
hooks:
- id: pyright
Exit codes: Pyright exits with 0 (success) or 1 (errors found). Use this to fail CI on type errors.
Pyright vs. mypy Feature Comparison
| Feature | Pyright | mypy |
|---|---|---|
| Speed | Fast | Slower on large codebases |
| Incremental checking | Yes | Yes (with cache) |
| VS Code integration | Native (Pylance) | Via mypy plugin |
| Strict mode | More aggressive | Less aggressive |
| TypedDict | Excellent | Good |
| dataclasses | Excellent | Good |
| Protocols | Full support | Full support |
| Plugins | Not supported | Plugin ecosystem |
| Configuration | pyrightconfig.json / pyproject.toml | mypy.ini / pyproject.toml |
| Python version | Ships via pip/npm | Ships via pip |
mypy's plugin system is its unique advantage — some third-party libraries (Django, SQLAlchemy) provide mypy plugins for accurate type inference that Pyright can't replicate without vendor support.
Gradual Adoption
You don't need to type-annotate everything at once. Pyright works with partially-typed code:
- Run
pyrighton existing code — errors will appear - Add
# type: ignorecomments to suppress false positives in third-party code - Add type annotations to new code you write
- Fix existing annotations module by module
Per-file suppression:
# pyright: ignore[reportMissingImports]
import untyped_library
Pyright is a pragmatic choice for Python projects: faster than mypy, excellent VS Code integration, and aggressive enough in strict mode to catch real bugs while remaining practical in basic mode for existing codebases.