Skip to content
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
52 changes: 40 additions & 12 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
from gettext import gettext as _
from gettext import ngettext

lazy import _colorize

SUPPRESS = '==SUPPRESS=='

OPTIONAL = '?'
Expand Down Expand Up @@ -156,6 +158,15 @@ def _identity(value):
# Formatting Help
# ===============

class _ColorlessTheme:
# A 'fake' theme for no colors
def __getattr__(self, name):
# _colorize's no_color themes are just all empty strings
# by directly using empty strings the import is avoided
return ""

_colorless_theme = _ColorlessTheme()


class HelpFormatter(object):
"""Formatter for generating usage messages and argument help strings.
Expand Down Expand Up @@ -196,14 +207,32 @@ def __init__(
self._set_color(False)

def _set_color(self, color, *, file=None):
from _colorize import can_colorize, decolor, get_theme

if color and can_colorize(file=file):
self._theme = get_theme(force_color=True).argparse
self._decolor = decolor
# Set a new color setting and file, clear caches for theme and decolor
self._theme_color = color
self._theme_file = file
self._cached_theme = None
self._cached_decolor = None

def _get_theme_and_decolor(self):
# If self._theme_color is false, this prevents _colorize from importing
if self._theme_color and _colorize.can_colorize(file=self._theme_file):
self._cached_theme = _colorize.get_theme(force_color=True).argparse
self._cached_decolor = _colorize.decolor
else:
self._theme = get_theme(force_no_color=True).argparse
self._decolor = _identity
self._cached_theme = _colorless_theme
self._cached_decolor = _identity

@property
def _theme(self):
if self._cached_theme is None:
self._get_theme_and_decolor()
return self._cached_theme

@property
def _decolor(self):
if self._cached_decolor is None:
self._get_theme_and_decolor()
return self._cached_decolor

# ===============================
# Section and indentation methods
Expand Down Expand Up @@ -2856,12 +2885,11 @@ def _print_message(self, message, file=None):
pass

def _get_theme(self, file=None):
from _colorize import can_colorize, get_theme

if self.color and can_colorize(file=file):
return get_theme(force_color=True).argparse
# If self.color is False, _colorize is not imported
if self.color and _colorize.can_colorize(file=file):
return _colorize.get_theme(force_color=True).argparse
else:
return get_theme(force_no_color=True).argparse
return _colorless_theme

# ===============
# Exiting methods
Expand Down
12 changes: 11 additions & 1 deletion Lib/test/support/import_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def ready_to_import(name=None, source=""):
sys.modules.pop(name, None)


def ensure_lazy_imports(imported_module, modules_to_block):
def ensure_lazy_imports(imported_module, modules_to_block, *, additional_code=None):
"""Test that when imported_module is imported, none of the modules in
modules_to_block are imported as a side effect."""
modules_to_block = frozenset(modules_to_block)
Expand All @@ -343,6 +343,16 @@ def ensure_lazy_imports(imported_module, modules_to_block):
raise AssertionError(f'unexpectedly imported after importing {imported_module}: {{after}}')
"""
)
if additional_code:
script += additional_code
script += textwrap.dedent(
f"""
if unexpected := modules_to_block & sys.modules.keys():
after = ", ".join(unexpected)
raise AssertionError(f'unexpectedly imported after additional code: {{after}}')
"""
)

from .script_helper import assert_python_ok
assert_python_ok("-S", "-c", script)

Expand Down
43 changes: 43 additions & 0 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,49 @@ def test_skip_invalid_stdout(self):
self.assertRegex(mocked_stderr.getvalue(), r'usage:')


class TestLazyImports(unittest.TestCase):
LAZY_IMPORTS = {
"_colorize",
"copy",
"difflib",
"shutil",
"textwrap",
"warnings",
}
def test_module_import(self):
import_helper.ensure_lazy_imports(
"argparse",
self.LAZY_IMPORTS,
)

def test_create_parser(self):
# Test imports are still unused after
# creating a parser
create_parser = "argparse.ArgumentParser()"
imported_modules = {"shutil"}

import_helper.ensure_lazy_imports(
"argparse",
self.LAZY_IMPORTS - imported_modules,
additional_code=create_parser,
)

def test_add_subparser(self):
add_subparser = textwrap.dedent(
f"""
parser = argparse.ArgumentParser()
parser.add_subparsers(dest='command', required=False)
"""
)
imported_modules = {"shutil"}

import_helper.ensure_lazy_imports(
"argparse",
self.LAZY_IMPORTS - imported_modules,
additional_code=add_subparser,
)


class TestArgumentParserPickleable(unittest.TestCase):

@force_not_colorized
Expand Down
Loading