From 913411583ebef10fb74a9282d3175fb57e3c7d88 Mon Sep 17 00:00:00 2001 From: Richard Frank Date: Thu, 2 Feb 2023 10:59:58 -0500 Subject: [PATCH] Fix for sync error when the ireqs being merged have no names (#1802) * Fix for sync error when the ireqs being merged have no names When no name was provided, the ireqs would have no underlying req attribute. Calling ireq.specifier would raise an AttributeError. * Integration test for merging requirements without names --- piptools/sync.py | 6 +++++- tests/test_cli_sync.py | 26 ++++++++++++++++++++++++++ tests/test_sync.py | 20 ++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/piptools/sync.py b/piptools/sync.py index 7a0048b3d..184d78a1b 100644 --- a/piptools/sync.py +++ b/piptools/sync.py @@ -104,7 +104,11 @@ def merge( if existing_ireq: # NOTE: We check equality here since we can assume that the # requirements are all pinned - if ireq.specifier != existing_ireq.specifier: + if ( + ireq.req + and existing_ireq.req + and ireq.specifier != existing_ireq.specifier + ): raise IncompatibleRequirements(ireq, existing_ireq) # TODO: Always pick the largest specifier in case of a conflict diff --git a/tests/test_cli_sync.py b/tests/test_cli_sync.py index bb58b028a..de56f45ef 100644 --- a/tests/test_cli_sync.py +++ b/tests/test_cli_sync.py @@ -3,6 +3,7 @@ import os import subprocess import sys +from pathlib import Path from unittest import mock import pytest @@ -128,6 +129,31 @@ def test_merge_error(req_lines, should_raise, runner): assert out.exit_code == 1 +@pytest.mark.parametrize( + "req_line", + ( + "file:.", + "-e file:.", + ), +) +@mock.patch("piptools.sync.run") +def test_merge_no_name_urls(run, req_line, runner): + """ + Test sync succeeds when merging requirements that lack names. + """ + reqs_paths = [ + Path(name).absolute() for name in ("requirements.txt", "dev_requirements.txt") + ] + + for reqs_path in reqs_paths: + with reqs_path.open("w") as req_out: + req_out.write(f"{req_line} \n") + + out = runner.invoke(cli, [str(path) for path in reqs_paths]) + assert out.exit_code == 0 + assert run.call_count == 2 + + @pytest.mark.parametrize( ("cli_flags", "expected_install_flags"), ( diff --git a/tests/test_sync.py b/tests/test_sync.py index 93f80dc5e..f084336d7 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -115,6 +115,26 @@ def test_merge_urls(from_line): ) +@pytest.mark.parametrize( + "install_req", + ( + "from_line", + "from_editable", + ), +) +def test_merge_no_name_urls(install_req, request): + install_req = request.getfixturevalue(install_req) + url = "file:///example.zip" + requirements = [ + install_req(url), + install_req(url), + ] + + assert Counter(requirements[1:]) == Counter( + merge(requirements, ignore_conflicts=False) + ) + + def test_diff_should_do_nothing(): installed = [] # empty env reqs = [] # no requirements