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
This commit is contained in:
Massimiliano Culpo
2022-11-05 15:59:12 +01:00
committed by GitHub
parent c9fcb8aadc
commit 5558940ce6
7 changed files with 193 additions and 20 deletions

View File

@@ -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
1 Name Supported Versions Notes Requirement Reason
2 Python 2.7/3.6-3.10 2.7/3.6-3.11 Interpreter for Spack
3 C/C++ Compilers Building software
4 make Build software
5 patch Build software

View File

@@ -343,17 +343,21 @@ def add_command(self, cmd_name):
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)
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]
# 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)
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)

View File

@@ -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)

View File

@@ -1243,3 +1243,95 @@ def visit_withitem(self, node):
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)