Skip to content

Commit 5330964

Browse files
authored
Merge pull request jazzband#2013 from AndydeCleyre/bugfix/2004
Ensure consistent extras formatting in output
2 parents 347fec5 + 7a1fada commit 5330964

File tree

8 files changed

+82
-20
lines changed

8 files changed

+82
-20
lines changed

piptools/_compat/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from __future__ import annotations
22

33
from .pip_compat import (
4-
PIP_VERSION,
54
Distribution,
65
create_wheel_cache,
76
get_dev_pkgs,
87
parse_requirements,
98
)
109

1110
__all__ = [
12-
"PIP_VERSION",
1311
"Distribution",
1412
"parse_requirements",
1513
"create_wheel_cache",

piptools/_compat/pip_compat.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from dataclasses import dataclass
55
from typing import TYPE_CHECKING, Iterable, Iterator, Set, cast
66

7-
import pip
87
from pip._internal.cache import WheelCache
98
from pip._internal.index.package_finder import PackageFinder
109
from pip._internal.metadata import BaseDistribution
@@ -15,18 +14,17 @@
1514
from pip._internal.req import InstallRequirement
1615
from pip._internal.req import parse_requirements as _parse_requirements
1716
from pip._internal.req.constructors import install_req_from_parsed_requirement
18-
from pip._vendor.packaging.version import parse as parse_version
1917
from pip._vendor.pkg_resources import Requirement
2018

21-
PIP_VERSION = tuple(map(int, parse_version(pip.__version__).base_version.split(".")))
22-
2319
# The Distribution interface has changed between pkg_resources and
2420
# importlib.metadata, so this compat layer allows for a consistent access
2521
# pattern. In pip 22.1, importlib.metadata became the default on Python 3.11
2622
# (and later), but is overridable. `select_backend` returns what's being used.
2723
if TYPE_CHECKING:
2824
from pip._internal.metadata.importlib import Distribution as _ImportLibDist
2925

26+
from ..utils import PIP_VERSION, copy_install_requirement
27+
3028

3129
@dataclass(frozen=True)
3230
class Distribution:
@@ -91,7 +89,7 @@ def parse_requirements(
9189
file_link = FileLink(install_req.link.url)
9290
file_link._url = parsed_req.requirement
9391
install_req.link = file_link
94-
yield install_req
92+
yield copy_install_requirement(install_req)
9593

9694

9795
def create_wheel_cache(cache_dir: str, format_control: str | None = None) -> WheelCache:

piptools/build.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
import build.env
1414
import pyproject_hooks
1515
from pip._internal.req import InstallRequirement
16-
from pip._internal.req.constructors import install_req_from_line, parse_req_from_line
16+
from pip._internal.req.constructors import parse_req_from_line
1717
from pip._vendor.packaging.markers import Marker
1818
from pip._vendor.packaging.requirements import Requirement
1919

20+
from .utils import copy_install_requirement, install_req_from_line
21+
2022
if sys.version_info >= (3, 11):
2123
import tomllib
2224
else:
@@ -229,12 +231,14 @@ def _prepare_requirements(
229231
replaced_package_name = req.replace(package_name, str(package_dir), 1)
230232
parts = parse_req_from_line(replaced_package_name, comes_from)
231233

232-
yield InstallRequirement(
233-
parts.requirement,
234-
comes_from,
235-
link=parts.link,
236-
markers=parts.markers,
237-
extras=parts.extras,
234+
yield copy_install_requirement(
235+
InstallRequirement(
236+
parts.requirement,
237+
comes_from,
238+
link=parts.link,
239+
markers=parts.markers,
240+
extras=parts.extras,
241+
)
238242
)
239243

240244

piptools/resolver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
update_env_context_manager,
1515
)
1616
from pip._internal.req import InstallRequirement
17-
from pip._internal.req.constructors import install_req_from_line
1817
from pip._internal.resolution.resolvelib.base import Candidate
1918
from pip._internal.resolution.resolvelib.candidates import ExtrasCandidate
2019
from pip._internal.resolution.resolvelib.resolver import Resolver
@@ -36,6 +35,7 @@
3635
copy_install_requirement,
3736
format_requirement,
3837
format_specifier,
38+
install_req_from_line,
3939
is_pinned_requirement,
4040
is_url_requirement,
4141
key_from_ireq,

piptools/scripts/compile.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from build import BuildBackendException
1313
from click.utils import LazyFile, safecall
1414
from pip._internal.req import InstallRequirement
15-
from pip._internal.req.constructors import install_req_from_line
1615
from pip._internal.utils.misc import redact_auth_from_url
1716

1817
from .._compat import parse_requirements
@@ -23,7 +22,13 @@
2322
from ..repositories import LocalRequirementsRepository, PyPIRepository
2423
from ..repositories.base import BaseRepository
2524
from ..resolver import BacktrackingResolver, LegacyResolver
26-
from ..utils import dedup, drop_extras, is_pinned_requirement, key_from_ireq
25+
from ..utils import (
26+
dedup,
27+
drop_extras,
28+
install_req_from_line,
29+
is_pinned_requirement,
30+
key_from_ireq,
31+
)
2732
from ..writer import OutputWriter
2833
from . import options
2934
from .options import BuildTargetT

piptools/utils.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import tomli as tomllib
2121

2222
import click
23+
import pip
2324
from click.utils import LazyFile
2425
from pip._internal.req import InstallRequirement
25-
from pip._internal.req.constructors import install_req_from_line
26+
from pip._internal.req.constructors import (
27+
install_req_from_line as _install_req_from_line,
28+
)
2629
from pip._internal.resolution.resolvelib.base import Requirement as PipRequirement
2730
from pip._internal.utils.misc import redact_auth_from_url
2831
from pip._internal.vcs import is_url
@@ -31,9 +34,9 @@
3134
from pip._vendor.packaging.specifiers import SpecifierSet
3235
from pip._vendor.packaging.utils import canonicalize_name
3336
from pip._vendor.packaging.version import Version
37+
from pip._vendor.packaging.version import parse as parse_version
3438
from pip._vendor.pkg_resources import get_distribution
3539

36-
from piptools._compat import PIP_VERSION
3740
from piptools.locations import DEFAULT_CONFIG_FILE_NAMES
3841
from piptools.subprocess_utils import run_python_snippet
3942

@@ -42,6 +45,8 @@
4245
_T = TypeVar("_T")
4346
_S = TypeVar("_S")
4447

48+
PIP_VERSION = tuple(map(int, parse_version(pip.__version__).base_version.split(".")))
49+
4550
UNSAFE_PACKAGES = {"setuptools", "distribute", "pip"}
4651
COMPILE_EXCLUDE_OPTIONS = {
4752
"--dry-run",
@@ -88,6 +93,10 @@ def comment(text: str) -> str:
8893
return click.style(text, fg="green")
8994

9095

96+
def install_req_from_line(*args: Any, **kwargs: Any) -> InstallRequirement:
97+
return copy_install_requirement(_install_req_from_line(*args, **kwargs))
98+
99+
91100
def make_install_requirement(
92101
name: str, version: str | Version, ireq: InstallRequirement
93102
) -> InstallRequirement:
@@ -515,6 +524,10 @@ def copy_install_requirement(
515524
if "req" not in kwargs:
516525
kwargs["req"] = copy.deepcopy(template.req)
517526

527+
kwargs["extras"] = set(map(canonicalize_name, kwargs["extras"]))
528+
if kwargs["req"]:
529+
kwargs["req"].extras = set(kwargs["extras"])
530+
518531
ireq = InstallRequirement(**kwargs)
519532

520533
# If the original_link was None, keep it so. Passing `link` as an

tests/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from pip._vendor.packaging.version import Version
2929
from pip._vendor.pkg_resources import Requirement
3030

31-
from piptools._compat import PIP_VERSION, Distribution
31+
from piptools._compat import Distribution
3232
from piptools.cache import DependencyCache
3333
from piptools.exceptions import NoCandidateFound
3434
from piptools.locations import DEFAULT_CONFIG_FILE_NAMES
@@ -37,6 +37,7 @@
3737
from piptools.repositories.base import BaseRepository
3838
from piptools.resolver import BacktrackingResolver, LegacyResolver
3939
from piptools.utils import (
40+
PIP_VERSION,
4041
as_tuple,
4142
is_url_requirement,
4243
key_from_ireq,

tests/test_cli_compile.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2375,6 +2375,49 @@ def test_combine_different_extras_of_the_same_package(
23752375
)
23762376

23772377

2378+
def test_canonicalize_extras(pip_conf, runner, tmp_path, make_package, make_wheel):
2379+
"""
2380+
Ensure extras are written in a consistent format.
2381+
"""
2382+
pkgs = [
2383+
make_package(
2384+
"fake-sqlalchemy",
2385+
version="0.1",
2386+
extras_require={"fake-postgresql_psycoPG2BINARY": ["fake-greenlet"]},
2387+
),
2388+
make_package(
2389+
"fake-greenlet",
2390+
version="0.2",
2391+
),
2392+
]
2393+
2394+
dists_dir = tmp_path / "dists"
2395+
for pkg in pkgs:
2396+
make_wheel(pkg, dists_dir)
2397+
2398+
with open("requirements.in", "w") as req_in:
2399+
req_in.write("fake-sqlalchemy[FAKE_postgresql-psycopg2binary]\n")
2400+
2401+
out = runner.invoke(
2402+
cli,
2403+
[
2404+
"--output-file",
2405+
"-",
2406+
"--find-links",
2407+
str(dists_dir),
2408+
"--no-header",
2409+
"--no-emit-options",
2410+
"--no-annotate",
2411+
"--no-strip-extras",
2412+
],
2413+
)
2414+
assert out.exit_code == 0
2415+
assert (
2416+
"fake-sqlalchemy[fake-postgresql-psycopg2binary]==0.1"
2417+
in out.stdout.splitlines()
2418+
)
2419+
2420+
23782421
@pytest.mark.parametrize(
23792422
("pkg2_install_requires", "req_in_content", "out_expected_content"),
23802423
(

0 commit comments

Comments
 (0)