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

Improve formatting of long "via" annotations #1237

Merged
merged 5 commits into from
Nov 26, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 40 additions & 18 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,14 @@ If you have a ``setup.py`` with ``install_requires=['django']``, then run
#
# pip-compile
#
asgiref==3.2.3 # via django
django==3.0.3 # via my_django_project (setup.py)
pytz==2019.3 # via django
sqlparse==0.3.0 # via django
asgiref==3.2.3
# via django
django==3.0.3
# via my_django_project (setup.py)
pytz==2019.3
# via django
sqlparse==0.3.0
# via django

``pip-compile`` will produce your ``requirements.txt``, with all the Django
dependencies (and all underlying dependencies) pinned.
Expand All @@ -109,10 +113,14 @@ Now, run ``pip-compile requirements.in``:
#
# pip-compile requirements.in
#
asgiref==3.2.3 # via django
django==3.0.3 # via -r requirements.in
pytz==2019.3 # via django
sqlparse==0.3.0 # via django
asgiref==3.2.3
# via django
django==3.0.3
# via -r requirements.in
pytz==2019.3
# via django
sqlparse==0.3.0
# via django

And it will produce your ``requirements.txt``, with all the Django dependencies
(and all underlying dependencies) pinned.
Expand Down Expand Up @@ -225,10 +233,14 @@ generated at the top of requirements files by setting the
#
# ./pipcompilewrapper
#
asgiref==3.2.3 # via django
django==3.0.3 # via -r requirements.in
pytz==2019.3 # via django
sqlparse==0.3.0 # via django
asgiref==3.2.3
# via django
django==3.0.3
# via -r requirements.in
pytz==2019.3
# via django
sqlparse==0.3.0
# via django

Workflow for layered requirements
---------------------------------
Expand Down Expand Up @@ -267,8 +279,10 @@ First, compile ``requirements.txt`` as usual:
#
# pip-compile
#
django==2.1.15 # via -r requirements.in
pytz==2019.3 # via django
django==2.1.15
# via -r requirements.in
pytz==2019.3
# via django


Now compile the dev requirements and the ``requirements.txt`` file is used as
Expand All @@ -283,10 +297,18 @@ a constraint:
#
# pip-compile dev-requirements.in
#
django-debug-toolbar==2.2 # via -r dev-requirements.in
django==2.1.15 # via -c requirements.txt, django-debug-toolbar
pytz==2019.3 # via -c requirements.txt, django
sqlparse==0.3.0 # via django-debug-toolbar
django-debug-toolbar==2.2
# via -r dev-requirements.in
django==2.1.15
# via
# -c requirements.txt
# django-debug-toolbar
pytz==2019.3
# via
# -c requirements.txt
# django
sqlparse==0.3.0
# via django-debug-toolbar

As you can see above, even though a ``2.2`` release of Django is available, the
dev requirements only include a ``2.1`` version of Django because they were
Expand Down
16 changes: 10 additions & 6 deletions piptools/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,14 @@ def _format_requirement(self, ireq, marker=None, hashes=None):
elif ireq.comes_from:
required_by.add(_comes_from_as_string(ireq))
if required_by:
annotation = ", ".join(sorted(required_by))
line = "{:24}{}{}".format(
line,
" \\\n " if ireq_hashes else " ",
comment("# via " + annotation),
)
required_by = sorted(required_by)
if len(required_by) == 1:
source = required_by[0]
annotation = " # via " + source
else:
annotation_lines = [" # via"]
for source in required_by:
annotation_lines.append(" # " + source)
annotation = "\n".join(annotation_lines)
line = "{}\n{}".format(line, comment(annotation))
return line
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ markers =
network: mark tests that require internet access

