Skip to content

Commit ca261cc

Browse files
committed
Add subcommand alias mechanism
1 parent 6a7bf94 commit ca261cc

File tree

8 files changed

+62
-40
lines changed

8 files changed

+62
-40
lines changed

news/8130.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add aliases 'add' and 'remove' aliases for 'install' and 'uninstall',
2+
respectively.

src/pip/_internal/cli/autocompletion.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from itertools import chain
88

99
from pip._internal.cli.main_parser import create_main_parser
10-
from pip._internal.commands import commands_dict, create_command
10+
from pip._internal.commands import create_command, subcommands_set
1111
from pip._internal.utils.misc import get_installed_distributions
1212
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
1313

@@ -30,7 +30,7 @@ def autocomplete():
3030
current = ''
3131

3232
parser = create_main_parser()
33-
subcommands = list(commands_dict)
33+
subcommands = list(subcommands_set)
3434
options = []
3535

3636
# subcommand

src/pip/_internal/cli/main_parser.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@
99
ConfigOptionParser,
1010
UpdatingDefaultsHelpFormatter,
1111
)
12-
from pip._internal.commands import commands_dict, get_similar_commands
13-
from pip._internal.exceptions import CommandError
12+
from pip._internal.commands import (
13+
aliases_of_commands,
14+
check_subcommand,
15+
commands_dict,
16+
)
1417
from pip._internal.utils.misc import get_pip_version, get_prog
1518
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
1619

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

4952
# create command listing for description
50-
description = [''] + [
51-
'{name:27} {command_info.summary}'.format(**locals())
52-
for name, command_info in commands_dict.items()
53-
]
53+
description = ['']
54+
for name, info in commands_dict.items():
55+
names = ', '.join(aliases_of_commands[name])
56+
description.append('{:27} {.summary}'.format(names, info))
5457
parser.description = '\n'.join(description)
5558

5659
return parser
@@ -82,16 +85,7 @@ def parse_command(args):
8285

8386
# the subcommand name
8487
cmd_name = args_else[0]
85-
86-
if cmd_name not in commands_dict:
87-
guess = get_similar_commands(cmd_name)
88-
89-
msg = ['unknown command "{}"'.format(cmd_name)]
90-
if guess:
91-
msg.append('maybe you meant "{}"'.format(guess))
92-
93-
raise CommandError(' - '.join(msg))
94-
88+
check_subcommand(cmd_name)
9589
# all the args without the subcommand
9690
cmd_args = args[:]
9791
cmd_args.remove(cmd_name)

src/pip/_internal/commands/__init__.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
import importlib
1515
from collections import OrderedDict, namedtuple
1616

17+
from pip._internal.exceptions import CommandError
1718
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
1819

1920
if MYPY_CHECK_RUNNING:
20-
from typing import Any
21+
from typing import Any, Dict, List, Set
2122
from pip._internal.cli.base_command import Command
2223

2324

@@ -94,13 +95,27 @@
9495
)),
9596
]) # type: OrderedDict[str, CommandInfo]
9697

98+
aliases_dict = {
99+
'add': 'install',
100+
'remove': 'uninstall',
101+
} # type: Dict[str, str]
102+
aliases_of_commands = {
103+
name: [name] for name in commands_dict} # type: Dict[str, List[str]]
104+
for alias, name in aliases_dict.items():
105+
aliases_of_commands[name].append(alias)
106+
subcommands_set = {cmd for aliases in aliases_of_commands.values()
107+
for cmd in aliases} # type: Set[str]
108+
97109

98110
def create_command(name, **kwargs):
99111
# type: (str, **Any) -> Command
100112
"""
101113
Create an instance of the Command class with the given name.
102114
"""
103-
module_path, class_name, summary = commands_dict[name]
115+
try:
116+
module_path, class_name, summary = commands_dict[name]
117+
except KeyError:
118+
module_path, class_name, summary = commands_dict[aliases_dict[name]]
104119
module = importlib.import_module(module_path)
105120
command_class = getattr(module, class_name)
106121
command = command_class(name=name, summary=summary, **kwargs)
@@ -114,9 +129,20 @@ def get_similar_commands(name):
114129

