Skip to content

Commit d1e9215

Browse files
atugushevwebknjaztheryanwalker
authored
Fix revealed default config in header if requirements in subfolder (#1904)
Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua> Co-authored-by: Ryan Walker <theryanwalker@proton.me>
1 parent 19db875 commit d1e9215

File tree

8 files changed

+136
-18
lines changed

8 files changed

+136
-18
lines changed

piptools/locations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
# The user_cache_dir helper comes straight from pip itself
66
CACHE_DIR = user_cache_dir("pip-tools")
77

8-
# The project defaults specific to pip-tools should be written to this filename
9-
CONFIG_FILE_NAME = ".pip-tools.toml"
8+
# The project defaults specific to pip-tools should be written to this filenames
9+
DEFAULT_CONFIG_FILE_NAMES = (".pip-tools.toml", "pyproject.toml")

piptools/scripts/compile.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .._compat import parse_requirements
2121
from ..cache import DependencyCache
2222
from ..exceptions import NoCandidateFound, PipToolsError
23-
from ..locations import CACHE_DIR, CONFIG_FILE_NAME
23+
from ..locations import CACHE_DIR, DEFAULT_CONFIG_FILE_NAMES
2424
from ..logging import log
2525
from ..repositories import LocalRequirementsRepository, PyPIRepository
2626
from ..repositories.base import BaseRepository
@@ -314,8 +314,10 @@ def _determine_linesep(
314314
allow_dash=False,
315315
path_type=str,
316316
),
317-
help=f"Read configuration from TOML file. By default, looks for a {CONFIG_FILE_NAME} or "
318-
"pyproject.toml.",
317+
help=(
318+
f"Read configuration from TOML file. By default, looks for the following "
319+
f"files in the given order: {', '.join(DEFAULT_CONFIG_FILE_NAMES)}."
320+
),
319321
is_eager=True,
320322
callback=override_defaults_from_config_file,
321323
)

piptools/scripts/sync.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from .. import sync
1818
from .._compat import Distribution, parse_requirements
1919
from ..exceptions import PipToolsError
20-
from ..locations import CONFIG_FILE_NAME
20+
from ..locations import DEFAULT_CONFIG_FILE_NAMES
2121
from ..logging import log
2222
from ..repositories import PyPIRepository
2323
from ..utils import (
@@ -98,8 +98,10 @@
9898
allow_dash=False,
9999
path_type=str,
100100
),
101-
help=f"Read configuration from TOML file. By default, looks for a {CONFIG_FILE_NAME} or "
102-
"pyproject.toml.",
101+
help=(
102+
f"Read configuration from TOML file. By default, looks for the following "
103+
f"files in the given order: {', '.join(DEFAULT_CONFIG_FILE_NAMES)}."
104+
),
103105
is_eager=True,
104106
callback=override_defaults_from_config_file,
105107
)

piptools/utils.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from pathlib import Path
1313
from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator, TypeVar, cast
1414

15+
from click.core import ParameterSource
16+
1517
if sys.version_info >= (3, 11):
1618
import tomllib
1719
else:
@@ -31,7 +33,7 @@
3133
from pip._vendor.pkg_resources import get_distribution
3234

3335
from piptools._compat import PIP_VERSION
34-
from piptools.locations import CONFIG_FILE_NAME
36+
from piptools.locations import DEFAULT_CONFIG_FILE_NAMES
3537
from piptools.subprocess_utils import run_python_snippet
3638

3739
if TYPE_CHECKING:
@@ -369,8 +371,11 @@ def get_compile_command(click_ctx: click.Context) -> str:
369371

370372
# Exclude config option if it's the default one
371373
if option_long_name == "--config":
372-
default_config = select_config_file(click_ctx.params.get("src_files", ()))
373-
if value == default_config:
374+
parameter_source = click_ctx.get_parameter_source(option_name)
375+
if (
376+
str(value) in DEFAULT_CONFIG_FILE_NAMES
377+
or parameter_source == ParameterSource.DEFAULT
378+
):
374379
continue
375380

