Skip to content

Commit 2cd26a2

Browse files
authored
Merge pull request #6694 from cjerdonek/remove-command-imports
Address #6692: Only import a Command class when needed
2 parents 2e51624 + 4cd8258 commit 2cd26a2

28 files changed

+164
-137
lines changed

docs/pip_sphinxext.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
from docutils.statemachine import ViewList
1010

1111
from pip._internal.cli import cmdoptions
12-
from pip._internal.commands import commands_dict as commands
12+
from pip._internal.commands import create_command
1313

1414

1515
class PipCommandUsage(rst.Directive):
1616
required_arguments = 1
1717

1818
def run(self):
19-
cmd = commands[self.arguments[0]]
19+
cmd = create_command(self.arguments[0])
2020
usage = dedent(
2121
cmd.usage.replace('%prog', 'pip {}'.format(cmd.name))
2222
).strip()
@@ -31,7 +31,8 @@ def run(self):
3131
node = nodes.paragraph()
3232
node.document = self.state.document
3333
desc = ViewList()
34-
description = dedent(commands[self.arguments[0]].__doc__)
34+
cmd = create_command(self.arguments[0])
35+
description = dedent(cmd.__doc__)
3536
for line in description.split('\n'):
3637
desc.append(line, "")
3738
self.state.nested_parse(desc, 0, node)
@@ -95,7 +96,7 @@ class PipCommandOptions(PipOptions):
9596
required_arguments = 1
9697

9798
def process_options(self):
98-
cmd = commands[self.arguments[0]]()
99+
cmd = create_command(self.arguments[0])
99100
self._format_options(
100101
cmd.parser.option_groups[0].option_list,
101102
cmd_name=cmd.name,

src/pip/_internal/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
from pip._internal.cli.autocompletion import autocomplete
4141
from pip._internal.cli.main_parser import parse_command
42-
from pip._internal.commands import commands_dict
42+
from pip._internal.commands import create_command
4343
from pip._internal.exceptions import PipError
4444
from pip._internal.utils import deprecation
4545
from pip._vendor.urllib3.exceptions import InsecureRequestWarning
@@ -73,5 +73,6 @@ def main(args=None):
7373
except locale.Error as e:
7474
# setlocale can apparently crash if locale are uninitialized
7575
logger.debug("Ignoring error %s when setting locale", e)
76-
command = commands_dict[cmd_name](isolated=("--isolated" in cmd_args))
76+
command = create_command(cmd_name, isolated=("--isolated" in cmd_args))
77+
7778
return command.main(cmd_args)

src/pip/_internal/cli/autocompletion.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import sys
77

88
from pip._internal.cli.main_parser import create_main_parser
9-
from pip._internal.commands import commands_dict, get_summaries
9+
from pip._internal.commands import commands_dict, create_command
1010
from pip._internal.utils.misc import get_installed_distributions
1111

1212

@@ -23,7 +23,7 @@ def autocomplete():
2323
except IndexError:
2424
current = ''
2525

26-
subcommands = [cmd for cmd, summary in get_summaries()]
26+
subcommands = list(commands_dict)
2727
options = []
2828
# subcommand
2929
try:
@@ -54,7 +54,7 @@ def autocomplete():
5454
print(dist)
5555
sys.exit(1)
5656

57-
subcommand = commands_dict[subcommand_name]()
57+
subcommand = create_command(subcommand_name)
5858

5959
for opt in subcommand.parser.option_list_all:
6060
if opt.help != optparse.SUPPRESS_HELP:

src/pip/_internal/cli/base_command.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,20 @@ class Command(object):
6161
usage = None # type: Optional[str]
6262
ignore_require_venv = False # type: bool
6363

64-
def __init__(self, isolated=False):
65-
# type: (bool) -> None
64+
def __init__(self, name, summary, isolated=False):
65+
# type: (str, str, bool) -> None
6666
parser_kw = {
6767
'usage': self.usage,
6868
'prog': '%s %s' % (get_prog(), self.name),
6969
'formatter': UpdatingDefaultsHelpFormatter(),
7070
'add_help_option': False,
71-
'name': self.name,
71+
'name': name,
7272
'description': self.__doc__,
7373
'isolated': isolated,
7474
}
7575

76+
self.name = name
77+
self.summary = summary
7678
self.parser = ConfigOptionParser(**parser_kw)
7779

7880
# Commands should add options to this option group

src/pip/_internal/cli/main_parser.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@
99
ConfigOptionParser,
1010
UpdatingDefaultsHelpFormatter,
1111
)
12-
from pip._internal.commands import (
13-
commands_dict,
14-
get_similar_commands,
15-
get_summaries,
16-
)
12+
from pip._internal.commands import commands_dict, get_similar_commands
1713
from pip._internal.exceptions import CommandError
1814
from pip._internal.utils.misc import get_pip_version, get_prog
1915
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
@@ -51,8 +47,10 @@ def create_main_parser():
5147
parser.main = True # type: ignore
5248

