Skip to content

Commit

Permalink
Merge branch 'main' into fix-2006
Browse files Browse the repository at this point in the history
  • Loading branch information
csalerno-asml committed Jan 15, 2024
2 parents 6c7e65a + dee50c4 commit 51eafd4
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 12 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 23.10.1
rev: 23.11.0
hooks:
- id: black
args: [--target-version=py38]
Expand All @@ -20,7 +20,7 @@ repos:
additional_dependencies:
- flake8-pytest-style
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.6.1
rev: v1.7.1
hooks:
- id: mypy
# Avoid error: Duplicate module named 'setup'
Expand All @@ -43,7 +43,7 @@ repos:
args: [--ini, .bandit]
exclude: ^tests/
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.3
rev: v3.1.0
hooks:
- id: prettier
additional_dependencies:
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ By default, both `pip-compile` and `pip-sync` will look first
for a `.pip-tools.toml` file and then in your `pyproject.toml`. You can
also specify an alternate TOML configuration file with the `--config` option.

It is possible to specify configuration values both globally and command-specific.
For example, to by default generate `pip` hashes in the resulting
requirements file output, you can specify in a configuration file:

Expand All @@ -311,6 +312,28 @@ so the above could also be specified in this format:
generate_hashes = true
```

Configuration defaults specific to `pip-compile` and `pip-sync` can be put beneath
separate sections. For example, to by default perform a dry-run with `pip-compile`:

```toml
[tool.pip-tools.compile] # "sync" for pip-sync
dry-run = true
```

This does not affect the `pip-sync` command, which also has a `--dry-run` option.
Note that local settings take preference over the global ones of the same name,
whenever both are declared, thus this would also make `pip-compile` generate hashes,
but discard the global dry-run setting:

```toml
[tool.pip-tools]
generate-hashes = true
dry-run = true

[tool.pip-tools.compile]
dry-run = false
```

You might be wrapping the `pip-compile` command in another script. To avoid
confusing consumers of your custom script you can override the update command
generated at the top of requirements files by setting the
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@

linkcheck_ignore = [
r"^https://matrix\.to/#",
r"^https://img.shields.io/matrix",
]

suppress_warnings = ["myst.xref_missing"]
9 changes: 8 additions & 1 deletion piptools/scripts/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ def _determine_linesep(
}[strategy]


@click.command(context_settings={"help_option_names": options.help_option_names})
@click.command(
name="pip-compile",
context_settings={"help_option_names": options.help_option_names},
)
@click.pass_context
@options.version
@options.color
@options.verbose
@options.quiet
@options.dry_run
Expand Down Expand Up @@ -115,6 +119,7 @@ def _determine_linesep(
@options.only_build_deps
def cli(
ctx: click.Context,
color: bool | None,
verbose: int,
quiet: int,
dry_run: bool,
Expand Down Expand Up @@ -162,6 +167,8 @@ def cli(
Compiles requirements.txt from requirements.in, pyproject.toml, setup.cfg,
or setup.py specs.
"""
if color is not None:
ctx.color = color

Check warning on line 171 in piptools/scripts/compile.py

View check run for this annotation

Codecov / codecov/patch

piptools/scripts/compile.py#L171

Added line #L171 was not covered by tests
log.verbosity = verbose - quiet

# If ``src-files` was not provided as an input, but rather as config,
Expand Down
6 changes: 6 additions & 0 deletions piptools/scripts/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ def _get_default_option(option_name: str) -> Any:

version = click.version_option(package_name="pip-tools")

color = click.option(
"--color/--no-color",
default=None,
help="Force output to be colorized or not, instead of auto-detecting color support",
)