115130
name = name.lower()
116131

117-
close_commands = get_close_matches(name, commands_dict.keys())
132+
close_commands = get_close_matches(name, subcommands_set)
118133

119134
if close_commands:
120135
return close_commands[0]
121136
else:
122137
return False
138+
139+
140+
def check_subcommand(name):
141+
# type: (str) -> None
142+
"""Raise CommandError if the given subcommand not found."""
143+
if name not in aliases_dict and name not in commands_dict:
144+
guess = get_similar_commands(name)
145+
msg = 'unknown command "{}"'.format(name)
146+
if guess:
147+
msg += ' - maybe you meant "{}"'.format(guess)
148+
raise CommandError(msg)

src/pip/_internal/commands/help.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from pip._internal.cli.base_command import Command
77
from pip._internal.cli.status_codes import SUCCESS
8-
from pip._internal.exceptions import CommandError
98

109

1110
class HelpCommand(Command):
@@ -16,25 +15,15 @@ class HelpCommand(Command):
1615
ignore_require_venv = True
1716

1817
def run(self, options, args):
19-
from pip._internal.commands import (
20-
commands_dict, create_command, get_similar_commands,
21-
)
18+
from pip._internal.commands import check_subcommand, create_command
2219

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

29-
if cmd_name not in commands_dict:
30-
guess = get_similar_commands(cmd_name)
31-
32-
msg = ['unknown command "{}"'.format(cmd_name)]
33-
if guess:
34-
msg.append('maybe you meant "{}"'.format(guess))
35-
36-
raise CommandError(' - '.join(msg))
37-
26+
check_subcommand(cmd_name)
3827
command = create_command(cmd_name)
3928
command.parser.print_help()
4029

tests/functional/test_help.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from mock import Mock
33

44
from pip._internal.cli.base_command import ERROR, SUCCESS
5-
from pip._internal.commands import commands_dict, create_command
5+
from pip._internal.commands import create_command, subcommands_set
66
from pip._internal.exceptions import CommandError
77

88

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

82-
for name in commands_dict:
82+
for name in subcommands_set:
8383
assert (
8484
in_memory_pip.pip('help', name).stdout ==
8585
in_memory_pip.pip(name, '--help').stdout != ""

tests/lib/options_helpers.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from pip._internal.cli import cmdoptions
77
from pip._internal.cli.base_command import Command
8-
from pip._internal.commands import CommandInfo, commands_dict
8+
from pip._internal.commands import CommandInfo, commands_dict, subcommands_set
99
from tests.lib.configuration_helpers import reset_os_environ
1010

1111

@@ -27,7 +27,9 @@ def setup(self):
2727
commands_dict['fake'] = CommandInfo(
2828
'tests.lib.options_helpers', 'FakeCommand', 'fake summary',
2929
)
30+
subcommands_set.add('fake')
3031

3132
def teardown(self):
3233
reset_os_environ(self.environ_before)
3334
commands_dict.pop('fake')
35+
subcommands_set.remove('fake')

tests/unit/test_commands.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
RequirementCommand,
77
SessionCommandMixin,
88
)
9-
from pip._internal.commands import commands_dict, create_command
9+
from pip._internal.commands import (
10+
aliases_dict,
11+
commands_dict,
12+
create_command,
13+
subcommands_set,
14+
)
1015

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

3540

36-
@pytest.mark.parametrize('name', list(commands_dict))
41+
@pytest.mark.parametrize('name', subcommands_set)
3742
def test_create_command(name):
3843
"""Test creating an instance of each available command."""
3944
command = create_command(name)
4045
assert command.name == name
41-
assert command.summary == commands_dict[name].summary
46+
try:
47+
summary = commands_dict[name]
48+
except KeyError:
49+
summary = commands_dict[aliases_dict[name]]
50+
assert command.summary == summary
4251

4352

4453
def test_session_commands():

0 commit comments

Comments
 (0)