diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml index 299c3d95..223e1057 100644 --- a/.github/workflows/msrv.yml +++ b/.github/workflows/msrv.yml @@ -58,17 +58,18 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: + - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - uses: davidB/rust-cargo-make@v1 - - uses: actions/checkout@v1 - uses: syphar/restore-virtualenv@v1 - name: Run quil-py tests, lints, and formatting checks. - run: cargo make --cwd quil-py + run: | + cargo make --cwd quil-py fmt: name: Rustfmt diff --git a/.github/workflows/validate-downstream.yml b/.github/workflows/validate-downstream.yml new file mode 100644 index 00000000..b0d732bc --- /dev/null +++ b/.github/workflows/validate-downstream.yml @@ -0,0 +1,116 @@ +name: Check pyQuil Compatibility +on: + pull_request: + +jobs: + check-pyquil: + name: Check compatibility with pyQuil + runs-on: ubuntu-latest + steps: + - name: Checkout quil-rs Repository + uses: actions/checkout@v4 + with: + path: quil-rs + + - name: Checkout pyQuil Repository + uses: actions/checkout@v4 + with: + repository: "rigetti/pyquil" + path: pyquil + + - name: Install Rust Toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Restore Python Virtual Environment + uses: syphar/restore-virtualenv@v1 + + - name: Setup pyQuil Repository + run: | + pip uninstall -y -r <(pip freeze) || true + pip install "./pyquil[latex]" maturin mypy ruff pytest pytest-mock + maturin develop -m quil-rs/quil-py/Cargo.toml + + - name: Run mypy + id: mypy + continue-on-error: true + working-directory: ./pyquil + run: | + mypy pyquil + + - name: Run ruff + id: ruff + continue-on-error: true + working-directory: ./pyquil + run: | + ruff check pyquil + + - name: Run pytest + id: pytest + continue-on-error: true + working-directory: ./pyquil + run: | + pytest test/unit -x + + - name: Post PR Comment and Fail if Necessary + uses: actions/github-script@v6 + with: + script: | + const mypyFailed = '${{ steps.mypy.outcome }}' === 'failure'; + const ruffFailed = '${{ steps.ruff.outcome }}' === 'failure'; + const pytestFailed = '${{ steps.pytest.outcome }}' === 'failure'; + + const owner = context.repo.owner; + const repo = context.repo.repo; + const issue_number = context.issue.number; + + // Fetch existing comment, if it exists + const identifier = `pyQuil-checks-comment`; + const comments = await github.rest.issues.listComments({ + owner, + repo, + issue_number, + }); + const existingComment = comments.data.find(comment => comment.body.startsWith(``)); + + + async function postOrUpdateComment(body) { + if (existingComment) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existingComment.id, + body + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body + }); + } + } + + var body = "" + + if (mypyFailed || ruffFailed || pytestFailed) { + body += `⚠️ **pyQuil Compatibility Checks Failed**:\n\n| Tool | Status |\n-----|-----|\n`; + + if (mypyFailed) { + body += `| mypy | ❌ Failed |\n`; + } + if (ruffFailed) { + body += `| ruff | ❌ Failed |\n`; + } + if (pytestFailed) { + body += `| pytest | ❌ Failed |\n`; + } + body += `\n**Note**: These failures don't necessarily block the PR but both authors and reviewers should check the results for unintentional breaking changes.`; + } else { + body += `✅ **pyQuil Compatibility Checks Passed**. Great job!`; + } + + await postOrUpdateComment(body); diff --git a/quil-py/pyproject.toml b/quil-py/pyproject.toml index 174e21eb..c9b98f56 100644 --- a/quil-py/pyproject.toml +++ b/quil-py/pyproject.toml @@ -27,7 +27,7 @@ sdist-include = ["README.md"] dev = [ "ruff>=0.3.7", "maturin>=1.2.3", - "mypy>=1.1.1", + "mypy>=1.13.0", "pytest>=7.2.2", "pdoc>=14.1.0", "syrupy>=3.0.6" diff --git a/quil-py/quil/instructions/__init__.pyi b/quil-py/quil/instructions/__init__.pyi index ba491304..1af96957 100644 --- a/quil-py/quil/instructions/__init__.pyi +++ b/quil-py/quil/instructions/__init__.pyi @@ -834,11 +834,13 @@ class UnaryLogic: def __copy__(self) -> Self: """Returns a shallow copy of the class.""" -class Calibration: +class CalibrationIdentifier: def __new__( cls, - identifier: CalibrationIdentifier, - instructions: Sequence[Instruction], + name: str, + parameters: Sequence[Expression], + qubits: Sequence[Qubit], + modifiers: Sequence[GateModifier], ) -> Self: ... @property def name(self) -> str: ... @@ -853,14 +855,6 @@ class Calibration: @qubits.setter def qubits(self, qubits: Sequence[Qubit]) -> None: ... @property - def identifier(self) -> CalibrationIdentifier: ... - @identifier.setter - def identifier(self, identifier: CalibrationIdentifier) -> None: ... - @property - def instructions(self) -> List[Instruction]: ... - @instructions.setter - def instructions(self, instructions: Sequence[Instruction]) -> None: ... - @property def modifiers(self) -> List[GateModifier]: ... @modifiers.setter def modifiers(self, modifiers: Sequence[GateModifier]) -> None: ... @@ -885,30 +879,31 @@ class Calibration: def __copy__(self) -> Self: """Returns a shallow copy of the class.""" -class CalibrationIdentifier: +class Calibration: def __new__( cls, - name: str, - parameters: Sequence[Expression], - qubits: Sequence[Qubit], - modifiers: Sequence[GateModifier], + identifier: CalibrationIdentifier, + instructions: Sequence[Instruction], ) -> Self: ... + def name(self) -> str: + """Get the name that identifies this calibration.""" + ... + def parameters(self) -> List[Expression]: + """Get the list of parameters that this calibration will expand into.""" + ... + def qubits(self) -> List[Qubit]: + """Get the list of qubits that this calibration will expand into.""" + ... + def modifiers(self) -> List[GateModifier]: + """Get the list of parameters that this calibration will expand into.""" @property - def name(self) -> str: ... - @name.setter - def name(self, name: str) -> None: ... - @property - def parameters(self) -> List[Expression]: ... - @parameters.setter - def parameters(self, parameters: Sequence[Expression]) -> None: ... - @property - def qubits(self) -> List[Qubit]: ... - @qubits.setter - def qubits(self, qubits: Sequence[Qubit]) -> None: ... + def identifier(self) -> CalibrationIdentifier: ... + @identifier.setter + def identifier(self, identifier: CalibrationIdentifier) -> None: ... @property - def modifiers(self) -> List[GateModifier]: ... - @modifiers.setter - def modifiers(self, modifiers: Sequence[GateModifier]) -> None: ... + def instructions(self) -> List[Instruction]: ... + @instructions.setter + def instructions(self, instructions: Sequence[Instruction]) -> None: ... def to_quil(self) -> str: """Attempt to convert the instruction to a valid Quil string. @@ -930,11 +925,11 @@ class CalibrationIdentifier: def __copy__(self) -> Self: """Returns a shallow copy of the class.""" -class MeasureCalibrationDefinition: +class MeasureCalibrationIdentifier: def __new__( cls, - identifier: MeasureCalibrationIdentifier, - instructions: Sequence[Instruction], + qubit: Optional[Qubit], + parameter: str, ) -> Self: ... @property def qubit(self) -> Optional[Qubit]: ... @@ -944,14 +939,6 @@ class MeasureCalibrationDefinition: def parameter(self) -> str: ... @parameter.setter def parameter(self, parameter: str) -> None: ... - @property - def identifier(self) -> MeasureCalibrationIdentifier: ... - @identifier.setter - def identifier(self, identifier: MeasureCalibrationIdentifier) -> None: ... - @property - def instructions(self) -> List[Instruction]: ... - @instructions.setter - def instructions(self, instructions: Sequence[Instruction]) -> None: ... def to_quil(self) -> str: """Attempt to convert the instruction to a valid Quil string. @@ -973,20 +960,22 @@ class MeasureCalibrationDefinition: def __copy__(self) -> Self: """Returns a shallow copy of the class.""" -class MeasureCalibrationIdentifier: +class MeasureCalibrationDefinition: def __new__( cls, - qubit: Optional[Qubit], - parameter: str, + identifier: MeasureCalibrationIdentifier, + instructions: Sequence[Instruction], ) -> Self: ... - @property def qubit(self) -> Optional[Qubit]: ... - @qubit.setter - def qubit(self, qubit: Optional[Qubit]) -> None: ... - @property def parameter(self) -> str: ... - @parameter.setter - def parameter(self, parameter: str) -> None: ... + @property + def identifier(self) -> MeasureCalibrationIdentifier: ... + @identifier.setter + def identifier(self, identifier: MeasureCalibrationIdentifier) -> None: ... + @property + def instructions(self) -> List[Instruction]: ... + @instructions.setter + def instructions(self, instructions: Sequence[Instruction]) -> None: ... def to_quil(self) -> str: """Attempt to convert the instruction to a valid Quil string.