Quickstart¶
Install¶
pip install stubpy
# or
uv add stubpy
Requires Python 3.10+.
Single file¶
stubpy path/to/module.py # writes module.pyi alongside source
stubpy path/to/module.py -o stubs/ # write to custom directory
stubpy path/to/module.py --print # also print to stdout
Multiple files¶
Pass several paths in one invocation — stubs are written alongside each source:
stubpy a.py b.py c.py
stubpy "src/*.py" # quoted glob (Python-level expansion)
stubpy module.py mypackage/ # mix files and directories
stubpy "**/*.py" # recursive glob
Whole package¶
stubpy mypackage/ # stubs written alongside source files
stubpy mypackage/ -o stubs/ # custom output directory
stubpy mypackage/ --union-style legacy # emit Optional[X] instead of X | None
Configuration file¶
Place a stubpy.toml in the project root (or add [tool.stubpy] to
pyproject.toml):
# stubpy.toml (or [tool.stubpy] in pyproject.toml)
include_private = false
union_style = "modern" # "modern" (X | None) | "legacy" (Optional[X])
alias_style = "compatible" # "compatible" | "pep695" | "auto"
execution_mode = "runtime" # "runtime" | "ast_only" | "auto"
include_docstrings = false # embed docstrings in stub bodies
infer_types = false # infer types from docstrings as # type: comments
incremental = false # preserve manual edits via begin/end markers
respect_all = true # false = stub all symbols even when __all__ defined
output_dir = "stubs"
exclude = ["**/test_*.py", "docs/conf.py"]
All flags have CLI equivalents; CLI flags override file values.
Python API¶
from stubpy import generate_stub, generate_package, StubConfig, StubContext
# Single file — returns stub content as a string
content = generate_stub("mymodule.py")
content = generate_stub("mymodule.py", "stubs/mymodule.pyi")
# Whole package
result = generate_package("mypackage/", "stubs/")
print(result.summary()) # "Generated 12 stubs, 0 failed."
# Custom config
cfg = StubConfig(
include_private = True,
union_style = "legacy",
include_docstrings = True,
)
ctx = StubContext(config=cfg)
content = generate_stub("mymodule.py", ctx=ctx)
CLI reference¶
usage: stubpy [-h] [-o PATH] [--print] [--include-private]
[--include-docstrings] [--verbose] [--strict]
[--union-style {modern,legacy}]
[--alias-style {compatible,pep695,auto}]
[--execution-mode {runtime,ast_only,auto}]
[--no-config]
path [path ...]
positional arguments:
path One or more .py files, package directories, or
quoted glob patterns (e.g. "src/*.py").
optional arguments:
-o PATH Output .pyi path (file) or root directory (package).
Ignored when multiple paths are given.
--print Print stub to stdout (single-file mode only).
--include-private Include symbols starting with _.
--include-docstrings Embed docstrings in stub bodies instead of ...
--verbose Print INFO/WARNING/ERROR diagnostics to stderr.
--strict Exit 1 if any ERROR diagnostic was recorded.
--union-style STYLE modern (X | None, default) or legacy (Optional[X]).
--alias-style STYLE compatible (default), pep695, or auto.
--execution-mode MODE runtime (default), ast_only, or auto.
--no-config Ignore stubpy.toml / pyproject.toml.
Type alias style¶
Use --alias-style to control how type alias declarations are emitted:
stubpy mymodule.py --alias-style compatible # Name: TypeAlias = rhs (3.10+)
stubpy mymodule.py --alias-style pep695 # type Name = rhs (3.12+)
stubpy mymodule.py --alias-style auto # selects based on runtime Python
Extending the annotation dispatcher¶
Use register_annotation_handler() to teach stubpy
how to render custom annotation types:
from stubpy.annotations import register_annotation_handler, annotation_to_str
from mylib import Validated
@register_annotation_handler(lambda a: isinstance(a, Validated))
def _handle_validated(annotation, ctx):
inner = annotation_to_str(annotation.inner_type, ctx)
return f"Validated[{inner}]"
Handlers registered this way are appended after all built-in handlers and are called for any annotation that no built-in handler matches.