argparse: lazily construct common arguments

Continuing to shave small bits of time off startup --
`spack.cmd.common.arguments` constructs many `Args` objects at module
scope, which has to be done for all commands that import it.  Instead of
doing this at load time, do it lazily.

- [x] construct Args objects lazily

- [x] remove the module-scoped argparse fixture

- [x] make the mock config scope set dirty to False by default (like the
  regular scope)

This *seems* to reduce load time slightly
This commit is contained in:
Todd Gamblin 2019-12-27 23:17:34 -08:00
parent e7dc8a2bea
commit b2e9696052
3 changed files with 108 additions and 60 deletions

View file

@ -18,9 +18,22 @@
__all__ = ['add_common_arguments'] __all__ = ['add_common_arguments']
#: dictionary of argument-generating functions, keyed by name
_arguments = {} _arguments = {}
def arg(fn):
"""Decorator for a function that generates a common argument.
This ensures that argument bunches are created lazily. Decorate
argument-generating functions below with @arg so that
``add_common_arguments()`` can find them.
"""
_arguments[fn.__name__] = fn
return fn
def add_common_arguments(parser, list_of_arguments): def add_common_arguments(parser, list_of_arguments):
"""Extend a parser with extra arguments """Extend a parser with extra arguments
@ -32,7 +45,8 @@ def add_common_arguments(parser, list_of_arguments):
if argument not in _arguments: if argument not in _arguments:
message = 'Trying to add non existing argument "{0}" to a command' message = 'Trying to add non existing argument "{0}" to a command'
raise KeyError(message.format(argument)) raise KeyError(message.format(argument))
x = _arguments[argument]
x = _arguments[argument]()
parser.add_argument(*x.flags, **x.kwargs) parser.add_argument(*x.flags, **x.kwargs)
@ -118,64 +132,104 @@ def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, deptype) setattr(namespace, self.dest, deptype)
_arguments['constraint'] = Args( @arg
'constraint', nargs=argparse.REMAINDER, action=ConstraintAction, def constraint():
help='constraint to select a subset of installed packages') return Args(
'constraint', nargs=argparse.REMAINDER, action=ConstraintAction,
help='constraint to select a subset of installed packages')
_arguments['yes_to_all'] = Args(
'-y', '--yes-to-all', action='store_true', dest='yes_to_all',
help='assume "yes" is the answer to every confirmation request')
_arguments['recurse_dependencies'] = Args( @arg
'-r', '--dependencies', action='store_true', dest='recurse_dependencies', def yes_to_all():
help='recursively traverse spec dependencies') return Args(
'-y', '--yes-to-all', action='store_true', dest='yes_to_all',
help='assume "yes" is the answer to every confirmation request')
_arguments['recurse_dependents'] = Args(
'-R', '--dependents', action='store_true', dest='dependents',
help='also uninstall any packages that depend on the ones given '
'via command line')
_arguments['clean'] = Args( @arg
'--clean', def recurse_dependencies():
action='store_false', return Args(
default=spack.config.get('config:dirty'), '-r', '--dependencies', action='store_true',
dest='dirty', dest='recurse_dependencies',
help='unset harmful variables in the build environment (default)') help='recursively traverse spec dependencies')
_arguments['deptype'] = Args(
'--deptype', action=DeptypeAction, default=dep.all_deptypes,
help="comma-separated list of deptypes to traverse\ndefault=%s"
% ','.join(dep.all_deptypes))
_arguments['dirty'] = Args( @arg
'--dirty', def recurse_dependents():
action='store_true', return Args(
default=spack.config.get('config:dirty'), '-R', '--dependents', action='store_true', dest='dependents',
dest='dirty', help='also uninstall any packages that depend on the ones given '
help='preserve user environment in the spack build environment (danger!)') 'via command line')
_arguments['long'] = Args(
'-l', '--long', action='store_true',
help='show dependency hashes as well as versions')
_arguments['very_long'] = Args( @arg
'-L', '--very-long', action='store_true', def clean():
help='show full dependency hashes as well as versions') return Args(
'--clean',
action='store_false',
default=spack.config.get('config:dirty'),
dest='dirty',
help='unset harmful variables in the build environment (default)')
_arguments['tags'] = Args(
'-t', '--tags', action='append',
help='filter a package query by tags')
_arguments['jobs'] = Args( @arg
'-j', '--jobs', action=SetParallelJobs, type=int, dest='jobs', def deptype():
help='explicitly set number of parallel jobs') return Args(
'--deptype', action=DeptypeAction, default=dep.all_deptypes,
help="comma-separated list of deptypes to traverse\ndefault=%s"
% ','.join(dep.all_deptypes))
_arguments['install_status'] = Args(
'-I', '--install-status', action='store_true', default=False,
help='show install status of packages. packages can be: '
'installed [+], missing and needed by an installed package [-], '
'or not installed (no annotation)')
_arguments['no_checksum'] = Args( @arg
'-n', '--no-checksum', action='store_true', default=False, def dirty():
help="do not use checksums to verify downloaded files (unsafe)") return Args(
'--dirty',
action='store_true',
default=spack.config.get('config:dirty'),
dest='dirty',
help="preserve user environment in spack's build environment (danger!)"
)
@arg
def long():
return Args(
'-l', '--long', action='store_true',
help='show dependency hashes as well as versions')
@arg
def very_long():
return Args(
'-L', '--very-long', action='store_true',
help='show full dependency hashes as well as versions')
@arg
def tags():
return Args(
'-t', '--tags', action='append',
help='filter a package query by tags')
@arg
def jobs():
return Args(
'-j', '--jobs', action=SetParallelJobs, type=int, dest='jobs',
help='explicitly set number of parallel jobs')
@arg
def install_status():
return Args(
'-I', '--install-status', action='store_true', default=False,
help='show install status of packages. packages can be: '
'installed [+], missing and needed by an installed package [-], '
'or not installed (no annotation)')
@arg
def no_checksum():
return Args(
'-n', '--no-checksum', action='store_true', default=False,
help="do not use checksums to verify downloaded files (unsafe)")

View file

@ -30,14 +30,6 @@
add = SpackCommand('add') add = SpackCommand('add')
@pytest.fixture(scope='module')
def parser():
"""Returns the parser for the module command"""
parser = argparse.ArgumentParser()
spack.cmd.install.setup_parser(parser)
return parser
@pytest.fixture() @pytest.fixture()
def noop_install(monkeypatch): def noop_install(monkeypatch):
def noop(*args, **kwargs): def noop(*args, **kwargs):
@ -115,7 +107,9 @@ def test_install_package_already_installed(
(['--clean'], False), (['--clean'], False),
(['--dirty'], True), (['--dirty'], True),
]) ])
def test_install_dirty_flag(parser, arguments, expected): def test_install_dirty_flag(arguments, expected):
parser = argparse.ArgumentParser()
spack.cmd.install.setup_parser(parser)
args = parser.parse_args(arguments) args = parser.parse_args(arguments)
assert args.dirty == expected assert args.dirty == expected

View file

@ -11,4 +11,4 @@ config:
misc_cache: ~/.spack/cache misc_cache: ~/.spack/cache
verify_ssl: true verify_ssl: true
checksum: true checksum: true
dirty: True dirty: false