Skip to content

Commit 122692d

Browse files
authored
Merge pull request #13213 from sbidoul/pip-lock-pep751-sbi
PEP 751 experimental `pip lock` command
2 parents ab40476 + 5c7025d commit 122692d

File tree

16 files changed

+953
-4
lines changed

16 files changed

+953
-4
lines changed

docs/html/cli/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ pip_freeze
2323
pip_check
2424
```
2525

26+
```{toctree}
27+
:maxdepth: 1
28+
:caption: Resolving dependencies
29+
30+
pip_lock
31+
```
32+
2633
```{toctree}
2734
:maxdepth: 1
2835
:caption: Handling Distribution Files

docs/html/cli/pip_lock.rst

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
.. _`pip lock`:
3+
4+
========
5+
pip lock
6+
========
7+
8+
9+
10+
Usage
11+
=====
12+
13+
.. tab:: Unix/macOS
14+
15+
.. pip-command-usage:: lock "python -m pip"
16+
17+
.. tab:: Windows
18+
19+
.. pip-command-usage:: lock "py -m pip"
20+
21+
22+
Description
23+
===========
24+
25+
.. pip-command-description:: lock
26+
27+
Options
28+
=======
29+
30+
.. pip-command-options:: lock
31+
32+
.. pip-index-options:: lock
33+
34+
35+
Examples
36+
========
37+
38+
#. Emit a ``pylock.toml`` for the the project in the current directory
39+
40+
.. tab:: Unix/macOS
41+
42+
.. code-block:: shell
43+
44+
python -m pip lock -e .
45+
46+
.. tab:: Windows
47+
48+
.. code-block:: shell
49+
50+
py -m pip lock -e .

docs/man/commands/lock.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
:orphan:
2+
3+
========
4+
pip-lock
5+
========
6+
7+
Description
8+
***********
9+
10+
.. pip-command-description:: lock
11+
12+
Usage
13+
*****
14+
15+
.. pip-command-usage:: lock
16+
17+
Options
18+
*******
19+
20+
.. pip-command-options:: lock

docs/man/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ pip-uninstall(1)
3434
pip-freeze(1)
3535
Output installed packages in requirements format.
3636

37+
pip-lock(1)
38+
Generate a lock file for requirements and their dependencies.
39+
3740
pip-list(1)
3841
List installed packages.
3942

news/tomli-w.vendor.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Vendor tomli-w 1.2.0

