Skip to content

Commit f0e3bba

Browse files
committed
parent 8ae44bc
author Dan Ryan <[email protected]> 1554074378 -0400 committer Dan Ryan <[email protected]> 1558982736 -0400 gpgsig -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEb6jpcpb+5zzDideCsyDQTvvOpJUFAlzsMFAACgkQsyDQTvvO pJWZmRAAtge5wdprlLnKdWUYK5USZb7Uk3zJHi9UIvnO7nKP6UA/L2D/5nxZitvx pZI7cGG+8sLp2yZNtQZdW6LNM1jmRXgzdMLYQh/5zo5gbj6KLOw7erh0FU3L3uM6 wNBNM135Eqt7b+4a4C5TEK2UjwRxBHAsF+3ZzUy+UJQqgQEKxFVxW4gC4yxpfMtL jipE8ludwuOIM88ZJapmLpv2R6adQTxWZedTlmczdsy2/WKGHTCCpWs96PBbntdI pVBmoXfMhgZi+IuGR3iBYU0qS97vjJ8Te9tQZAaB9JGSqv3hHDWo1ht/rrG2RXzp 3i0Cf2vG4035EUh56VYE9FCC9m6Vu3U9iIR34BZG9K5+lDP7pmJmjT+GymEgMP0N GoP3LYUO+dJjMjaUEMsC6QIi6DAots3uk4lxIw3wcA4Im/N/i5xafsRj1Eu3UdBL wBDKMz/FQjH+tD+mnvTlzaxD5vdhhCdBu1gK59rjNMlzg8hz6EF61QbHCaQHd4UI VOGIa8ThLlLI3addxzq/McceAc+OsLJ9hm06jkjvvoIuKrHyE3DybdbYQC7uEwyw 2AvVuMDCPcciYQnkJhNTKmGvPcUDYD7cF91GKcUJKdPdyzDCeFEo5SUTKnLV4Cj2 VD2sdVUk4jnmYfE4pCFvHKYooxQDMKQk/VCKl1c9QHL9/ijCx+o= =b+H1 -----END PGP SIGNATURE----- Ensure resolver doesn't compare editable specifiers - Don't compare versions of editable dependencies when updating using `--keep-outdated` -- editable dependencies will now be updated to the latest version - Ensure we don't drop markers from the lockfile when versions are not updated - Fixes #3656 - Fixes #3659 Signed-off-by: Dan Ryan <[email protected]> Add future import for print function Signed-off-by: Dan Ryan <[email protected]> Handle all possible markers in lockfiles Signed-off-by: Dan Ryan <[email protected]> Fix json import Signed-off-by: Dan Ryan <[email protected]> point to correct reference for lockfile Signed-off-by: Dan Ryan <[email protected]> Fix marker merging errors Signed-off-by: Dan Ryan <[email protected]> Prevent automatically setting `editable=True` - Fixes #3647 Signed-off-by: Dan Ryan <[email protected]> Add new feature toggle for VCS dependency resolution - Fixes #3577 Signed-off-by: Dan Ryan <[email protected]> Fix syntax error Signed-off-by: Dan Ryan <[email protected]> Use string for environment Signed-off-by: Dan Ryan <[email protected]> Fix class name resolution for py27 Signed-off-by: Dan Ryan <[email protected]> Write json files as unicode Signed-off-by: Dan Ryan <[email protected]> Fix resolution with env var Signed-off-by: Dan Ryan <[email protected]>
1 parent b3b6b1f commit f0e3bba

File tree

10 files changed

+84
-17
lines changed

10 files changed

+84
-17
lines changed

news/3577.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added a new environment variable, ``PIPENV_RESOLVE_VCS``, to toggle dependency resolution off for non-editable VCS, file, and URL based dependencies.

news/3647.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Pipenv will no longer inadvertently set ``editable=True`` on all vcs dependencies.

news/3656.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The ``--keep-outdated`` argument to ``pipenv install`` and ``pipenv lock`` will now drop specifier constraints when encountering editable dependencies.
2+
- In addition, ``--keep-outdated`` will retain specifiers that would otherwise be dropped from any entries that have not been updated.

