Skip to content

Add alias support for install and uninstall #11028

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
8 changes: 8 additions & 0 deletions docs/html/reference/build-system/pyproject-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ hook will be called by pip, and dependencies it describes will also be installed
in the build environment. For example, newer versions of setuptools expose the
contents of `setup_requires` to pip via this hook.

Build-time requirement specifiers follow {pep}`508`, so it's possible to
reference packages with URLs. For example:

```toml
[build-system]
requires = ["setuptools @ git+https://github.com/pypa/setuptools.git@main"]
```

### Metadata Generation

```{versionadded} 19.0
Expand Down
4 changes: 2 additions & 2 deletions docs/html/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ to your system, which can be run from the command prompt as follows:

``python -m pip`` executes pip using the Python interpreter you
specified as python. So ``/usr/bin/python3.7 -m pip`` means
you are executing pip for your interpreter located at /usr/bin/python3.7.
you are executing pip for your interpreter located at ``/usr/bin/python3.7``.

.. tab:: Windows

Expand Down Expand Up @@ -629,7 +629,7 @@ Moreover, the "user scheme" can be customized by setting the
``PYTHONUSERBASE`` environment variable, which updates the value of
``site.USER_BASE``.

To install "SomePackage" into an environment with site.USER_BASE customized to
To install "SomePackage" into an environment with ``site.USER_BASE`` customized to
'/myappenv', do the following:

.. tab:: Unix/macOS
Expand Down
2 changes: 2 additions & 0 deletions news/8130.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add 'add' and 'i' aliases for 'install', 'remove' and 'u' aliases for
'uninstall'.
4 changes: 2 additions & 2 deletions src/pip/_internal/cli/autocompletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any, Iterable, List, Optional

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.metadata import get_default_environment


Expand All @@ -25,7 +25,7 @@ def autocomplete() -> None:
current = ""

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

# subcommand
Expand Down
21 changes: 6 additions & 15 deletions src/pip/_internal/cli/main_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@

from pip._internal.cli import cmdoptions
from pip._internal.cli.parser import 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

__all__ = ["create_main_parser", "parse_command"]
Expand Down Expand Up @@ -36,10 +35,10 @@ def create_main_parser() -> ConfigOptionParser:
parser.main = True # type: ignore

# create command listing for description
description = [""] + [
f"{name:27} {command_info.summary}"
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 @@ -70,15 +69,7 @@ def parse_command(args: List[str]) -> Tuple[str, List[str]]:

# the subcommand name
cmd_name = args_else[0]

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

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

raise CommandError(" - ".join(msg))
check_subcommand(cmd_name)

# all the args without the subcommand
cmd_args = args[:]
Expand Down
39 changes: 36 additions & 3 deletions src/pip/_internal/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

import importlib
from collections import namedtuple
from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional, Set

from pip._internal.cli.base_command import Command
from pip._internal.exceptions import CommandError

CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")

Expand Down Expand Up @@ -101,11 +102,32 @@
}


aliases_dict = {
"add": "install",
"i": "install",
"remove": "uninstall",
"u": "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: str, **kwargs: 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 @@ -119,9 +141,20 @@ def get_similar_commands(name: str) -> Optional[str]:

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 None


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)
17 changes: 2 additions & 15 deletions src/pip/_internal/commands/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

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


class HelpCommand(Command):
Expand All @@ -14,27 +13,15 @@ class HelpCommand(Command):
ignore_require_venv = True

def run(self, options: Values, args: 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 = [f'unknown command "{cmd_name}"']
if guess:
msg.append(f'maybe you meant "{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 @@ -3,7 +3,7 @@
import pytest

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
from tests.conftest import InMemoryPip
from tests.lib import PipTestEnvironment
Expand Down Expand Up @@ -110,7 +110,7 @@ def test_help_commands_equally_functional(in_memory_pip: InMemoryPip) -> None:
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
12 changes: 11 additions & 1 deletion tests/lib/options_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,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 @@ -29,5 +34,10 @@ def setup(self) -> None:
"fake summary",
)

aliases_of_commands["fake"] = ["fake"]
subcommands_set.add("fake")

def teardown(self) -> None:
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 @@ -9,7 +9,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 @@ -36,12 +41,16 @@ def test_commands_dict__order() -> None:
assert names[-1] == "help"


@pytest.mark.parametrize("name", list(commands_dict))
@pytest.mark.parametrize("name", subcommands_set)
def test_create_command(name: str) -> None:
"""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() -> None:
Expand Down