Skip to content

Commit 6ac1451

Browse files
authored
stop using requirementslib models (#5793)
* Move away from requirementslib models * Revise test since PEP-440 does not support wildcard versions but does support equivalent compatible release specifiers. * simplify and remove dead code * Ensure the os_name marker is AND with the other markers. * Move what we still need from requirementslib into the pipenv utils and stop vendoring it. * Remove requirementslib. * force upgrade of virtualenv for python 3.12 * remove virtualenv-clone * Update vcs specifiers documentation; infer name from specific pip line formats where possible. * Provide helpful text and error for recently removed commands * Set the right log levels and verbosity to show users the errors generated by pip resolver when supplying -v flag * Fix the collection of all matching package hashes for non-pypi indexes. Plus lesson from testing torch which contains local identifiers.
1 parent be8a084 commit 6ac1451

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+3609
-10321
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
strategy:
7676
fail-fast: false
7777
matrix:
78-
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
78+
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] # "3.12-dev" Windows CI hangs indefinitely
7979
os: [MacOS, Ubuntu, Windows]
8080

8181
steps:

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ myst-parser = {extras = ["linkify"], version = "*"}
2525
invoke = "==2.0.0"
2626
exceptiongroup = "==1.1.0"
2727
tomli = "*"
28+
pyyaml = "==6.0.1"
2829

2930
[packages]
3031
pytz = "*"

Pipfile.lock

Lines changed: 386 additions & 377 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/specifiers.md

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,30 +103,54 @@ All sub-dependencies will get added to the `Pipfile.lock` as well. Sub-dependenc
103103

104104
## VCS Dependencies
105105

106-
VCS dependencies from git and other version control systems using URLs formatted according to the following rule:
106+
VCS dependencies from git and other version control systems using URLs formatted using preferred pip line formats:
107107

108-
<vcs_type>+<scheme>://<location>/<user_or_organization>/<repository>@<branch_or_tag>#egg=<package_name>
108+
<vcs_type>+<scheme>://<location>/<user_or_organization>/<repository>@<branch_or_tag>
109109

110-
The only optional section is the `@<branch_or_tag>` section. When using git over SSH, you may use the shorthand vcs and scheme alias `git+git@<location>:<user_or_organization>/<repository>@<branch_or_tag>#egg=<package_name>`. Note that this is translated to `git+ssh://git@<location>` when parsed.
110+
Extras may be specified using the following format when issuing install command:
111+
112+
<package_name><possible_extras>@ <vcs_type>+<scheme>://<location>/<user_or_organization>/<repository>@<branch_or_tag>
113+
114+
Note: that the #egg fragments should only be used for legacy pip lines which are still required in editable requirements.
115+
116+
$ pipenv install -e git+https://github.com/requests/requests.git@v2.31.0#egg=requests
111117

112-
Note that it is **strongly recommended** that you install any version-controlled dependencies in editable mode, using `pipenv install -e`, in order to ensure that dependency resolution can be performed with an up-to-date copy of the repository each time it is performed, and that it includes all known dependencies.
113118

114119
Below is an example usage which installs the git repository located at `https://github.com/requests/requests.git` from tag `v2.20.1` as package name `requests`:
115120

116121
$ pipenv install -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests
117-
Creating a Pipfile for this project...
118122
Installing -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests...
119-
[...snipped...]
120-
Adding -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests to Pipfile's [packages]...
121-
[...]
123+
Resolving -e git+https://github.com/requests/requests.git@v2.20.1#egg=requests...
124+
Added requests to Pipfile's [packages] ...
125+
Installation Succeeded
126+
Pipfile.lock not found, creating...
127+
Locking [packages] dependencies...
128+
Building requirements...
129+
Resolving dependencies...
130+
Success!
131+
Locking [dev-packages] dependencies...
132+
Updated Pipfile.lock (389441cc656bb774aaa28c7e53a35137aace7499ca01668765d528fa79f8acc8)!
133+
Installing dependencies from Pipfile.lock (f8acc8)...
134+
To activate this project's virtualenv, run pipenv shell.
135+
Alternatively, run a command inside the virtualenv with pipenv run.
122136

123137
$ cat Pipfile
124138
[packages]
125-
requests = {git = "https://github.com/requests/requests.git", editable = true, ref = "v2.20.1"}
139+
requests = {editable = true, ref = "v2.20.1", git = "git+https://github.com/requests/requests.git"}
140+
141+
$ cat Pipfile.lock
142+
...
143+
"requests": {
144+
"editable": true,
145+
"git": "git+https://github.com/requests/requests.git",
146+
"markers": "python_version >= '3.7'",
147+
"ref": "6cfbe1aedd56f8c2f9ff8b968efe65b22669795b"
148+
},
149+
...
126150