[flake8]
max-line-length = 88
max-line-length = 100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like a bad standard :(

extend-ignore = E203 # E203 conflicts with PEP8; see https://github.com/psf/black#slices

# flake8-pytest-style
Expand Down
172 changes: 154 additions & 18 deletions tests/test_cli_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,19 @@ def test_command_line_setuptools_read(pip_conf, runner):
"""
)
)
out = runner.invoke(cli)

assert "This file is autogenerated by pip-compile" in out.stderr
assert (
"small-fake-a==0.1 # via fake-setuptools-a (setup.py)"
in out.stderr.splitlines()
out = runner.invoke(cli, ["--no-emit-find-links"])

assert out.stderr == dedent(
"""\
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --no-emit-find-links
#
small-fake-a==0.1
# via fake-setuptools-a (setup.py)
"""
)

# check that pip-compile generated a configuration file
Expand Down Expand Up @@ -739,6 +746,83 @@ def test_generate_hashes_verbose(pip_conf, runner):
assert expected_verbose_text in out.stderr


@pytest.mark.network
def test_generate_hashes_with_annotations(runner):
with open("requirements.in", "w") as fp:
fp.write("six==1.15.0")

out = runner.invoke(cli, ["--generate-hashes"])
assert out.stderr == dedent(
"""\
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --generate-hashes
#
six==1.15.0 \\
--hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \\
--hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced
# via -r requirements.in
"""
)


@pytest.mark.network
def test_generate_hashes_with_long_annotations(runner):
with open("requirements.in", "w") as fp:
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("Django==1.11.29\n")
fp.write("pytz==2020.4\n")
fp.write("sqlparse==0.3.1\n")

out = runner.invoke(cli, ["--generate-hashes"])
assert out.stderr == dedent(
"""\
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --generate-hashes
#
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
django==1.11.29 \\
--hash=sha256:014e3392058d94f40569206a24523ce254d55ad2f9f46c6550b0fe2e4f94cf3f \\
--hash=sha256:4200aefb6678019a0acf0005cd14cfce3a5e6b9b90d06145fcdd2e474ad4329c
# via
# -r requirements.in
# django-debug-toolbar
# django-storages
# django-taggit
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 @@ -831,10 +915,24 @@ def test_stdin(pip_conf, runner):
Test compile requirements from STDIN.
"""
out = runner.invoke(
cli, ["-", "--output-file", "requirements.txt", "-n"], input="small-fake-a==0.1"
cli,
["-", "--output-file", "requirements.txt", "-n", "--no-emit-find-links"],
input="small-fake-a==0.1",
)

assert "small-fake-a==0.1 # via -r -" in out.stderr.splitlines()
assert out.stderr == dedent(
"""\
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --no-emit-find-links --output-file=requirements.txt -
#
small-fake-a==0.1
# via -r -
Dry-run, so nothing updated.
"""
)


def test_multiple_input_files_without_output_file(runner):
Expand All @@ -858,12 +956,40 @@ def test_multiple_input_files_without_output_file(runner):
@pytest.mark.parametrize(
("option", "expected"),
(
(
pytest.param(
"--annotate",
"small-fake-a==0.1 "
"# via -c constraints.txt, small-fake-with-deps\n",
"""\
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --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",
),
pytest.param(
"--no-annotate",
"""\
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --no-annotate --no-emit-find-links
#
small-fake-a==0.1
small-fake-with-deps==0.1
Dry-run, so nothing updated.
""",
id="no annotate",
),
("--no-annotate", "small-fake-a==0.1\n"),
),
)
def test_annotate_option(pip_conf, runner, option, expected):
Expand All @@ -876,9 +1002,9 @@ def test_annotate_option(pip_conf, runner, option, expected):
req_in.write("-c constraints.txt\n")
req_in.write("small_fake_with_deps")

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

assert expected in out.stderr
assert out.stderr == dedent(expected)
assert out.exit_code == 0


Expand Down Expand Up @@ -1087,11 +1213,21 @@ def test_upgrade_package_doesnt_remove_annotation(pip_conf, runner):
"small-fake-a==0.1 # via small-fake-with-deps\n"
)

runner.invoke(cli, ["-P", "small-fake-a"])
runner.invoke(cli, ["-P", "small-fake-a", "--no-emit-find-links"])
with open("requirements.txt", "r") as req_txt:
assert (
"small-fake-a==0.1 # via small-fake-with-deps"
in req_txt.read().splitlines()
assert req_txt.read() == dedent(
"""\
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile --no-emit-find-links
#
small-fake-a==0.1
# via small-fake-with-deps
small-fake-with-deps==0.1
# via -r requirements.in
"""
)


Expand Down
18 changes: 5 additions & 13 deletions tests/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,45 +53,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 " + 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 " + 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 " + 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 " + 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 " + 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