Skip to content

Add subcommand alias mechanism #8137

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

Closed
wants to merge 1 commit into from
Closed
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/8130.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add aliases 'add' and 'remove' aliases for 'install' and 'uninstall',
respectively.
4 changes: 2 additions & 2 deletions src/pip/_internal/cli/autocompletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from itertools import chain

from pip._internal.cli.main_parser import create_main_parser
from pip._internal.commands import commands_dict, create_command
from pip._internal.commands import create_command, subcommands_set
from pip._internal.utils.misc import get_installed_distributions
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

Expand All @@ -30,7 +30,7 @@ def autocomplete():
current = ''

parser = create_main_parser()
subcommands = list(commands_dict)
subcommands = list(subcommands_set)
options = []

# subcommand
Expand Down
26 changes: 10 additions & 16 deletions src/pip/_internal/cli/main_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
ConfigOptionParser,
UpdatingDefaultsHelpFormatter,
)
from pip._internal.commands import commands_dict, get_similar_commands
from pip._internal.exceptions import CommandError
from pip._internal.commands import (
aliases_of_commands,
check_subcommand,
commands_dict,
)
from pip._internal.utils.misc import get_pip_version, get_prog
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

Expand Down Expand Up @@ -47,10 +50,10 @@ def create_main_parser():
parser.main = True # type: ignore

# create command listing for description
description = [''] + [
'{name:27} {command_info.summary}'.format(**locals())
for name, command_info in commands_dict.items()
]
description = ['']
for name, info in commands_dict.items():
names = ', '.join(aliases_of_commands[name])
description.append('{:27} {.summary}'.format(names, info))
parser.description = '\n'.join(description)

return parser
Expand Down Expand Up @@ -82,16 +85,7 @@ def parse_command(args):

# the subcommand name
cmd_name = args_else[0]

if cmd_name not in commands_dict:
guess = get_similar_commands(cmd_name)

msg = ['unknown command "{}"'.format(cmd_name)]
if guess:
msg.append('maybe you meant "{}"'.format(guess))

raise CommandError(' - '.join(msg))

check_subcommand(cmd_name)
# all the args without the subcommand
cmd_args = args[:]
cmd_args.remove(cmd_name)
Expand Down
34 changes: 31 additions & 3 deletions src/pip/_internal/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
import importlib
from collections import OrderedDict, namedtuple

from pip._internal.exceptions import CommandError
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

if MYPY_CHECK_RUNNING:
from typing import Any
from typing import Any, Dict, List, Set
from pip._internal.cli.base_command import Command


Expand Down Expand Up @@ -94,13 +95,29 @@
)),
]) # type: OrderedDict[str, CommandInfo]

aliases_dict = {
'add': 'install',
'remove': 'uninstall',
} # type: Dict[str, str]

aliases_of_commands = {
name: [name] for name in commands_dict} # type: Dict[str, List[str]]
for alias, name in aliases_dict.items():
aliases_of_commands[name].append(alias)

subcommands_set = {cmd for aliases in aliases_of_commands.values()
for cmd in aliases} # type: Set[str]


def create_command(name, **kwargs):
# type: (str, **Any) -> Command
"""
Create an instance of the Command class with the given name.
"""
module_path, class_name, summary = commands_dict[name]
try:
module_path, class_name, summary = commands_dict[name]
except KeyError:
module_path, class_name, summary = commands_dict[aliases_dict[name]]
module = importlib.import_module(module_path)
command_class = getattr(module, class_name)
command = command_class(name=name, summary=summary, **kwargs)
Expand All @@ -114,9 +131,20 @@ def get_similar_commands(name):

name = name.lower()

close_commands = get_close_matches(name, commands_dict.keys())
close_commands = get_close_matches(name, subcommands_set)

if close_commands:
return close_commands[0]
else:
return False


def check_subcommand(name):
# type: (str) -> None
"""Raise CommandError if the given subcommand not found."""
if name not in aliases_dict and name not in commands_dict:
guess = get_similar_commands(name)
msg = 'unknown command "{}"'.format(name)
if guess:
msg += ' - maybe you meant "{}"'.format(guess)
raise CommandError(msg)
15 changes: 2 additions & 13 deletions src/pip/_internal/commands/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from pip._internal.cli.base_command import Command
from pip._internal.cli.status_codes import SUCCESS
from pip._internal.exceptions import CommandError
from pip._internal.utils.typing import MYPY_CHECK_RUNNING

if MYPY_CHECK_RUNNING:
Expand All @@ -19,25 +18,15 @@ class HelpCommand(Command):

def run(self, options, args):
# type: (Values, List[str]) -> int
from pip._internal.commands import (
commands_dict, create_command, get_similar_commands,
)
from pip._internal.commands import check_subcommand, create_command

try:
# 'pip help' with no args is handled by pip.__init__.parseopt()
cmd_name = args[0] # the command we need help for
except IndexError:
return SUCCESS

if cmd_name not in commands_dict:
guess = get_similar_commands(cmd_name)

msg = ['unknown command "{}"'.format(cmd_name)]
if guess:
msg.append('maybe you meant "{}"'.format(guess))

raise CommandError(' - '.join(msg))

check_subcommand(cmd_name)
command = create_command(cmd_name)
command.parser.print_help()

Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from mock import Mock

from pip._internal.cli.status_codes import ERROR, SUCCESS
from pip._internal.commands import commands_dict, create_command
from pip._internal.commands import create_command, subcommands_set
from pip._internal.exceptions import CommandError


Expand Down Expand Up @@ -79,7 +79,7 @@ def test_help_commands_equally_functional(in_memory_pip):
assert sum(ret) == 0, 'exit codes of: ' + msg
assert all(len(o) > 0 for o in out)

for name in commands_dict:
for name in subcommands_set:
assert (
in_memory_pip.pip('help', name).stdout ==
in_memory_pip.pip(name, '--help').stdout != ""
Expand Down
11 changes: 10 additions & 1 deletion tests/lib/options_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@

from pip._internal.cli import cmdoptions
from pip._internal.cli.base_command import Command
from pip._internal.commands import CommandInfo, commands_dict
from pip._internal.commands import (
CommandInfo,
aliases_of_commands,
commands_dict,
subcommands_set,
)


class FakeCommand(Command):
Expand All @@ -23,6 +28,10 @@ def setup(self):
commands_dict['fake'] = CommandInfo(
'tests.lib.options_helpers', 'FakeCommand', 'fake summary',
)
aliases_of_commands['fake'] = ['fake']
subcommands_set.add('fake')

def teardown(self):
commands_dict.pop('fake')
aliases_of_commands.pop('fake')
subcommands_set.remove('fake')
15 changes: 12 additions & 3 deletions tests/unit/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
RequirementCommand,
SessionCommandMixin,
)
from pip._internal.commands import commands_dict, create_command
from pip._internal.commands import (
aliases_dict,
commands_dict,
create_command,
subcommands_set,
)

# These are the expected names of the commands whose classes inherit from
# IndexGroupCommand.
Expand All @@ -33,12 +38,16 @@ def test_commands_dict__order():
assert names[-1] == 'help'


@pytest.mark.parametrize('name', list(commands_dict))
@pytest.mark.parametrize('name', subcommands_set)
def test_create_command(name):
"""Test creating an instance of each available command."""
command = create_command(name)
assert command.name == name
assert command.summary == commands_dict[name].summary
try:
summary = commands_dict[name].summary
except KeyError:
summary = commands_dict[aliases_dict[name]].summary
assert command.summary == summary


def test_session_commands():
Expand Down