Skip to content

Commit 09378f5

Browse files
authored
Use a patched argparse only in Python 2.X (spack#25376)
Spack is internally using a patched version of `argparse` mainly to backport Python 3 functionality into Python 2. This PR makes it such that for the supported Python 3 versions we use `argparse` from the standard Python library. This PR has been extracted from spack#25371 where it was needed to be able to use recent versions of `pytest`. * Fixed formatting issues when using a pristine argparse.py * Fix error message for Python 3.X when missing positional arguments * Account for the change of API in Python 3.7 * Layout multi-valued args into columns in error messages * Seamless transition in develop if argparse.pyc is in external * Be more defensive in case we can't remove the file.
1 parent f444303 commit 09378f5

File tree

3 files changed

+49
-3
lines changed

3 files changed

+49
-3
lines changed

bin/spack

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ exit 1
2828
from __future__ import print_function
2929

3030
import os
31+
import os.path
3132
import sys
3233

3334
min_python3 = (3, 5)
@@ -70,6 +71,28 @@ if "ruamel.yaml" in sys.modules:
7071
if "ruamel" in sys.modules:
7172
del sys.modules["ruamel"]
7273

74+
# The following code is here to avoid failures when updating
75+
# the develop version, due to spurious argparse.pyc files remaining
76+
# in the libs/spack/external directory, see:
77+
# https://github.com/spack/spack/pull/25376
78+
# TODO: Remove in v0.18.0 or later
79+
try:
80+
import argparse
81+
except ImportError:
82+
argparse_pyc = os.path.join(spack_external_libs, 'argparse.pyc')
83+
if not os.path.exists(argparse_pyc):
84+
raise
85+
try:
86+
os.remove(argparse_pyc)
87+
import argparse # noqa
88+
except Exception:
89+
msg = ('The file\n\n\t{0}\n\nis corrupted and cannot be deleted by Spack. '
90+
'Either delete it manually or ask some administrator to '
91+
'delete it for you.')
92+
print(msg.format(argparse_pyc))
93+
sys.exit(1)
94+
95+
7396
import spack.main # noqa
7497

7598
# Once we've set up the system path, run the spack main method
File renamed without changes.

lib/spack/spack/main.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import llnl.util.filesystem as fs
2929
import llnl.util.tty as tty
30+
import llnl.util.tty.colify
3031
import llnl.util.tty.color as color
3132
from llnl.util.tty.log import log_output
3233

@@ -173,14 +174,16 @@ def _format_actions_usage(self, actions, groups):
173174
usage = super(
174175
SpackHelpFormatter, self)._format_actions_usage(actions, groups)
175176

177+
# Eliminate any occurrence of two or more consecutive spaces
178+
usage = re.sub(r'[ ]{2,}', ' ', usage)
179+
176180
# compress single-character flags that are not mutually exclusive
177181
# at the beginning of the usage string
178182
chars = ''.join(re.findall(r'\[-(.)\]', usage))
179183
usage = re.sub(r'\[-.\] ?', '', usage)
180184
if chars:
181-
return '[-%s] %s' % (chars, usage)
182-
else:
183-
return usage
185+
usage = '[-%s] %s' % (chars, usage)
186+
return usage.strip()
184187

185188

186189
class SpackArgumentParser(argparse.ArgumentParser):
@@ -293,7 +296,18 @@ def add_subcommand_group(title, commands):
293296
def add_subparsers(self, **kwargs):
294297
"""Ensure that sensible defaults are propagated to subparsers"""
295298
kwargs.setdefault('metavar', 'SUBCOMMAND')
299+
300+
# From Python 3.7 we can require a subparser, earlier versions
301+
# of argparse will error because required=True is unknown
302+
if sys.version_info[:2] > (3, 6):
303+
kwargs.setdefault('required', True)
304+
296305
sp = super(SpackArgumentParser, self).add_subparsers(**kwargs)
306+
# This monkey patching is needed for Python 3.5 and 3.6, which support
307+
# having a required subparser but don't expose the API used above
308+
if sys.version_info[:2] == (3, 5) or sys.version_info[:2] == (3, 6):
309+
sp.required = True
310+
297311
old_add_parser = sp.add_parser
298312

299313
def add_parser(name, **kwargs):
@@ -336,6 +350,15 @@ def format_help(self, level='short'):
336350
# in subparsers, self.prog is, e.g., 'spack install'
337351
return super(SpackArgumentParser, self).format_help()
338352

353+
def _check_value(self, action, value):
354+
# converted value must be one of the choices (if specified)
355+
if action.choices is not None and value not in action.choices:
356+
cols = llnl.util.tty.colify.colified(
357+
sorted(action.choices), indent=4, tty=True
358+
)
359+
msg = 'invalid choice: %r choose from:\n%s' % (value, cols)
360+
raise argparse.ArgumentError(action, msg)
361+
339362

340363
def make_argument_parser(**kwargs):
341364
"""Create an basic argument parser without any subcommands added."""

0 commit comments

Comments
 (0)