From 266ea342eb14c785e0a1e813b065bed361cc13df Mon Sep 17 00:00:00 2001 From: Mitch D'Ewart Date: Wed, 7 Jan 2026 11:38:43 -0800 Subject: [PATCH 1/7] Add switch statement support Adds Switch, Case, and Default context managers for generating OpenQASM 3 switch statements. Usage: with oqpy.Switch(prog, selector) as switch: with oqpy.Case(switch, 0): prog.set(result, 10) with oqpy.Case(switch, 1, 2): # Multiple values prog.set(result, 20) with oqpy.Default(switch): prog.set(result, 100) Changes: - control_flow.py: Add Switch class, Case and Default context managers - program.py: Add visit_SwitchStatement to MergeCalStatementsPass - test_directives.py: Add comprehensive tests for switch functionality --- oqpy/control_flow.py | 93 ++++++++++++++++++- oqpy/program.py | 7 ++ tests/test_directives.py | 195 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 294 insertions(+), 1 deletion(-) diff --git a/oqpy/control_flow.py b/oqpy/control_flow.py index 69162bc..198cdf0 100644 --- a/oqpy/control_flow.py +++ b/oqpy/control_flow.py @@ -38,7 +38,7 @@ from oqpy.program import Program -__all__ = ["If", "Else", "ForIn", "While", "Range"] +__all__ = ["If", "Else", "ForIn", "While", "Range", "Switch", "Case", "Default"] @contextlib.contextmanager @@ -176,3 +176,94 @@ def While(program: Program, condition: OQPyExpression) -> Iterator[None]: yield state = program._pop() program._add_statement(ast.WhileLoop(to_ast(program, condition), state.body)) + + +class Switch(contextlib.AbstractContextManager): + """Context manager for switch statement control flow. + + .. code-block:: python + + selector = IntVar(0) + with Switch(program, selector) as switch: + with Case(switch, 0): + program.increment(result, 1) + with Case(switch, 1, 2): # Multiple values in one case + program.increment(result, 2) + with Default(switch): + program.increment(result, 100) + + """ + + def __init__(self, program: "Program", target: OQPyExpression): + self.program = program + self.target = target + self.cases: list[tuple[list[ast.Expression], list[ast.Statement]]] = [] + self.default: list[ast.Statement] | None = None + + def __enter__(self) -> "Switch": + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> bool: + if exc_type is not None: + return False + # Build the case tuples as (list of expressions, CompoundStatement) + case_tuples = [ + (values, ast.CompoundStatement(body)) for values, body in self.cases + ] + default_stmt = ast.CompoundStatement(self.default) if self.default else None + stmt = ast.SwitchStatement( + to_ast(self.program, self.target), + case_tuples, + default_stmt, + ) + self.program._add_statement(stmt) + return False + + +@contextlib.contextmanager +def Case(switch: Switch, *values: AstConvertible) -> Iterator[None]: + """Context manager for a case within a switch statement. + + Must be used inside a Switch context. Multiple values can be provided + for a single case block. + + .. code-block:: python + + with Switch(program, selector) as switch: + with Case(switch, 0): + # Handle case 0 + program.increment(result, 1) + with Case(switch, 1, 2): + # Handle cases 1 and 2 + program.increment(result, 2) + + """ + if not values: + raise ValueError("Case requires at least one value") + switch.program._push() + yield + state = switch.program._pop() + case_values = [to_ast(switch.program, v) for v in values] + switch.cases.append((case_values, state.body)) + + +@contextlib.contextmanager +def Default(switch: Switch) -> Iterator[None]: + """Context manager for the default case within a switch statement. + + Must be used inside a Switch context. + + .. code-block:: python + + with Switch(program, selector) as switch: + with Case(switch, 0): + program.increment(result, 1) + with Default(switch): + # Handle all other cases + program.increment(result, 100) + + """ + switch.program._push() + yield + state = switch.program._pop() + switch.default = state.body diff --git a/oqpy/program.py b/oqpy/program.py index 54c6536..110a51d 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -709,6 +709,13 @@ def visit_BranchingStatement(self, node: ast.BranchingStatement, context: None = node.else_block = self.process_statement_list(node.else_block) self.generic_visit(node, context) + def visit_SwitchStatement(self, node: ast.SwitchStatement, context: None = None) -> None: + for case_values, case_block in node.cases: + case_block.statements = self.process_statement_list(case_block.statements) + if node.default: + node.default.statements = self.process_statement_list(node.default.statements) + self.generic_visit(node, context) + def visit_CalibrationStatement( self, node: ast.CalibrationStatement, context: None = None ) -> None: diff --git a/tests/test_directives.py b/tests/test_directives.py index 3d3f717..3d30d03 100644 --- a/tests/test_directives.py +++ b/tests/test_directives.py @@ -847,6 +847,201 @@ def test_while(): _check_respects_type_hints(prog) +def test_switch_basic(): + prog = Program() + selector = IntVar(0, "selector") + result = IntVar(0, "result") + + with oqpy.Switch(prog, selector) as switch: + with oqpy.Case(switch, 0): + prog.set(result, 10) + with oqpy.Case(switch, 1): + prog.set(result, 20) + with oqpy.Default(switch): + prog.set(result, 100) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] result = 0; + int[32] selector = 0; + switch (selector) { + case 0 { + result = 10; + } + case 1 { + result = 20; + } + default { + result = 100; + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + +def test_switch_multiple_case_values(): + prog = Program() + selector = IntVar(0, "selector") + result = IntVar(0, "result") + + with oqpy.Switch(prog, selector) as switch: + with oqpy.Case(switch, 0, 1, 2): + prog.set(result, 10) + with oqpy.Case(switch, 3, 4): + prog.set(result, 20) + with oqpy.Default(switch): + prog.set(result, 100) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] result = 0; + int[32] selector = 0; + switch (selector) { + case 0, 1, 2 { + result = 10; + } + case 3, 4 { + result = 20; + } + default { + result = 100; + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + +def test_switch_without_default(): + prog = Program() + selector = IntVar(0, "selector") + result = IntVar(0, "result") + + with oqpy.Switch(prog, selector) as switch: + with oqpy.Case(switch, 0): + prog.set(result, 10) + with oqpy.Case(switch, 1): + prog.set(result, 20) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] result = 0; + int[32] selector = 0; + switch (selector) { + case 0 { + result = 10; + } + case 1 { + result = 20; + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + +def test_switch_nested_in_loop(): + prog = Program() + selector = IntVar(0, "selector") + result = IntVar(0, "result") + + with oqpy.ForIn(prog, range(3), "i") as i: + prog.set(selector, i) + with oqpy.Switch(prog, selector) as switch: + with oqpy.Case(switch, 0): + prog.increment(result, 1) + with oqpy.Case(switch, 1): + prog.increment(result, 2) + with oqpy.Default(switch): + prog.increment(result, 10) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] selector = 0; + int[32] result = 0; + for int i in [0:2] { + selector = i; + switch (selector) { + case 0 { + result += 1; + } + case 1 { + result += 2; + } + default { + result += 10; + } + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + +def test_switch_with_negative_cases(): + prog = Program() + selector = IntVar(0, "selector") + result = IntVar(0, "result") + + with oqpy.Switch(prog, selector) as switch: + with oqpy.Case(switch, -2): + prog.set(result, 1) + with oqpy.Case(switch, -1): + prog.set(result, 2) + with oqpy.Case(switch, 0): + prog.set(result, 3) + with oqpy.Default(switch): + prog.set(result, 100) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] result = 0; + int[32] selector = 0; + switch (selector) { + case -2 { + result = 1; + } + case -1 { + result = 2; + } + case 0 { + result = 3; + } + default { + result = 100; + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + +def test_switch_case_requires_value(): + prog = Program() + selector = IntVar(0, "selector") + + with oqpy.Switch(prog, selector) as switch: + with pytest.raises(ValueError, match="Case requires at least one value"): + with oqpy.Case(switch): + pass + + def test_create_frame(): prog = Program() port = PortVar("storage") From 9053611f683cfbe96318a07e2329d063c4101169 Mon Sep 17 00:00:00 2001 From: Mitch D'Ewart Date: Wed, 7 Jan 2026 11:41:33 -0800 Subject: [PATCH 2/7] Add test for nested switch statements Tests nested switches with empty case bodies and multiple values per case (case 1, 2, 5, 12 { }). --- tests/test_directives.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_directives.py b/tests/test_directives.py index 3d30d03..6127b5f 100644 --- a/tests/test_directives.py +++ b/tests/test_directives.py @@ -1042,6 +1042,45 @@ def test_switch_case_requires_value(): pass +def test_switch_nested(): + """Test nested switch statements with empty cases and multiple values per case.""" + prog = Program() + + q = oqpy.PhysicalQubits[0] + i = IntVar(5, "i") + j = IntVar(30, "j") + + with oqpy.Switch(prog, i) as outer_switch: + with oqpy.Case(outer_switch, 1, 2, 5, 12): + pass # Empty case body + with oqpy.Case(outer_switch, 3): + with oqpy.Switch(prog, j) as inner_switch: + with oqpy.Case(inner_switch, 10, 15, 20): + prog.gate(q, "h") + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] j = 30; + int[32] i = 5; + switch (i) { + case 1, 2, 5, 12 { + } + case 3 { + switch (j) { + case 10, 15, 20 { + h $0; + } + } + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + def test_create_frame(): prog = Program() port = PortVar("storage") From ce9962e3752de6cc4812cc5390b7bacc17e1b500 Mon Sep 17 00:00:00 2001 From: Mitch D'Ewart Date: Wed, 7 Jan 2026 14:43:25 -0800 Subject: [PATCH 3/7] Add validation to prevent multiple default cases in switch statements The Default() function now raises RuntimeError if called more than once per Switch statement, preventing silent overwrites where only the last default block would be kept. This follows the existing validation pattern used by Else (which raises "Else without If"). --- oqpy/control_flow.py | 2 ++ tests/test_directives.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/oqpy/control_flow.py b/oqpy/control_flow.py index 198cdf0..0bee3b3 100644 --- a/oqpy/control_flow.py +++ b/oqpy/control_flow.py @@ -263,6 +263,8 @@ def Default(switch: Switch) -> Iterator[None]: program.increment(result, 100) """ + if switch.default is not None: + raise RuntimeError("Switch statement can only have one default case") switch.program._push() yield state = switch.program._pop() diff --git a/tests/test_directives.py b/tests/test_directives.py index 6127b5f..a0343ac 100644 --- a/tests/test_directives.py +++ b/tests/test_directives.py @@ -1042,6 +1042,18 @@ def test_switch_case_requires_value(): pass +def test_switch_multiple_defaults_raises(): + prog = Program() + selector = IntVar(0, "selector") + + with oqpy.Switch(prog, selector) as switch: + with oqpy.Default(switch): + pass + with pytest.raises(RuntimeError, match="Switch statement can only have one default case"): + with oqpy.Default(switch): + pass + + def test_switch_nested(): """Test nested switch statements with empty cases and multiple values per case.""" prog = Program() From 1136144a0bbbd4504f15919d176093f8693cf4f2 Mon Sep 17 00:00:00 2001 From: Mitch D'Ewart Date: Tue, 13 Jan 2026 08:58:28 -0800 Subject: [PATCH 4/7] Fix style issues and update openpulse dependency for switch support - Fix type annotations in Switch class (__exit__ method) - Fix import sorting in control_flow.py - Fix unused variable in MergeCalStatementsPass.visit_SwitchStatement - Update openpulse dependency to >=1.0.0 (required for SwitchStatement AST) --- oqpy/control_flow.py | 30 +++++--- oqpy/program.py | 14 ++-- poetry.lock | 159 +++++++++++++++++++++++++++++++++++-------- pyproject.toml | 4 +- 4 files changed, 164 insertions(+), 43 deletions(-) diff --git a/oqpy/control_flow.py b/oqpy/control_flow.py index 0bee3b3..3d4de1e 100644 --- a/oqpy/control_flow.py +++ b/oqpy/control_flow.py @@ -18,7 +18,16 @@ from __future__ import annotations import contextlib -from typing import TYPE_CHECKING, Iterable, Iterator, Optional, TypeVar, overload +from typing import ( + TYPE_CHECKING, + Any, + Iterable, + Iterator, + Literal, + Optional, + TypeVar, + overload, +) from openpulse import ast @@ -85,7 +94,8 @@ def ForIn( program: Program, iterator: Iterable[AstConvertible] | range | AstConvertible, identifier_name: Optional[str], -) -> contextlib._GeneratorContextManager[IntVar]: ... # pragma: no cover +) -> contextlib._GeneratorContextManager[IntVar]: + ... # pragma: no cover @overload @@ -94,7 +104,8 @@ def ForIn( iterator: Iterable[AstConvertible] | range | AstConvertible, identifier_name: Optional[str], identifier_type: type[ClassicalVarT], -) -> contextlib._GeneratorContextManager[ClassicalVarT]: ... # pragma: no cover +) -> contextlib._GeneratorContextManager[ClassicalVarT]: + ... # pragma: no cover @contextlib.contextmanager @@ -178,7 +189,7 @@ def While(program: Program, condition: OQPyExpression) -> Iterator[None]: program._add_statement(ast.WhileLoop(to_ast(program, condition), state.body)) -class Switch(contextlib.AbstractContextManager): +class Switch(contextlib.AbstractContextManager["Switch"]): """Context manager for switch statement control flow. .. code-block:: python @@ -203,13 +214,16 @@ def __init__(self, program: "Program", target: OQPyExpression): def __enter__(self) -> "Switch": return self - def __exit__(self, exc_type, exc_val, exc_tb) -> bool: + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: Any, + ) -> Literal[False]: if exc_type is not None: return False # Build the case tuples as (list of expressions, CompoundStatement) - case_tuples = [ - (values, ast.CompoundStatement(body)) for values, body in self.cases - ] + case_tuples = [(values, ast.CompoundStatement(body)) for values, body in self.cases] default_stmt = ast.CompoundStatement(self.default) if self.default else None stmt = ast.SwitchStatement( to_ast(self.program, self.target), diff --git a/oqpy/program.py b/oqpy/program.py index 110a51d..b9558e3 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -133,7 +133,9 @@ def __iadd__(self, other: Program) -> Program: self.defcals.update(other.defcals) for name, subroutine_stmt in other.subroutines.items(): self._add_subroutine( - name, subroutine_stmt, needs_declaration=name not in other.declared_subroutines + name, + subroutine_stmt, + needs_declaration=name not in other.declared_subroutines, ) for name, gate_stmt in other.gates.items(): self._add_gate(name, gate_stmt, needs_declaration=name not in other.declared_gates) @@ -418,7 +420,9 @@ def declare( return self def delay( - self, time: AstConvertible, qubits_or_frames: AstConvertible | Iterable[AstConvertible] = () + self, + time: AstConvertible, + qubits_or_frames: AstConvertible | Iterable[AstConvertible] = (), ) -> Program: """Apply a delay to a set of qubits or frames.""" if not isinstance(qubits_or_frames, Iterable): @@ -608,7 +612,9 @@ def reset(self, qubit: quantum_types.Qubit) -> Program: return self def measure( - self, qubit: quantum_types.Qubit, output_location: classical_types.BitVar | None = None + self, + qubit: quantum_types.Qubit, + output_location: classical_types.BitVar | None = None, ) -> Program: """Measure a particular qubit. @@ -710,7 +716,7 @@ def visit_BranchingStatement(self, node: ast.BranchingStatement, context: None = self.generic_visit(node, context) def visit_SwitchStatement(self, node: ast.SwitchStatement, context: None = None) -> None: - for case_values, case_block in node.cases: + for _, case_block in node.cases: case_block.statements = self.process_statement_list(case_block.statements) if node.default: node.default.statements = self.process_statement_list(node.default.statements) diff --git a/poetry.lock b/poetry.lock index de7695f..c737f0e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -6,6 +6,7 @@ version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, @@ -17,6 +18,7 @@ version = "4.11.1" description = "ANTLR 4.11.1 runtime for Python 3" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "antlr4-python3-runtime-4.11.1.tar.gz", hash = "sha256:a53de701312f9bdacc5258a6872cd6c62b90d3a90ae25e494026f76267333b60"}, {file = "antlr4_python3_runtime-4.11.1-py3-none-any.whl", hash = "sha256:ff1954eda1ca9072c02bf500387d0c86cb549bef4dbb3b64f39468b547ec5f6b"}, @@ -28,6 +30,7 @@ version = "2.12.13" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.7.2" +groups = ["dev"] files = [ {file = "astroid-2.12.13-py3-none-any.whl", hash = "sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907"}, {file = "astroid-2.12.13.tar.gz", hash = "sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"}, @@ -47,16 +50,17 @@ version = "21.4.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, ] [package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] +tests-no-zope = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] [[package]] name = "Babel" @@ -64,6 +68,7 @@ version = "2.10.3" description = "Internationalization utilities" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, @@ -78,6 +83,7 @@ version = "24.3.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, @@ -114,7 +120,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -124,6 +130,7 @@ version = "5.0.1" description = "An easy safelist-based HTML-sanitizing tool." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, @@ -135,7 +142,7 @@ webencodings = "*" [package.extras] css = ["tinycss2 (>=1.1.0,<1.2)"] -dev = ["Sphinx (==4.3.2)", "black (==22.3.0)", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] +dev = ["Sphinx (==4.3.2)", "black (==22.3.0) ; implementation_name == \"cpython\"", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961) ; implementation_name == \"cpython\"", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] [[package]] name = "certifi" @@ -143,6 +150,7 @@ version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, @@ -154,6 +162,7 @@ version = "2.0.0" description = "Command line program to validate and convert CITATION.cff files." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "cffconvert-2.0.0-py3-none-any.whl", hash = "sha256:573c825e4e16173d99396dc956bd22ff5d4f84215cc16b6ab05299124f5373bb"}, {file = "cffconvert-2.0.0.tar.gz", hash = "sha256:b4379ee415c6637dc9e3e7ba196605cb3cedcea24613e4ea242c607d9e98eb50"}, @@ -177,6 +186,8 @@ version = "1.15.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"linux\" and platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, @@ -253,6 +264,7 @@ version = "2.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.6.0" +groups = ["dev"] files = [ {file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"}, {file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"}, @@ -267,6 +279,7 @@ version = "8.1.3" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, @@ -281,6 +294,8 @@ version = "0.4.5" description = "Cross-platform colored terminal text." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" files = [ {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, @@ -292,6 +307,7 @@ version = "0.9.1" description = "Python parser for the CommonMark Markdown spec" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, @@ -306,6 +322,7 @@ version = "6.4.4" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, @@ -363,7 +380,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -371,6 +388,8 @@ version = "42.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "sys_platform == \"linux\"" files = [ {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, @@ -425,6 +444,7 @@ version = "0.3.5.1" description = "serialize all of python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["dev"] files = [ {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, @@ -439,6 +459,7 @@ version = "0.6.2" description = "Pythonic argument parser, that will make you smile" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] @@ -449,6 +470,7 @@ version = "0.17.1" description = "Docutils -- Python Documentation Utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, @@ -460,6 +482,8 @@ version = "1.1.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version <= \"3.10\"" files = [ {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, @@ -474,6 +498,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -485,6 +510,7 @@ version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, @@ -496,10 +522,12 @@ version = "4.12.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, ] +markers = {main = "python_version < \"3.10\""} [package.dependencies] zipp = ">=0.5" @@ -507,7 +535,7 @@ zipp = ">=0.5" [package.extras] docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" @@ -515,6 +543,7 @@ version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, @@ -526,6 +555,7 @@ version = "5.10.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.6.1,<4.0" +groups = ["dev"] files = [ {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, @@ -543,6 +573,8 @@ version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "sys_platform == \"linux\"" files = [ {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, @@ -550,7 +582,7 @@ files = [ [package.extras] test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] -trio = ["async_generator", "trio"] +trio = ["async_generator ; python_version == \"3.6\"", "trio"] [[package]] name = "jinja2" @@ -558,6 +590,7 @@ version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, @@ -575,6 +608,7 @@ version = "3.2.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, @@ -596,6 +630,7 @@ version = "23.7.0" description = "Store and access your passwords safely." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "keyring-23.7.0-py3-none-any.whl", hash = "sha256:e67fc91a7955785fd2efcbccdd72d7dacf136dbc381d27de305b2b660b3de886"}, {file = "keyring-23.7.0.tar.gz", hash = "sha256:782e1cd1132e91bf459fcd243bcf25b326015c1ac0b198e4408f91fa6791062b"}, @@ -609,7 +644,7 @@ SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} [package.extras] docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +testing = ["pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\""] [[package]] name = "lazy-object-proxy" @@ -617,6 +652,7 @@ version = "1.7.1" description = "A fast and thorough lazy object proxy." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, @@ -663,6 +699,7 @@ version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, @@ -687,6 +724,7 @@ version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, @@ -736,6 +774,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -747,6 +786,7 @@ version = "0.3.1" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mdit-py-plugins-0.3.1.tar.gz", hash = "sha256:3fc13298497d6e04fe96efdd41281bfe7622152f9caa1815ea99b5c893de9441"}, {file = "mdit_py_plugins-0.3.1-py3-none-any.whl", hash = "sha256:606a7f29cf56dbdfaf914acb21709b8f8ee29d857e8f29dcc33d8cb84c57bfa1"}, @@ -766,6 +806,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -777,6 +818,7 @@ version = "1.4.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, @@ -823,6 +865,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["main", "dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -834,6 +877,7 @@ version = "0.18.1" description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "myst-parser-0.18.1.tar.gz", hash = "sha256:79317f4bb2c13053dd6e64f9da1ba1da6cd9c40c8a430c447a7b146a594c246d"}, {file = "myst_parser-0.18.1-py3-none-any.whl", hash = "sha256:61b275b85d9f58aa327f370913ae1bec26ebad372cc99f3ab85c8ec3ee8d9fb8"}, @@ -860,6 +904,7 @@ version = "1.23.4" description = "NumPy is the fundamental package for array computing with Python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "numpy-1.23.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2"}, {file = "numpy-1.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f"}, @@ -893,19 +938,20 @@ files = [ [[package]] name = "openpulse" -version = "0.5.0" +version = "1.0.1" description = "Reference OpenPulse AST in Python" optional = false python-versions = "*" +groups = ["main"] files = [ - {file = "openpulse-0.5.0-py3-none-any.whl", hash = "sha256:c91b69633366381f3fdbc0c9be8c37c114b2d8e469f667ff9b0f78632e00c395"}, - {file = "openpulse-0.5.0.tar.gz", hash = "sha256:d7b1d940c0e081975f5ebdad2b378d24e4a612b73fce1bc2e26948d907f5db1c"}, + {file = "openpulse-1.0.1-py3-none-any.whl", hash = "sha256:75fb2d4d7f74db3a86027719744541fcb725e1f5b79e14b78dc5b34ed8c66e87"}, + {file = "openpulse-1.0.1.tar.gz", hash = "sha256:4c184e3012907ec35f04202ed72621037b1a06d70195769576bfc9e62c01bf94"}, ] [package.dependencies] -antlr4-python3-runtime = ">=4.7,<4.12" +antlr4-python3-runtime = ">=4.7,<4.14" importlib-metadata = {version = "*", markers = "python_version < \"3.10\""} -openqasm3 = {version = ">=0.5.0", extras = ["parser"]} +openqasm3 = {version = ">=1.0.0,<2.0", extras = ["parser"]} [package.extras] all = ["pytest (>=6.0)", "pyyaml"] @@ -917,6 +963,7 @@ version = "0.1.1" description = "Pygments tools for OpenQASM" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "openqasm-pygments-0.1.1.tar.gz", hash = "sha256:31f70d540c44ca3ff00f0806e1e8dbfd23ab1d060bc0e125328994a7e0168067"}, {file = "openqasm_pygments-0.1.1-py3-none-any.whl", hash = "sha256:7a65b3d8e87bb54e525fc8cd973b83b5bc45efeea64ba84cd611cace0277901e"}, @@ -927,22 +974,23 @@ Pygments = ">=2.0,<3.0" [[package]] name = "openqasm3" -version = "0.5.0" +version = "1.0.1" description = "Reference OpenQASM AST in Python" optional = false python-versions = "*" +groups = ["main"] files = [ - {file = "openqasm3-0.5.0-py3-none-any.whl", hash = "sha256:40991ac057b9e3c208d1b34242b0aad8a3b9840df0335a652b1e4e4248937b1c"}, - {file = "openqasm3-0.5.0.tar.gz", hash = "sha256:bf8bf4ed098393447e552eaea18b0a34a2429d228477683d6b579348bc17bfc8"}, + {file = "openqasm3-1.0.1-py3-none-any.whl", hash = "sha256:0d3a1ebe3465e3ea619bcaa369858bba8944cbb0c49604b24f94662d3ec41d41"}, + {file = "openqasm3-1.0.1.tar.gz", hash = "sha256:c589dc05d4ced50ca24167d14e0f2c916e717499ba0442e0ff2a3030ef312d0a"}, ] [package.dependencies] -antlr4-python3-runtime = {version = ">=4.7,<4.14", optional = true, markers = "extra == \"parser\""} -importlib-metadata = {version = "*", optional = true, markers = "python_version < \"3.10\" and extra == \"parser\""} +antlr4_python3_runtime = {version = ">=4.7,<4.14", optional = true, markers = "extra == \"parser\""} +importlib_metadata = {version = "*", optional = true, markers = "python_version < \"3.10\" and extra == \"parser\""} [package.extras] -all = ["antlr4-python3-runtime (>=4.7,<4.14)", "importlib-metadata", "pytest (>=6.0)", "pyyaml"] -parser = ["antlr4-python3-runtime (>=4.7,<4.14)", "importlib-metadata"] +all = ["antlr4_python3_runtime (>=4.7,<4.14)", "importlib_metadata ; python_version < \"3.10\"", "pytest (>=6.0)", "pyyaml"] +parser = ["antlr4_python3_runtime (>=4.7,<4.14)", "importlib_metadata ; python_version < \"3.10\""] tests = ["pytest (>=6.0)", "pyyaml"] [[package]] @@ -951,6 +999,7 @@ version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, @@ -962,6 +1011,7 @@ version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +groups = ["dev"] files = [ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, @@ -973,6 +1023,7 @@ version = "1.8.3" description = "Query metadatdata from sdists / bdists / installed packages." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +groups = ["dev"] files = [ {file = "pkginfo-1.8.3-py2.py3-none-any.whl", hash = "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594"}, {file = "pkginfo-1.8.3.tar.gz", hash = "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c"}, @@ -987,6 +1038,7 @@ version = "2.5.2" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, @@ -1002,6 +1054,7 @@ version = "1.0.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, @@ -1017,6 +1070,7 @@ version = "8.0.0" description = "Get CPU info with pure Python 2 & 3" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "py-cpuinfo-8.0.0.tar.gz", hash = "sha256:5f269be0e08e33fd959de96b34cd4aeeeacac014dd8305f70eb28d06de2345c5"}, ] @@ -1027,6 +1081,8 @@ version = "2.21" description = "C parser in Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] +markers = "sys_platform == \"linux\" and platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, @@ -1038,6 +1094,7 @@ version = "6.1.1" description = "Python docstring style checker" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, @@ -1055,13 +1112,14 @@ version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] -plugins = ["importlib-metadata"] +plugins = ["importlib-metadata ; python_version < \"3.8\""] [[package]] name = "pykwalify" @@ -1069,6 +1127,7 @@ version = "1.8.0" description = "Python lib/cli for JSON/YAML schema validation" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pykwalify-1.8.0-py2.py3-none-any.whl", hash = "sha256:731dfa87338cca9f559d1fca2bdea37299116e3139b73f78ca90a543722d6651"}, {file = "pykwalify-1.8.0.tar.gz", hash = "sha256:796b2ad3ed4cb99b88308b533fb2f559c30fa6efb4fa9fda11347f483d245884"}, @@ -1085,6 +1144,7 @@ version = "2.15.6" description = "python code static checker" optional = false python-versions = ">=3.7.2" +groups = ["dev"] files = [ {file = "pylint-2.15.6-py3-none-any.whl", hash = "sha256:15060cc22ed6830a4049cf40bc24977744df2e554d38da1b2657591de5bcd052"}, {file = "pylint-2.15.6.tar.gz", hash = "sha256:25b13ddcf5af7d112cf96935e21806c1da60e676f952efb650130f2a4483421c"}, @@ -1111,6 +1171,7 @@ version = "0.19.2" description = "Persistent/Functional/Immutable data structures" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"}, {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"}, @@ -1142,6 +1203,7 @@ version = "7.4.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, @@ -1164,6 +1226,7 @@ version = "4.0.0" description = "A ``pytest`` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-benchmark-4.0.0.tar.gz", hash = "sha256:fb0785b83efe599a6a956361c0691ae1dbb5318018561af10f3e915caa0048d1"}, {file = "pytest_benchmark-4.0.0-py3-none-any.whl", hash = "sha256:fdb7db64e31c8b277dff9850d2a2556d8b60bcb0ea6524e36e28ffd7c87f71d6"}, @@ -1184,6 +1247,7 @@ version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -1202,6 +1266,7 @@ version = "2.8.2" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -1216,6 +1281,7 @@ version = "2022.4" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"}, {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"}, @@ -1227,6 +1293,8 @@ version = "0.2.0" description = "" optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, @@ -1238,6 +1306,7 @@ version = "6.0" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, @@ -1287,6 +1356,7 @@ version = "35.0" description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "readme_renderer-35.0-py3-none-any.whl", hash = "sha256:73b84905d091c31f36e50b4ae05ae2acead661f6a09a9abb4df7d2ddcdb6a698"}, {file = "readme_renderer-35.0.tar.gz", hash = "sha256:a727999acfc222fc21d82a12ed48c957c4989785e5865807c65a487d21677497"}, @@ -1306,6 +1376,7 @@ version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1327,6 +1398,7 @@ version = "0.9.1" description = "A utility belt for advanced users of python-requests" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, @@ -1341,6 +1413,7 @@ version = "2.0.0" description = "Validating URI References per RFC 3986" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, @@ -1355,6 +1428,7 @@ version = "12.5.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.6.3,<4.0.0" +groups = ["dev"] files = [ {file = "rich-12.5.1-py3-none-any.whl", hash = "sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb"}, {file = "rich-12.5.1.tar.gz", hash = "sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca"}, @@ -1374,6 +1448,7 @@ version = "0.17.21" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3" +groups = ["dev"] files = [ {file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"}, {file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"}, @@ -1392,6 +1467,8 @@ version = "0.2.7" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false python-versions = ">=3.5" +groups = ["dev"] +markers = "python_version <= \"3.10\" and platform_python_implementation == \"CPython\"" files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7"}, @@ -1438,6 +1515,8 @@ version = "3.3.2" description = "Python bindings to FreeDesktop.org Secret Service API" optional = false python-versions = ">=3.6" +groups = ["dev"] +markers = "sys_platform == \"linux\"" files = [ {file = "SecretStorage-3.3.2-py3-none-any.whl", hash = "sha256:755dc845b6ad76dcbcbc07ea3da75ae54bb1ea529eb72d15f83d26499a5df319"}, {file = "SecretStorage-3.3.2.tar.gz", hash = "sha256:0a8eb9645b320881c222e827c26f4cfcf55363e8b374a021981ef886657a912f"}, @@ -1453,6 +1532,7 @@ version = "65.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, @@ -1460,7 +1540,7 @@ files = [ [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov ; platform_python_implementation != \"PyPy\"", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\"", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] @@ -1469,6 +1549,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["dev"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -1480,6 +1561,7 @@ version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, @@ -1491,6 +1573,7 @@ version = "5.3.0" description = "Python documentation generator" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, @@ -1518,7 +1601,7 @@ sphinxcontrib-serializinghtml = ">=1.1.5" [package.extras] docs = ["sphinxcontrib-websupport"] lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast ; python_version < \"3.8\""] [[package]] name = "sphinx-rtd-theme" @@ -1526,6 +1609,7 @@ version = "1.0.0" description = "Read the Docs theme for Sphinx" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +groups = ["dev"] files = [ {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, @@ -1544,6 +1628,7 @@ version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, @@ -1559,6 +1644,7 @@ version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, @@ -1574,6 +1660,7 @@ version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, @@ -1589,6 +1676,7 @@ version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, @@ -1603,6 +1691,7 @@ version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, @@ -1618,6 +1707,7 @@ version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, @@ -1633,6 +1723,7 @@ version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["dev"] files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, @@ -1644,6 +1735,8 @@ version = "1.2.3" description = "A lil' TOML parser" optional = false python-versions = ">=3.6" +groups = ["dev"] +markers = "python_version <= \"3.10\"" files = [ {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, @@ -1655,6 +1748,7 @@ version = "0.11.6" description = "Style preserving TOML library" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "tomlkit-0.11.6-py3-none-any.whl", hash = "sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b"}, {file = "tomlkit-0.11.6.tar.gz", hash = "sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"}, @@ -1666,6 +1760,7 @@ version = "4.0.1" description = "Collection of utilities for publishing packages on PyPI" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "twine-4.0.1-py3-none-any.whl", hash = "sha256:42026c18e394eac3e06693ee52010baa5313e4811d5a11050e7d48436cf41b9e"}, {file = "twine-4.0.1.tar.gz", hash = "sha256:96b1cf12f7ae611a4a40b6ae8e9570215daff0611828f5fe1f37a16255ab24a0"}, @@ -1688,6 +1783,7 @@ version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, @@ -1699,13 +1795,14 @@ version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1716,6 +1813,7 @@ version = "0.5.1" description = "Character encoding aliases for legacy web content" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, @@ -1727,6 +1825,7 @@ version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +groups = ["dev"] files = [ {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, @@ -1810,16 +1909,18 @@ version = "3.8.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, ] +markers = {main = "python_version < \"3.10\""} [package.extras] docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7) ; platform_python_implementation != \"PyPy\"", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1) ; platform_python_implementation != \"PyPy\""] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.8,<4.0" -content-hash = "6f83048a984aecc5f411089449a5698420891a0a298f9d16c62f20b03ac51fd9" +content-hash = "01dfcc9444df27307b4995963a164320fd7ffb4bd1a42e3fa1a94be3ffc7e574" diff --git a/pyproject.toml b/pyproject.toml index 3c7a1dc..86e411a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,8 @@ classifiers = [ [tool.poetry.dependencies] python = ">=3.8,<4.0" -# 0.4 loosens the antlr4-python3-runtime constraints -openpulse = ">=0.5.0" +# 1.0 adds SwitchStatement and CompoundStatement AST support +openpulse = ">=1.0.0" numpy = [ {version = ">=1.17.3", python = ">=3.8,<3.9"}, {version = ">=1.19.3", python = ">=3.9,<3.10"}, From d595f8add4cf0d27cc8d5b82b219090af719373c Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 13 Jan 2026 10:00:23 -0800 Subject: [PATCH 5/7] fixes for 3.8 and linting --- oqpy/control_flow.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/oqpy/control_flow.py b/oqpy/control_flow.py index 3d4de1e..771c2da 100644 --- a/oqpy/control_flow.py +++ b/oqpy/control_flow.py @@ -21,6 +21,7 @@ from typing import ( TYPE_CHECKING, Any, + ContextManager, Iterable, Iterator, Literal, @@ -94,8 +95,7 @@ def ForIn( program: Program, iterator: Iterable[AstConvertible] | range | AstConvertible, identifier_name: Optional[str], -) -> contextlib._GeneratorContextManager[IntVar]: - ... # pragma: no cover +) -> contextlib._GeneratorContextManager[IntVar]: ... # pragma: no cover @overload @@ -104,8 +104,7 @@ def ForIn( iterator: Iterable[AstConvertible] | range | AstConvertible, identifier_name: Optional[str], identifier_type: type[ClassicalVarT], -) -> contextlib._GeneratorContextManager[ClassicalVarT]: - ... # pragma: no cover +) -> contextlib._GeneratorContextManager[ClassicalVarT]: ... # pragma: no cover @contextlib.contextmanager @@ -189,7 +188,7 @@ def While(program: Program, condition: OQPyExpression) -> Iterator[None]: program._add_statement(ast.WhileLoop(to_ast(program, condition), state.body)) -class Switch(contextlib.AbstractContextManager["Switch"]): +class Switch(ContextManager["Switch"]): """Context manager for switch statement control flow. .. code-block:: python From 368771593aa56bf2fc314c024cad37540d125f3b Mon Sep 17 00:00:00 2001 From: Mitch D'Ewart Date: Wed, 14 Jan 2026 11:46:16 -0800 Subject: [PATCH 6/7] Add coverage tests for switch statements and make mypy non-blocking - Add test for exception propagation in Switch context manager (covers control_flow.py __exit__ branch) - Add tests for MergeCalStatementsPass.visit_SwitchStatement with encal_declarations=True (with and without default case) - Make mypy check continue-on-error in CI workflow due to type incompatibilities introduced by openpulse 1.0.0 upgrade - Fix black formatting in control_flow.py overload stubs Coverage is now 100%. The mypy errors are pre-existing type issues exposed by the openpulse AST type definition changes and should be addressed in a separate PR. --- .github/workflows/test.yml | 2 ++ poetry.lock | 59 +++++++++++++++++++------------------- tests/test_directives.py | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 29 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b214090..01397dd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,6 +58,8 @@ jobs: run: poetry run make check-format - name: Run mypy + # TODO: Remove continue-on-error after fixing openpulse 1.0.0 type incompatibilities + continue-on-error: true run: poetry run make check-mypy - name: Run pylint diff --git a/poetry.lock b/poetry.lock index c737f0e..e6ef49c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -814,38 +814,39 @@ files = [ [[package]] name = "mypy" -version = "1.4.1" +version = "1.10.1" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, + {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, + {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, + {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, + {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, + {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, + {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, + {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, + {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, + {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, + {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, + {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, + {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, + {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, + {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, + {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, + {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, + {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, ] [package.dependencies] @@ -856,7 +857,7 @@ typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] diff --git a/tests/test_directives.py b/tests/test_directives.py index a0343ac..29fefb5 100644 --- a/tests/test_directives.py +++ b/tests/test_directives.py @@ -1093,6 +1093,64 @@ def test_switch_nested(): _check_respects_type_hints(prog) +def test_switch_exception_propagation(): + """Test that exceptions raised inside Switch context propagate correctly.""" + prog = Program() + selector = IntVar(0, "selector") + + class TestException(Exception): + pass + + with pytest.raises(TestException): + with oqpy.Switch(prog, selector) as switch: + with oqpy.Case(switch, 0): + raise TestException("test error") + + +def test_switch_with_encal_declarations(): + """Test switch statement with encal_declarations=True to cover MergeCalStatementsPass.""" + prog = Program() + selector = IntVar(0, "selector") + port = PortVar("storage") + frame = FrameVar(port, 6e9, name="storage_frame") + + with oqpy.Switch(prog, selector) as switch: + with oqpy.Case(switch, 0): + prog.set_phase(frame, 0.5) + with oqpy.Case(switch, 1): + prog.set_phase(frame, 1.0) + with oqpy.Default(switch): + prog.set_phase(frame, 0.0) + + # This triggers MergeCalStatementsPass.visit_SwitchStatement + qasm = prog.to_qasm(encal_declarations=True) + assert "switch" in qasm + assert "case 0" in qasm + assert "case 1" in qasm + assert "default" in qasm + + +def test_switch_without_default_encal_declarations(): + """Test switch statement without default case with encal_declarations=True.""" + prog = Program() + selector = IntVar(0, "selector") + port = PortVar("storage") + frame = FrameVar(port, 6e9, name="storage_frame") + + with oqpy.Switch(prog, selector) as switch: + with oqpy.Case(switch, 0): + prog.set_phase(frame, 0.5) + with oqpy.Case(switch, 1): + prog.set_phase(frame, 1.0) + + # This triggers MergeCalStatementsPass.visit_SwitchStatement without default + qasm = prog.to_qasm(encal_declarations=True) + assert "switch" in qasm + assert "case 0" in qasm + assert "case 1" in qasm + assert "default" not in qasm + + def test_create_frame(): prog = Program() port = PortVar("storage") From 4578908e2a70d99b166c4e5686750751a91ae31c Mon Sep 17 00:00:00 2001 From: Mitch D'Ewart Date: Tue, 20 Jan 2026 13:41:24 -0800 Subject: [PATCH 7/7] Address PR review feedback for switch statement support --- .github/workflows/test.yml | 2 - oqpy/control_flow.py | 51 +++--- oqpy/program.py | 12 +- pyproject.toml | 2 +- tests/test_directives.py | 339 +++++++++++++++++++++++++++++++------ 5 files changed, 329 insertions(+), 77 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 01397dd..b214090 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,8 +58,6 @@ jobs: run: poetry run make check-format - name: Run mypy - # TODO: Remove continue-on-error after fixing openpulse 1.0.0 type incompatibilities - continue-on-error: true run: poetry run make check-mypy - name: Run pylint diff --git a/oqpy/control_flow.py b/oqpy/control_flow.py index 771c2da..dc21ba8 100644 --- a/oqpy/control_flow.py +++ b/oqpy/control_flow.py @@ -188,18 +188,18 @@ def While(program: Program, condition: OQPyExpression) -> Iterator[None]: program._add_statement(ast.WhileLoop(to_ast(program, condition), state.body)) -class Switch(ContextManager["Switch"]): +class Switch(ContextManager[None]): """Context manager for switch statement control flow. .. code-block:: python selector = IntVar(0) - with Switch(program, selector) as switch: - with Case(switch, 0): + with Switch(program, selector): + with Case(program, 0): program.increment(result, 1) - with Case(switch, 1, 2): # Multiple values in one case + with Case(program, 1, 2): # Multiple values in one case program.increment(result, 2) - with Default(switch): + with Default(program): program.increment(result, 100) """ @@ -210,8 +210,9 @@ def __init__(self, program: "Program", target: OQPyExpression): self.cases: list[tuple[list[ast.Expression], list[ast.Statement]]] = [] self.default: list[ast.Statement] | None = None - def __enter__(self) -> "Switch": - return self + def __enter__(self) -> None: + self.program._push() + self.program._state.active_switch = self def __exit__( self, @@ -219,7 +220,10 @@ def __exit__( exc_val: BaseException | None, exc_tb: Any, ) -> Literal[False]: + # Pop the switch context state + self.program._pop() if exc_type is not None: + # Don't add statement if an exception occurred; propagate the exception return False # Build the case tuples as (list of expressions, CompoundStatement) case_tuples = [(values, ast.CompoundStatement(body)) for values, body in self.cases] @@ -230,11 +234,12 @@ def __exit__( default_stmt, ) self.program._add_statement(stmt) + # Return False to indicate exceptions should not be suppressed return False @contextlib.contextmanager -def Case(switch: Switch, *values: AstConvertible) -> Iterator[None]: +def Case(program: "Program", *values: AstConvertible) -> Iterator[None]: """Context manager for a case within a switch statement. Must be used inside a Switch context. Multiple values can be provided @@ -242,43 +247,49 @@ def Case(switch: Switch, *values: AstConvertible) -> Iterator[None]: .. code-block:: python - with Switch(program, selector) as switch: - with Case(switch, 0): + with Switch(program, selector): + with Case(program, 0): # Handle case 0 program.increment(result, 1) - with Case(switch, 1, 2): + with Case(program, 1, 2): # Handle cases 1 and 2 program.increment(result, 2) """ if not values: raise ValueError("Case requires at least one value") - switch.program._push() + switch = program._state.active_switch + if switch is None: + raise RuntimeError("Case must be used inside a Switch context") + program._push() yield - state = switch.program._pop() - case_values = [to_ast(switch.program, v) for v in values] + state = program._pop() + case_values = [to_ast(program, v) for v in values] switch.cases.append((case_values, state.body)) @contextlib.contextmanager -def Default(switch: Switch) -> Iterator[None]: +def Default(program: "Program") -> Iterator[None]: """Context manager for the default case within a switch statement. Must be used inside a Switch context. .. code-block:: python - with Switch(program, selector) as switch: - with Case(switch, 0): + with Switch(program, selector): + with Case(program, 0): program.increment(result, 1) - with Default(switch): + with Default(program): # Handle all other cases program.increment(result, 100) """ + switch = program._state.active_switch + if switch is None: + raise RuntimeError("Default must be used inside a Switch context") if switch.default is not None: raise RuntimeError("Switch statement can only have one default case") - switch.program._push() + program._push() yield - state = switch.program._pop() + state = program._pop() switch.default = state.body diff --git a/oqpy/program.py b/oqpy/program.py index b9558e3..ad06294 100644 --- a/oqpy/program.py +++ b/oqpy/program.py @@ -25,7 +25,7 @@ import warnings from copy import deepcopy -from typing import Any, Hashable, Iterable, Iterator, Optional +from typing import TYPE_CHECKING, Any, Hashable, Iterable, Iterator, Optional from openpulse import ast from openpulse.printer import dumps @@ -44,6 +44,9 @@ from oqpy.pulse import FrameVar, PortVar, WaveformVar from oqpy.timing import convert_duration_to_float, convert_float_to_duration +if TYPE_CHECKING: + from oqpy.control_flow import Switch + __all__ = ["Program"] @@ -59,6 +62,7 @@ def __init__(self) -> None: self.body: list[ast.Statement | ast.Pragma] = [] self.if_clause: Optional[ast.BranchingStatement] = None self.annotations: list[ast.Annotation] = [] + self.active_switch: Optional["Switch"] = None # Set when inside a switch context def add_if_clause(self, condition: ast.Expression, if_clause: list[ast.Statement]) -> None: if_clause_annotations, self.annotations = self.annotations, [] @@ -82,6 +86,10 @@ def add_statement(self, stmt: ast.Statement | ast.Pragma) -> None: # it seems to conflict with the definition of ast.Program. # Issue raised in https://github.com/openqasm/openqasm/issues/468 assert isinstance(stmt, (ast.Statement, ast.Pragma)) + if self.active_switch is not None: + raise RuntimeError( + "Statements inside a Switch block must be within a Case or Default context" + ) if isinstance(stmt, ast.Statement) and self.annotations: stmt.annotations = self.annotations + list(stmt.annotations) self.annotations = [] @@ -718,7 +726,7 @@ def visit_BranchingStatement(self, node: ast.BranchingStatement, context: None = def visit_SwitchStatement(self, node: ast.SwitchStatement, context: None = None) -> None: for _, case_block in node.cases: case_block.statements = self.process_statement_list(case_block.statements) - if node.default: + if node.default is not None: node.default.statements = self.process_statement_list(node.default.statements) self.generic_visit(node, context) diff --git a/pyproject.toml b/pyproject.toml index 86e411a..e700179 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ classifiers = [ [tool.poetry.dependencies] python = ">=3.8,<4.0" -# 1.0 adds SwitchStatement and CompoundStatement AST support +# 1.0 contains SwitchStatement and CompoundStatement AST nodes openpulse = ">=1.0.0" numpy = [ {version = ">=1.17.3", python = ">=3.8,<3.9"}, diff --git a/tests/test_directives.py b/tests/test_directives.py index 29fefb5..1d2ce8f 100644 --- a/tests/test_directives.py +++ b/tests/test_directives.py @@ -852,12 +852,12 @@ def test_switch_basic(): selector = IntVar(0, "selector") result = IntVar(0, "result") - with oqpy.Switch(prog, selector) as switch: - with oqpy.Case(switch, 0): + with oqpy.Switch(prog, selector): + with oqpy.Case(prog, 0): prog.set(result, 10) - with oqpy.Case(switch, 1): + with oqpy.Case(prog, 1): prog.set(result, 20) - with oqpy.Default(switch): + with oqpy.Default(prog): prog.set(result, 100) expected = textwrap.dedent( @@ -888,12 +888,12 @@ def test_switch_multiple_case_values(): selector = IntVar(0, "selector") result = IntVar(0, "result") - with oqpy.Switch(prog, selector) as switch: - with oqpy.Case(switch, 0, 1, 2): + with oqpy.Switch(prog, selector): + with oqpy.Case(prog, 0, 1, 2): prog.set(result, 10) - with oqpy.Case(switch, 3, 4): + with oqpy.Case(prog, 3, 4): prog.set(result, 20) - with oqpy.Default(switch): + with oqpy.Default(prog): prog.set(result, 100) expected = textwrap.dedent( @@ -924,10 +924,10 @@ def test_switch_without_default(): selector = IntVar(0, "selector") result = IntVar(0, "result") - with oqpy.Switch(prog, selector) as switch: - with oqpy.Case(switch, 0): + with oqpy.Switch(prog, selector): + with oqpy.Case(prog, 0): prog.set(result, 10) - with oqpy.Case(switch, 1): + with oqpy.Case(prog, 1): prog.set(result, 20) expected = textwrap.dedent( @@ -957,12 +957,12 @@ def test_switch_nested_in_loop(): with oqpy.ForIn(prog, range(3), "i") as i: prog.set(selector, i) - with oqpy.Switch(prog, selector) as switch: - with oqpy.Case(switch, 0): + with oqpy.Switch(prog, selector): + with oqpy.Case(prog, 0): prog.increment(result, 1) - with oqpy.Case(switch, 1): + with oqpy.Case(prog, 1): prog.increment(result, 2) - with oqpy.Default(switch): + with oqpy.Default(prog): prog.increment(result, 10) expected = textwrap.dedent( @@ -996,14 +996,14 @@ def test_switch_with_negative_cases(): selector = IntVar(0, "selector") result = IntVar(0, "result") - with oqpy.Switch(prog, selector) as switch: - with oqpy.Case(switch, -2): + with oqpy.Switch(prog, selector): + with oqpy.Case(prog, -2): prog.set(result, 1) - with oqpy.Case(switch, -1): + with oqpy.Case(prog, -1): prog.set(result, 2) - with oqpy.Case(switch, 0): + with oqpy.Case(prog, 0): prog.set(result, 3) - with oqpy.Default(switch): + with oqpy.Default(prog): prog.set(result, 100) expected = textwrap.dedent( @@ -1036,21 +1036,52 @@ def test_switch_case_requires_value(): prog = Program() selector = IntVar(0, "selector") - with oqpy.Switch(prog, selector) as switch: + with oqpy.Switch(prog, selector): with pytest.raises(ValueError, match="Case requires at least one value"): - with oqpy.Case(switch): + with oqpy.Case(prog): pass +def test_switch_case_outside_switch_raises(): + """Test that Case used outside of Switch context raises an error.""" + prog = Program() + + with pytest.raises(RuntimeError, match="Case must be used inside a Switch context"): + with oqpy.Case(prog, 0): + pass + + +def test_switch_default_outside_switch_raises(): + """Test that Default used outside of Switch context raises an error.""" + prog = Program() + + with pytest.raises(RuntimeError, match="Default must be used inside a Switch context"): + with oqpy.Default(prog): + pass + + +def test_switch_statement_outside_case_raises(): + """Test that statements directly inside Switch (not in Case/Default) raise an error.""" + prog = Program() + selector = IntVar(0, "selector") + result = IntVar(0, "result") + + with pytest.raises( + RuntimeError, match="Statements inside a Switch block must be within a Case or Default" + ): + with oqpy.Switch(prog, selector): + prog.set(result, 10) # This should raise - not inside a Case + + def test_switch_multiple_defaults_raises(): prog = Program() selector = IntVar(0, "selector") - with oqpy.Switch(prog, selector) as switch: - with oqpy.Default(switch): + with oqpy.Switch(prog, selector): + with oqpy.Default(prog): pass with pytest.raises(RuntimeError, match="Switch statement can only have one default case"): - with oqpy.Default(switch): + with oqpy.Default(prog): pass @@ -1062,12 +1093,12 @@ def test_switch_nested(): i = IntVar(5, "i") j = IntVar(30, "j") - with oqpy.Switch(prog, i) as outer_switch: - with oqpy.Case(outer_switch, 1, 2, 5, 12): + with oqpy.Switch(prog, i): + with oqpy.Case(prog, 1, 2, 5, 12): pass # Empty case body - with oqpy.Case(outer_switch, 3): - with oqpy.Switch(prog, j) as inner_switch: - with oqpy.Case(inner_switch, 10, 15, 20): + with oqpy.Case(prog, 3): + with oqpy.Switch(prog, j): + with oqpy.Case(prog, 10, 15, 20): prog.gate(q, "h") expected = textwrap.dedent( @@ -1101,9 +1132,9 @@ def test_switch_exception_propagation(): class TestException(Exception): pass - with pytest.raises(TestException): - with oqpy.Switch(prog, selector) as switch: - with oqpy.Case(switch, 0): + with pytest.raises(TestException, match="test error"): + with oqpy.Switch(prog, selector): + with oqpy.Case(prog, 0): raise TestException("test error") @@ -1114,20 +1145,38 @@ def test_switch_with_encal_declarations(): port = PortVar("storage") frame = FrameVar(port, 6e9, name="storage_frame") - with oqpy.Switch(prog, selector) as switch: - with oqpy.Case(switch, 0): + with oqpy.Switch(prog, selector): + with oqpy.Case(prog, 0): prog.set_phase(frame, 0.5) - with oqpy.Case(switch, 1): + with oqpy.Case(prog, 1): prog.set_phase(frame, 1.0) - with oqpy.Default(switch): + with oqpy.Default(prog): prog.set_phase(frame, 0.0) - # This triggers MergeCalStatementsPass.visit_SwitchStatement - qasm = prog.to_qasm(encal_declarations=True) - assert "switch" in qasm - assert "case 0" in qasm - assert "case 1" in qasm - assert "default" in qasm + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcalgrammar "openpulse"; + cal { + port storage; + frame storage_frame = newframe(storage, 6000000000.0, 0); + } + int[32] selector = 0; + switch (selector) { + case 0 { + set_phase(storage_frame, 0.5); + } + case 1 { + set_phase(storage_frame, 1.0); + } + default { + set_phase(storage_frame, 0.0); + } + } + """ + ).strip() + + assert prog.to_qasm(encal_declarations=True) == expected def test_switch_without_default_encal_declarations(): @@ -1137,18 +1186,204 @@ def test_switch_without_default_encal_declarations(): port = PortVar("storage") frame = FrameVar(port, 6e9, name="storage_frame") - with oqpy.Switch(prog, selector) as switch: - with oqpy.Case(switch, 0): + with oqpy.Switch(prog, selector): + with oqpy.Case(prog, 0): prog.set_phase(frame, 0.5) - with oqpy.Case(switch, 1): + with oqpy.Case(prog, 1): prog.set_phase(frame, 1.0) - # This triggers MergeCalStatementsPass.visit_SwitchStatement without default - qasm = prog.to_qasm(encal_declarations=True) - assert "switch" in qasm - assert "case 0" in qasm - assert "case 1" in qasm - assert "default" not in qasm + expected = textwrap.dedent( + """ + OPENQASM 3.0; + defcalgrammar "openpulse"; + cal { + port storage; + frame storage_frame = newframe(storage, 6000000000.0, 0); + } + int[32] selector = 0; + switch (selector) { + case 0 { + set_phase(storage_frame, 0.5); + } + case 1 { + set_phase(storage_frame, 1.0); + } + } + """ + ).strip() + + assert prog.to_qasm(encal_declarations=True) == expected + + +def test_switch_nested_with_defaults(): + """Test nested switch statements with default cases on both levels.""" + prog = Program() + i = IntVar(0, "i") + j = IntVar(0, "j") + result = IntVar(0, "result") + + with oqpy.Switch(prog, i): + with oqpy.Case(prog, 0): + with oqpy.Switch(prog, j): + with oqpy.Case(prog, 1): + prog.set(result, 10) + with oqpy.Default(prog): + prog.set(result, 20) + with oqpy.Default(prog): + prog.set(result, 100) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] result = 0; + int[32] j = 0; + int[32] i = 0; + switch (i) { + case 0 { + switch (j) { + case 1 { + result = 10; + } + default { + result = 20; + } + } + } + default { + result = 100; + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + +def test_switch_with_expression_selector(): + """Test switch statement with an expression as selector.""" + prog = Program() + a = IntVar(3, "a") + b = IntVar(2, "b") + result = IntVar(0, "result") + + with oqpy.Switch(prog, a + b): + with oqpy.Case(prog, 5): + prog.set(result, 1) + with oqpy.Case(prog, 6): + prog.set(result, 2) + with oqpy.Default(prog): + prog.set(result, 99) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] result = 0; + int[32] a = 3; + int[32] b = 2; + switch (a + b) { + case 5 { + result = 1; + } + case 6 { + result = 2; + } + default { + result = 99; + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + +def test_switch_with_modulo_expression_selector(): + """Test switch statement with modulo expression as selector. + + Per OpenQASM3 spec: The controlling expression of a switch statement + shall be of integer type. + """ + prog = Program() + i = IntVar(7, "i") + result = IntVar(0, "result") + + with oqpy.Switch(prog, i % 3): + with oqpy.Case(prog, 0): + prog.set(result, 10) + with oqpy.Case(prog, 1): + prog.set(result, 20) + with oqpy.Case(prog, 2): + prog.set(result, 30) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] result = 0; + int[32] i = 7; + switch (i % 3) { + case 0 { + result = 10; + } + case 1 { + result = 20; + } + case 2 { + result = 30; + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) + + +def test_switch_with_bitwise_expression_selector(): + """Test switch statement with bitwise AND expression as selector. + + Per OpenQASM3 spec: The controlling expression of a switch statement + shall be of integer type. + """ + prog = Program() + flags = IntVar(0b1010, "flags") + result = IntVar(0, "result") + + with oqpy.Switch(prog, flags & 0b0011): + with oqpy.Case(prog, 0): + prog.set(result, 100) + with oqpy.Case(prog, 1): + prog.set(result, 101) + with oqpy.Case(prog, 2): + prog.set(result, 102) + with oqpy.Case(prog, 3): + prog.set(result, 103) + + expected = textwrap.dedent( + """ + OPENQASM 3.0; + int[32] result = 0; + int[32] flags = 10; + switch (flags & 3) { + case 0 { + result = 100; + } + case 1 { + result = 101; + } + case 2 { + result = 102; + } + case 3 { + result = 103; + } + } + """ + ).strip() + + assert prog.to_qasm() == expected + _check_respects_type_hints(prog) def test_create_frame():