77import platform
88import subprocess
99import sys
10+ import tempfile
1011import time
1112from collections import defaultdict
1213from collections .abc import Sequence
2930from mypy .errors import CompileError
3031from mypy .find_sources import InvalidSourceList , create_source_list
3132from mypy .fscache import FileSystemCache
33+ from mypy .installtypes import make_runtime_constraints , read_locked_packages , resolve_stub_packages_from_lock
3234from mypy .modulefinder import (
3335 BuildSource ,
3436 FindModuleCache ,
@@ -124,6 +126,22 @@ def main(
124126 if options .non_interactive and not options .install_types :
125127 fail ("error: --non-interactive is only supported with --install-types" , stderr , options )
126128
129+ if options .install_types_from_pylock is not None and not options .install_types :
130+ fail (
131+ "error: --install-types-from-pylock is only supported with --install-types" ,
132+ stderr ,
133+ options ,
134+ )
135+
136+ if options .install_types_from_pylock is not None and not os .path .isfile (
137+ options .install_types_from_pylock
138+ ):
139+ fail (
140+ f"error: Can't find lock file '{ options .install_types_from_pylock } '" ,
141+ stderr ,
142+ options ,
143+ )
144+
127145 if options .install_types and not options .incremental :
128146 fail (
129147 "error: --install-types not supported with incremental mode disabled" , stderr , options
@@ -137,9 +155,22 @@ def main(
137155 )
138156
139157 if options .install_types and not sources :
140- install_types (formatter , options , non_interactive = options .non_interactive )
158+ install_types (
159+ formatter ,
160+ options ,
161+ non_interactive = options .non_interactive ,
162+ pylock_path = options .install_types_from_pylock ,
163+ )
141164 return
142165
166+ if options .install_types and options .install_types_from_pylock :
167+ install_types (
168+ formatter ,
169+ options ,
170+ non_interactive = options .non_interactive ,
171+ pylock_path = options .install_types_from_pylock ,
172+ )
173+
143174 res , messages , blockers = run_build (sources , options , fscache , t0 , stdout , stderr )
144175
145176 if options .non_interactive :
@@ -1204,6 +1235,15 @@ def add_invertible_flag(
12041235 dest = "special-opts:find_occurrences" ,
12051236 help = "Print out all usages of a class member (experimental)" ,
12061237 )
1238+ misc_group .add_argument (
1239+ "--install-types-from-pylock" ,
1240+ metavar = "FILE" ,
1241+ dest = "install_types_from_pylock" ,
1242+ help = (
1243+ "With --install-types, read packages from a pylock TOML file and "
1244+ "install matching known stub packages"
1245+ ),
1246+ )
12071247 misc_group .add_argument (
12081248 "--scripts-are-modules" ,
12091249 action = "store_true" ,
@@ -1711,24 +1751,48 @@ def install_types(
17111751 * ,
17121752 after_run : bool = False ,
17131753 non_interactive : bool = False ,
1754+ pylock_path : str | None = None ,
17141755) -> bool :
17151756 """Install stub packages using pip if some missing stubs were detected."""
1716- packages = read_types_packages_to_install (options .cache_dir , after_run )
1757+ constraints : list [str ] = []
1758+ if pylock_path is None :
1759+ packages = read_types_packages_to_install (options .cache_dir , after_run )
1760+ else :
1761+ locked = read_locked_packages (pylock_path )
1762+ packages = resolve_stub_packages_from_lock (locked )
1763+ constraints = make_runtime_constraints (locked )
1764+
17171765 if not packages :
17181766 # If there are no missing stubs, generate no output.
17191767 return False
17201768 if after_run and not non_interactive :
17211769 print ()
17221770 print ("Installing missing stub packages:" )
17231771 assert options .python_executable , "Python executable required to install types"
1724- cmd = [options .python_executable , "-m" , "pip" , "install" ] + packages
1772+ cmd = [options .python_executable , "-m" , "pip" , "install" ]
1773+ constraints_file = None
1774+ if pylock_path is not None :
1775+ cmd .append ("--no-deps" )
1776+ if pylock_path is not None and constraints :
1777+ constraints_file = tempfile .NamedTemporaryFile ("w" , delete = False , encoding = "utf-8" )
1778+ with constraints_file as f :
1779+ f .write ("\n " .join (constraints ))
1780+ f .write ("\n " )
1781+ cmd += ["--constraint" , constraints_file .name ]
1782+ cmd += packages
17251783 print (formatter .style (" " .join (cmd ), "none" , bold = True ))
17261784 print ()
17271785 if not non_interactive :
17281786 x = input ("Install? [yN] " )
17291787 if not x .strip () or not x .lower ().startswith ("y" ):
17301788 print (formatter .style ("mypy: Skipping installation" , "red" , bold = True ))
1789+ if constraints_file is not None :
1790+ os .unlink (constraints_file .name )
17311791 sys .exit (2 )
17321792 print ()
1733- subprocess .run (cmd )
1793+ try :
1794+ subprocess .run (cmd )
1795+ finally :
1796+ if constraints_file is not None :
1797+ os .unlink (constraints_file .name )
17341798 return True
0 commit comments