127151
Valid values for `<vcs_type>` include `git`, `bzr`, `svn`, and `hg`. Valid values for `<scheme>` include `http`, `https`, `ssh`, and `file`. In specific cases you also have access to other schemes: `svn` may be combined with `svn` as a scheme, and `bzr` can be combined with `sftp` and `lp`.
128152

129-
You can read more about pip's implementation of VCS support `here <https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support>`__. For more information about other options available when specifying VCS dependencies, please check the `Pipfile spec <https://github.com/pypa/pipfile>`_.
153+
You can read more about pip's implementation of VCS support `here <https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support>`__.
130154

131155

132156
## Specifying Package Categories

news/5793.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Drop requirementslib for managing pip lines and InstallRequirements, bring remaining requirementslib functionality into pipenv.
2+
Fixes numerous reports about extras installs with vcs and file installs; format pip lines correctly to not generate deprecation warnings.

pipenv/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,18 @@
33
import sys
44
import warnings
55

6+
# This has to come before imports of pipenv
7+
PIPENV_ROOT = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
8+
PIP_ROOT = os.sep.join([PIPENV_ROOT, "patched", "pip"])
9+
sys.path.insert(0, PIPENV_ROOT)
10+
sys.path.insert(0, PIP_ROOT)
11+
12+
# Load patched pip instead of system pip
13+
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
14+
615

