Skip to content

Commit 5e6fcb6

Browse files
committed
Rename constraints param of DefaultIsolatedEnv.install
This is a vestige from a previous version where the constraints.txt file was incorrectly interpreted as a requirement list.
1 parent 9a2e1e9 commit 5e6fcb6

4 files changed

Lines changed: 45 additions & 77 deletions

File tree

docs/changelog/1160.removal.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Remove ``constraints`` parameter of ``env.DefaultIsolatedEnv.install`` and replace with ``constraints_txt_path``. - by
2+
:user:`layday`

src/build/__main__.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -218,17 +218,7 @@ def _bootstrap_build_env(
218218
with DefaultIsolatedEnv(installer=installer, path=env_dir) as env:
219219
builder = ProjectBuilder.from_isolated_env(env, srcdir, runner=runner)
220220

221-
install = env.install
222-
if dependency_constraints_txt:
223-
with open(dependency_constraints_txt, encoding='utf-8') as dependency_constraints_file:
224-
constraints_text = dependency_constraints_file.read()
225-
if constraints_text.strip():
226-
# Passed through as a single element instead of re-parsed into lines, so a requirement can never
227-
# be split from its `--hash` continuation lines (as produced by `pip-compile --generate-hashes`)
228-
# and silently lose its hash check. env.py's installer backends reconstruct the original file via
229-
# `'\n'.join(constraints)` (see `_PipInstaller`/`_UvInstaller.install_dependencies`), a no-op here
230-
# since there is only one element.
231-
install = partial(install, constraints=(constraints_text,))
221+
install = partial(env.install, constraints_txt_path=dependency_constraints_txt)
232222

233223
# first install the build dependencies
234224
install(builder.build_system_requires, _fresh=True)

src/build/env.py

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
else:
5858
from typing import Self
5959

60+
from . import _types
61+
6062

6163
Installer = typing.Literal['pip', 'uv']
6264

@@ -206,7 +208,7 @@ def installed_versions(self, requirements: Collection[str]) -> dict[str, str]:
206208
def install(
207209
self,
208210
requirements: Collection[str],
209-
constraints: Collection[str] = (),
211+
constraints_txt_path: _types.StrPath | None = None,
210212
*,
211213
_fresh: bool = False, # Used internally by CLI to support preset PYTHONPATH
212214
) -> None:
@@ -225,7 +227,7 @@ def install(
225227
'Installing packages in isolated environment:\n' + '\n'.join(f'- {r}' for r in sorted(requirements)),
226228
kind=('step',),
227229
)
228-
self._env_backend.install_dependencies(requirements, constraints, _fresh=_fresh)
230+
self._env_backend.install_dependencies(requirements, constraints_txt_path, _fresh=_fresh)
229231

230232

231233
def _canonical_requirement_name(requirement: str) -> str | None:
@@ -245,7 +247,7 @@ def create(self, path: str) -> None: ...
245247
def install_dependencies(
246248
self,
247249
requirements: Collection[str],
248-
constraints: Collection[str],
250+
constraints_txt_path: _types.StrPath | None,
249251
*,
250252
_fresh: bool = False,
251253
) -> None: ...
@@ -390,7 +392,7 @@ def create(self, path: str) -> None:
390392
def install_dependencies(
391393
self,
392394
requirements: Collection[str],
393-
constraints: Collection[str],
395+
constraints_txt_path: _types.StrPath | None,
394396
*,
395397
_fresh: bool = False,
396398
) -> None:
@@ -418,14 +420,8 @@ def install_dependencies(
418420

419421
cmd += ['-r', os.path.abspath(requirement_file.name)]
420422

421-
if constraints:
422-
with tempfile.NamedTemporaryFile(
423-
'w', prefix='build-constraints-', suffix='.txt', delete=False, encoding='utf-8'
424-
) as constraint_file:
425-
constraint_file.write('\n'.join(constraints))
426-
exit_stack.callback(functools.partial(os.unlink, constraint_file.name))
427-
428-
cmd += ['-c', os.path.abspath(constraint_file.name)]
423+
if constraints_txt_path:
424+
cmd += ['-c', str(constraints_txt_path)]
429425

430426
run_subprocess(cmd, env=_pip_env())
431427

@@ -459,32 +455,25 @@ def create(self, path: str) -> None:
459455
def install_dependencies( # pragma: no cover -- uv tests are skipped on PyPy, covered on CPython
460456
self,
461457
requirements: Collection[str],
462-
constraints: Collection[str],
458+
constraints_txt_path: _types.StrPath | None,
463459
*,
464460
_fresh: bool = False,
465461
) -> None:
466-
with contextlib.ExitStack() as exit_stack:
467-
cmd = [self._uv_bin, 'pip']
468-
469-
if (verbosity := _ctx.verbosity) > 1:
470-
cmd += [f'-{"v" * min(2, verbosity - 1)}']
462+
cmd = [self._uv_bin, 'pip']
471463

472-
cmd += ['install', *requirements, '--python', self.python_executable]
464+
if (verbosity := _ctx.verbosity) > 1:
465+
cmd += [f'-{"v" * min(2, verbosity - 1)}']
473466

474-
if constraints:
475-
with tempfile.NamedTemporaryFile(
476-
'w', prefix='build-constraints-', suffix='.txt', delete=False, encoding='utf-8'
477-
) as constraint_file:
478-
constraint_file.write('\n'.join(constraints))
479-
exit_stack.callback(functools.partial(os.unlink, constraint_file.name))
467+
cmd += ['install', *requirements, '--python', self.python_executable]
480468

481-
cmd += ['-c', os.path.abspath(constraint_file.name)]
469+
if constraints_txt_path:
470+
cmd += ['-c', str(constraints_txt_path)]
482471

483-
env = {k: v for k, v in os.environ.items() if k != 'PYTHONPATH'}
484-
env['VIRTUAL_ENV'] = self._env_path
485-
if 'UV_KEYRING_PROVIDER' not in os.environ and _has_keyring_cli():
486-
env['UV_KEYRING_PROVIDER'] = 'subprocess'
487-
run_subprocess(cmd, env=env)
472+
env = {k: v for k, v in os.environ.items() if k != 'PYTHONPATH'}
473+
env['VIRTUAL_ENV'] = self._env_path
474+
if 'UV_KEYRING_PROVIDER' not in os.environ and _has_keyring_cli():
475+
env['UV_KEYRING_PROVIDER'] = 'subprocess'
476+
run_subprocess(cmd, env=env)
488477

489478
@property
490479
def display_name(self) -> str:

tests/test_env.py

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -286,21 +286,27 @@ def test_install_short_circuits(
286286

287287

288288
@pytest.mark.parametrize('verbosity', range(3))
289-
@pytest.mark.parametrize('constraints', [[], ['foo']])
289+
@pytest.mark.parametrize('constraints_txt', ['', 'foo'])
290290
@pytest.mark.parametrize('fresh', [False, True])
291291
@pytest.mark.usefixtures('local_pip')
292292
def test_default_impl_install_cmd_well_formed(
293+
tmp_path: Path,
293294
mocker: pytest_mock.MockerFixture,
294295
verbosity: int,
295-
constraints: list[str],
296+
constraints_txt: str,
296297
fresh: bool,
297298
) -> None:
298299
mocker.patch.object(_ctx, 'verbosity', verbosity)
299300

301+
constraints_txt_path = None
302+
if constraints_txt:
303+
constraints_txt_path = tmp_path.joinpath('constraints.txt')
304+
constraints_txt_path.write_text(constraints_txt, encoding='utf-8')
305+
300306
with build.env.DefaultIsolatedEnv() as env:
301307
run_subprocess = mocker.patch('build.env.run_subprocess')
302308

303-
env.install(['some', 'requirements'], constraints, _fresh=fresh)
309+
env.install(['some', 'requirements'], constraints_txt_path, _fresh=fresh)
304310

305311
run_subprocess.assert_called_once_with(
306312
[
@@ -316,29 +322,35 @@ def test_default_impl_install_cmd_well_formed(
316322
'--no-input',
317323
'-r',
318324
mocker.ANY,
319-
*(['-c', mocker.ANY] if constraints else []),
325+
*(['-c', str(constraints_txt_path)] if constraints_txt_path else []),
320326
],
321327
env=mocker.ANY,
322328
)
323329

324330

325331
@pytest.mark.parametrize('verbosity', range(3))
326-
@pytest.mark.parametrize('constraints', [[], ['foo']])
332+
@pytest.mark.parametrize('constraints_txt', ['', 'foo'])
327333
@pytest.mark.parametrize('fresh', [False, True])
328334
@pytest.mark.skipif(IS_PYPY, reason='uv cannot find PyPy executable')
329335
@pytest.mark.skipif(MISSING_UV, reason='uv executable not found')
330336
def test_uv_impl_install_cmd_well_formed( # pragma: no cover -- uv tests are skipped on PyPy, covered on CPython
337+
tmp_path: Path,
331338
mocker: pytest_mock.MockerFixture,
332339
verbosity: int,
333-
constraints: list[str],
340+
constraints_txt: str,
334341
fresh: bool,
335342
) -> None:
336343
mocker.patch.object(_ctx, 'verbosity', verbosity)
337344

345+
constraints_txt_path = None
346+
if constraints_txt:
347+
constraints_txt_path = tmp_path.joinpath('constraints.txt')
348+
constraints_txt_path.write_text(constraints_txt, encoding='utf-8')
349+
338350
with build.env.DefaultIsolatedEnv(installer='uv') as env:
339351
run_subprocess = mocker.patch('build.env.run_subprocess')
340352

341-
env.install(['some', 'requirements'], constraints, _fresh=fresh)
353+
env.install(['some', 'requirements'], constraints_txt_path, _fresh=fresh)
342354

343355
run_subprocess.assert_called_once_with(
344356
[
@@ -350,7 +362,7 @@ def test_uv_impl_install_cmd_well_formed( # pragma: no cover -- uv tests are sk
350362
'requirements',
351363
'--python',
352364
mocker.ANY,
353-
*(['-c', mocker.ANY] if constraints else []),
365+
*(['-c', str(constraints_txt_path)] if constraints_txt_path else []),
354366
],
355367
env=mocker.ANY,
356368
)
@@ -360,7 +372,7 @@ def test_uv_impl_install_cmd_well_formed( # pragma: no cover -- uv tests are sk
360372

361373
@pytest.mark.usefixtures('local_pip')
362374
def test_default_impl_install_files_line_endings_not_doubled(mocker: pytest_mock.MockerFixture) -> None:
363-
# The requirements/constraints files are opened in text mode, which translates every
375+
# The requirements files are opened in text mode, which translates every
364376
# '\n' written to os.linesep -- '\r\n' on disk is correct on Windows. Joining with
365377
# os.linesep first (instead of '\n') would double-translate there, turning '\r\n' into
366378
# '\r\r\n'. Read back as bytes, before the files are deleted by the
@@ -372,38 +384,13 @@ def fake_run_subprocess(cmd: list[str], **_kwargs: object) -> None:
372384
for arg in args:
373385
if arg == '-r':
374386
written['requirements'] = Path(next(args)).read_bytes()
375-
elif arg == '-c':
376-
written['constraints'] = Path(next(args)).read_bytes()
377387

378388
with build.env.DefaultIsolatedEnv() as env:
379389
mocker.patch('build.env.run_subprocess', side_effect=fake_run_subprocess)
380-
env.install(['some', 'requirements'], ['a-constraint', 'b-constraint'])
390+
env.install(['some', 'requirements'])
381391

382392
assert b'\r\r' not in written['requirements']
383393
assert written['requirements'].splitlines() == [b'some', b'requirements']
384-
assert b'\r\r' not in written['constraints']
385-
assert written['constraints'].splitlines() == [b'a-constraint', b'b-constraint']
386-
387-
388-
@pytest.mark.skipif(IS_PYPY, reason='uv cannot find PyPy executable')
389-
@pytest.mark.skipif(MISSING_UV, reason='uv executable not found')
390-
def test_uv_impl_install_files_line_endings_not_doubled( # pragma: no cover -- skipped on PyPy, covered on CPython
391-
mocker: pytest_mock.MockerFixture,
392-
) -> None:
393-
written: dict[str, bytes] = {}
394-
395-
def fake_run_subprocess(cmd: list[str], **_kwargs: object) -> None:
396-
args = iter(cmd)
397-
for arg in args:
398-
if arg == '-c':
399-
written['constraints'] = Path(next(args)).read_bytes()
400-
401-
with build.env.DefaultIsolatedEnv(installer='uv') as env:
402-
mocker.patch('build.env.run_subprocess', side_effect=fake_run_subprocess)
403-
env.install(['some', 'requirements'], ['a-constraint', 'b-constraint'])
404-
405-
assert b'\r\r' not in written['constraints']
406-
assert written['constraints'].splitlines() == [b'a-constraint', b'b-constraint']
407394

408395

409396
@pytest.mark.usefixtures('local_pip')

0 commit comments

Comments
 (0)