Skip to content

Commit 45e49eb

Browse files
committed
Merge branch 'main' into release/21.2.2
2 parents c4af3fd + 6a22806 commit 45e49eb

22 files changed

+398
-371
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ jobs:
3030
tests:
3131
# Anything that's touching testable stuff
3232
- ".github/workflows/ci.yml"
33-
- "tools/requirements/tests.txt"
3433
- "src/**"
3534
- "tests/**"
3635
if: github.event_name == 'pull_request'

.pre-commit-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ repos:
2626
^src/pip/_internal/index|
2727
^src/pip/_internal/models|
2828
^src/pip/_internal/operations|
29-
^src/pip/_internal/req|
3029
^src/pip/_internal/vcs|
3130
^src/pip/_internal/\w+\.py$|
3231
# Tests

.readthedocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ sphinx:
77
python:
88
version: 3.8
99
install:
10-
- requirements: tools/requirements/docs.txt
10+
- requirements: docs/requirements.txt

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ recursive-include src/pip/_vendor *LICENSE*
1010
recursive-include src/pip/_vendor *COPYING*
1111

1212
include docs/docutils.conf
13+
include docs/requirements.txt
1314

1415
exclude .coveragerc
1516
exclude .mailmap

tools/requirements/docs.txt renamed to docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
sphinx == 3.2.1
1+
sphinx ~= 4.1.0
22
towncrier
33
furo
44
myst_parser

news/10128.removal.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve deprecation warning regarding the copying of source trees when installing from a local directory.

news/10165.trivial.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add a ``feature_flag`` optional kwarg to the ``deprecated()`` function
2+
``pip._internal.utils.deprecation:deprecated``. Also formulate a corresponding canned
3+
message which suggests using the ``--use-feature={feature_flag}`` to test upcoming
4+
behavior.

news/10233.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
New resolver: When a package is specified with extras in constraints, and with
2+
extras in non-constraint requirements, the resolver now correctly identifies the
3+
constraint's existence and avoids backtracking.

noxfile.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
"protected-pip": "tools/tox_pip.py",
2525
}
2626
REQUIREMENTS = {
27-
"docs": "tools/requirements/docs.txt",
28-
"tests": "tools/requirements/tests.txt",
29-
"common-wheels": "tools/requirements/tests-common_wheels.txt",
27+
"docs": "docs/requirements.txt",
28+
"tests": "tests/requirements.txt",
29+
"common-wheels": "tests/requirements-common_wheels.txt",
3030
}
3131

3232
AUTHORS_FILE = "AUTHORS.txt"