716
def _ensure_modules():
17+
# Can be removed when we drop pydantic
818
spec = importlib.util.spec_from_file_location(
919
"typing_extensions",
1020
location=os.path.join(
@@ -14,6 +24,14 @@ def _ensure_modules():
1424
typing_extensions = importlib.util.module_from_spec(spec)
1525
sys.modules["typing_extensions"] = typing_extensions
1626
spec.loader.exec_module(typing_extensions)
27+
# Ensure when pip gets invoked it uses our patched version
28+
spec = importlib.util.spec_from_file_location(
29+
"pip",
30+
location=os.path.join(os.path.dirname(__file__), "patched", "pip", "__init__.py"),
31+
)
32+
pip = importlib.util.module_from_spec(spec)
33+
sys.modules["pip"] = pip
34+
spec.loader.exec_module(pip)
1735

1836

1937
_ensure_modules()
@@ -26,8 +44,6 @@ def _ensure_modules():
2644
warnings.filterwarnings("ignore", category=ResourceWarning)
2745
warnings.filterwarnings("ignore", category=UserWarning)
2846

29-
# Load patched pip instead of system pip
30-
os.environ["PIP_DISABLE_PIP_VERSION_CHECK"] = "1"
3147

3248
if os.name == "nt":
3349
from pipenv.vendor import colorama

pipenv/cli/command.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def install(state, **kwargs):
216216
requirementstxt=state.installstate.requirementstxt,
217217
pre=state.installstate.pre,
218218
deploy=state.installstate.deploy,
219-
index_url=state.index,
219+
index=state.index,
220220
packages=state.installstate.packages,
221221
editable_packages=state.installstate.editables,
222222
site_packages=state.site_packages,
@@ -610,7 +610,7 @@ def run_open(state, module, *args, **kwargs):
610610
[
611611
state.project._which("python"),
612612
"-c",
613-
"import {0}; print({0}.__file__)".format(module),
613+
f"import {module}; print({module}.__file__)",
614614
]
615615
)
616616
if c.returncode:
@@ -693,11 +693,10 @@ def scripts(state):
693693
scripts = state.project.parsed_pipfile.get("scripts", {})
694694
first_column_width = max(len(word) for word in ["Command"] + list(scripts))
695695
second_column_width = max(len(word) for word in ["Script"] + list(scripts.values()))
696-
lines = ["{0:<{width}} Script".format("Command", width=first_column_width)]
697-
lines.append("{} {}".format("-" * first_column_width, "-" * second_column_width))
696+
lines = [f"{command:<{first_column_width}} Script" for command in ["Command"]]
697+
lines.append(f"{'-' * first_column_width} {'-' * second_column_width}")
698698
lines.extend(
699-
"{0:<{width}} {1}".format(name, script, width=first_column_width)
700-
for name, script in scripts.items()
699+
f"{name:<{first_column_width}} {script}" for name, script in scripts.items()
701700
)
702701
console.print("\n".join(line for line in lines))
703702

pipenv/cli/options.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33

44
from pipenv.project import Project
5+
from pipenv.utils import err
56
from pipenv.utils.internet import is_valid_url
67
from pipenv.vendor.click import (
78
BadArgumentUsage,
@@ -483,6 +484,83 @@ def validate_pypi_mirror(ctx, param, value):
483484
return value
484485

485486

487+
# OLD REMOVED COMMANDS THAT WE STILL DISPLAY HELP TEXT FOR #
488+
def skip_lock_option(f):
489+
def callback(ctx, param, value):
490+
if value:
491+
err.print(
492+
"The flag --skip-lock has been functionally removed. "
493+
"Without running the lock resolver it is not possible to manage multiple package indexes. "
494+
"Additionally it bypassed the build consistency guarantees provided by maintaining a lock file.",
495+
style="yellow bold",
496+
)
497+
raise ValueError("The flag --skip-lock flag has been removed.")
498+
return value
499+
500+
return option(
501+
"--skip-lock",
502+
is_flag=True,
503+
default=False,
504+
expose_value=False,
505+
envvar="PIPENV_SKIP_LOCK",
506+
callback=callback,
507+
type=click_types.BOOL,
508+
show_envvar=True,
509+
hidden=True, # This hides the option from the help text.
510+
)(f)
511+
512+
513+
def keep_outdated_option(f):
514+
def callback(ctx, param, value):
515+
state = ctx.ensure_object(State)
516+
state.installstate.keep_outdated = value
517+
if value:
518+
err.print(
519+
"The flag --keep-outdated has been removed. "
520+
"The flag did not respect package resolver results and lead to inconsistent lock files. "
521+
"Consider using the `pipenv upgrade` command to selectively upgrade packages.",
522+
style="yellow bold",
523+
)
524+
raise ValueError("The flag --keep-outdated flag has been removed.")
525+
return value
526+
527+
return option(
528+
"--keep-outdated",
529+
is_flag=True,
530+
default=False,
531+
expose_value=False,
532+
callback=callback,
533+
type=click_types.BOOL,
534+
show_envvar=True,
535+
hidden=True, # This hides the option from the help text.
536+
)(f)
537+
538+
539+
def selective_upgrade_option(f):
540+
def callback(ctx, param, value):
541+
state = ctx.ensure_object(State)
542+
state.installstate.selective_upgrade = value
543+
if value:
544+
err.print(
545+
"The flag --selective-upgrade has been removed. "
546+
"The flag was buggy and lead to inconsistent lock files. "
547+
"Consider using the `pipenv upgrade` command to selectively upgrade packages.",
548+
style="yellow bold",
549+
)
550+
raise ValueError("The flag --selective-upgrade flag has been removed.")
551+
return value
552+
553+
return option(
554+
"--selective-upgrade",
555+
is_flag=True,
556+
default=False,
557+
type=click_types.BOOL,
558+
help="Update specified packages.",
559+
callback=callback,
560+
expose_value=False,
561+
)(f)
562+
563+
486564
def common_options(f):
487565
f = pypi_mirror_option(f)
488566
f = verbose_option(f)
@@ -496,6 +574,7 @@ def install_base_options(f):
496574
f = common_options(f)
497575
f = pre_option(f)
498576
f = extra_pip_args(f)
577+
f = keep_outdated_option(f) # Removed, but still displayed in help text.
499578
return f
500579

501580

@@ -505,6 +584,7 @@ def uninstall_options(f):
505584
f = uninstall_dev_option(f)
506585
f = editable_option(f)
507586
f = package_arg(f)
587+
f = skip_lock_option(f) # Removed, but still displayed in help text.
508588
return f
509589

510590

@@ -530,6 +610,8 @@ def install_options(f):
530610
f = ignore_pipfile_option(f)
531611
f = editable_option(f)
532612
f = package_arg(f)
613+
f = skip_lock_option(f) # Removed, but still display help text.
614+
f = selective_upgrade_option(f) # Removed, but still display help text.
533615
return f
534616

535617

pipenv/environment.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,23 @@
1212
from pathlib import Path
1313
from sysconfig import get_paths, get_python_version, get_scheme_names
1414
from urllib.parse import urlparse
15-
from urllib.request import url2pathname
1615

1716
import pipenv
1817
from pipenv.patched.pip._internal.commands.install import InstallCommand
1918
from pipenv.patched.pip._internal.index.package_finder import PackageFinder
19+
from pipenv.patched.pip._internal.req.req_install import InstallRequirement
2020
from pipenv.patched.pip._vendor import pkg_resources
21+
from pipenv.patched.pip._vendor.packaging.specifiers import SpecifierSet
2122
from pipenv.patched.pip._vendor.packaging.utils import canonicalize_name
2223
from pipenv.utils import console
24+
from pipenv.utils.constants import VCS_LIST
25+
from pipenv.utils.dependencies import as_pipfile
26+
from pipenv.utils.fileutils import normalize_path, temp_path
2327
from pipenv.utils.funktools import chunked, unnest
2428
from pipenv.utils.indexes import prepare_pip_source_args
2529
from pipenv.utils.processes import subprocess_run
26-
from pipenv.utils.shell import make_posix
30+
from pipenv.utils.shell import make_posix, temp_environ
2731
from pipenv.vendor.pythonfinder.utils import is_in_path
28-
from pipenv.vendor.requirementslib.fileutils import normalize_path, temp_path
29-
from pipenv.vendor.requirementslib.utils import temp_environ
3032

3133
try:
3234
# this is only in Python3.8 and later
@@ -200,11 +202,6 @@ def base_paths(self) -> dict[str, str]:
200202
.. note:: The implementation of this is borrowed from a combination of pip and
201203
virtualenv and is likely to change at some point in the future.
202204
203-
>>> from pipenv.core import project
204-
>>> from pipenv.environment import Environment
205-
>>> env = Environment(prefix=project.virtualenv_location, is_venv=True, sources=project.sources)
206-
>>> import pprint
207-
>>> pprint.pprint(env.base_paths)
208205
{'PATH': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/bin::/bin:/usr/bin',
209206
'PYTHONPATH': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW/lib/python3.7/site-packages',
210207
'data': '/home/hawk/.virtualenvs/pipenv-MfOPs1lW',
@@ -760,38 +757,46 @@ def is_installed(self, pkgname):
760757

761758
return any(d for d in self.get_distributions() if d.project_name == pkgname)
762759

763-
def is_satisfied(self, req):
760+
def is_satisfied(self, req: InstallRequirement):
764761
match = next(
765762
iter(
766763
d
767764
for d in self.get_distributions()
768-
if canonicalize_name(d.project_name) == req.normalized_name
765+
if req.name
766+
and canonicalize_name(d.project_name) == canonicalize_name(req.name)
769767
),
770768
None,
771769
)
772770
if match is not None:
773-
if req.editable and req.line_instance.is_local and self.find_egg(match):
774-
requested_path = req.line_instance.path
771+
if req.editable and req.link and req.link.is_file:
772+
requested_path = req.link.file_path
775773
if os.path.exists(requested_path):
776774
local_path = requested_path
777775
else:
778776
parsed_url = urlparse(requested_path)
779-
local_path = url2pathname(parsed_url.path)
777+
local_path = parsed_url.path
780778
return requested_path and os.path.samefile(local_path, match.location)
781779
elif match.has_metadata("direct_url.json"):
782780
direct_url_metadata = json.loads(match.get_metadata("direct_url.json"))
783-
commit_id = direct_url_metadata.get("vcs_info", {}).get("commit_id", "")
781+
requested_revision = direct_url_metadata.get("vcs_info", {}).get(
782+
"requested_revision", ""
783+
)
784784
vcs_type = direct_url_metadata.get("vcs_info", {}).get("vcs", "")
785-
_, pipfile_part = req.as_pipfile().popitem()
785+
_, pipfile_part = as_pipfile(req).popitem()
786+
vcs_ref = ""
787+
for vcs in VCS_LIST:
788+
if pipfile_part.get(vcs):
789+
vcs_ref = pipfile_part[vcs].rsplit("@", 1)[-1]
790+
break
786791
return (
787-
vcs_type == req.vcs
788-
and commit_id == req.commit_hash
789-
and direct_url_metadata["url"] == pipfile_part[req.vcs]
792+
vcs_type == req.link.scheme
793+
and vcs_ref == requested_revision
794+
and direct_url_metadata["url"] == pipfile_part[req.link.scheme]
790795
)
791-
elif req.is_vcs or req.is_file_or_url:
796+
elif req.link and req.link.is_vcs:
792797
return False
793-
elif req.line_instance.specifiers is not None:
794-
return req.line_instance.specifiers.contains(
798+
elif req.specifier is not None:
799+
return SpecifierSet(str(req.specifier)).contains(
795800
match.version, prereleases=True
796801
)
797802
return True

pipenv/environments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import sys
66

77
from pipenv.patched.pip._vendor.platformdirs import user_cache_dir
8+
from pipenv.utils.fileutils import normalize_drive
89
from pipenv.utils.shell import env_to_bool, is_env_truthy, isatty
9-
from pipenv.vendor.requirementslib.fileutils import normalize_drive
1010

1111
# HACK: avoid resolver.py uses the wrong byte code files.
1212
# I hope I can remove this one day.

0 commit comments

Comments
 (0)