Skip to content

Commit e9d6e63

Browse files
authored
Handle properly pip --no-binary / --only-binary options in requirements.txt format files. (#2834)
Fixes #2814
1 parent b9b4d02 commit e9d6e63

File tree

5 files changed

+66
-11
lines changed

5 files changed

+66
-11
lines changed

docs/changelog/2814.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Handle properly pip ``--no-binary`` / ``--only-binary`` options in requirements.txt format files.

src/tox/tox_env/python/pip/req/args.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def _global_options(parser: ArgumentParser) -> None:
3333
parser.add_argument("-r", "--requirement", action=AddUniqueAction, dest="requirements")
3434
parser.add_argument("-e", "--editable", action=AddUniqueAction, dest="editables")
3535
parser.add_argument("-f", "--find-links", action=AddUniqueAction)
36-
parser.add_argument("--no-binary", choices=[":all:", ":none:"]) # TODO: colon separated package names
37-
parser.add_argument("--only-binary", choices=[":all:", ":none:"]) # TODO: colon separated package names
36+
parser.add_argument("--no-binary")
37+
parser.add_argument("--only-binary")
3838
parser.add_argument("--prefer-binary", action="store_true", default=False)
3939
parser.add_argument("--require-hashes", action="store_true", default=False)
4040
parser.add_argument("--pre", action="store_true", default=False)

src/tox/tox_env/python/pip/req/file.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from packaging.requirements import InvalidRequirement, Requirement
1616

1717
from .args import build_parser
18-
from .util import VCS, get_url_scheme, is_url, url_to_path
18+
from .util import VCS, get_url_scheme, handle_binary_option, is_url, url_to_path
1919

2020
# Matches environment variable-style values in '${MY_VARIABLE_1}' with the variable name consisting of only uppercase
2121
# letters, digits or the '_' (underscore). This follows the POSIX standard defined in IEEE Std 1003.1, 2013 Edition.
@@ -341,10 +341,17 @@ def _merge_option_line(self, base_opt: Namespace, opt: Namespace, filename: str)
341341
base_opt.trusted_hosts = []
342342
if host not in base_opt.trusted_hosts:
343343
base_opt.trusted_hosts.append(host)
344+
345+
no_binary = base_opt.no_binary if hasattr(base_opt, "no_binary") else set()
346+
only_binary = base_opt.only_binary if hasattr(base_opt, "only_binary") else set()
344347
if opt.no_binary:
345-
base_opt.no_binary = opt.no_binary
348+
handle_binary_option(opt.no_binary, no_binary, only_binary)
346349
if opt.only_binary:
347-
base_opt.only_binary = opt.only_binary
350+
handle_binary_option(opt.only_binary, only_binary, no_binary)
351+
if no_binary:
352+
base_opt.no_binary = no_binary
353+
if only_binary:
354+
base_opt.only_binary = only_binary
348355

349356
@staticmethod
350357
def _break_args_options(line: str) -> tuple[str, str]:

src/tox/tox_env/python/pip/req/util.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from urllib.parse import urlsplit
55
from urllib.request import url2pathname
66

7+
from packaging.utils import canonicalize_name
8+
79
VCS = ["ftp", "ssh", "git", "hg", "bzr", "sftp", "svn"]
810
VALID_SCHEMAS = ["http", "https", "file"] + VCS
911

@@ -26,3 +28,21 @@ def url_to_path(url: str) -> str:
2628
raise ValueError(f"non-local file URIs are not supported on this platform: {url!r}")
2729
path = url2pathname(netloc + path)
2830
return path
31+
32+
33+
def handle_binary_option(value: str, target: set[str], other: set[str]) -> None:
34+
new = value.split(",")
35+
while ":all:" in new:
36+
other.clear()
37+
target.clear()
38+
target.add(":all:")
39+
del new[: new.index(":all:") + 1]
40+
if ":none:" not in new:
41+
return
42+
for name in new:
43+
if name == ":none:":
44+
target.clear()
45+
continue
46+
name = canonicalize_name(name)
47+
other.discard(name)
48+
target.add(name)

tests/tox_env/python/pip/req/test_file.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,16 +140,43 @@
140140
["--use-feature", "2020-resolver", "--use-feature", "fast-deps"],
141141
id="use-feature multiple duplicate different line",
142142
),
143-
pytest.param("--no-binary :all:", {"no_binary": ":all:"}, [], ["--no-binary", ":all:"], id="no-binary all"),
144-
pytest.param("--no-binary :none:", {"no_binary": ":none:"}, [], ["--no-binary", ":none:"], id="no-binary none"),
145-
pytest.param("--only-binary :all:", {"only_binary": ":all:"}, [], ["--only-binary", ":all:"], id="only-binary all"),
143+
pytest.param("--no-binary :all:", {"no_binary": {":all:"}}, [], ["--no-binary", {":all:"}], id="no-binary all"),
144+
pytest.param("--no-binary :none:", {"no_binary": {":none:"}}, [], [], id="no-binary none"),
145+
pytest.param(
146+
"--only-binary :all:",
147+
{"only_binary": {":all:"}},
148+
[],
149+
["--only-binary", {":all:"}],
150+
id="only-binary all",
151+
),
146152
pytest.param(
147153
"--only-binary :none:",
148-
{"only_binary": ":none:"},
154+
{"only_binary": {":none:"}},
155+
[],
149156
[],
150-
["--only-binary", ":none:"],
151157
id="only-binary none",
152158
),
159+
pytest.param(
160+
"--no-binary=foo --only-binary=foo",
161+
{"only_binary": {"foo"}},
162+
[],
163+
["--only-binary", {"foo"}],
164+
id="no-binary-and-only-binary",
165+
),
166+
pytest.param(
167+
"--no-binary=foo --no-binary=:none:",
168+
{},
169+
[],
170+
[],
171+
id="no-binary-none-last",
172+
),
173+
pytest.param(
174+
"--only-binary=:none: --no-binary=foo",
175+
{"no_binary": {"foo"}},
176+
[],
177+
["--no-binary", {"foo"}],
178+
id="no-binary-none-first",
179+
),
153180
pytest.param("####### example-requirements.txt #######", {}, [], [], id="comment"),
154181
pytest.param("\t##### Requirements without Version Specifiers ######", {}, [], [], id="tab and comment"),
155182
pytest.param(" # start", {}, [], [], id="space and comment"),
@@ -289,7 +316,7 @@ def test_req_file(tmp_path: Path, req: str, opts: dict[str, Any], requirements:
289316
req_file = RequirementsFile(requirements_txt, constraint=False)
290317
assert req_file.as_root_args == as_args
291318
assert str(req_file) == f"-r {requirements_txt}"
292-
assert vars(req_file.options) == opts
319+
assert vars(req_file.options) == (opts if {":none:"} not in opts.values() else {})
293320
found = [str(i) for i in req_file.requirements]
294321
assert found == requirements
295322

0 commit comments

Comments
 (0)