Skip to content

Commit c3b6749

Browse files
miettalclaude
andcommitted
Add --list-install-types flag to print missing stub packages without installing
Implements the feature requested in #18156. The new flag prints detected missing stub packages one per line to stdout, enabling pipe-based workflows like: mypy --list-install-types src/ | xargs uv pip install Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4c68d1b commit c3b6749

2 files changed

Lines changed: 52 additions & 5 deletions

File tree

mypy/main.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ def main(
117117
options,
118118
)
119119

120+
if options.install_types and options.list_install_types:
121+
fail(
122+
"error: --install-types and --list-install-types cannot be used together",
123+
stderr,
124+
options,
125+
)
126+
120127
if options.install_types and (stdout is not sys.stdout or stderr is not sys.stderr):
121128
# Since --install-types performs user input, we want regular stdout and stderr.
122129
fail("error: --install-types not supported in this mode of running mypy", stderr, options)
@@ -136,11 +143,26 @@ def main(
136143
options,
137144
)
138145

146+
if options.list_install_types and not options.incremental:
147+
fail(
148+
"error: --list-install-types not supported with incremental mode disabled",
149+
stderr,
150+
options,
151+
)
152+
139153
if options.install_types and not sources:
140154
install_types(formatter, options, non_interactive=options.non_interactive)
141155
return
142156

143-
res, messages, blockers = run_build(sources, options, fscache, t0, stdout, stderr)
157+
if options.list_install_types and not sources:
158+
list_install_types(options, out=stdout)
159+
return
160+
161+
# When --list-install-types is active, route all mypy diagnostic output to
162+
# stderr so that only the package names end up on stdout (enabling pipe usage
163+
# like: mypy --list-install-types src/ | xargs uv pip install).
164+
build_stdout = stderr if options.list_install_types else stdout
165+
res, messages, blockers = run_build(sources, options, fscache, t0, build_stdout, stderr)
144166

145167
if options.non_interactive:
146168
missing_pkgs = read_types_packages_to_install(options.cache_dir, after_run=True)
@@ -166,11 +188,14 @@ def main(
166188
summary = formatter.format_error(
167189
n_errors, n_files, len(sources), blockers=blockers, use_color=options.color_output
168190
)
169-
stdout.write(summary + "\n")
191+
build_stdout.write(summary + "\n")
170192
# Only notes should also output success
171193
elif not messages or n_notes == len(messages):
172-
stdout.write(formatter.format_success(len(sources), options.color_output) + "\n")
173-
stdout.flush()
194+
build_stdout.write(formatter.format_success(len(sources), options.color_output) + "\n")
195+
build_stdout.flush()
196+
197+
if options.list_install_types:
198+
list_install_types(options, after_run=True, out=stdout)
174199

175200
if options.install_types and not options.non_interactive:
176201
result = install_types(formatter, options, after_run=True, non_interactive=False)
@@ -1228,6 +1253,13 @@ def add_invertible_flag(
12281253
group=misc_group,
12291254
inverse="--interactive",
12301255
)
1256+
add_invertible_flag(
1257+
"--list-install-types",
1258+
default=False,
1259+
strict_flag=False,
1260+
help="Print detected missing library stub packages (one per line) without installing",
1261+
group=misc_group,
1262+
)
12311263

12321264
if server_options:
12331265
misc_group.add_argument(
@@ -1479,7 +1511,7 @@ def set_strict_flags() -> None:
14791511
special_opts.files,
14801512
]
14811513
)
1482-
if code_methods == 0 and not options.install_types:
1514+
if code_methods == 0 and not options.install_types and not options.list_install_types:
14831515
parser.error("Missing target module, package, files, or command.")
14841516
elif code_methods > 1:
14851517
parser.error("May only specify one of: module/package, files, or command.")
@@ -1705,6 +1737,19 @@ def read_types_packages_to_install(cache_dir: str, after_run: bool) -> list[str]
17051737
return [line.strip() for line in f]
17061738

17071739

1740+
def list_install_types(options: Options, *, after_run: bool = False, out: TextIO) -> None:
1741+
"""Print missing stub packages one per line to *out*.
1742+
1743+
Callers should pass stdout here; mypy diagnostic output is routed to stderr
1744+
by the caller so that only package names reach stdout, making pipe usage
1745+
possible: mypy --list-install-types src/ | xargs uv pip install
1746+
"""
1747+
packages = read_types_packages_to_install(options.cache_dir, after_run)
1748+
for pkg in packages:
1749+
out.write(pkg + "\n")
1750+
out.flush()
1751+
1752+
17081753
def install_types(
17091754
formatter: util.FancyFormatter,
17101755
options: Options,

mypy/options.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,8 @@ def __init__(self) -> None:
409409
# Install missing stub packages in non-interactive mode (don't prompt for
410410
# confirmation, and don't show any errors)
411411
self.non_interactive = False
412+
# List missing stub packages to stdout (instead of installing them)
413+
self.list_install_types = False
412414
# When we encounter errors that may cause many additional errors,
413415
# skip most errors after this many messages have been reported.
414416
# -1 means unlimited.

0 commit comments

Comments
 (0)