Skip to content

Commit

Permalink
Print warning to stderr if package not found
Browse files Browse the repository at this point in the history
  • Loading branch information
alanhamlett committed Aug 13, 2023
1 parent c353606 commit e11bae6
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 11 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ You can also use Pur directly from Python:
file.
-d, --dry-run Output changes to STDOUT instead of overwriting the
requirements.txt file.
--dry-run-changed When running with --dry-run, only output packages
with updates, not packages that are already the
latest.
--dry-run-changed Enable dry run and only output packages with
updates, not packages that are already the latest.
-n, --no-recursive Prevents updating nested requirements files.
--skip TEXT Comma separated list of packages to skip updating.
--skip-gt Skip updating packages using > or >= spec, to allow
Expand Down
27 changes: 21 additions & 6 deletions pur/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from collections import defaultdict

import click
from click import echo as _echo
from click import secho as _echo

try:
from StringIO import StringIO
Expand Down Expand Up @@ -45,7 +45,7 @@
handle_line)

from .__about__ import __version__
from .exceptions import StopUpdating
from .exceptions import InvalidPackage, StopUpdating
from .utils import (ExitCodeException, build_package_finder, can_check_version,
current_version, format_list_arg, join_lines,
latest_version, old_version, requirements_line,
Expand Down Expand Up @@ -140,8 +140,8 @@ def pur(**options):
minor=options['minor'],
patch=options['patch'],
pre=options['pre'],
dry_run=options['dry_run'],
dry_run_changed=options['dry_run'] and options['dry_run_changed'],
dry_run=options['dry_run'] or options['dry_run_changed'],
dry_run_changed=options['dry_run_changed'],
no_recursive=options['no_recursive'],
echo=options['echo'],
index_urls=options['index_url'],
Expand All @@ -156,7 +156,7 @@ def pur(**options):
raise ExitCodeException(70, message=traceback.format_exc().rstrip())
raise

if not options['dry_run']:
if not options['dry_run'] and not options['dry_run_changed']:
_echo('All requirements up-to-date.')

if options['nonzero_exit_code'] and PUR_GLOBAL_UPDATED > 0:
Expand Down Expand Up @@ -391,7 +391,22 @@ def _get_requirements_and_latest(filename, updates=[], force=False,
continue
spec_ver = current_version(install_req)
if spec_ver or force:
latest_ver = latest_version(install_req, spec_ver, finder, minor=minor, patch=patch, pre=pre)
try:
latest_ver = latest_version(install_req, spec_ver, finder, minor=minor, patch=patch, pre=pre)
except InvalidPackage:
latest_ver = None

# output warning for invalid package
if not parsed_req.is_editable:
_echo(
'No matching distribution found for {req_name} from {comes_from}'.format(
req_name=parsed_req.requirement,
comes_from=parsed_req.comes_from,
),
err=True,
fg='red',
)

yield (orig_line, install_req, spec_ver, latest_ver)


Expand Down
5 changes: 5 additions & 0 deletions pur/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
"""


class InvalidPackage(Exception):
"""The package has no releases."""
pass


class StopUpdating(Exception):
"""Stop updating a requirements file and exit."""
pass
5 changes: 4 additions & 1 deletion pur/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pip._internal.req.req_file import COMMENT_RE
from pip._vendor.packaging.version import InvalidVersion, Version, parse

from .exceptions import StopUpdating
from .exceptions import InvalidPackage, StopUpdating


def build_package_finder(session=None, index_urls=[]):
Expand Down Expand Up @@ -141,6 +141,7 @@ def old_version(spec_ver):

def latest_version(req, spec_ver, finder, minor=[], patch=[], pre=[]):
"""Returns a Version instance with the latest version for the package.
Raises InvalidPackage error if no candidates available.
:param req: Instance of pip.req.req_install.InstallRequirement.
:param spec_ver: Tuple of current versions from the requirements file.
Expand All @@ -156,6 +157,8 @@ def latest_version(req, spec_ver, finder, minor=[], patch=[], pre=[]):
return None

all_candidates = finder.find_all_candidates(req.name)
if len(all_candidates) == 0:
raise InvalidPackage()

if req.name.lower() in patch or '*' in patch:
all_candidates = [c for c in all_candidates
Expand Down
1 change: 1 addition & 0 deletions tests/samples/results/test_dry_run_changed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
flask==0.10.1
6 changes: 6 additions & 0 deletions tests/samples/results/test_dry_run_invalid_package
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
flask==0.9
flask==12.0
# an empty line below

-e git://github.com/kennethreitz/inbox.py.git@551b4f44b144564504c687cebdb4c543cb8e9adf#egg=inbox
flask
88 changes: 87 additions & 1 deletion tests/test_pur.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,10 @@ def test_invalid_package(self):
mock_find_all_candidates.return_value = []

result = self.runner.invoke(pur, args)
expected_output = "All requirements up-to-date.\n"
expected_output = 'No matching distribution found for flask==0.9 from -r ' + tmpfile + ' (line 1)\n' + \
'No matching distribution found for flask==12.0 from -r ' + tmpfile + ' (line 2)\n' + \
'No matching distribution found for flask from -r ' + tmpfile + ' (line 6)\n' + \
'All requirements up-to-date.\n'
self.assertEqual(u(result.output), u(expected_output))
self.assertIsNone(result.exception)
self.assertEqual(result.exit_code, 0)
Expand Down Expand Up @@ -873,6 +876,89 @@ def test_dry_run_with_nested_requirements_file(self):
expected_requirements = open('tests/samples/requirements-nested.txt').read()
self.assertEqual(open(requirements_nested).read(), expected_requirements)

def test_dry_run_invalid_package(self):
requirements = 'tests/samples/requirements.txt'
tempdir = tempfile.mkdtemp()
tmpfile = os.path.join(tempdir, 'requirements.txt')
shutil.copy(requirements, tmpfile)
args = ['-r', tmpfile, '--dry-run']

with patch('pip._internal.index.package_finder.PackageFinder.find_all_candidates') as mock_find_all_candidates:
mock_find_all_candidates.return_value = []

result = self.runner.invoke(pur, args)
expected_output = 'No matching distribution found for flask==0.9 from -r ' + tmpfile + ' (line 1)\n' + \
'No matching distribution found for flask==12.0 from -r ' + tmpfile + ' (line 2)\n' + \
'No matching distribution found for flask from -r ' + tmpfile + ' (line 6)\n' + \
'==> ' + tmpfile + ' <==\n' + \
open('tests/samples/results/test_dry_run_invalid_package').read() + "\n"
self.assertEqual(u(result.output), u(expected_output))
self.assertIsNone(result.exception)
self.assertEqual(result.exit_code, 0)
self.assertEqual(open(tmpfile).read(), open(requirements).read())

def test_dry_run_changed(self):
requirements = 'tests/samples/requirements.txt'
tempdir = tempfile.mkdtemp()
tmpfile = os.path.join(tempdir, 'requirements.txt')
shutil.copy(requirements, tmpfile)
args = ['-r', tmpfile, '--dry-run-changed']

with patch('pip._internal.index.package_finder.PackageFinder.find_all_candidates') as mock_find_all_candidates:
project = 'flask'
version = '0.10.1'
link = Link('')
candidate = InstallationCandidate(project, version, link)
mock_find_all_candidates.return_value = [candidate]

result = self.runner.invoke(pur, args)
self.assertIsNone(result.exception)
expected_output = '==> ' + tmpfile + ' <==\n' + \
open('tests/samples/results/test_dry_run_changed').read()
self.assertEqual(u(result.output), u(expected_output))
self.assertEqual(result.exit_code, 0)
self.assertEqual(open(tmpfile).read(), open(requirements).read())

def test_dry_run_changed_no_updates(self):
requirements = 'tests/samples/requirements-up-to-date.txt'
tempdir = tempfile.mkdtemp()
tmpfile = os.path.join(tempdir, 'requirements.txt')
shutil.copy(requirements, tmpfile)
args = ['-r', tmpfile, '--dry-run-changed']

with patch('pip._internal.index.package_finder.PackageFinder.find_all_candidates') as mock_find_all_candidates:
project = 'flask'
version = '0.10.1'
link = Link('')
candidate = InstallationCandidate(project, version, link)
mock_find_all_candidates.return_value = [candidate]

result = self.runner.invoke(pur, args)
self.assertIsNone(result.exception)
expected_output = ''
self.assertEqual(u(result.output), u(expected_output))
self.assertEqual(result.exit_code, 0)
self.assertEqual(open(tmpfile).read(), open(requirements).read())

def test_dry_run_changed_invalid_package(self):
requirements = 'tests/samples/requirements.txt'
tempdir = tempfile.mkdtemp()
tmpfile = os.path.join(tempdir, 'requirements.txt')
shutil.copy(requirements, tmpfile)
args = ['-r', tmpfile, '--dry-run-changed']

with patch('pip._internal.index.package_finder.PackageFinder.find_all_candidates') as mock_find_all_candidates:
mock_find_all_candidates.return_value = []

result = self.runner.invoke(pur, args)
expected_output = 'No matching distribution found for flask==0.9 from -r ' + tmpfile + ' (line 1)\n' + \
'No matching distribution found for flask==12.0 from -r ' + tmpfile + ' (line 2)\n' + \
'No matching distribution found for flask from -r ' + tmpfile + ' (line 6)\n'
self.assertEqual(u(result.output), u(expected_output))
self.assertIsNone(result.exception)
self.assertEqual(result.exit_code, 0)
self.assertEqual(open(tmpfile).read(), open(requirements).read())

def test_updates_from_alt_index_url(self):
requirements = 'tests/samples/requirements-with-alt-index-url.txt'
tempdir = tempfile.mkdtemp()
Expand Down

0 comments on commit e11bae6

Please sign in to comment.