src/pip/_internal/operations/prepare.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,14 +216,16 @@ def unpack_url(
216216
# be removed.
217217
if link.is_existing_dir():
218218
deprecated(
219-
"A future pip version will change local packages to be built "
220-
"in-place without first copying to a temporary directory. "
221-
"We recommend you use --use-feature=in-tree-build to test "
222-
"your packages with this new behavior before it becomes the "
223-
"default.\n",
219+
reason=(
220+
"pip copied the source tree into a temporary directory "
221+
"before building it. This is changing so that packages "
222+
"are built in-place "
223+
'within the original source tree ("in-tree build").'
224+
),
224225
replacement=None,
225226
gone_in="21.3",
226-
issue=7555
227+
feature_flag="in-tree-build",
228+
issue=7555,
227229
)
228230
if os.path.isdir(location):
229231
rmtree(location)

src/pip/_internal/req/__init__.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
from .req_set import RequirementSet
1010

1111
__all__ = [
12-
"RequirementSet", "InstallRequirement",
13-
"parse_requirements", "install_given_reqs",
12+
"RequirementSet",
13+
"InstallRequirement",
14+
"parse_requirements",
15+
"install_given_reqs",
1416
]
1517

1618
logger = logging.getLogger(__name__)
@@ -52,20 +54,18 @@ def install_given_reqs(
5254

5355
if to_install:
5456
logger.info(
55-
'Installing collected packages: %s',
56-
', '.join(to_install.keys()),
57+
"Installing collected packages: %s",
58+
", ".join(to_install.keys()),
5759
)
5860

5961
installed = []
6062

6163
with indent_log():
6264
for req_name, requirement in to_install.items():
6365
if requirement.should_reinstall:
64-
logger.info('Attempting uninstall: %s', req_name)
66+
logger.info("Attempting uninstall: %s", req_name)
6567
with indent_log():
66-
uninstalled_pathset = requirement.uninstall(
67-
auto_confirm=True
68-
)
68+
uninstalled_pathset = requirement.uninstall(auto_confirm=True)
6969
else:
7070
uninstalled_pathset = None
7171

src/pip/_internal/req/constructors.py

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,17 @@
3131
from pip._internal.vcs import is_url, vcs
3232

3333
__all__ = [
34-
"install_req_from_editable", "install_req_from_line",
35-
"parse_editable"
34+
"install_req_from_editable",
35+
"install_req_from_line",
36+
"parse_editable",
3637
]
3738

3839
logger = logging.getLogger(__name__)
3940
operators = Specifier._operators.keys()
4041

4142

4243
def _strip_extras(path: str) -> Tuple[str, Optional[str]]:
43-
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
44+
m = re.match(r"^(.+)(\[[^\]]+\])$", path)
4445
extras = None
4546
if m:
4647
path_no_extras = m.group(1)
@@ -74,26 +75,25 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
7475
url_no_extras, extras = _strip_extras(url)
7576

7677
if os.path.isdir(url_no_extras):
77-
setup_py = os.path.join(url_no_extras, 'setup.py')
78-
setup_cfg = os.path.join(url_no_extras, 'setup.cfg')
78+
setup_py = os.path.join(url_no_extras, "setup.py")
79+
setup_cfg = os.path.join(url_no_extras, "setup.cfg")
7980
if not os.path.exists(setup_py) and not os.path.exists(setup_cfg):
8081
msg = (
8182
'File "setup.py" or "setup.cfg" not found. Directory cannot be '
82-
'installed in editable mode: {}'
83-
.format(os.path.abspath(url_no_extras))
83+
"installed in editable mode: {}".format(os.path.abspath(url_no_extras))
8484
)
8585
pyproject_path = make_pyproject_path(url_no_extras)
8686
if os.path.isfile(pyproject_path):
8787
msg += (
8888
'\n(A "pyproject.toml" file was found, but editable '
89-
'mode currently requires a setuptools-based build.)'
89+
"mode currently requires a setuptools-based build.)"
9090
)
9191
raise InstallationError(msg)
9292

9393
# Treating it as code that has already been checked out
9494
url_no_extras = path_to_url(url_no_extras)
9595

96-
if url_no_extras.lower().startswith('file:'):
96+
if url_no_extras.lower().startswith("file:"):
9797
package_name = Link(url_no_extras).egg_fragment
9898
if extras:
9999
return (
@@ -105,18 +105,18 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
105105
return package_name, url_no_extras, set()
106106

107107
for version_control in vcs:
108-
if url.lower().startswith(f'{version_control}:'):
109-
url = f'{version_control}+{url}'
108+
if url.lower().startswith(f"{version_control}:"):
109+
url = f"{version_control}+{url}"
110110
break
111111

112112
link = Link(url)
113113

114114
if not link.is_vcs:
115115
backends = ", ".join(vcs.all_schemes)
116116
raise InstallationError(
117-
f'{editable_req} is not a valid editable requirement. '
118-
f'It should either be a path to a local project or a VCS URL '
119-
f'(beginning with {backends}).'
117+
f"{editable_req} is not a valid editable requirement. "
118+
f"It should either be a path to a local project or a VCS URL "
119+
f"(beginning with {backends})."
120120
)
121121

122122
package_name = link.egg_fragment
@@ -150,21 +150,19 @@ def deduce_helpful_msg(req: str) -> str:
150150
" the packages specified within it."
151151
).format(req)
152152
except RequirementParseError:
153-
logger.debug(
154-
"Cannot parse '%s' as requirements file", req, exc_info=True
155-
)
153+
logger.debug("Cannot parse '%s' as requirements file", req, exc_info=True)
156154
else:
157155
msg += f" File '{req}' does not exist."
158156
return msg
159157

160158

161159
class RequirementParts:
162160
def __init__(
163-
self,
164-
requirement: Optional[Requirement],
165-
link: Optional[Link],
166-
markers: Optional[Marker],
167-
extras: Set[str],
161+
self,
162+
requirement: Optional[Requirement],
163+
link: Optional[Link],
164+
markers: Optional[Marker],
165+
extras: Set[str],
168166
):
169167
self.requirement = requirement
170168
self.link = link
@@ -258,24 +256,23 @@ def _get_url_from_path(path: str, name: str) -> Optional[str]:
258256
return None
259257
if os.path.isfile(path):
260258
return path_to_url(path)
261-
urlreq_parts = name.split('@', 1)
259+
urlreq_parts = name.split("@", 1)
262260
if len(urlreq_parts) >= 2 and not _looks_like_path(urlreq_parts[0]):
263261
# If the path contains '@' and the part before it does not look
264262
# like a path, try to treat it as a PEP 440 URL req instead.
265263
return None
266264
logger.warning(
267-
'Requirement %r looks like a filename, but the '
268-
'file does not exist',
269-
name
265+
"Requirement %r looks like a filename, but the file does not exist",
266+
name,
270267
)
271268
return path_to_url(path)
272269

273270

274271
def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementParts:
275272
if is_url(name):
276-
marker_sep = '; '
273+
marker_sep = "; "
277274
else:
278-
marker_sep = ';'
275+
marker_sep = ";"
279276
if marker_sep in name:
280277
name, markers_as_string = name.split(marker_sep, 1)
281278
markers_as_string = markers_as_string.strip()
@@ -302,9 +299,8 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
302299
# it's a local file, dir, or url
303300
if link:
304301
# Handle relative file URLs
305-
if link.scheme == 'file' and re.search(r'\.\./', link.url):
306-
link = Link(
307-
path_to_url(os.path.normpath(os.path.abspath(link.path))))
302+
if link.scheme == "file" and re.search(r"\.\./", link.url):
303+
link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path))))
308304
# wheel file
309305
if link.is_wheel:
310306
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
@@ -323,7 +319,7 @@ def parse_req_from_line(name: str, line_source: Optional[str]) -> RequirementPar
323319
def with_source(text: str) -> str:
324320
if not line_source:
325321
return text
326-
return f'{text} (from {line_source})'
322+
return f"{text} (from {line_source})"
327323

