Pyright Type Checker
Why Type Checking? (Tier 1: Motivation)
Python is dynamically typed, meaning you can write:
x = "hello"
x = 42
x = [1, 2, 3]
Same variable, three different types. Python doesn't complain—it just changes.
But this causes bugs:
def greet(name):
return f"Hello, {name}"
result = greet(42) # Oops! Passing a number instead of a string
Python runs this fine until you expect result to be a string:
upper = result.upper() # Crash: int has no method .upper()
Problem: The bug is in greet(42) call, but the crash is at .upper(). You waste time debugging the wrong place.
Solution: Type hints tell Python what types to expect:
def greet(name: str) -> str:
return f"Hello, {name}"
result = greet(42) # Type checker: ERROR! 42 is not a str
Now the error is caught immediately, before running code.
Benefit: Catch 30-50% of bugs statically (without running code). That's huge.
Source: Verified in intelligence/001-verified-tool-documentation.md
What Are Type Hints? (Tier 1: Learn Syntax)
Type hints are annotations that tell Python what types variables/functions expect.
Function Parameter Types
def add(x: int, y: int) -> int:
"""Add two integers and return the result."""
return x + y
Read as:
x: int— Parameterxshould be an integery: int— Parameteryshould be an integer-> int— Function returns an integer
Python 3.13+ Modern Syntax
Python 3.13 introduced new syntax for optional types using | instead of Union:
def process(data: str | None) -> int:
"""Accept string or None, return integer."""
if data is None:
return 0
return len(data)
Read as:
data: str | None— Accept string or None (called "union type")-> int— Returns an integer
Older syntax (Python 3.9-3.12) used Union[str, None]. We use 3.13+ syntax because it's cleaner.
Note: Type hints are optional syntax. Python still runs without them. They're for tools like Pyright to catch errors.
Installing Pyright (Tier 1: Direct Command)
Just like Ruff, install via uv:
uv add pyright --dev
Verify installation:
uv run pyright --version
Expected output: pyright 1.1.407 (or similar version)
Running Pyright (Tier 1: Direct Command)
Let's catch a type error!
Create a file types.py:
def greet(name: str) -> str:
return f"Hello, {name}"
result = greet(42) # ERROR: 42 is not a str
Run Pyright:
uv run pyright types.py
Expected output:
/path/to/types.py:4:21 - error: Argument of type "Literal[42]" cannot be assigned to parameter "name" of type "str" in function "greet"
"Literal[42]" is not assignable to "str"
1 error, 0 warnings
Read the error:
- Line 4, column 21: The error location
"Literal[42]"cannot be assigned to"str": You passed an integer where a string was expected1 error: Pyright found one type problem
Fix the code:
result = greet("Alice") # Correct: pass a string
Run Pyright again:
uv run pyright types.py
Expected output:
0 errors, 0 warnings, 0 informations
Success! No type errors.
Note: This code demonstrates type checking, not Python programming. You'll learn more about functions and types in Chapter 13.
Source: Verified in intelligence/001-verified-tool-documentation.md
Type Checking Modes (Tier 1: Understand Options)
Pyright has four modes controlling how strict type checking is:
| Mode | Strictness | Use Case |
|---|---|---|
| off | No checking | Legacy code (not recommended) |
| basic | Standard checking | Most projects (default) |
| standard | Stricter checking | Type-aware projects |
| strict | Maximum safety | Type-first projects |
Basic mode (default) is usually right. Strict mode catches more potential issues but requires more type hints.
Configure in pyproject.toml:
[tool.pyright]
typeCheckingMode = "standard" # Change to "strict" for more checking
pythonVersion = "3.13"
Common Type Errors (Tier 1: Learn Patterns)
Here are errors Pyright catches:
Error 1: Type Mismatch
def add(x: int, y: int) -> int:
return x + y
result = add("5", "10") # ERROR: strings are not ints
Error 2: Return Type Wrong
def get_count() -> int:
return "five" # ERROR: returns str, not int
Error 3: Optional Handling
def process(data: str | None) -> int:
return len(data) # ERROR: data might be None; len(None) crashes
Fix by checking for None:
def process(data: str | None) -> int:
return len(data) if data else 0
Tier 2: Configure Pyright in pyproject.toml
Tell your AI:
I want to configure Pyright for my Python project.
I want:
- Standard type checking (not too strict)
- Python 3.13
- Report missing imports as errors
- Report unknown variable types as warnings
Generate the [tool.pyright] section for pyproject.toml.
AI generates:
[tool.pyright]
typeCheckingMode = "standard"
pythonVersion = "3.13"
reportMissingImports = "error"
reportUnknownVariableType = "warning"
Copy-paste into your pyproject.toml and run:
uv run pyright
Pyright now uses your configuration.
Source: Verified in intelligence/001-verified-tool-documentation.md
Pyright + Zed Integration (Tier 2: Editor Display)
You'll configure Zed to show Pyright type errors inline (next lesson, Lesson 11).
For now, just know: Pyright can run in your editor, showing errors as you type—no need to open terminal.
Type Hints vs. Tests (Tier 1: Understand Relationship)
Type hints and tests are complementary:
| Tool | Catches | Doesn't Catch |
|---|---|---|
| Pyright (type checking) | Type mismatches | Logic errors, runtime failures |
| Tests | Logic errors, edge cases | Type mismatches (without hints) |
| Together | Most bugs | Edge cases you didn't think of |
Good practice: Use both.
Try With AI
Use your AI companion for these exercises.
Prompt 1: Install & Run (Tier 1 — Direct)
1. Install Pyright in a uv project: `uv add pyright --dev`
2. Create a Python file with a function that has type hints
3. Add a function call with the wrong type (string instead of int)
4. Run `uv run pyright` and show me the error
Show me the exact commands and the error message.
Expected outcome: Pyright installed, catches type error, you understand error message.
Prompt 2: Understand Type Hints (Tier 1 — Learn)
Show me Python 3.13+ type hint syntax for:
1. A function that accepts two integers and returns a string
2. A function that accepts an optional string (string or None)
3. A function that accepts a list of integers
For each, show an example function with realistic code.
Expected outcome: Understand modern Python 3.13+ type hint syntax (using | for unions).
Prompt 3: Configure Pyright (Tier 2 — AI Generates)
Configure Pyright in my pyproject.toml with:
- Standard type checking mode (not strict)
- Python 3.13 target
- Report missing imports as warnings (not errors)
Show me the [tool.pyright] section.
Expected outcome: Configuration snippet; you add it to your pyproject.toml.
Red Flags to Watch
Problem: "Pyright says type error but my code runs fine"
- What it means: Type hints catch static errors; not all runtime issues
- What to do: Understand: Pyright prevents bugs early, but tests still catch logic errors
- Normal: This is expected behavior; type hints are a safety net, not a guarantee
Problem: "Pyright says str | None is invalid syntax"
- What it means: You're using Python 3.12 or older;
|syntax is Python 3.13+ only - What to do: Upgrade Python to 3.13 (
uv.lockshould handle this), or useUnion[str, None]syntax - Check version: Run
python --version
Problem: "Pyright shows 'LSP not connected' in Zed"
- What it means: Zed can't find Pyright LSP
- What to do: Verify Pyright installed (
uv add pyright --dev); restart Zed - Not urgent: You can still run
uv run pyrightfrom terminal; inline checking comes later