summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@gmail.com>2022-11-05 15:59:12 +0100
committerGitHub <noreply@github.com>2022-11-05 15:59:12 +0100
commit5558940ce6fee399155bd31be7f1c8c361691979 (patch)
tree9caf76796896087b0f1b127152996d9e891cd3fe /lib
parentc9fcb8aadc2ded5bcaa999f108f0c92213824ee2 (diff)
downloadspack-5558940ce6fee399155bd31be7f1c8c361691979.tar.gz
spack-5558940ce6fee399155bd31be7f1c8c361691979.tar.bz2
spack-5558940ce6fee399155bd31be7f1c8c361691979.tar.xz
spack-5558940ce6fee399155bd31be7f1c8c361691979.zip
Add support for Python 3.11 (#33505)
Argparse started raising ArgumentError exceptions when the same parser is added twice. Therefore, we perform the addition only if the parser is not there already Port match syntax to our unparser
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/tables/system_prerequisites.csv2
-rw-r--r--lib/spack/spack/main.py26
-rw-r--r--lib/spack/spack/test/util/unparse/unparse.py74
-rw-r--r--lib/spack/spack/util/unparse/unparser.py92
4 files changed, 182 insertions, 12 deletions
diff --git a/lib/spack/docs/tables/system_prerequisites.csv b/lib/spack/docs/tables/system_prerequisites.csv
index 5f661883d3..af8dcafffa 100644
--- a/lib/spack/docs/tables/system_prerequisites.csv
+++ b/lib/spack/docs/tables/system_prerequisites.csv
@@ -1,5 +1,5 @@
Name, Supported Versions, Notes, Requirement Reason
-Python, 2.7/3.6-3.10, , Interpreter for Spack
+Python, 2.7/3.6-3.11, , Interpreter for Spack
C/C++ Compilers, , , Building software
make, , , Build software
patch, , , Build software
diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py
index 4c3f19d75d..a9a9d20df7 100644
--- a/lib/spack/spack/main.py
+++ b/lib/spack/spack/main.py
@@ -343,17 +343,21 @@ class SpackArgumentParser(argparse.ArgumentParser):
self._remove_action(self._actions[-1])
self.subparsers = self.add_subparsers(metavar="COMMAND", dest="command")
- # each command module implements a parser() function, to which we
- # pass its subparser for setup.
- module = spack.cmd.get_module(cmd_name)
-
- # build a list of aliases
- alias_list = [k for k, v in aliases.items() if v == cmd_name]
-
- subparser = self.subparsers.add_parser(
- cmd_name, aliases=alias_list, help=module.description, description=module.description
- )
- module.setup_parser(subparser)
+ if cmd_name not in self.subparsers._name_parser_map:
+ # each command module implements a parser() function, to which we
+ # pass its subparser for setup.
+ module = spack.cmd.get_module(cmd_name)
+
+ # build a list of aliases
+ alias_list = [k for k, v in aliases.items() if v == cmd_name]
+
+ subparser = self.subparsers.add_parser(
+ cmd_name,
+ aliases=alias_list,
+ help=module.description,
+ description=module.description,
+ )
+ module.setup_parser(subparser)
# return the callable function for the command
return spack.cmd.get_command(cmd_name)
diff --git a/lib/spack/spack/test/util/unparse/unparse.py b/lib/spack/spack/test/util/unparse/unparse.py
index 49009ae98e..82148c9dc8 100644
--- a/lib/spack/spack/test/util/unparse/unparse.py
+++ b/lib/spack/spack/test/util/unparse/unparse.py
@@ -178,6 +178,71 @@ async def f():
"""
+match_literal = """\
+match status:
+ case 400:
+ return "Bad request"
+ case 404 | 418:
+ return "Not found"
+ case _:
+ return "Something's wrong with the internet"
+"""
+
+match_with_noop = """\
+match status:
+ case 400:
+ return "Bad request"
+"""
+
+match_literal_and_variable = """\
+match point:
+ case (0, 0):
+ print("Origin")
+ case (0, y):
+ print(f"Y={y}")
+ case (x, 0):
+ print(f"X={x}")
+ case (x, y):
+ print(f"X={x}, Y={y}")
+ case _:
+ raise ValueError("Not a point")
+"""
+
+
+match_classes = """\
+class Point:
+ x: int
+ y: int
+
+def location(point):
+ match point:
+ case Point(x=0, y=0):
+ print("Origin is the point's location.")
+ case Point(x=0, y=y):
+ print(f"Y={y} and the point is on the y-axis.")
+ case Point(x=x, y=0):
+ print(f"X={x} and the point is on the x-axis.")
+ case Point():
+ print("The point is located somewhere else on the plane.")
+ case _:
+ print("Not a point")
+"""
+
+match_nested = """\
+match points:
+ case []:
+ print("No points in the list.")
+ case [Point(0, 0)]:
+ print("The origin is the only point in the list.")
+ case [Point(x, y)]:
+ print(f"A single point {x}, {y} is in the list.")
+ case [Point(0, y1), Point(0, y2)]:
+ print(f"Two points on the Y axis at {y1}, {y2} are in the list.")
+ case _:
+ print("Something else is found in the list.")
+"""
+
+
def check_ast_roundtrip(code1, filename="internal", mode="exec"):
ast1 = compile(str(code1), filename, mode, ast.PyCF_ONLY_AST)
code2 = spack.util.unparse.unparse(ast1)
@@ -512,3 +577,12 @@ def test_async_with():
@pytest.mark.skipif(sys.version_info < (3, 5), reason="Not supported < 3.5")
def test_async_with_as():
check_ast_roundtrip(async_with_as)
+
+
+@pytest.mark.skipif(sys.version_info < (3, 10), reason="Not supported < 3.10")
+@pytest.mark.parametrize(
+ "literal",
+ [match_literal, match_with_noop, match_literal_and_variable, match_classes, match_nested],
+)
+def test_match_literal(literal):
+ check_ast_roundtrip(literal)
diff --git a/lib/spack/spack/util/unparse/unparser.py b/lib/spack/spack/util/unparse/unparser.py
index a46d19fa76..c204aea25a 100644
--- a/lib/spack/spack/util/unparse/unparser.py
+++ b/lib/spack/spack/util/unparse/unparser.py
@@ -1243,3 +1243,95 @@ class Unparser:
if node.optional_vars:
self.write(" as ")
self.dispatch(node.optional_vars)
+
+ def visit_Match(self, node):
+ self.fill("match ")
+ self.dispatch(node.subject)
+ with self.block():
+ for case in node.cases:
+ self.dispatch(case)
+
+ def visit_match_case(self, node):
+ self.fill("case ")
+ self.dispatch(node.pattern)
+ if node.guard:
+ self.write(" if ")
+ self.dispatch(node.guard)
+ with self.block():
+ self.dispatch(node.body)
+
+ def visit_MatchValue(self, node):
+ self.dispatch(node.value)
+
+ def visit_MatchSingleton(self, node):
+ self._write_constant(node.value)
+
+ def visit_MatchSequence(self, node):
+ with self.delimit("[", "]"):
+ interleave(lambda: self.write(", "), self.dispatch, node.patterns)
+
+ def visit_MatchStar(self, node):
+ name = node.name
+ if name is None:
+ name = "_"
+ self.write("*{}".format(name))
+
+ def visit_MatchMapping(self, node):
+ def write_key_pattern_pair(pair):
+ k, p = pair
+ self.dispatch(k)
+ self.write(": ")
+ self.dispatch(p)
+
+ with self.delimit("{", "}"):
+ keys = node.keys
+ interleave(
+ lambda: self.write(", "),
+ write_key_pattern_pair,
+ zip(keys, node.patterns),
+ )
+ rest = node.rest
+ if rest is not None:
+ if keys:
+ self.write(", ")
+ self.write("**{}".format(rest))
+
+ def visit_MatchClass(self, node):
+ self.set_precedence(_Precedence.ATOM, node.cls)
+ self.dispatch(node.cls)
+ with self.delimit("(", ")"):
+ patterns = node.patterns
+ interleave(lambda: self.write(", "), self.dispatch, patterns)
+ attrs = node.kwd_attrs
+ if attrs:
+
+ def write_attr_pattern(pair):
+ attr, pattern = pair
+ self.write("{}=".format(attr))
+ self.dispatch(pattern)
+
+ if patterns:
+ self.write(", ")
+ interleave(
+ lambda: self.write(", "),
+ write_attr_pattern,
+ zip(attrs, node.kwd_patterns),
+ )
+
+ def visit_MatchAs(self, node):
+ name = node.name
+ pattern = node.pattern
+ if name is None:
+ self.write("_")
+ elif pattern is None:
+ self.write(node.name)
+ else:
+ with self.require_parens(_Precedence.TEST, node):
+ self.set_precedence(_Precedence.BOR, node.pattern)
+ self.dispatch(node.pattern)
+ self.write(" as {}".format(node.name))
+
+ def visit_MatchOr(self, node):
+ with self.require_parens(_Precedence.BOR, node):
+ self.set_precedence(pnext(_Precedence.BOR), *node.patterns)
+ interleave(lambda: self.write(" | "), self.dispatch, node.patterns)