diff options
-rw-r--r-- | .github/workflows/audit.yaml | 2 | ||||
-rw-r--r-- | .github/workflows/unit_tests.yaml | 15 | ||||
-rw-r--r-- | .github/workflows/valid-style.yml | 6 | ||||
-rw-r--r-- | lib/spack/docs/tables/system_prerequisites.csv | 2 | ||||
-rw-r--r-- | lib/spack/spack/main.py | 26 | ||||
-rw-r--r-- | lib/spack/spack/test/util/unparse/unparse.py | 74 | ||||
-rw-r--r-- | lib/spack/spack/util/unparse/unparser.py | 92 |
7 files changed, 195 insertions, 22 deletions
diff --git a/.github/workflows/audit.yaml b/.github/workflows/audit.yaml index 5b463a3e0c..275abb2d53 100644 --- a/.github/workflows/audit.yaml +++ b/.github/workflows/audit.yaml @@ -25,7 +25,7 @@ jobs: python-version: ${{inputs.python_version}} - name: Install Python packages run: | - pip install --upgrade pip six setuptools pytest codecov 'coverage[toml]<=6.2' + pip install --upgrade pip six setuptools pytest codecov coverage[toml] - name: Package audits (with coverage) if: ${{ inputs.with_coverage == 'true' }} run: | diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml index 80b4593c23..6a21d166f8 100644 --- a/.github/workflows/unit_tests.yaml +++ b/.github/workflows/unit_tests.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10'] + python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11'] concretizer: ['clingo'] on_develop: - ${{ github.ref == 'refs/heads/develop' }} @@ -22,7 +22,7 @@ jobs: - python-version: 2.7 concretizer: original on_develop: ${{ github.ref == 'refs/heads/develop' }} - - python-version: '3.10' + - python-version: '3.11' concretizer: original on_develop: ${{ github.ref == 'refs/heads/develop' }} exclude: @@ -35,6 +35,9 @@ jobs: - python-version: '3.9' concretizer: 'clingo' on_develop: false + - python-version: '3.10' + concretizer: 'clingo' + on_develop: false steps: - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # @v2 @@ -86,7 +89,7 @@ jobs: SPACK_TEST_SOLVER: ${{ matrix.concretizer }} SPACK_TEST_PARALLEL: 2 COVERAGE: true - UNIT_TEST_COVERAGE: ${{ (matrix.python-version == '3.10') }} + UNIT_TEST_COVERAGE: ${{ (matrix.python-version == '3.11') }} run: | share/spack/qa/run-unit-tests - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 @@ -101,7 +104,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # @v2 with: - python-version: '3.10' + python-version: '3.11' - name: Install System packages run: | sudo apt-get -y update @@ -109,7 +112,7 @@ jobs: sudo apt-get install -y coreutils kcov csh zsh tcsh fish dash bash - name: Install Python packages run: | - pip install --upgrade pip six setuptools pytest codecov coverage[toml]==6.2 pytest-xdist + pip install --upgrade pip six setuptools pytest codecov coverage[toml] pytest-xdist - name: Setup git configuration run: | # Need this for the git tests to succeed. @@ -158,7 +161,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # @v2 with: - python-version: '3.10' + python-version: '3.11' - name: Install System packages run: | sudo apt-get -y update diff --git a/.github/workflows/valid-style.yml b/.github/workflows/valid-style.yml index d91f6e958a..a82c786b44 100644 --- a/.github/workflows/valid-style.yml +++ b/.github/workflows/valid-style.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # @v2 - uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # @v2 with: - python-version: '3.10' + python-version: '3.11' cache: 'pip' - name: Install Python Packages run: | @@ -40,7 +40,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-python@13ae5bb136fac2878aff31522b9efb785519f984 # @v2 with: - python-version: '3.10' + python-version: '3.11' cache: 'pip' - name: Install Python packages run: | @@ -57,4 +57,4 @@ jobs: uses: ./.github/workflows/audit.yaml with: with_coverage: ${{ inputs.with_coverage }} - python_version: '3.10' + python_version: '3.11' 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) |