pipenv/core.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,8 @@ def pip_install(
13941394
src_dir = os.environ["PIP_SRC"]
13951395
src = ["--src", os.environ["PIP_SRC"]]
13961396
if not requirement.editable:
1397+
# Leave this off becauase old lockfiles don't have all deps included
1398+
# TODO: When can it be turned back on?
13971399
no_deps = False
13981400

13991401
if src_dir is not None:
@@ -1412,7 +1414,7 @@ def pip_install(
14121414
prefix="pipenv-", suffix="-requirement.txt", dir=requirements_dir,
14131415
delete=False
14141416
)
1415-
line = "-e" if requirement.editable else ""
1417+
line = "-e " if requirement.editable else ""
14161418
if requirement.editable or requirement.name is not None:
14171419
name = requirement.name
14181420
if requirement.extras:
@@ -1640,7 +1642,6 @@ def system_which(command, mult=False):
16401642
return result
16411643

16421644

1643-
16441645
def format_help(help):
16451646
"""Formats the help string."""
16461647
help = help.replace("Options:", str(crayons.normal("Options:", bold=True)))
@@ -1784,8 +1785,8 @@ def do_py(system=False):
17841785
),
17851786
err=True,
17861787
)
1787-
return
1788-
1788+
return
1789+
17891790
try:
17901791
click.echo(which("python", allow_global=system))
17911792
except AttributeError:

