Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/+code-quality-reliability.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fixed corrupt Pipfile and lockfile errors so they retain the affected path and
backup location while reporting the file-specific error message.
3 changes: 2 additions & 1 deletion pipenv/routines/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ def run_pep508_check(project, system, python):


def check_pep508_requirements(project, results, quiet):
p = plette.Pipfile.load(open(project.pipfile_location))
with open(project.pipfile_location) as pipfile:
p = plette.Pipfile.load(pipfile)
p = plette.Lockfile.with_meta_from(p)
failed = False

Expand Down
3 changes: 2 additions & 1 deletion pipenv/routines/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ def run_pep508_check(project, system, python):

def check_pep508_requirements(project, results, quiet):
"""Verify PEP 508 environment markers in Pipfile match the current environment."""
p = plette.Pipfile.load(open(project.pipfile_location))
with open(project.pipfile_location) as pipfile:
p = plette.Pipfile.load(pipfile)
p = plette.Lockfile.with_meta_from(p)
failed = False

Expand Down
12 changes: 2 additions & 10 deletions pipenv/utils/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@ def show(self):

class LockfileCorruptException(FileCorruptException):
def __init__(self, path, backup_path=None):
self.message = self.get_message(path, backup_path=backup_path)
super().__init__(self.message)
super().__init__(path, backup_path=backup_path)

def get_message(self, path, backup_path=None):
message = f"ERROR: Failed to load lockfile at {path}"
Expand All @@ -68,14 +67,10 @@ def get_message(self, path, backup_path=None):
message = f"{message}\nYour lockfile is corrupt, {msg}"
return message

def show(self, path, backup_path=None):
print(self.message, file=sys.stderr, flush=True)


class PipfileCorruptException(FileCorruptException):
def __init__(self, path, backup_path=None):
self.message = self.get_message(path, backup_path=backup_path)
super().__init__(self.message)
super().__init__(path, backup_path=backup_path)

def get_message(self, path, backup_path=None):
message = f"ERROR: Failed to load Pipfile at {path}"
Expand All @@ -86,9 +81,6 @@ def get_message(self, path, backup_path=None):
message = f"{message}\nYour Pipfile is corrupt, {msg}"
return message

def show(self, path, backup_path=None):
print(self.message, file=sys.stderr, flush=True)


class PipfileNotFound(FileNotFoundError):
def __init__(self, path, *args, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion pipenv/utils/locking.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ def load(cls, path: Optional[str], create: bool = True) -> "Lockfile":
backup_path = f"{formatted_path}.bak"

# Show error and create backup
LockfileCorruptException.show(formatted_path, backup_path=backup_path)
LockfileCorruptException(formatted_path, backup_path=backup_path).show()
path_obj.rename(backup_path)

# Try loading again after backing up corrupted file
Expand Down
6 changes: 4 additions & 2 deletions tests/integration/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ def test_pipfile_envvar_expansion(pipenv_instance_pypi):
os.environ["TEST_HOST"] = "localhost:5000"
project = Project()
assert project.sources.all[0]["url"] == "https://localhost:5000/simple"
assert "localhost:5000" not in str(Pipfile.load(open(p.pipfile_path)))
print(str(Pipfile.load(open(p.pipfile_path))))
with open(p.pipfile_path) as pipfile:
loaded = Pipfile.load(pipfile)
assert "localhost:5000" not in str(loaded)
print(str(loaded))


@pytest.mark.project
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/test_routine_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,14 @@ class TestFromCliKeywordOnly:
def test_from_cli_positional_raises(self):
# The first positional after cls would land on `system`.
with pytest.raises(TypeError):
# Deliberately violate the keyword-only API to test its contract.
# codeql[py/call/wrong-arguments]
RoutineContext.from_cli(True) # type: ignore[misc]

def test_from_cli_two_positionals_raises(self):
with pytest.raises(TypeError):
# Deliberately violate the keyword-only API to test its contract.
# codeql[py/call/wrong-arguments]
RoutineContext.from_cli(True, False) # type: ignore[misc]


Expand Down
28 changes: 28 additions & 0 deletions tests/unit/test_utils_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from pipenv.utils.exceptions import (
LockfileCorruptException,
PipfileCorruptException,
)


def test_lockfile_corrupt_exception_preserves_specific_message(capsys, tmp_path):
path = tmp_path / "Pipfile.lock"
backup_path = tmp_path / "Pipfile.lock.bak"

error = LockfileCorruptException(path, backup_path=backup_path)
error.show()

assert "Failed to load lockfile" in error.message
assert str(path) in error.message
assert str(backup_path) in error.message
assert error.message in capsys.readouterr().err


def test_pipfile_corrupt_exception_preserves_specific_message(capsys, tmp_path):
path = tmp_path / "Pipfile"

error = PipfileCorruptException(path)
error.show()

assert "Failed to load Pipfile" in error.message
assert str(path) in error.message
assert error.message in capsys.readouterr().err