verbose = click.option(
"-v",
"--verbose",
Expand Down
4 changes: 3 additions & 1 deletion piptools/scripts/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
DEFAULT_REQUIREMENTS_FILE = "requirements.txt"


@click.command(context_settings={"help_option_names": options.help_option_names})
@click.command(
name="pip-sync", context_settings={"help_option_names": options.help_option_names}
)
@options.version
@options.ask
@options.dry_run
Expand Down
27 changes: 22 additions & 5 deletions piptools/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ def override_defaults_from_config_file(
``None`` is returned if no such file is found.
``pip-tools`` will use the first config file found, searching in this order:
an explicitly given config file, a d``.pip-tools.toml``, a ``pyproject.toml``
an explicitly given config file, a ``.pip-tools.toml``, a ``pyproject.toml``
file. Those files are searched for in the same directory as the requirements
input file, or the current working directory if requirements come via stdin.
"""
Expand All @@ -547,9 +547,9 @@ def override_defaults_from_config_file(
config_file = Path(value)

config = parse_config_file(ctx, config_file)
if config:
_validate_config(ctx, config)
_assign_config_to_cli_context(ctx, config)

_validate_config(ctx, config)
_assign_config_to_cli_context(ctx, config)

return config_file

Expand Down Expand Up @@ -678,13 +678,24 @@ def parse_config_file(
hint=f"Could not parse '{config_file !s}': {value_err !s}",
)

# In a TOML file, we expect the config to be under `[tool.pip-tools]`
# In a TOML file, we expect the config to be under `[tool.pip-tools]`,
# `[tool.pip-tools.compile]` or `[tool.pip-tools.sync]`
piptools_config: dict[str, Any] = config.get("tool", {}).get("pip-tools", {})

assert click_context.command.name is not None
# TODO: Replace with `str.removeprefix()` once dropped 3.8
config_section_name = click_context.command.name[len("pip-") :]

piptools_config.update(piptools_config.pop(config_section_name, {}))
piptools_config.pop("compile", {})
piptools_config.pop("sync", {})

piptools_config = _normalize_keys_in_config(piptools_config)
piptools_config = _invert_negative_bool_options_in_config(
ctx=click_context,
config=piptools_config,
)

return piptools_config


Expand All @@ -702,7 +713,13 @@ def _invert_negative_bool_options_in_config(
# Transform config key to its equivalent in the CLI
long_option = _convert_to_long_option(key)
new_key = cli_opts[long_option].name if long_option in cli_opts else key
negative_option_prefix = "no_"
assert new_key is not None
if (
new_key.startswith(negative_option_prefix)
and long_option not in ONLY_NEGATIVE_OPTIONS
):
new_key = new_key[len(negative_option_prefix) :]

# Invert negative boolean according to the CLI
new_value = (
Expand Down
8 changes: 7 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,14 +501,20 @@ def _maker(
pyproject_param: str,
new_default: Any,
config_file_name: str = DEFAULT_CONFIG_FILE_NAMES[0],
section: str = "pip-tools",
subsection: str | None = None,
) -> Path:
# Create a nested directory structure if config_file_name includes directories
config_dir = (tmpdir_cwd / config_file_name).parent
config_dir.mkdir(exist_ok=True, parents=True)

# Make a config file with this one config default override
config_file = tmpdir_cwd / config_file_name
config_to_dump = {"tool": {"pip-tools": {pyproject_param: new_default}}}

nested_config = {pyproject_param: new_default}
if subsection:
nested_config = {subsection: nested_config}
config_to_dump = {"tool": {section: nested_config}}
config_file.write_text(tomli_w.dumps(config_to_dump))
return cast(Path, config_file.relative_to(tmpdir_cwd))

Expand Down
39 changes: 38 additions & 1 deletion tests/test_cli_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -3517,7 +3517,7 @@ def test_invalid_cli_boolean_flag_config_option_captured(
out = runner.invoke(cli, [req_in.as_posix(), "--config", config_file.as_posix()])

assert out.exit_code == 2
assert "No such config key 'no_annnotate'." in out.stderr
assert "No such config key 'annnotate'." in out.stderr


strip_extras_warning = (
Expand Down Expand Up @@ -3598,3 +3598,40 @@ def test_origin_of_extra_requirement_not_written_to_annotations(
)
== out.stdout
)


def test_tool_specific_config_option(pip_conf, runner, tmp_path, make_config_file):
config_file = make_config_file(
"dry-run", True, section="pip-tools", subsection="compile"
)

req_in = tmp_path / "requirements.in"
req_in.touch()

out = runner.invoke(cli, [req_in.as_posix(), "--config", config_file.as_posix()])

assert out.exit_code == 0
assert "Dry-run, so nothing updated" in out.stderr


@pytest.mark.xfail(reason="https://github.com/jazzband/pip-tools/issues/2012")
@mock.patch("piptools.scripts.compile.parse_requirements")
def test_stdout_should_not_be_read_when_stdin_is_not_a_plain_file(
parse_req,
runner,
tmp_path,
):
parse_req.side_effect = lambda fname, finder, options, session: pytest.fail(
"Must not be called when output is a fifo"
)

req_in = tmp_path / "requirements.txt"
req_in.touch()

fifo = tmp_path / "fifo"

os.mkfifo(fifo)

out = runner.invoke(cli, [req_in.as_posix(), "--output-file", fifo.as_posix()])

assert out.exit_code == 0, out

Check warning on line 3637 in tests/test_cli_compile.py

View check run for this annotation

Codecov / codecov/patch

tests/test_cli_compile.py#L3637

Added line #L3637 was not covered by tests
15 changes: 15 additions & 0 deletions tests/test_cli_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,18 @@ def test_allow_in_config_pip_compile_option(run, runner, tmp_path, make_config_f

assert out.exit_code == 0
assert "Using pip-tools configuration defaults found" in out.stderr


@mock.patch("piptools.sync.run")
def test_tool_specific_config_option(run, runner, make_config_file):
config_file = make_config_file(
"dry-run", True, section="pip-tools", subsection="sync"
)

with open(sync.DEFAULT_REQUIREMENTS_FILE, "w") as reqs_txt:
reqs_txt.write("six==1.10.0")

out = runner.invoke(cli, ["--config", config_file.as_posix()])

assert out.exit_code == 1
assert "Would install:" in out.stdout

0 comments on commit 51eafd4

Please sign in to comment.