Creating Type Stubs for Type Checking Without Installing Dependencies
The Problem
When type-checking Python code, tools like Pyright need to import all dependencies to understand function signatures. But some packages have heavy dependencies (NetCDF libraries, scientific computing stacks, databases) that you don’t want to install just for type checking.
The Solution: Manual Type Stubs
You can create a fake package in your virtual environment that provides type information without any implementation.
How It Works
Python’s import system doesn’t distinguish between packages installed via pip
and packages manually created in site-packages. Both are treated identically.
# These are equivalent to Python's import system:
pip install phase-gap # Real package
mkdir -p .venv/lib/python3.X/site-packages/phase_gap # Fake stub
Example Implementation
#!/bin/bash
# Create path to stub package
STUB_DIR=".venv/lib/python$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')/site-packages/phase_gap"
mkdir -p "$STUB_DIR"
# Create minimal __init__.py with type signatures only
cat > "$STUB_DIR/__init__.py" << 'EOF'
"""Type stubs for phase-gap package (type checking only)."""
import pandas as pd
def run_compute_job(
netcdf_file: str,
export_csv: bool = False,
output_filename: str | None = None,
verbose: bool = False
) -> pd.DataFrame:
"""Process NetCDF tidal data."""
... # Ellipsis = stub implementation
def run_analysis(
external_tidal_data: pd.DataFrame,
external_profile_data: pd.DataFrame,
output_path: str
) -> int:
"""Run analysis. Returns error code (0 = success)."""
...
EOF
Key Points
Include every parameter with its type annotation and the return type. That’s
the whole point of the file. Use ... for the body, which is the standard
Python way to mark a stub. Import only what the type hints need (pandas here,
for pd.DataFrame). The code can’t run, but Pyright can still validate against
it.
What Pyright Sees
When analysing your code:
from phase_gap import run_compute_job
tidal_df = run_compute_job(netcdf_file="foo.nc")
Pyright:
- Looks in
site-packages/phase_gap/__init__.py - Reads the stub signature
- Validates that
netcdf_file="foo.nc"matchesnetcdf_file: str - Infers
tidal_dfhas typepd.DataFrame - Never tries to execute the
...implementation
Benefits
Writing a text file is faster than installing packages, and it skips the heavy
dependencies (C libraries, databases) entirely. Pyright still catches type
errors. The ... makes it obvious to a reader that the module exists only for
type checking, and the script that generates it can go into version control.
Alternative: .pyi Files
The official Python approach is .pyi stub files:
# phase_gap.pyi
import pandas as pd
def run_compute_job(
netcdf_file: str,
export_csv: bool = ...,
output_filename: str | None = ...,
verbose: bool = ...
) -> pd.DataFrame: ...
Both approaches work, but creating a minimal Python module is simpler and
doesn’t require understanding the .pyi stub file format.
Real-World Use Case
In our project, phase-gap has dependencies on:
- NetCDF4 (C library bindings)
- XBeach simulation tools
- Matplotlib, NumPy, Pandas
- Various scientific computing libraries
For type checking analysis.py, we only need to know:
- What functions exist
- What parameters they accept
- What they return
Creating a stub lets us type-check without installing any of the heavy dependencies.