Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable single-line annotations with pip-compile --annotation-style=line #1477

Merged
merged 3 commits into from Sep 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions piptools/scripts/compile.py
Expand Up @@ -123,6 +123,12 @@ def _get_default_option(option_name: str) -> Any:
default=True,
help="Annotate results, indicating where dependencies come from",
)
@click.option(
"--annotation-style",
type=click.Choice(("line", "split")),
default="split",
help="Choose the format of annotation comments",
)
@click.option(
"-U",
"--upgrade/--no-upgrade",
Expand Down Expand Up @@ -244,6 +250,7 @@ def cli(
header: bool,
emit_trusted_host: bool,
annotate: bool,
annotation_style: str,
upgrade: bool,
upgrade_packages: Tuple[str, ...],
output_file: Union[LazyFile, IO[Any], None],
Expand Down Expand Up @@ -471,6 +478,7 @@ def cli(
emit_index_url=emit_index_url,
emit_trusted_host=emit_trusted_host,
annotate=annotate,
annotation_style=annotation_style,
strip_extras=strip_extras,
generate_hashes=generate_hashes,
default_index_url=repository.DEFAULT_INDEX_URL,
Expand Down
40 changes: 30 additions & 10 deletions piptools/writer.py
Expand Up @@ -51,6 +51,23 @@ def _comes_from_as_string(ireq: InstallRequirement) -> str:
return key_from_ireq(ireq.comes_from)


def annotation_style_split(required_by: Set[str]) -> str:
sorted_required_by = sorted(required_by)
if len(sorted_required_by) == 1:
source = sorted_required_by[0]
annotation = "# via " + source
else:
annotation_lines = ["# via"]
for source in sorted_required_by:
annotation_lines.append(" # " + source)
annotation = "\n".join(annotation_lines)
return annotation


def annotation_style_line(required_by: Set[str]) -> str:
return f"# via {', '.join(sorted(required_by))}"


class OutputWriter:
def __init__(
self,
Expand All @@ -61,6 +78,7 @@ def __init__(
emit_index_url: bool,
emit_trusted_host: bool,
annotate: bool,
annotation_style: str,
strip_extras: bool,
generate_hashes: bool,
default_index_url: str,
Expand All @@ -79,6 +97,7 @@ def __init__(
self.emit_index_url = emit_index_url
self.emit_trusted_host = emit_trusted_host
self.annotate = annotate
self.annotation_style = annotation_style
self.strip_extras = strip_extras
self.generate_hashes = generate_hashes
self.default_index_url = default_index_url
Expand Down Expand Up @@ -258,15 +277,16 @@ def _format_requirement(
required_by.add(_comes_from_as_string(ireq))

if required_by:
sorted_required_by = sorted(required_by)
if len(sorted_required_by) == 1:
source = sorted_required_by[0]
annotation = " # via " + source
else:
annotation_lines = [" # via"]
for source in sorted_required_by:
annotation_lines.append(" # " + source)
annotation = "\n".join(annotation_lines)
line = f"{line}\n{comment(annotation)}"
if self.annotation_style == "split":
annotation = annotation_style_split(required_by)
sep = "\n "
elif self.annotation_style == "line":
annotation = annotation_style_line(required_by)
sep = "\n " if ireq_hashes else " "
else: # pragma: no cover
raise ValueError("Invalid value for annotation style")
# 24 is one reasonable column size to use here, that we've used in the past
lines = f"{line:24}{sep}{comment(annotation)}".splitlines()
jdufresne marked this conversation as resolved.
Show resolved Hide resolved
line = "\n".join(ln.rstrip() for ln in lines)

return line
80 changes: 72 additions & 8 deletions tests/test_cli_compile.py
Expand Up @@ -891,7 +891,7 @@ def test_generate_hashes_with_annotations(runner):


@pytest.mark.network
def test_generate_hashes_with_long_annotations(runner):
def test_generate_hashes_with_split_style_annotations(runner):
with open("requirements.in", "w") as fp:
fp.write("Django==1.11.29\n")
fp.write("django-debug-toolbar==1.11\n")
Expand All @@ -900,7 +900,7 @@ def test_generate_hashes_with_long_annotations(runner):
fp.write("pytz==2020.4\n")
fp.write("sqlparse==0.3.1\n")

out = runner.invoke(cli, ["--generate-hashes"])
out = runner.invoke(cli, ["--generate-hashes", "--annotation-style", "split"])
assert out.stderr == dedent(
f"""\
#
Expand Down Expand Up @@ -946,6 +946,54 @@ def test_generate_hashes_with_long_annotations(runner):
)


@pytest.mark.network
def test_generate_hashes_with_line_style_annotations(runner):
with open("requirements.in", "w") as fp:
fp.write("Django==1.11.29\n")
fp.write("django-debug-toolbar==1.11\n")
fp.write("django-storages==1.9.1\n")
fp.write("django-taggit==0.24.0\n")
fp.write("pytz==2020.4\n")
fp.write("sqlparse==0.3.1\n")

out = runner.invoke(cli, ["--generate-hashes", "--annotation-style", "line"])
assert out.stderr == dedent(
f"""\
#
# This file is autogenerated by pip-compile with python \
{sys.version_info.major}.{sys.version_info.minor}
# To update, run:
#
# pip-compile --annotation-style=line --generate-hashes
#
django==1.11.29 \\
--hash=sha256:014e3392058d94f40569206a24523ce254d55ad2f9f46c6550b0fe2e4f94cf3f \\
--hash=sha256:4200aefb6678019a0acf0005cd14cfce3a5e6b9b90d06145fcdd2e474ad4329c
AndydeCleyre marked this conversation as resolved.
Show resolved Hide resolved
# via -r requirements.in, django-debug-toolbar, django-storages, django-taggit
django-debug-toolbar==1.11 \\
--hash=sha256:89d75b60c65db363fb24688d977e5fbf0e73386c67acf562d278402a10fc3736 \\
--hash=sha256:c2b0134119a624f4ac9398b44f8e28a01c7686ac350a12a74793f3dd57a9eea0
# via -r requirements.in
django-storages==1.9.1 \\
--hash=sha256:3103991c2ee8cef8a2ff096709973ffe7106183d211a79f22cf855f33533d924 \\
--hash=sha256:a59e9923cbce7068792f75344ed7727021ee4ac20f227cf17297d0d03d141e91
# via -r requirements.in
django-taggit==0.24.0 \\
--hash=sha256:710b4d15ec1996550cc68a0abbc41903ca7d832540e52b1336e6858737e410d8 \\
--hash=sha256:bb8f27684814cd1414b2af75b857b5e26a40912631904038a7ecacd2bfafc3ac
# via -r requirements.in
pytz==2020.4 \\
--hash=sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268 \\
--hash=sha256:5c55e189b682d420be27c6995ba6edce0c0a77dd67bfbe2ae6607134d5851ffd
# via -r requirements.in, django
sqlparse==0.3.1 \\
--hash=sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e \\
--hash=sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548
# via -r requirements.in, django-debug-toolbar
"""
)


def test_filter_pip_markers(pip_conf, runner):
"""
Check that pip-compile works with pip environment markers (PEP496)
Expand Down Expand Up @@ -1078,10 +1126,10 @@ def test_multiple_input_files_without_output_file(runner):


@pytest.mark.parametrize(
("option", "expected"),
("options", "expected"),
(
pytest.param(
"--annotate",
("--annotate",),
f"""\
#
# This file is autogenerated by pip-compile with python \
Expand All @@ -1101,7 +1149,23 @@ def test_multiple_input_files_without_output_file(runner):
id="annotate",
),
pytest.param(
"--no-annotate",
("--annotate", "--annotation-style", "line"),
f"""\
#
# This file is autogenerated by pip-compile with python \
{sys.version_info.major}.{sys.version_info.minor}
# To update, run:
#
# pip-compile --annotation-style=line --no-emit-find-links
#
small-fake-a==0.1 # via -c constraints.txt, small-fake-with-deps
small-fake-with-deps==0.1 # via -r requirements.in
Dry-run, so nothing updated.
""",
id="annotate line style",
),
pytest.param(
("--no-annotate",),
f"""\
#
# This file is autogenerated by pip-compile with python \
Expand All @@ -1118,17 +1182,17 @@ def test_multiple_input_files_without_output_file(runner):
),
),
)
def test_annotate_option(pip_conf, runner, option, expected):
def test_annotate_option(pip_conf, runner, options, expected):
"""
The output lines has have annotations if option is turned on.
The output lines have annotations if the option is turned on.
"""
with open("constraints.txt", "w") as constraints_in:
constraints_in.write("small-fake-a==0.1")
with open("requirements.in", "w") as req_in:
req_in.write("-c constraints.txt\n")
req_in.write("small_fake_with_deps")

out = runner.invoke(cli, [option, "-n", "--no-emit-find-links"])
out = runner.invoke(cli, [*options, "-n", "--no-emit-find-links"])

assert out.stderr == dedent(expected)
assert out.exit_code == 0
Expand Down
11 changes: 6 additions & 5 deletions tests/test_writer.py
Expand Up @@ -36,6 +36,7 @@ def writer(tmpdir_cwd):
emit_index_url=True,
emit_trusted_host=True,
annotate=True,
annotation_style="split",
generate_hashes=False,
default_index_url=None,
index_urls=[],
Expand All @@ -57,37 +58,37 @@ def test_format_requirement_annotation_editable(from_editable, writer):

assert writer._format_requirement(
ireq
) == "-e git+git://fake.org/x/y.git#egg=y\n" + comment(" # via xyz")
) == "-e git+git://fake.org/x/y.git#egg=y\n " + comment("# via xyz")


def test_format_requirement_annotation(from_line, writer):
ireq = from_line("test==1.2")
ireq.comes_from = "xyz"

assert writer._format_requirement(ireq) == "test==1.2\n" + comment(" # via xyz")
assert writer._format_requirement(ireq) == "test==1.2\n " + comment("# via xyz")


def test_format_requirement_annotation_lower_case(from_line, writer):
ireq = from_line("Test==1.2")
ireq.comes_from = "xyz"

assert writer._format_requirement(ireq) == "test==1.2\n" + comment(" # via xyz")
assert writer._format_requirement(ireq) == "test==1.2\n " + comment("# via xyz")


def test_format_requirement_for_primary(from_line, writer):
"Primary packages should get annotated."
ireq = from_line("test==1.2")
ireq.comes_from = "xyz"

assert writer._format_requirement(ireq) == "test==1.2\n" + comment(" # via xyz")
assert writer._format_requirement(ireq) == "test==1.2\n " + comment("# via xyz")


def test_format_requirement_for_primary_lower_case(from_line, writer):
"Primary packages should get annotated."
ireq = from_line("Test==1.2")
ireq.comes_from = "xyz"

assert writer._format_requirement(ireq) == "test==1.2\n" + comment(" # via xyz")
assert writer._format_requirement(ireq) == "test==1.2\n " + comment("# via xyz")


def test_format_requirement_environment_marker(from_line, writer):
Expand Down