328324
def _parse_req_string(req_as_string: str) -> Requirement:
329325
try:
@@ -332,16 +328,15 @@ def _parse_req_string(req_as_string: str) -> Requirement:
332328
if os.path.sep in req_as_string:
333329
add_msg = "It looks like a path."
334330
add_msg += deduce_helpful_msg(req_as_string)
335-
elif ('=' in req_as_string and
336-
not any(op in req_as_string for op in operators)):
331+
elif "=" in req_as_string and not any(
332+
op in req_as_string for op in operators
333+
):
337334
add_msg = "= is not a valid operator. Did you mean == ?"
338335
else:
339-
add_msg = ''
340-
msg = with_source(
341-
f'Invalid requirement: {req_as_string!r}'
342-
)
336+
add_msg = ""
337+
msg = with_source(f"Invalid requirement: {req_as_string!r}")
343338
if add_msg:
344-
msg += f'\nHint: {add_msg}'
339+
msg += f"\nHint: {add_msg}"
345340
raise InstallationError(msg)
346341
else:
347342
# Deprecate extras after specifiers: "name>=1.0[extras]"
@@ -350,7 +345,7 @@ def _parse_req_string(req_as_string: str) -> Requirement:
350345
# RequirementParts
351346
for spec in req.specifier:
352347
spec_str = str(spec)
353-
if spec_str.endswith(']'):
348+
if spec_str.endswith("]"):
354349
msg = f"Extras after version '{spec_str}'."
355350
raise InstallationError(msg)
356351
return req
@@ -382,8 +377,12 @@ def install_req_from_line(
382377
parts = parse_req_from_line(name, line_source)
383378

384379
return InstallRequirement(
385-
parts.requirement, comes_from, link=parts.link, markers=parts.markers,
386-
use_pep517=use_pep517, isolated=isolated,
380+
parts.requirement,
381+
comes_from,
382+
link=parts.link,
383+
markers=parts.markers,
384+
use_pep517=use_pep517,
385+
isolated=isolated,
387386
install_options=options.get("install_options", []) if options else [],
388387
global_options=options.get("global_options", []) if options else [],
389388
hash_options=options.get("hashes", {}) if options else {},
@@ -409,8 +408,12 @@ def install_req_from_req_string(
409408
PyPI.file_storage_domain,
410409
TestPyPI.file_storage_domain,
411410
]
412-
if (req.url and comes_from and comes_from.link and
413-
comes_from.link.netloc in domains_not_allowed):
411+
if (
412+
req.url
413+
and comes_from
414+
and comes_from.link
415+
and comes_from.link.netloc in domains_not_allowed
416+
):
414417
# Explicitly disallow pypi packages that depend on external urls
415418
raise InstallationError(
416419
"Packages installed from PyPI cannot depend on packages "

0 commit comments

Comments
 (0)