from gettext import gettext as _
from gettext import ngettext
+lazy import _colorize
+
SUPPRESS = '==SUPPRESS=='
OPTIONAL = '?'
# 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.
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
def _get_validation_formatter(self):
# Return cached formatter for read-only validation operations
# (_expand_help and _format_args). Avoids repeated slow _set_color calls.
+ # Validation never renders output, so force color off to avoid
+ # importing _colorize during add_argument.
if self._cached_formatter is None:
- self._cached_formatter = self._get_formatter()
+ formatter = self.formatter_class(prog=self.prog)
+ formatter._set_color(False)
+ self._cached_formatter = formatter
return self._cached_formatter
# =====================
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
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)
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)
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(
+ """
+ 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,
+ )
+
+ def test_parse_args(self):
+ example_parser = textwrap.dedent(
+ """
+ parser = argparse.ArgumentParser(prog='PROG')
+ parser.add_argument('-f', '--foo')
+ parser.add_argument('bar')
+ parser.parse_args(['BAR'])
+ parser.parse_args(['BAR', '--foo', 'FOO'])
+ """
+ )
+ imported_modules = {"shutil"}
+ import_helper.ensure_lazy_imports(
+ "argparse",
+ self.LAZY_IMPORTS - imported_modules,
+ additional_code=example_parser
+ )
+
+
class TestArgumentParserPickleable(unittest.TestCase):
@force_not_colorized
self.assertIn(output, calls)
self.assertNotIn('\x1b[', output.getvalue())
+ def test_fake_color_theme_matches_real(self):
+ from argparse import _colorless_theme
+ _colorize_nocolor = _colorize.get_theme(force_no_color=True).argparse
+ for k in _colorize_nocolor:
+ self.assertEqual(
+ getattr(_colorless_theme, k), getattr(_colorize_nocolor, k)
+ )
+
class TestModule(unittest.TestCase):
def test_deprecated__version__(self):