pipenv/environments.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,15 @@ def _is_env_truthy(name):
236236
NOTE: This only affects the ``install`` and ``uninstall`` commands.
237237
"""
238238

239+
PIPENV_RESOLVE_VCS = _is_env_truthy(os.environ.get("PIPENV_RESOLVE_VCS", 'true'))
240+
"""Tells Pipenv whether to resolve all VCS dependencies in full.
241+
242+
As of Pipenv 2018.11.26, only editable VCS dependencies were resolved in full.
243+
To retain this behavior and avoid handling any conflicts that arise from the new
244+
approach, you may set this to '0', 'off', or 'false'.
245+
"""
246+
247+
239248
PIPENV_PYUP_API_KEY = os.environ.get(
240249
"PIPENV_PYUP_API_KEY", "1ab8d58f-5122e025-83674263-bc1e79e0"
241250
)

pipenv/patched/piptools/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def clean_requires_python(candidates):
7070
all_candidates = []
7171
py_version = parse_version(os.environ.get('PIP_PYTHON_VERSION', '.'.join(map(str, sys.version_info[:3]))))
7272
for c in candidates:
73-
if c.requires_python:
73+
if getattr(c, "requires_python", None):
7474
# Old specifications had people setting this to single digits
7575
# which is effectively the same as '>=digit,<digit+1'
7676
if c.requires_python.isdigit():

pipenv/resolver.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from __future__ import absolute_import, print_function
14
import json
25
import logging
36
import os
@@ -106,9 +109,14 @@ def __init__(self, name, entry_dict, project, resolver, reverse_deps=None, dev=F
106109
self.pipfile = project.parsed_pipfile.get(pipfile_section, {})
107110
self.lockfile = project.lockfile_content.get(section, {})
108111
self.pipfile_dict = self.pipfile.get(self.pipfile_name, {})
109-
self.lockfile_dict = self.lockfile.get(name, entry_dict)
112+
if self.dev and self.name in project.lockfile_content.get("default", {}):
113+
self.lockfile_dict = project.lockfile_content["default"][name]
114+
else:
115+
self.lockfile_dict = self.lockfile.get(name, entry_dict)
110116
self.resolver = resolver
111117
self.reverse_deps = reverse_deps
118+
self._original_markers = None
119+
self._markers = None
112120
self._entry = None
113121
self._lockfile_entry = None
114122
self._pipfile_entry = None
@@ -232,6 +240,10 @@ def get_cleaned_dict(self, keep_outdated=False):
232240
entry_extras.extend(list(self.lockfile_entry.extras))
233241
self._entry.req.extras = entry_extras
234242
self.entry_dict["extras"] = self.entry.extras
243+
if self.original_markers and not self.markers:
244+
original_markers = self.marker_to_str(self.original_markers)
245+
self.markers = original_markers
246+
self.entry_dict["markers"] = self.marker_to_str(original_markers)
235247
entry_hashes = set(self.entry.hashes)
236248
locked_hashes = set(self.lockfile_entry.hashes)
237249
if entry_hashes != locked_hashes and not self.is_updated:
@@ -248,6 +260,10 @@ def lockfile_entry(self):
248260
self._lockfile_entry = self.make_requirement(self.name, self.lockfile_dict)
249261
return self._lockfile_entry
250262

263+
@lockfile_entry.setter
264+
def lockfile_entry(self, entry):
265+
self._lockfile_entry = entry
266+
251267
@property
252268
def pipfile_entry(self):
253269
if self._pipfile_entry is None:
@@ -359,6 +375,7 @@ def updated_version(self):
359375

360376
@property
361377
def updated_specifier(self):
378+
# type: () -> str
362379
return self.entry.specifiers
363380

364381
@property
@@ -373,7 +390,7 @@ def original_version(self):
373390
return None
374391

375392
def validate_specifiers(self):
376-
if self.is_in_pipfile:
393+
if self.is_in_pipfile and not self.pipfile_entry.editable:
377394
return self.pipfile_entry.requirement.specifier.contains(self.updated_version)
378395
return True
379396

@@ -550,8 +567,11 @@ def validate_constraints(self):
550567
constraint.check_if_exists(False)
551568
except Exception:
552569
from pipenv.exceptions import DependencyConflict
570+
from pipenv.environments import is_verbose
571+
if is_verbose():
572+
print("Tried constraint: {0!r}".format(constraint), file=sys.stderr)
553573
msg = (
554-
"Cannot resolve conflicting version {0}{1} while {1}{2} is "
574+
"Cannot resolve conflicting version {0}{1} while {2}{3} is "
555575
"locked.".format(
556576
self.name, self.updated_specifier, self.old_name, self.old_specifiers
557577
)
@@ -624,6 +644,7 @@ def clean_results(results, resolver, project, dev=False):
624644

625645
def clean_outdated(results, resolver, project, dev=False):
626646
from pipenv.vendor.requirementslib.models.requirements import Requirement
647+
from pipenv.environments import is_verbose
627648
if not project.lockfile_exists:
628649
return results
629650
lockfile = project.lockfile_content

pipenv/utils.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131
import parse
3232

3333
from . import environments
34-
from .exceptions import PipenvUsageError, ResolutionFailure, RequirementError, PipenvCmdError
34+
from .exceptions import (
35+
PipenvUsageError, RequirementError, PipenvCmdError, ResolutionFailure
36+
)
3537
from .pep508checker import lookup
3638
from .vendor.urllib3 import util as urllib3_util
3739

@@ -398,7 +400,6 @@ def parse_line(
398400
):
399401
# type: (...) -> Tuple[Requirement, Dict[str, str], Dict[str, str]]
400402
from .vendor.requirementslib.models.requirements import Requirement
401-
from .exceptions import ResolutionFailure
402403
if index_lookup is None:
403404
index_lookup = {}
404405
if markers_lookup is None:
@@ -444,6 +445,7 @@ def get_deps_from_req(cls, req, resolver=None):
444445
# type: (Requirement, Optional["Resolver"]) -> Tuple[Set[str], Dict[str, Dict[str, Union[str, bool, List[str]]]]]
445446
from .vendor.requirementslib.models.utils import _requirement_to_str_lowercase_name
446447
from .vendor.requirementslib.models.requirements import Requirement
448+
from requirementslib.utils import is_installable_dir
447449
constraints = set() # type: Set[str]
448450
locked_deps = dict() # type: Dict[str, Dict[str, Union[str, bool, List[str]]]]
449451
if (req.is_file_or_url or req.is_vcs) and not req.is_wheel:
@@ -463,7 +465,16 @@ def get_deps_from_req(cls, req, resolver=None):
463465
setup_info = req.req.setup_info
464466
setup_info.get_info()
465467
locked_deps[pep423_name(name)] = entry
466-
requirements = [v for v in getattr(setup_info, "requires", {}).values()]
468+
requirements = []
469+
# Allow users to toggle resolution off for non-editable VCS packages
470+
# but leave it on for local, installable folders on the filesystem
471+
if environments.PIPENV_RESOLVE_VCS or (
472+
req.editable or parsed_line.is_wheel or (
473+
req.is_file_or_url and parsed_line.is_local and
474+
is_installable_dir(parsed_line.path)
475+
)
476+
):
477+
requirements = [v for v in getattr(setup_info, "requires", {}).values()]
467478
for r in requirements:
468479
if getattr(r, "url", None) and not getattr(r, "editable", False):
469480
if r is not None:
@@ -1797,13 +1808,15 @@ def clean_resolved_dep(dep, is_top_level=False, pipfile_entry=None):
17971808

17981809
# If a package is **PRESENT** in the pipfile but has no markers, make sure we
17991810
# **NEVER** include markers in the lockfile
1800-
if "markers" in dep:
1811+
if "markers" in dep and dep.get("markers", "").strip():
18011812
# First, handle the case where there is no top level dependency in the pipfile
18021813
if not is_top_level:
1803-
try:
1804-
lockfile["markers"] = translate_markers(dep)["markers"]
1805-
except TypeError:
1806-
pass
1814+
translated = translate_markers(dep).get("markers", "").strip()
1815+
if translated:
1816+
try:
1817+
lockfile["markers"] = translated
1818+
except TypeError:
1819+
pass
18071820
# otherwise make sure we are prioritizing whatever the pipfile says about the markers
18081821
# If the pipfile says nothing, then we should put nothing in the lockfile
18091822
else:

tasks/vendoring/patches/patched/piptools.patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ index 9b4b4c2..8875543 100644
608608
+ all_candidates = []
609609
+ py_version = parse_version(os.environ.get('PIP_PYTHON_VERSION', '.'.join(map(str, sys.version_info[:3]))))
610610
+ for c in candidates:
611-
+ if c.requires_python:
611+
+ if getattr(c, "requires_python", None):
612612
+ # Old specifications had people setting this to single digits
613613
+ # which is effectively the same as '>=digit,<digit+1'
614614
+ if c.requires_python.isdigit():

tests/integration/test_lock.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import json
14
import os
25
import sys
36

47
import pytest
58

69
from flaky import flaky
710
from vistir.compat import Path
11+
from vistir.misc import to_text
812
from pipenv.utils import temp_environ
913

1014

@@ -122,6 +126,21 @@ def test_keep_outdated_doesnt_upgrade_pipfile_pins(PipenvInstance, pypi):
122126
assert p.lockfile["default"]["urllib3"]["version"] == "==1.21.1"
123127

124128

129+
def test_keep_outdated_keeps_markers_not_removed(PipenvInstance, pypi):
130+
with PipenvInstance(chdir=True, pypi=pypi) as p:
131+
c = p.pipenv("install tablib")
132+
assert c.ok
133+
lockfile = Path(p.lockfile_path)
134+
lockfile_content = lockfile.read_text()
135+
lockfile_json = json.loads(lockfile_content)
136+
assert "tablib" in lockfile_json["default"]
137+
lockfile_json["default"]["tablib"]["markers"] = "python_version >= '2.7'"
138+
lockfile.write_text(to_text(json.dumps(lockfile_json)))
139+
c = p.pipenv("lock --keep-outdated")
140+
assert c.ok
141+
assert p.lockfile["default"]["tablib"].get("markers", "") == "python_version >= '2.7'"
142+
143+
125144
@pytest.mark.lock
126145
@pytest.mark.keep_outdated
127146
def test_keep_outdated_doesnt_update_satisfied_constraints(PipenvInstance, pypi):

0 commit comments

Comments
 (0)