-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Implement new command 'pip index versions' #8978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
uranusjr
merged 6 commits into
pypa:main
from
NoahGorny:pip-get-all-versions-of-package
Jun 11, 2021
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
87560e8
search: Split info into print_dist_installation_info
e2a9ba3
commands: Add new "index" command with "versions" subcommand
3752753
index: Add news entry for new subcommand "pip index"
363a871
commands: index: Warn users that this is an experimental command
0e744bc
tests: functional: index: Add tests for "versions" subcommand
25ffadf
tests: unit: Fix test_command.py after new command addition
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Add new subcommand ``pip index`` used to interact with indexes, and implement | ||
``pip index version`` to list available versions of a package. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import logging | ||
from optparse import Values | ||
from typing import Any, Iterable, List, Optional, Union | ||
|
||
from pip._vendor.packaging.version import LegacyVersion, Version | ||
|
||
from pip._internal.cli import cmdoptions | ||
from pip._internal.cli.req_command import IndexGroupCommand | ||
from pip._internal.cli.status_codes import ERROR, SUCCESS | ||
from pip._internal.commands.search import print_dist_installation_info | ||
from pip._internal.exceptions import CommandError, DistributionNotFound, PipError | ||
from pip._internal.index.collector import LinkCollector | ||
from pip._internal.index.package_finder import PackageFinder | ||
from pip._internal.models.selection_prefs import SelectionPreferences | ||
from pip._internal.models.target_python import TargetPython | ||
from pip._internal.network.session import PipSession | ||
from pip._internal.utils.misc import write_output | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class IndexCommand(IndexGroupCommand): | ||
""" | ||
Inspect information available from package indexes. | ||
""" | ||
|
||
usage = """ | ||
%prog versions <package> | ||
""" | ||
|
||
def add_options(self): | ||
# type: () -> None | ||
cmdoptions.add_target_python_options(self.cmd_opts) | ||
|
||
self.cmd_opts.add_option(cmdoptions.ignore_requires_python()) | ||
self.cmd_opts.add_option(cmdoptions.pre()) | ||
self.cmd_opts.add_option(cmdoptions.no_binary()) | ||
self.cmd_opts.add_option(cmdoptions.only_binary()) | ||
|
||
index_opts = cmdoptions.make_option_group( | ||
cmdoptions.index_group, | ||
self.parser, | ||
) | ||
|
||
self.parser.insert_option_group(0, index_opts) | ||
self.parser.insert_option_group(0, self.cmd_opts) | ||
|
||
def run(self, options, args): | ||
uranusjr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# type: (Values, List[Any]) -> int | ||
handlers = { | ||
"versions": self.get_available_package_versions, | ||
} | ||
|
||
logger.warning( | ||
"pip index is currently an experimental command. " | ||
"It may be removed/changed in a future release " | ||
"without prior warning." | ||
) | ||
|
||
# Determine action | ||
if not args or args[0] not in handlers: | ||
logger.error( | ||
"Need an action (%s) to perform.", | ||
", ".join(sorted(handlers)), | ||
) | ||
return ERROR | ||
|
||
action = args[0] | ||
|
||
# Error handling happens here, not in the action-handlers. | ||
try: | ||
handlers[action](options, args[1:]) | ||
except PipError as e: | ||
logger.error(e.args[0]) | ||
return ERROR | ||
|
||
return SUCCESS | ||
|
||
def _build_package_finder( | ||
self, | ||
options, # type: Values | ||
session, # type: PipSession | ||
target_python=None, # type: Optional[TargetPython] | ||
ignore_requires_python=None, # type: Optional[bool] | ||
): | ||
# type: (...) -> PackageFinder | ||
""" | ||
Create a package finder appropriate to the index command. | ||
""" | ||
link_collector = LinkCollector.create(session, options=options) | ||
|
||
# Pass allow_yanked=False to ignore yanked versions. | ||
selection_prefs = SelectionPreferences( | ||
allow_yanked=False, | ||
allow_all_prereleases=options.pre, | ||
ignore_requires_python=ignore_requires_python, | ||
) | ||
|
||
return PackageFinder.create( | ||
link_collector=link_collector, | ||
selection_prefs=selection_prefs, | ||
target_python=target_python, | ||
) | ||
|
||
def get_available_package_versions(self, options, args): | ||
# type: (Values, List[Any]) -> None | ||
if len(args) != 1: | ||
raise CommandError('You need to specify exactly one argument') | ||
|
||
target_python = cmdoptions.make_target_python(options) | ||
query = args[0] | ||
|
||
with self._build_session(options) as session: | ||
finder = self._build_package_finder( | ||
options=options, | ||
session=session, | ||
target_python=target_python, | ||
ignore_requires_python=options.ignore_requires_python, | ||
) | ||
|
||
versions: Iterable[Union[LegacyVersion, Version]] = ( | ||
candidate.version | ||
for candidate in finder.find_all_candidates(query) | ||
) | ||
|
||
if not options.pre: | ||
# Remove prereleases | ||
versions = (version for version in versions | ||
if not version.is_prerelease) | ||
versions = set(versions) | ||
|
||
if not versions: | ||
raise DistributionNotFound( | ||
'No matching distribution found for {}'.format(query)) | ||
|
||
formatted_versions = [str(ver) for ver in sorted( | ||
versions, reverse=True)] | ||
latest = formatted_versions[0] | ||
|
||
write_output('{} ({})'.format(query, latest)) | ||
write_output('Available versions: {}'.format( | ||
', '.join(formatted_versions))) | ||
print_dist_installation_info(query, latest) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import pytest | ||
|
||
from pip._internal.cli.status_codes import ERROR, SUCCESS | ||
from pip._internal.commands import create_command | ||
|
||
|
||
@pytest.mark.network | ||
def test_list_all_versions_basic_search(script): | ||
""" | ||
End to end test of index versions command. | ||
""" | ||
output = script.pip('index', 'versions', 'pip', allow_stderr_warning=True) | ||
assert 'Available versions:' in output.stdout | ||
assert ( | ||
'20.2.3, 20.2.2, 20.2.1, 20.2, 20.1.1, 20.1, 20.0.2' | ||
', 20.0.1, 19.3.1, 19.3, 19.2.3, 19.2.2, 19.2.1, 19.2, 19.1.1' | ||
', 19.1, 19.0.3, 19.0.2, 19.0.1, 19.0, 18.1, 18.0, 10.0.1, 10.0.0, ' | ||
'9.0.3, 9.0.2, 9.0.1, 9.0.0, 8.1.2, 8.1.1, ' | ||
'8.1.0, 8.0.3, 8.0.2, 8.0.1, 8.0.0, 7.1.2, 7.1.1, 7.1.0, 7.0.3, ' | ||
'7.0.2, 7.0.1, 7.0.0, 6.1.1, 6.1.0, 6.0.8, 6.0.7, 6.0.6, 6.0.5, ' | ||
'6.0.4, 6.0.3, 6.0.2, 6.0.1, 6.0, 1.5.6, 1.5.5, 1.5.4, 1.5.3, ' | ||
'1.5.2, 1.5.1, 1.5, 1.4.1, 1.4, 1.3.1, 1.3, 1.2.1, 1.2, 1.1, 1.0.2,' | ||
' 1.0.1, 1.0, 0.8.3, 0.8.2, 0.8.1, 0.8, 0.7.2, 0.7.1, 0.7, 0.6.3, ' | ||
'0.6.2, 0.6.1, 0.6, 0.5.1, 0.5, 0.4, 0.3.1, ' | ||
'0.3, 0.2.1, 0.2' in output.stdout | ||
) | ||
|
||
|
||
@pytest.mark.network | ||
def test_list_all_versions_search_with_pre(script): | ||
""" | ||
See that adding the --pre flag adds pre-releases | ||
""" | ||
output = script.pip( | ||
'index', 'versions', 'pip', '--pre', allow_stderr_warning=True) | ||
assert 'Available versions:' in output.stdout | ||
assert ( | ||
'20.2.3, 20.2.2, 20.2.1, 20.2, 20.2b1, 20.1.1, 20.1, 20.1b1, 20.0.2' | ||
', 20.0.1, 19.3.1, 19.3, 19.2.3, 19.2.2, 19.2.1, 19.2, 19.1.1' | ||
', 19.1, 19.0.3, 19.0.2, 19.0.1, 19.0, 18.1, 18.0, 10.0.1, 10.0.0, ' | ||
'10.0.0b2, 10.0.0b1, 9.0.3, 9.0.2, 9.0.1, 9.0.0, 8.1.2, 8.1.1, ' | ||
'8.1.0, 8.0.3, 8.0.2, 8.0.1, 8.0.0, 7.1.2, 7.1.1, 7.1.0, 7.0.3, ' | ||
'7.0.2, 7.0.1, 7.0.0, 6.1.1, 6.1.0, 6.0.8, 6.0.7, 6.0.6, 6.0.5, ' | ||
'6.0.4, 6.0.3, 6.0.2, 6.0.1, 6.0, 1.5.6, 1.5.5, 1.5.4, 1.5.3, ' | ||
'1.5.2, 1.5.1, 1.5, 1.4.1, 1.4, 1.3.1, 1.3, 1.2.1, 1.2, 1.1, 1.0.2,' | ||
' 1.0.1, 1.0, 0.8.3, 0.8.2, 0.8.1, 0.8, 0.7.2, 0.7.1, 0.7, 0.6.3, ' | ||
'0.6.2, 0.6.1, 0.6, 0.5.1, 0.5, 0.4, 0.3.1, ' | ||
'0.3, 0.2.1, 0.2' in output.stdout | ||
) | ||
|
||
|
||
@pytest.mark.network | ||
def test_list_all_versions_returns_no_matches_found_when_name_not_exact(): | ||
""" | ||
Test that non exact name do not match | ||
""" | ||
command = create_command('index') | ||
cmdline = "versions pand" | ||
with command.main_context(): | ||
options, args = command.parse_args(cmdline.split()) | ||
status = command.run(options, args) | ||
assert status == ERROR | ||
|
||
|
||
@pytest.mark.network | ||
def test_list_all_versions_returns_matches_found_when_name_is_exact(): | ||
""" | ||
Test that exact name matches | ||
""" | ||
command = create_command('index') | ||
cmdline = "versions pandas" | ||
with command.main_context(): | ||
options, args = command.parse_args(cmdline.split()) | ||
status = command.run(options, args) | ||
assert status == SUCCESS |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.