src/pip/_internal/commands/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
"InstallCommand",
2424
"Install packages.",
2525
),
26+
"lock": CommandInfo(
27+
"pip._internal.commands.lock",
28+
"LockCommand",
29+
"Generate a lock file.",
30+
),
2631
"download": CommandInfo(
2732
"pip._internal.commands.download",
2833
"DownloadCommand",

src/pip/_internal/commands/lock.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import sys
2+
from optparse import Values
3+
from pathlib import Path
4+
from typing import List
5+
6+
from pip._internal.cache import WheelCache
7+
from pip._internal.cli import cmdoptions
8+
from pip._internal.cli.req_command import (
9+
RequirementCommand,
10+
with_cleanup,
11+
)
12+
from pip._internal.cli.status_codes import SUCCESS
13+
from pip._internal.models.pylock import Pylock, is_valid_pylock_file_name
14+
from pip._internal.operations.build.build_tracker import get_build_tracker
15+
from pip._internal.req.req_install import (
16+
check_legacy_setup_py_options,
17+
)
18+
from pip._internal.utils.logging import getLogger
19+
from pip._internal.utils.misc import (
20+
get_pip_version,
21+
)
22+
from pip._internal.utils.temp_dir import TempDirectory
23+
24+
logger = getLogger(__name__)
25+
26+
27+
class LockCommand(RequirementCommand):
28+
"""
29+
EXPERIMENTAL - Lock packages and their dependencies from:
30+
31+
- PyPI (and other indexes) using requirement specifiers.
32+
- VCS project urls.
33+
- Local project directories.
34+
- Local or remote source archives.
35+
36+
pip also supports locking from "requirements files", which provide an easy
37+
way to specify a whole environment to be installed.
38+
39+
The generated lock file is only guaranteed to be valid for the current
40+
python version and platform.
41+
"""
42+
43+
usage = """
44+
%prog [options] [-e] <local project path> ...
45+
%prog [options] <requirement specifier> [package-index-options] ...
46+
%prog [options] -r <requirements file> [package-index-options] ...
47+
%prog [options] <archive url/path> ..."""
48+
49+
def add_options(self) -> None:
50+
self.cmd_opts.add_option(
51+
cmdoptions.PipOption(
52+
"--output",
53+
"-o",
54+
dest="output_file",
55+
metavar="path",
56+
type="path",
57+
default="pylock.toml",
58+
help="Lock file name (default=pylock.toml). Use - for stdout.",
59+
)
60+
)
61+
self.cmd_opts.add_option(cmdoptions.requirements())
62+
self.cmd_opts.add_option(cmdoptions.constraints())
63+
self.cmd_opts.add_option(cmdoptions.no_deps())
64+
self.cmd_opts.add_option(cmdoptions.pre())
65+
66+
self.cmd_opts.add_option(cmdoptions.editable())
67+
68+
self.cmd_opts.add_option(cmdoptions.src())
69+
70+
self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
71+
self.cmd_opts.add_option(cmdoptions.no_build_isolation())
72+
self.cmd_opts.add_option(cmdoptions.use_pep517())
73+
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
74+
self.cmd_opts.add_option(cmdoptions.check_build_deps())
75+
76+
self.cmd_opts.add_option(cmdoptions.config_settings())
77+
78+
self.cmd_opts.add_option(cmdoptions.no_binary())
79+
self.cmd_opts.add_option(cmdoptions.only_binary())
80+
self.cmd_opts.add_option(cmdoptions.prefer_binary())
81+
self.cmd_opts.add_option(cmdoptions.require_hashes())
82+
self.cmd_opts.add_option(cmdoptions.progress_bar())
83+
84+
index_opts = cmdoptions.make_option_group(
85+
cmdoptions.index_group,
86+
self.parser,
87+
)
88+
89+
self.parser.insert_option_group(0, index_opts)
90+
self.parser.insert_option_group(0, self.cmd_opts)
91+
92+
@with_cleanup
93+
def run(self, options: Values, args: List[str]) -> int:
94+
logger.verbose("Using %s", get_pip_version())
95+
96+
logger.warning(
97+
"pip lock is currently an experimental command. "
98+
"It may be removed/changed in a future release "
99+
"without prior warning."
100+
)
101+
102+
session = self.get_default_session(options)
103+
104+
finder = self._build_package_finder(
105+
options=options,
106+
session=session,
107+
ignore_requires_python=options.ignore_requires_python,
108+
)
109+
build_tracker = self.enter_context(get_build_tracker())
110+
111+
directory = TempDirectory(
112+
delete=not options.no_clean,
113+
kind="install",
114+
globally_managed=True,
115+
)
116+
117+
reqs = self.get_requirements(args, options, finder, session)
118+
check_legacy_setup_py_options(options, reqs)
119+
120+
wheel_cache = WheelCache(options.cache_dir)
121+
122+
# Only when installing is it permitted to use PEP 660.
123+
# In other circumstances (pip wheel, pip download) we generate
124+
# regular (i.e. non editable) metadata and wheels.
125+
for req in reqs:
126+
req.permit_editable_wheels = True
127+
128+
preparer = self.make_requirement_preparer(
129+
temp_build_dir=directory,
130+
options=options,
131+
build_tracker=build_tracker,
132+
session=session,
133+
finder=finder,
134+
use_user_site=False,
135+
verbosity=self.verbosity,
136+
)
137+
resolver = self.make_resolver(
138+
preparer=preparer,
139+
finder=finder,
140+
options=options,
141+
wheel_cache=wheel_cache,
142+
use_user_site=False,
143+
ignore_installed=True,
144+
ignore_requires_python=options.ignore_requires_python,
145+
upgrade_strategy="to-satisfy-only",
146+
use_pep517=options.use_pep517,
147+
)
148+
149+
self.trace_basic_info(finder)
150+
151+
requirement_set = resolver.resolve(reqs, check_supported_wheels=True)
152+
153+
if options.output_file == "-":
154+
base_dir = Path.cwd()
155+
else:
156+
output_file_path = Path(options.output_file)
157+
if not is_valid_pylock_file_name(output_file_path):
158+
logger.warning(
159+
"%s is not a valid lock file name.",
160+
output_file_path,
161+
)
162+
base_dir = output_file_path.parent
163+
pylock_toml = Pylock.from_install_requirements(
164+
requirement_set.requirements.values(), base_dir=base_dir
165+
).as_toml()
166+
if options.output_file == "-":
167+
sys.stdout.write(pylock_toml)
168+
else:
169+
output_file_path.write_text(pylock_toml, encoding="utf-8")
170+
171+
return SUCCESS

0 commit comments

Comments
 (0)