5349
# create command listing for description
54-
command_summaries = get_summaries()
55-
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
50+
description = [''] + [
51+
'%-27s %s' % (name, command_info.summary)
52+
for name, command_info in commands_dict.items()
53+
]
5654
parser.description = '\n'.join(description)
5755

5856
return parser

src/pip/_internal/commands/__init__.py

Lines changed: 84 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,97 @@
33
"""
44
from __future__ import absolute_import
55

6-
from pip._internal.commands.completion import CompletionCommand
7-
from pip._internal.commands.configuration import ConfigurationCommand
8-
from pip._internal.commands.debug import DebugCommand
9-
from pip._internal.commands.download import DownloadCommand
10-
from pip._internal.commands.freeze import FreezeCommand
11-
from pip._internal.commands.hash import HashCommand
12-
from pip._internal.commands.help import HelpCommand
13-
from pip._internal.commands.list import ListCommand
14-
from pip._internal.commands.check import CheckCommand
15-
from pip._internal.commands.search import SearchCommand
16-
from pip._internal.commands.show import ShowCommand
17-
from pip._internal.commands.install import InstallCommand
18-
from pip._internal.commands.uninstall import UninstallCommand
19-
from pip._internal.commands.wheel import WheelCommand
6+
import importlib
7+
from collections import namedtuple, OrderedDict
208

219
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
2210

2311
if MYPY_CHECK_RUNNING:
24-
from typing import List, Type
12+
from typing import Any
2513
from pip._internal.cli.base_command import Command
2614

27-
commands_order = [
28-
InstallCommand,
29-
DownloadCommand,
30-
UninstallCommand,
31-
FreezeCommand,
32-
ListCommand,
33-
ShowCommand,
34-
CheckCommand,
35-
ConfigurationCommand,
36-
SearchCommand,
37-
WheelCommand,
38-
HashCommand,
39-
CompletionCommand,
40-
DebugCommand,
41-
HelpCommand,
42-
] # type: List[Type[Command]]
4315

44-
commands_dict = {c.name: c for c in commands_order}
45-
46-
47-
def get_summaries(ordered=True):
48-
"""Yields sorted (command name, command summary) tuples."""
49-
50-
if ordered:
51-
cmditems = _sort_commands(commands_dict, commands_order)
52-
else:
53-
cmditems = commands_dict.items()
54-
55-
for name, command_class in cmditems:
56-
yield (name, command_class.summary)
16+
CommandInfo = namedtuple('CommandInfo', 'module_path, class_name, summary')
17+
18+
# The ordering matters for help display.
19+
# Also, even though the module path starts with the same
20+
# "pip._internal.commands" prefix in each case, we include the full path
21+
# because it makes testing easier (specifically when modifying commands_dict
22+
# in test setup / teardown by adding info for a FakeCommand class defined
23+
# in a test-related module).
24+
# Finally, we need to pass an iterable of pairs here rather than a dict
25+
# so that the ordering won't be lost when using Python 2.7.
26+
commands_dict = OrderedDict([
27+
('install', CommandInfo(
28+
'pip._internal.commands.install', 'InstallCommand',
29+
'Install packages.',
30+
)),
31+
('download', CommandInfo(
32+
'pip._internal.commands.download', 'DownloadCommand',
33+
'Download packages.',
34+
)),
35+
('uninstall', CommandInfo(
36+
'pip._internal.commands.uninstall', 'UninstallCommand',
37+
'Uninstall packages.',
38+
)),
39+
('freeze', CommandInfo(
40+
'pip._internal.commands.freeze', 'FreezeCommand',
41+
'Output installed packages in requirements format.',
42+
)),
43+
('list', CommandInfo(
44+
'pip._internal.commands.list', 'ListCommand',
45+
'List installed packages.',
46+
)),
47+
('show', CommandInfo(
48+
'pip._internal.commands.show', 'ShowCommand',
49+
'Show information about installed packages.',
50+
)),
51+
('check', CommandInfo(
52+
'pip._internal.commands.check', 'CheckCommand',
53+
'Verify installed packages have compatible dependencies.',
54+
)),
55+
('config', CommandInfo(
56+
'pip._internal.commands.configuration', 'ConfigurationCommand',
57+
'Manage local and global configuration.',
58+
)),
59+
('search', CommandInfo(
60+
'pip._internal.commands.search', 'SearchCommand',
61+
'Search PyPI for packages.',
62+
)),
63+
('wheel', CommandInfo(
64+
'pip._internal.commands.wheel', 'WheelCommand',
65+
'Build wheels from your requirements.',
66+
)),
67+
('hash', CommandInfo(
68+
'pip._internal.commands.hash', 'HashCommand',
69+
'Compute hashes of package archives.',
70+
)),
71+
('completion', CommandInfo(
72+
'pip._internal.commands.completion', 'CompletionCommand',
73+
'A helper command used for command completion.',
74+
)),
75+
('debug', CommandInfo(
76+
'pip._internal.commands.debug', 'DebugCommand',
77+
'Show information useful for debugging.',
78+
)),
79+
('help', CommandInfo(
80+
'pip._internal.commands.help', 'HelpCommand',
81+
'Show help for commands.',
82+
)),
83+
]) # type: OrderedDict[str, CommandInfo]
84+
85+
86+
def create_command(name, **kwargs):
87+
# type: (str, **Any) -> Command
88+
"""
89+
Create an instance of the Command class with the given name.
90+
"""
91+
module_path, class_name, summary = commands_dict[name]
92+
module = importlib.import_module(module_path)
93+
command_class = getattr(module, class_name)
94+
command = command_class(name=name, summary=summary, **kwargs)
95+
96+
return command
5797

5898

5999
def get_similar_commands(name):
@@ -68,14 +108,3 @@ def get_similar_commands(name):
68108
return close_commands[0]
69109
else:
70110
return False
71-
72-
73-
def _sort_commands(cmddict, order):
74-
def keyfn(key):
75-
try:
76-
return order.index(key[1])
77-
except ValueError:
78-
# unordered items should come last
79-
return 0xff
80-
81-
return sorted(cmddict.items(), key=keyfn)

src/pip/_internal/commands/check.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@
1111

1212
class CheckCommand(Command):
1313
"""Verify installed packages have compatible dependencies."""
14-
name = 'check'
14+
1515
usage = """
1616
%prog [options]"""
17-
summary = 'Verify installed packages have compatible dependencies.'
1817

1918
def run(self, options, args):
2019
package_set, parsing_probs = create_package_set_from_installed()

src/pip/_internal/commands/completion.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@
4747

4848
class CompletionCommand(Command):
4949
"""A helper command to be used for command completion."""
50-
name = 'completion'
51-
summary = 'A helper command used for command completion.'
50+
5251
ignore_require_venv = True
5352

5453
def __init__(self, *args, **kw):

src/pip/_internal/commands/configuration.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ class ConfigurationCommand(Command):
3434
default.
3535
"""
3636

37-
name = 'config'
3837
usage = """
3938
%prog [<file-option>] list
4039
%prog [<file-option>] [--editor <editor-path>] edit
@@ -44,8 +43,6 @@ class ConfigurationCommand(Command):
4443
%prog [<file-option>] unset name
4544
"""
4645

47-
summary = "Manage local and global configuration."
48-
4946
def __init__(self, *args, **kwargs):
5047
super(ConfigurationCommand, self).__init__(*args, **kwargs)
5148

src/pip/_internal/commands/debug.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,8 @@ class DebugCommand(Command):
7777
Display debug information.
7878
"""
7979

80-
name = 'debug'
8180
usage = """
8281
%prog <options>"""
83-
summary = 'Show information useful for debugging.'
8482
ignore_require_venv = True
8583

8684
def __init__(self, *args, **kw):

0 commit comments

Comments
 (0)