376381
# Skip options without a value
@@ -656,7 +661,7 @@ def select_config_file(src_files: tuple[str, ...]) -> Path | None:
656661
(
657662
candidate_dir / config_file
658663
for candidate_dir in candidate_dirs
659-
for config_file in (CONFIG_FILE_NAME, "pyproject.toml")
664+
for config_file in DEFAULT_CONFIG_FILE_NAMES
660665
if (candidate_dir / config_file).is_file()
661666
),
662667
None,

tests/conftest.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from piptools._compat import PIP_VERSION, Distribution
3232
from piptools.cache import DependencyCache
3333
from piptools.exceptions import NoCandidateFound
34-
from piptools.locations import CONFIG_FILE_NAME
34+
from piptools.locations import DEFAULT_CONFIG_FILE_NAMES
3535
from piptools.logging import log
3636
from piptools.repositories import PyPIRepository
3737
from piptools.repositories.base import BaseRepository
@@ -452,13 +452,16 @@ def make_config_file(tmpdir_cwd):
452452
"""
453453

454454
def _maker(
455-
pyproject_param: str, new_default: Any, config_file_name: str = CONFIG_FILE_NAME
455+
pyproject_param: str,
456+
new_default: Any,
457+
config_file_name: str = DEFAULT_CONFIG_FILE_NAMES[0],
456458
) -> Path:
457-
# Make a config file with this one config default override
458-
config_path = tmpdir_cwd / pyproject_param
459-
config_file = config_path / config_file_name
460-
config_path.mkdir(exist_ok=True)
459+
# Create a nested directory structure if config_file_name includes directories
460+
config_dir = (tmpdir_cwd / config_file_name).parent
461+
config_dir.mkdir(exist_ok=True, parents=True)
461462

463+
# Make a config file with this one config default override
464+
config_file = tmpdir_cwd / config_file_name
462465
config_to_dump = {"tool": {"pip-tools": {pyproject_param: new_default}}}
463466
config_file.write_text(tomli_w.dumps(config_to_dump))
464467
return cast(Path, config_file.relative_to(tmpdir_cwd))

tests/test_cli_compile.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2978,6 +2978,18 @@ def test_config_option(pip_conf, runner, tmp_path, make_config_file):
29782978
assert "Dry-run, so nothing updated" in out.stderr
29792979

29802980

2981+
def test_default_config_option(pip_conf, runner, make_config_file, tmpdir_cwd):
2982+
make_config_file("dry-run", True)
2983+
2984+
req_in = tmpdir_cwd / "requirements.in"
2985+
req_in.touch()
2986+
2987+
out = runner.invoke(cli)
2988+
2989+
assert out.exit_code == 0
2990+
assert "Dry-run, so nothing updated" in out.stderr
2991+
2992+
29812993
def test_no_config_option_overrides_config_with_defaults(
29822994
pip_conf, runner, tmp_path, make_config_file
29832995
):

tests/test_cli_sync.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,19 @@ def test_default_python_executable_option(run, runner):
374374
]
375375

376376

377+
@mock.patch("piptools.sync.run")
378+
def test_default_config_option(run, runner, make_config_file, tmpdir_cwd):
379+
make_config_file("dry-run", True)
380+
381+
with open(sync.DEFAULT_REQUIREMENTS_FILE, "w") as reqs_txt:
382+
reqs_txt.write("six==1.10.0")
383+
384+
out = runner.invoke(cli)
385+
386+
assert out.exit_code == 1
387+
assert "Would install:" in out.stdout
388+
389+
377390
@mock.patch("piptools.sync.run")
378391
def test_config_option(run, runner, make_config_file):
379392
config_file = make_config_file("dry-run", True)

tests/test_utils.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import shlex
77
import sys
88
from pathlib import Path
9+
from textwrap import dedent
910

1011
import pip
1112
import pytest
@@ -31,6 +32,7 @@
3132
lookup_table,
3233
lookup_table_from_tuples,
3334
override_defaults_from_config_file,
35+
select_config_file,
3436
)
3537

3638

@@ -415,6 +417,36 @@ def test_get_compile_command_with_config(tmpdir_cwd, config_file, expected_comma
415417
assert get_compile_command(ctx) == expected_command
416418

417419

420+
@pytest.mark.parametrize("config_file", ("pyproject.toml", ".pip-tools.toml"))
421+
@pytest.mark.parametrize(
422+
"config_file_content",
423+
(
424+
pytest.param("", id="empty config file"),
425+
pytest.param("[tool.pip-tools]", id="empty config section"),
426+
pytest.param("[tool.pip-tools]\ndry-run = true", id="non-empty config section"),
427+
),
428+
)
429+
def test_get_compile_command_does_not_include_default_config_if_reqs_file_in_subdir(
430+
tmpdir_cwd, config_file, config_file_content
431+
):
432+
"""
433+
Test that ``get_compile_command`` does not include default config file
434+
if requirements file is in a subdirectory.
435+
Regression test for issue GH-1903.
436+
"""
437+
default_config_file = Path(config_file)
438+
default_config_file.write_text(config_file_content)
439+
440+
(tmpdir_cwd / "subdir").mkdir()
441+
req_file = Path("subdir/requirements.in")
442+
req_file.touch()
443+
req_file.write_bytes(b"")
444+
445+
# Make sure that the default config file is not included
446+
with compile_cli.make_context("pip-compile", [req_file.as_posix()]) as ctx:
447+
assert get_compile_command(ctx) == f"pip-compile {req_file.as_posix()}"
448+
449+
418450
def test_get_compile_command_escaped_filenames(tmpdir_cwd):
419451
"""
420452
Test that get_compile_command output (re-)escapes ' -- '-escaped filenames.
@@ -683,3 +715,52 @@ def test_callback_config_file_defaults_unreadable_toml(make_config_file):
683715
"config",
684716
"/dev/null/path/does/not/exist/my-config.toml",
685717
)
718+
719+
720+
def test_select_config_file_no_files(tmpdir_cwd):
721+
assert select_config_file(()) is None
722+
723+
724+
@pytest.mark.parametrize("filename", ("pyproject.toml", ".pip-tools.toml"))
725+
def test_select_config_file_returns_config_in_cwd(make_config_file, filename):
726+
config_file = make_config_file("dry-run", True, filename)
727+
assert select_config_file(()) == config_file
728+
729+
730+
def test_select_config_file_returns_empty_config_file_in_cwd(tmpdir_cwd):
731+
config_file = Path(".pip-tools.toml")
732+
config_file.touch()
733+
734+
assert select_config_file(()) == config_file
735+
736+
737+
def test_select_config_file_cannot_find_config_in_cwd(tmpdir_cwd, make_config_file):
738+
make_config_file("dry-run", True, "subdir/pyproject.toml")
739+
assert select_config_file(()) is None
740+
741+
742+
def test_select_config_file_with_config_file_in_subdir(tmpdir_cwd, make_config_file):
743+
config_file = make_config_file("dry-run", True, "subdir/.pip-tools.toml")
744+
745+
requirement_file = Path("subdir/requirements.in")
746+
requirement_file.touch()
747+
748+
assert select_config_file((requirement_file.as_posix(),)) == config_file
749+
750+
751+
def test_select_config_file_prefers_pip_tools_toml_over_pyproject_toml(tmpdir_cwd):
752+
pip_tools_file = Path(".pip-tools.toml")
753+
pip_tools_file.touch()
754+
755+
pyproject_file = Path("pyproject.toml")
756+
pyproject_file.write_text(
757+
dedent(
758+
"""\
759+
[build-system]
760+
requires = ["setuptools>=63", "setuptools_scm[toml]>=7"]
761+
build-backend = "setuptools.build_meta"
762+
"""
763+
)
764+
)
765+
766+
assert select_config_file(()) == pip_tools_file

0 commit comments

Comments
 (0)