diff --git a/.github/actions/setup-python-with-uv/action.yml b/.github/actions/setup-python-with-uv/action.yml new file mode 100644 index 0000000..c572d26 --- /dev/null +++ b/.github/actions/setup-python-with-uv/action.yml @@ -0,0 +1,32 @@ +name: Install Python with uv +description: | + This GitHub Action installs Python using the uv tool. + It pins the specified Python version, caches uv files, and installs dependencies. + +inputs: + python-version: + description: Python version + required: true + +runs: + using: composite + steps: + - name: Install uv + run: curl -LsSf https://astral.sh/uv/install.sh | sh + shell: bash + + - name: Pin Python Version + run: | + export PYTHONUNBUFFERED=True + uv python pin ${{ inputs.python-version }} + shell: bash + + - uses: actions/cache@v4 + id: cache-uv + with: + path: ~/.cache/uv + key: ${{ runner.os }}-python-${{ inputs.python-version }}-uv + + - name: Install Dependencies + run: uv sync + shell: bash diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 76c47f3..9cf4869 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,38 +7,41 @@ on: branches: [main] jobs: - test: - timeout-minutes: 60 + pytest: runs-on: ubuntu-latest + strategy: matrix: - python-version: ['3.10', '3.12'] - + python-version: ["3.10", "3.11", "3.12"] + steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install uv - uses: astral-sh/setup-uv@v3 - - - name: Check code formatting - run: uv run ruff check - - - name: Check Python types - run: uv run mypy - - - name: Run tests - run: uv run pytest - - - name: Upload coverage to Codecov - if: matrix.python-version == '3.12' - uses: codecov/codecov-action@v5 - with: - files: ./coverage.xml - token: ${{ secrets.CODECOV_TOKEN }} - slug: oqtopus-team/tranqu + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python ${{ matrix.python-version }} with uv + uses: ./.github/actions/setup-python-with-uv + with: + python-version: ${{ matrix.python-version }} + + - name: Lint + run: uv run ruff check --output-format=github . + + - name: Format + run: uv run ruff format . --check --diff + + - name: Check Python types + run: uv run mypy + + - name: Run Pytest if directory exists + run: | + if [ -d "./tests/" ]; then + uv run pytest -s + fi + + - name: Upload coverage to Codecov + if: matrix.python-version == '3.12' + uses: codecov/codecov-action@v5 + with: + files: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + slug: oqtopus-team/tranqu diff --git a/src/tranqu/transpiler_dispatcher.py b/src/tranqu/transpiler_dispatcher.py index fcd3025..8266390 100644 --- a/src/tranqu/transpiler_dispatcher.py +++ b/src/tranqu/transpiler_dispatcher.py @@ -147,7 +147,9 @@ def _convert_program(self, program: Any, from_lib: str, to_lib: Any) -> Any: # to_lib, ) if not (can_convert_to_qiskit and can_convert_to_target): - msg = f"No ProgramConverter path found to convert from {from_lib} to {to_lib}" # noqa: E501 + msg = ( + f"No ProgramConverter path found to convert from {from_lib} to {to_lib}" + ) raise ProgramConversionPathNotFoundError(msg) return self._program_converter_manager.fetch_converter( @@ -183,7 +185,9 @@ def _convert_device( to_lib, ) if not (can_convert_to_qiskit and can_convert_to_target): - msg = f"No DeviceConverter path found to convert from {from_lib} to {to_lib}" # noqa: E501 + msg = ( + f"No DeviceConverter path found to convert from {from_lib} to {to_lib}" + ) raise DeviceConversionPathNotFoundError(msg) return self._device_converter_manager.fetch_converter("qiskit", to_lib).convert( diff --git a/tests/tranqu/test_tranqu.py b/tests/tranqu/test_tranqu.py index eae8fcc..fa6f024 100644 --- a/tests/tranqu/test_tranqu.py +++ b/tests/tranqu/test_tranqu.py @@ -2,10 +2,15 @@ import re +import pytest from qiskit import QuantumCircuit as QiskitCircuit from tranqu import Tranqu, __version__ from tranqu.program_converter import ProgramConverter +from tranqu.transpiler_dispatcher import ( + DeviceConversionPathNotFoundError, + ProgramConversionPathNotFoundError, +) class EnigmaCircuit: @@ -134,3 +139,30 @@ def test_transpile_openqasm3_program_for_oqtopus_device_with_qiskit_transpiler( c[1] = measure $2; """ assert result.transpiled_program == expected_program + + def test_program_conversion_path_not_found(self): + tranqu = Tranqu() + circuit = EnigmaCircuit() + + with pytest.raises( + ProgramConversionPathNotFoundError, + match="No ProgramConverter path found to convert from enigma to qiskit", + ): + tranqu.transpile(circuit, "enigma", "qiskit") + + def test_device_conversion_path_not_found(self): + tranqu = Tranqu() + circuit = QiskitCircuit(2) + device = {"name": "custom_device", "qubits": [], "couplings": []} + + with pytest.raises( + DeviceConversionPathNotFoundError, + match="No DeviceConverter path found to convert from custom to qiskit", + ): + tranqu.transpile( + circuit, + program_lib="qiskit", + transpiler_lib="qiskit", + device=device, + device_lib="custom", + )