44import tempfile
55import textwrap
66import unittest
7- import sys
8- from unittest .mock import MagicMock , patch
9- from mypy .options import Options
10- from mypy .util import FancyFormatter
11-
7+ from types import SimpleNamespace # ADDED
8+ from unittest .mock import patch # ADDED
129
1310from mypy .installtypes import (
1411 make_runtime_constraints ,
1512 read_locked_packages ,
1613 resolve_stub_packages_from_lock ,
1714)
18- from mypy .main import install_types
15+ from mypy .main import install_types # ADDED
1916
2017
2118class TestInstallTypesFromPylock (unittest .TestCase ):
2219 def test_read_locked_packages (self ) -> None :
23- content = textwrap .dedent (
24- """
20+ content = textwrap .dedent ("""
2521 [[package]]
2622 name = "requests"
2723 version = "2.32.3"
@@ -33,8 +29,7 @@ def test_read_locked_packages(self) -> None:
3329 [[package]]
3430 name = "types-requests"
3531 version = "2.32.0"
36- """
37- )
32+ """ )
3833 with tempfile .NamedTemporaryFile ("w" , suffix = ".toml" , delete = False , encoding = "utf-8" ) as f :
3934 f .write (content )
4035 path = f .name
@@ -91,27 +86,24 @@ def test_read_locked_packages_empty_file(self) -> None:
9186 assert locked == {}
9287
9388 def test_resolve_stub_packages_from_lock (self ) -> None :
94- locked = {
95- "requests" : "2.32.3" ,
96- "python-dateutil" : "2.9.0" ,
97- "types-requests" : "2.32.0" ,
98- }
89+ locked = {"requests" : "2.32.3" , "python-dateutil" : "2.9.0" , "types-requests" : "2.32.0" }
9990 stubs = resolve_stub_packages_from_lock (locked )
91+ # requests already worked before fix because distribution name == module name.
10092 assert "types-requests" in stubs
101- assert "types-python-dateutil" in stubs
10293
103- #TEST: checks explicit distribution->module mapping
94+ # FIX: Before my fix, this failed because the lock file used the distribution name
95+ # "python-dateutil" but stubinfo.py knows the module/import name "dateutil".
96+ # After adding the explicit distribution->module mapping, it should resolve.
97+ assert "types-python-dateutil" in stubs # FIX
98+
99+ # TEST: checks explicit distribution->module mapping
104100 def test_resolve_stub_packages_from_lock_handles_distribution_module_mismatch (self ) -> None :
105- locked = {
106- "python-dateutil" : "2.9.0" ,
107- }
101+ locked = {"python-dateutil" : "2.9.0" }
108102 stubs = resolve_stub_packages_from_lock (locked )
109103 assert "types-python-dateutil" in stubs
110-
104+
111105 def test_resolve_stub_packages_skips_types_packages (self ) -> None :
112- locked = {
113- "types-requests" : "2.32.0" ,
114- }
106+ locked = {"types-requests" : "2.32.0" }
115107 stubs = resolve_stub_packages_from_lock (locked )
116108 # Should not produce "types-types-requests"
117109 assert "types-types-requests" not in stubs
@@ -131,11 +123,7 @@ def test_resolve_stub_packages_pyyaml_mapping(self) -> None:
131123 assert "types-PyYAML" in stubs
132124
133125 def test_make_runtime_constraints (self ) -> None :
134- locked = {
135- "requests" : "2.32.3" ,
136- "python-dateutil" : "2.9.0" ,
137- "no-version" : None ,
138- }
126+ locked = {"requests" : "2.32.3" , "python-dateutil" : "2.9.0" , "no-version" : None }
139127 constraints = make_runtime_constraints (locked )
140128 assert constraints == ["python-dateutil==2.9.0" , "requests==2.32.3" ]
141129
@@ -146,50 +134,49 @@ def test_make_runtime_constraints_skips_none_versions(self) -> None:
146134 assert "python-dateutil==2.9.0" in constraints
147135
148136 def test_make_runtime_constraints_empty (self ) -> None :
149- locked : dict [ str , str | None ] = {}
137+ locked = {}
150138 assert make_runtime_constraints (locked ) == []
151139
152140 def test_make_runtime_constraints_is_sorted (self ) -> None :
153141 locked = {"zebra-lib" : "1.0" , "alpha-lib" : "2.0" }
154142 constraints = make_runtime_constraints (locked )
155143 assert constraints == sorted (constraints )
156144
157- #TEST: integrations tests
145+
146+ # FIX: stub/mock object that pretends to be a formatter so tests don’t crash.
147+ class DummyFormatter :
148+ def style (self , text : str , * args : object , ** kwargs : object ) -> str :
149+ return text
150+
151+
152+ # TEST: integrations tests
158153class TestInstallTypesFromPylockIntegration (unittest .TestCase ):
159- def make_options (self ) -> Options :
160- options = Options ()
161- options .python_executable = "python"
162- options .cache_dir = "unused"
163- return options
164- def make_formatter (self ) -> FancyFormatter :
165- return FancyFormatter (sys .stdout , sys .stderr , False )
154+ def make_options (self ) -> SimpleNamespace :
155+ return SimpleNamespace (python_executable = "python" , cache_dir = "unused" )
166156
167157 @patch ("mypy.main.subprocess.run" )
168- def test_install_types_builds_correct_pip_command (self , mock_run : MagicMock ) -> None :
169- content = textwrap .dedent (
170- """
158+ def test_install_types_builds_correct_pip_command (self , mock_run ) -> None :
159+ content = textwrap .dedent ("""
171160 [[package]]
172161 name = "requests"
173162 version = "2.32.3"
174163
175164 [[package]]
176165 name = "python-dateutil"
177166 version = "2.9.0"
178- """
179- )
167+ """ )
180168
181- with tempfile .NamedTemporaryFile (
182- "w" , suffix = ".toml" , delete = False , encoding = "utf-8"
183- ) as f :
169+ with tempfile .NamedTemporaryFile ("w" , suffix = ".toml" , delete = False , encoding = "utf-8" ) as f :
184170 f .write (content )
185171 path = f .name
186172
187173 try :
188174 options = self .make_options ()
189- formatter = self . make_formatter ()
175+ formatter = DummyFormatter () # FIX
190176
191177 result = install_types (
192- formatter = formatter ,
178+ # formatter=None,
179+ formatter = formatter , # FIX
193180 options = options ,
194181 non_interactive = True ,
195182 pylock_path = path ,
@@ -208,34 +195,38 @@ def test_install_types_builds_correct_pip_command(self, mock_run: MagicMock) ->
208195 self .assertIn ("--constraint" , cmd )
209196
210197 # Stub packages should be installed
198+ # requests already resolved before the fix.
211199 self .assertIn ("types-requests" , cmd )
200+
201+ # FIX: Before the fix, this was missing from the pip command because
202+ # resolve_stub_packages_from_lock only tried "python-dateutil" and
203+ # "python_dateutil", but stubinfo.py maps "dateutil" -> "types-python-dateutil".
204+ # After adding the explicit distribution->module mapping, install_types()
205+ # should include the correct stub package in the pip command.
212206 self .assertIn ("types-python-dateutil" , cmd )
213207
214208 finally :
215209 os .unlink (path )
216210
217211 @patch ("mypy.main.subprocess.run" )
218- def test_no_stubs_found_skips_install (self , mock_run : MagicMock ) -> None :
219- content = textwrap .dedent (
220- """
212+ def test_no_stubs_found_skips_install (self , mock_run ) -> None :
213+ content = textwrap .dedent ("""
221214 [[package]]
222215 name = "unknown-lib"
223216 version = "1.0.0"
224- """
225- )
217+ """ )
226218
227- with tempfile .NamedTemporaryFile (
228- "w" , suffix = ".toml" , delete = False , encoding = "utf-8"
229- ) as f :
219+ with tempfile .NamedTemporaryFile ("w" , suffix = ".toml" , delete = False , encoding = "utf-8" ) as f :
230220 f .write (content )
231221 path = f .name
232222
233223 try :
234224 options = self .make_options ()
235- formatter = self . make_formatter ()
225+ formatter = DummyFormatter () # FIX
236226
237227 result = install_types (
238- formatter = formatter ,
228+ # formatter=None,
229+ formatter = formatter , # FIX
239230 options = options ,
240231 non_interactive = True ,
241232 pylock_path = path ,
@@ -248,14 +239,12 @@ def test_no_stubs_found_skips_install(self, mock_run: MagicMock) -> None:
248239 os .unlink (path )
249240
250241 @patch ("mypy.main.subprocess.run" )
251- def test_constraint_file_cleaned_up_after_success (self , mock_run : MagicMock ) -> None :
252- content = textwrap .dedent (
253- """
242+ def test_constraint_file_cleaned_up_after_success (self , mock_run ) -> None :
243+ content = textwrap .dedent ("""
254244 [[package]]
255245 name = "requests"
256246 version = "2.32.3"
257- """
258- )
247+ """ )
259248 with tempfile .NamedTemporaryFile ("w" , suffix = ".toml" , delete = False , encoding = "utf-8" ) as f :
260249 f .write (content )
261250 path = f .name
@@ -270,7 +259,7 @@ def capture_cmd(cmd: list[str], **kwargs: object) -> None:
270259
271260 try :
272261 install_types (
273- formatter = self . make_formatter (),
262+ formatter = DummyFormatter (),
274263 options = self .make_options (),
275264 non_interactive = True ,
276265 pylock_path = path ,
@@ -285,14 +274,12 @@ def capture_cmd(cmd: list[str], **kwargs: object) -> None:
285274 )
286275
287276 @patch ("mypy.main.subprocess.run" )
288- def test_constraint_file_cleaned_up_even_if_subprocess_fails (self , mock_run : MagicMock ) -> None :
289- content = textwrap .dedent (
290- """
277+ def test_constraint_file_cleaned_up_even_if_subprocess_fails (self , mock_run ) -> None :
278+ content = textwrap .dedent ("""
291279 [[package]]
292280 name = "requests"
293281 version = "2.32.3"
294- """
295- )
282+ """ )
296283 with tempfile .NamedTemporaryFile ("w" , suffix = ".toml" , delete = False , encoding = "utf-8" ) as f :
297284 f .write (content )
298285 path = f .name
@@ -309,7 +296,7 @@ def capture_and_raise(cmd: list[str], **kwargs: object) -> None:
309296 try :
310297 with self .assertRaises (RuntimeError ):
311298 install_types (
312- formatter = self . make_formatter (),
299+ formatter = DummyFormatter (),
313300 options = self .make_options (),
314301 non_interactive = True ,
315302 pylock_path = path ,
@@ -321,4 +308,4 @@ def capture_and_raise(cmd: list[str], **kwargs: object) -> None:
321308 self .assertFalse (
322309 os .path .exists (captured [0 ]),
323310 "Temp constraint file should be deleted even when subprocess raises" ,
324- )
311+ )
0 commit comments