From 064ffb573b43491fb374017c0dcc524c05233302 Mon Sep 17 00:00:00 2001 From: Melf Date: Tue, 13 May 2025 13:47:42 +0100 Subject: [PATCH 1/5] update ruff files --- ruff.toml | 116 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/ruff.toml b/ruff.toml index f2146f67..2a77dad5 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,44 +1,82 @@ -target-version = "py39" - -line-length = 88 +exclude = [ + "docs/pytket-docs-theming", +] -extend-exclude = ["examples"] +target-version = "py312" -select = [ - "E", # pycodestyle Errors - "W", # pycodestyle Warnings +lint.select = [ + "A", + "AIR", + # "ANN", + # "ARG", # TODO + "ASYNC", + "B", + "BLE", + # "C", + "C4", + # "C90", + "COM", + # "CPY", + # "D", + "DJ", + # "DOC", + "DTZ", + "E", + # "EM", + # "ERA", # TODO + "EXE", + "F", + "FA", + # "FAST", + # "FBT", + # "FIX", + "FLY", + "FURB", + "G", + "I", + # "ICN", + # "INP", + "INT", + "ISC", + "LOG", + # "N", + "NPY", + # "PD", + "PERF", + # "PGH", + "PIE", + "PL", + # "PT", + # "PTH", # TODO + # "PYI", # TODO + "Q", + "R", + "RET", + "RSE", + "RUF", + # "S", + "SIM", + "SLF", + "SLOT", + "T10", + "T20", + "TCH", + # "TD", + # "TID", + # "TRY", + "UP", + "W", + "YTT", +] - # "A", # flake8-builtins - # "B", # flake8-Bugbear - # "C4", # flake8-comprehensions - # "COM", # flake8-commas - # "EXE", # flake8-executable - "F", # pyFlakes - # "FA", # flake8-future-annotations - # "FIX", # flake8-fixme - # "FLY", # flynt - "I", # isort - # "INP", # flake8-no-pep420 - # "ISC", # flake8-implicit-str-concat - # "N", # pep8-Naming - # "NPY", # NumPy-specific - # "PERF", # Perflint - # "PGH", # pygrep-hooks - # "PIE", # flake8-pie - # "PL", # pylint - # "PT", # flake8-pytest-style - # "RSE", # flake8-raise - # "RUF", # Ruff-specific - # "S", # flake8-bandit (Security) - "SIM", # flake8-simplify - # "SLF", # flake8-self - "T20", # flake8-print - "TCH", # flake8-type-checking - # "TRY", # tryceratops - "UP", # pyupgrade - # "YTT", # flake8-2020 +lint.ignore = [ + "E501", # Allow long lines in strings + "E731", # OK to assign to lambdas + "E741", # Allow variable names like "l" + "F403", # Allow wildcard imports in init files + "COM812", # flake8-commas "Trailing comma missing" ] -[per-file-ignores] -".github/workflows/docs/conf.py" = ["E402"] -"__init__.py" = ["F401"] # module imported but unused (6) +[lint.per-file-ignores] +"__init__.py" = ["F401"] +"**/{tests}/*" = ["PLR2004"] From ae8daefe0f2b1e17d4789a189195c6f17803bfee Mon Sep 17 00:00:00 2001 From: Melf Date: Tue, 13 May 2025 13:56:10 +0100 Subject: [PATCH 2/5] update CI files --- .github/workflows/lint.yml | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e17a5453..aec3d1f8 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,9 +1,13 @@ name: Lint python projects on: - pull_request: + pull_request: {} + workflow_dispatch: {} + + push: branches: - - main + - 'wheel/**' + - 'runci/**' jobs: lint: @@ -18,11 +22,11 @@ jobs: python-version: '3.x' - name: Update pip run: pip install --upgrade pip - - name: Install black and pylint - run: pip install "black[jupyter]" pylint ruff - - name: Check files are formatted with black - run: black --check . - - name: Run ruff - run: ruff check . - - name: Run pylint - run: pylint */ + - name: Install formatters and linters + run: pip install ruff + - name: Run ruff lint + run: | + ruff check . + - name: Check formatting + run: | + ruff format --check . From 48a2e55f52d8176d5ead66a59daba8341232bc01 Mon Sep 17 00:00:00 2001 From: Melf Date: Tue, 13 May 2025 14:20:57 +0100 Subject: [PATCH 3/5] fix ruff format and lint --- pytket/extensions/quantinuum/__init__.py | 18 +- .../quantinuum/backends/__init__.py | 14 +- .../quantinuum/backends/api_wrappers.py | 136 +++++++------- .../backends/calendar_visualisation.py | 8 +- .../extensions/quantinuum/backends/config.py | 14 +- .../quantinuum/backends/credential_storage.py | 57 +++--- .../quantinuum/backends/leakage_gadget.py | 3 +- .../quantinuum/backends/quantinuum.py | 170 +++++++++--------- ruff.toml | 2 + tests/conftest.py | 17 +- tests/integration/backend_test.py | 31 ++-- tests/integration/local_emulator_test.py | 2 +- tests/unit/api1_test.py | 12 +- tests/unit/api_test.py | 36 ++-- tests/unit/convert_test.py | 10 +- tests/unit/offline_backend_test.py | 18 +- 16 files changed, 269 insertions(+), 279 deletions(-) diff --git a/pytket/extensions/quantinuum/__init__.py b/pytket/extensions/quantinuum/__init__.py index aa7c0875..8e2ee64f 100644 --- a/pytket/extensions/quantinuum/__init__.py +++ b/pytket/extensions/quantinuum/__init__.py @@ -16,29 +16,29 @@ # _metadata.py is copied to the folder after installation. from ._metadata import ( - __extension_name__ as __extension_name__, + __extension_name__ as __extension_name__, # noqa: PLC0414 ) from ._metadata import ( - __extension_version__ as __extension_version__, + __extension_version__ as __extension_version__, # noqa: PLC0414 ) from .backends import ( - Language as Language, + Language as Language, # noqa: PLC0414 ) from .backends import ( - QuantinuumAPI as QuantinuumAPI, + QuantinuumAPI as QuantinuumAPI, # noqa: PLC0414 ) from .backends import ( - QuantinuumAPIOffline as QuantinuumAPIOffline, + QuantinuumAPIOffline as QuantinuumAPIOffline, # noqa: PLC0414 ) from .backends import ( - QuantinuumBackend as QuantinuumBackend, + QuantinuumBackend as QuantinuumBackend, # noqa: PLC0414 ) from .backends import ( - QuantinuumBackendCompilationConfig as QuantinuumBackendCompilationConfig, + QuantinuumBackendCompilationConfig as QuantinuumBackendCompilationConfig, # noqa: PLC0414 ) from .backends import ( - have_pecos as have_pecos, + have_pecos as have_pecos, # noqa: PLC0414 ) from .backends import ( - prune_shots_detected_as_leaky as prune_shots_detected_as_leaky, + prune_shots_detected_as_leaky as prune_shots_detected_as_leaky, # noqa: PLC0414 ) diff --git a/pytket/extensions/quantinuum/backends/__init__.py b/pytket/extensions/quantinuum/backends/__init__.py index ea44ed58..13918ec9 100644 --- a/pytket/extensions/quantinuum/backends/__init__.py +++ b/pytket/extensions/quantinuum/backends/__init__.py @@ -15,23 +15,23 @@ """Backends for processing pytket circuits with Quantinuum devices""" from .api_wrappers import ( - QuantinuumAPI as QuantinuumAPI, + QuantinuumAPI as QuantinuumAPI, # noqa: PLC0414 ) from .api_wrappers import ( - QuantinuumAPIOffline as QuantinuumAPIOffline, + QuantinuumAPIOffline as QuantinuumAPIOffline, # noqa: PLC0414 ) from .leakage_gadget import ( - prune_shots_detected_as_leaky as prune_shots_detected_as_leaky, + prune_shots_detected_as_leaky as prune_shots_detected_as_leaky, # noqa: PLC0414 ) from .quantinuum import ( - Language as Language, + Language as Language, # noqa: PLC0414 ) from .quantinuum import ( - QuantinuumBackend as QuantinuumBackend, + QuantinuumBackend as QuantinuumBackend, # noqa: PLC0414 ) from .quantinuum import ( - QuantinuumBackendCompilationConfig as QuantinuumBackendCompilationConfig, + QuantinuumBackendCompilationConfig as QuantinuumBackendCompilationConfig, # noqa: PLC0414 ) from .quantinuum import ( - have_pecos as have_pecos, + have_pecos as have_pecos, # noqa: PLC0414 ) diff --git a/pytket/extensions/quantinuum/backends/api_wrappers.py b/pytket/extensions/quantinuum/backends/api_wrappers.py index d09357fc..6291b0e1 100644 --- a/pytket/extensions/quantinuum/backends/api_wrappers.py +++ b/pytket/extensions/quantinuum/backends/api_wrappers.py @@ -22,7 +22,7 @@ import json import time from http import HTTPStatus -from typing import Any, Optional, cast +from typing import Any, cast import nest_asyncio # type: ignore from requests import Session @@ -47,8 +47,8 @@ class _OverrideManager: def __init__( self, api_handler: "QuantinuumAPI", - timeout: Optional[int] = None, - retry_timeout: Optional[int] = None, + timeout: int | None = None, + retry_timeout: int | None = None, ): self._timeout = timeout self._retry = retry_timeout @@ -72,7 +72,7 @@ class QuantinuumAPI: Interface to the Quantinuum online remote API. """ - JOB_DONE = ["failed", "completed", "canceled"] + JOB_DONE = ["failed", "completed", "canceled"] # noqa: RUF012 DEFAULT_API_URL = "https://qapi.quantinuum.com/" @@ -82,17 +82,17 @@ class QuantinuumAPI: # mfa verification code is required during login ERROR_CODE_MFA_REQUIRED = 73 - def __init__( + def __init__( # noqa: PLR0913 self, - token_store: Optional[CredentialStorage] = None, - api_url: Optional[str] = None, + token_store: CredentialStorage | None = None, + api_url: str | None = None, api_version: int = 1, use_websocket: bool = True, - provider: Optional[str] = None, + provider: str | None = None, support_mfa: bool = True, - session: Optional[Session] = None, - __user_name: Optional[str] = None, - __pwd: Optional[str] = None, + session: Session | None = None, + __user_name: str | None = None, + __pwd: str | None = None, ): """Initialize Quantinuum API client. @@ -137,7 +137,7 @@ def __init__( # otherwise it will be stored in memory self._cred_store.save_user_name(__user_name) if __pwd is not None and isinstance(self._cred_store, MemoryCredentialStorage): - self._cred_store._password = __pwd + self._cred_store._password = __pwd # noqa: SLF001 self.api_version = api_version self.use_websocket = use_websocket @@ -146,10 +146,10 @@ def __init__( self.ws_timeout = 180 self.retry_timeout = 5 - self.timeout: Optional[int] = None # don't timeout by default + self.timeout: int | None = None # don't timeout by default def override_timeouts( - self, timeout: Optional[int] = None, retry_timeout: Optional[int] = None + self, timeout: int | None = None, retry_timeout: int | None = None ) -> _OverrideManager: return _OverrideManager(self, timeout=timeout, retry_timeout=retry_timeout) @@ -252,7 +252,7 @@ def _get_credentials(self) -> tuple[str, str]: user_name = self._cred_store.user_name pwd = None if isinstance(self._cred_store, MemoryCredentialStorage): - pwd = self._cred_store._password + pwd = self._cred_store._password # noqa: SLF001 if not user_name: user_name = input("Enter your Quantinuum email: ") @@ -320,15 +320,15 @@ def _response_check(self, res: Response, description: str) -> None: f"Authorization failure attempting: {description}." f"\n\nServer Response: {jr}" ) - elif res.status_code != HTTPStatus.OK: + if res.status_code != HTTPStatus.OK: jr = res.json() raise QuantinuumAPIError( f"HTTP error attempting: {description}.\n\nServer Response: {jr}" ) def retrieve_job_status( - self, job_id: str, use_websocket: Optional[bool] = None - ) -> Optional[dict]: + self, job_id: str, use_websocket: bool | None = None + ) -> dict | None: """ Retrieves job status from device. @@ -345,7 +345,7 @@ def retrieve_job_status( job_url += "?websocket=true" res = self.session.get(job_url, headers={"Authorization": id_token}) - jr: Optional[dict] = None + jr: dict | None = None # Check for invalid responses, and raise an exception if so self._response_check(res, "job status") # if we successfully got status return the decoded details @@ -354,8 +354,8 @@ def retrieve_job_status( return jr def retrieve_job( - self, job_id: str, use_websocket: Optional[bool] = None - ) -> Optional[dict]: + self, job_id: str, use_websocket: bool | None = None + ) -> dict | None: """ Retrieves job from device. @@ -385,7 +385,7 @@ def retrieve_job( jr = self._poll_results(job_id) return jr - def _poll_results(self, job_id: str) -> Optional[dict]: + def _poll_results(self, job_id: str) -> dict | None: jr = None start_time = time.time() while True: @@ -402,56 +402,52 @@ def _poll_results(self, job_id: str) -> Optional[dict]: return jr time.sleep(self.retry_timeout) except KeyboardInterrupt: - raise RuntimeError("Keyboard Interrupted") + raise RuntimeError("Keyboard Interrupted") # noqa: B904 return jr - async def _wait_results(self, job_id: str) -> Optional[dict]: + async def _wait_results(self, job_id: str) -> dict | None: start_time = time.time() while True: if self.timeout is not None and time.time() > (start_time + self.timeout): break self.login() jr = self.retrieve_job_status(job_id, True) - if jr is None or "status" in jr and jr["status"] in self.JOB_DONE: + if jr is None or ("status" in jr and jr["status"] in self.JOB_DONE): return jr - else: - task_token = jr["websocket"]["task_token"] - execution_arn = jr["websocket"]["executionArn"] - websocket_uri = self.url.replace("https://", "wss://ws.") - async with connect(websocket_uri) as websocket: - body = { - "action": "OpenConnection", - "task_token": task_token, - "executionArn": execution_arn, - "partial": False, - } - await websocket.send(json.dumps(body)) - while True: + task_token = jr["websocket"]["task_token"] + execution_arn = jr["websocket"]["executionArn"] + websocket_uri = self.url.replace("https://", "wss://ws.") + async with connect(websocket_uri) as websocket: + body = { + "action": "OpenConnection", + "task_token": task_token, + "executionArn": execution_arn, + "partial": False, + } + await websocket.send(json.dumps(body)) + while True: + try: + res = await asyncio.wait_for( + websocket.recv(), timeout=self.ws_timeout + ) + jr = json.loads(res) + if not isinstance(jr, dict): + raise RuntimeError("Unable to decode response.") + if "status" in jr and jr["status"] in self.JOB_DONE: + return jr + except (TimeoutError, exceptions.ConnectionClosed): try: - res = await asyncio.wait_for( - websocket.recv(), timeout=self.ws_timeout - ) - jr = json.loads(res) - if not isinstance(jr, dict): - raise RuntimeError("Unable to decode response.") - if "status" in jr and jr["status"] in self.JOB_DONE: - return jr - except ( - asyncio.TimeoutError, - exceptions.ConnectionClosed, - ): - try: - # Try to keep the connection alive... - pong = await websocket.ping() - await asyncio.wait_for(pong, timeout=10) - continue - except asyncio.TimeoutError: - # If we are failing, wait a little while, - # then start from the top - await asyncio.sleep(self.retry_timeout) - break - except KeyboardInterrupt: - raise RuntimeError("Keyboard Interrupted") + # Try to keep the connection alive... + pong = await websocket.ping() + await asyncio.wait_for(pong, timeout=10) + continue + except TimeoutError: + # If we are failing, wait a little while, + # then start from the top + await asyncio.sleep(self.retry_timeout) + break + except KeyboardInterrupt: + raise RuntimeError("Keyboard Interrupted") # noqa: B904 def status(self, machine: str) -> str: """ @@ -489,7 +485,7 @@ def cancel(self, job_id: str) -> dict: self._response_check(res, "job cancel") jr = res.json() - return jr # type: ignore + return jr # type: ignore # noqa: RET504 def get_calendar(self, start_date: str, end_date: str) -> list[dict[str, str]]: """ @@ -638,7 +634,7 @@ class QuantinuumAPIOffline: Offline copy of the interface to the Quantinuum remote API. """ - def __init__(self, machine_list: Optional[list] = None): + def __init__(self, machine_list: list | None = None): """Initialize offline API client. Tries to allow all the operations of the QuantinuumAPI without @@ -677,7 +673,7 @@ def get_machine_list(self) -> list[dict[str, Any]]: def full_login(self) -> None: """No login offline with the offline API""" - return None + return def login(self) -> str: """No login offline with the offline API, this function will always @@ -692,9 +688,8 @@ def _submit_job(self, body: dict) -> None: :return: None """ self.submitted.append(body) - return None - def get_jobs(self) -> Optional[list]: + def get_jobs(self) -> list | None: """The function will return all the jobs that have been submitted :return: List of all the submitted jobs @@ -706,12 +701,11 @@ def _response_check(self, res: Response, description: str) -> None: jr = res.json() raise QuantinuumAPIError( - f"Reponse can't be checked offline: {description}." - f"\n\nServer Response: {jr}" + f"Reponse can't be checked offline: {description}.\n\nServer Response: {jr}" ) def retrieve_job_status( - self, job_id: str, use_websocket: Optional[bool] = None + self, job_id: str, use_websocket: bool | None = None ) -> None: """No retrieve_job_status offline""" raise QuantinuumAPIError( @@ -719,7 +713,7 @@ def retrieve_job_status( f"\n use_websocket {use_websocket}" ) - def retrieve_job(self, job_id: str, use_websocket: Optional[bool] = None) -> None: + def retrieve_job(self, job_id: str, use_websocket: bool | None = None) -> None: """No retrieve_job_status offline""" raise QuantinuumAPIError( f"Can't retrieve job status offline: job_id {job_id}." diff --git a/pytket/extensions/quantinuum/backends/calendar_visualisation.py b/pytket/extensions/quantinuum/backends/calendar_visualisation.py index a21d10aa..dd1a232b 100644 --- a/pytket/extensions/quantinuum/backends/calendar_visualisation.py +++ b/pytket/extensions/quantinuum/backends/calendar_visualisation.py @@ -71,11 +71,9 @@ def __init__(self, year: int, month: int, title_prefix: str): i.e. 2 for February. :param title_prefix: A prefix to add to the title """ - self._title: str = ( - f"{title_prefix} Operations Calendar\n\ - {self.months[month-1]} {year}\ + self._title: str = f"{title_prefix} Operations Calendar\n\ + {self.months[month - 1]} {year}\ ({datetime.datetime.now().astimezone().strftime('%Z')})" - ) self._cal: np.ndarray = np.asarray(calendar.monthcalendar(year, month)) self._events: np.ndarray = np.full( self._cal.shape, fill_value=None, dtype=object @@ -102,7 +100,7 @@ def _add_event(self, day: int, event_str: str) -> None: self._events[week, week_day] = f"{event_str1}\n\n{event_str}" self._colors[week, week_day] = "mistyrose" except RuntimeError: - raise RuntimeError("Day outside of specified month") + raise RuntimeError("Day outside of specified month") # noqa: B904 def add_events(self, events_list: list[dict[str, object]]) -> None: """Add list of events. Each event is a dictionary and diff --git a/pytket/extensions/quantinuum/backends/config.py b/pytket/extensions/quantinuum/backends/config.py index 7c3d979b..591b262f 100644 --- a/pytket/extensions/quantinuum/backends/config.py +++ b/pytket/extensions/quantinuum/backends/config.py @@ -15,7 +15,7 @@ """Quantinuum config.""" from dataclasses import dataclass -from typing import Any, ClassVar, Optional +from typing import Any, ClassVar from pytket.config import PytketExtConfig @@ -26,15 +26,15 @@ class QuantinuumConfig(PytketExtConfig): ext_dict_key: ClassVar[str] = "quantinuum" - username: Optional[str] + username: str | None - refresh_token: Optional[str] + refresh_token: str | None - id_token: Optional[str] + id_token: str | None - refresh_token_timeout: Optional[str] + refresh_token_timeout: str | None - id_token_timeout: Optional[str] + id_token_timeout: str | None @classmethod def from_extension_dict( @@ -49,7 +49,7 @@ def from_extension_dict( ) -def set_quantinuum_config(username: Optional[str]) -> None: +def set_quantinuum_config(username: str | None) -> None: """Set default value for Quantinuum username. Can be overriden in backend construction.""" hconfig = QuantinuumConfig.from_default_config_file() diff --git a/pytket/extensions/quantinuum/backends/credential_storage.py b/pytket/extensions/quantinuum/backends/credential_storage.py index 86db64da..23424cc8 100644 --- a/pytket/extensions/quantinuum/backends/credential_storage.py +++ b/pytket/extensions/quantinuum/backends/credential_storage.py @@ -13,8 +13,7 @@ # limitations under the License. from abc import ABC, abstractmethod -from datetime import datetime, timedelta, timezone -from typing import Optional +from datetime import UTC, datetime, timedelta import jwt @@ -82,16 +81,16 @@ def save_tokens(self, id_token: str, refresh_token: str) -> None: def delete_credential(self) -> None: """Delete credential.""" - @property - def id_token(self) -> Optional[str]: + @property # noqa: B027 + def id_token(self) -> str | None: """Return the ID token if valid.""" - @property - def refresh_token(self) -> Optional[str]: + @property # noqa: B027 + def refresh_token(self) -> str | None: """Return the refresh token if valid.""" - @property - def user_name(self) -> Optional[str]: + @property # noqa: B027 + def user_name(self) -> str | None: """Return the username if exists.""" @@ -115,29 +114,27 @@ def __init__( is valid. Defaults to 29 days. """ super().__init__(id_token_timedelt, refresh_token_timedelt) - self._user_name: Optional[str] = None + self._user_name: str | None = None # Password storage is only included for debug purposes - self._password: Optional[str] = None - self._id_token: Optional[str] = None - self._refresh_token: Optional[str] = None - self._id_token_timeout: Optional[datetime] = None - self._refresh_token_timeout: Optional[datetime] = None + self._password: str | None = None + self._id_token: str | None = None + self._refresh_token: str | None = None + self._id_token_timeout: datetime | None = None + self._refresh_token_timeout: datetime | None = None def save_user_name(self, user_name: str) -> None: self._user_name = user_name def save_refresh_token(self, refresh_token: str) -> None: self._refresh_token = refresh_token - self._refresh_token_timeout = ( - datetime.now(timezone.utc) + self._refresh_timedelt - ) + self._refresh_token_timeout = datetime.now(UTC) + self._refresh_timedelt def save_id_token(self, id_token: str) -> None: self._id_token = id_token - self._id_token_timeout = datetime.now(timezone.utc) + self._id_timedelt + self._id_token_timeout = datetime.now(UTC) + self._id_timedelt @property - def id_token(self) -> Optional[str]: + def id_token(self) -> str | None: if self._id_token is not None: timeout = ( jwt.decode( @@ -149,22 +146,22 @@ def id_token(self) -> Optional[str]: ) if self._id_token_timeout is not None: timeout = min(timeout, self._id_token_timeout.timestamp()) - if datetime.now(timezone.utc).timestamp() > timeout: + if datetime.now(UTC).timestamp() > timeout: self._id_token = None return self._id_token @property - def refresh_token(self) -> Optional[str]: + def refresh_token(self) -> str | None: if ( self._refresh_token is not None and self._refresh_token_timeout is not None - and datetime.now(timezone.utc) > self._refresh_token_timeout + and datetime.now(UTC) > self._refresh_token_timeout ): self._refresh_token = None return self._refresh_token @property - def user_name(self) -> Optional[str]: + def user_name(self) -> str | None: return self._user_name def delete_credential(self) -> None: @@ -217,7 +214,7 @@ def save_user_name(self, user_name: str) -> None: def save_refresh_token(self, refresh_token: str) -> None: hconfig = QuantinuumConfig.from_default_config_file() hconfig.refresh_token = refresh_token - refresh_token_timeout = datetime.now(timezone.utc) + self._refresh_timedelt + refresh_token_timeout = datetime.now(UTC) + self._refresh_timedelt hconfig.refresh_token_timeout = refresh_token_timeout.strftime( "%Y-%m-%d %H:%M:%S.%z" ) @@ -226,12 +223,12 @@ def save_refresh_token(self, refresh_token: str) -> None: def save_id_token(self, id_token: str) -> None: hconfig = QuantinuumConfig.from_default_config_file() hconfig.id_token = id_token - id_token_timeout = datetime.now(timezone.utc) + self._id_timedelt + id_token_timeout = datetime.now(UTC) + self._id_timedelt hconfig.id_token_timeout = id_token_timeout.strftime("%Y-%m-%d %H:%M:%S.%z") hconfig.update_default_config_file() @property - def id_token(self) -> Optional[str]: + def id_token(self) -> str | None: hconfig = QuantinuumConfig.from_default_config_file() id_token = hconfig.id_token if id_token is not None: @@ -248,24 +245,24 @@ def id_token(self) -> Optional[str]: hconfig.id_token_timeout, "%Y-%m-%d %H:%M:%S.%z" ) timeout = min(timeout, id_token_timeout.timestamp()) - if datetime.now(timezone.utc).timestamp() > timeout: + if datetime.now(UTC).timestamp() > timeout: return None return id_token @property - def refresh_token(self) -> Optional[str]: + def refresh_token(self) -> str | None: hconfig = QuantinuumConfig.from_default_config_file() refresh_token = hconfig.refresh_token if refresh_token is not None and hconfig.refresh_token_timeout is not None: refresh_token_timeout = datetime.strptime( hconfig.refresh_token_timeout, "%Y-%m-%d %H:%M:%S.%z" ) - if datetime.now(timezone.utc) > refresh_token_timeout: + if datetime.now(UTC) > refresh_token_timeout: return None return refresh_token @property - def user_name(self) -> Optional[str]: + def user_name(self) -> str | None: hconfig = QuantinuumConfig.from_default_config_file() return hconfig.username diff --git a/pytket/extensions/quantinuum/backends/leakage_gadget.py b/pytket/extensions/quantinuum/backends/leakage_gadget.py index dd02c891..fdf56400 100644 --- a/pytket/extensions/quantinuum/backends/leakage_gadget.py +++ b/pytket/extensions/quantinuum/backends/leakage_gadget.py @@ -13,7 +13,6 @@ # limitations under the License. """Methods for generating a leakage detection Pytket Circuit.""" - from collections import Counter from typing import TYPE_CHECKING, cast @@ -55,7 +54,7 @@ def get_leakage_gadget_circuit( return c -def get_detection_circuit(circuit: Circuit, n_device_qubits: int) -> Circuit: +def get_detection_circuit(circuit: Circuit, n_device_qubits: int) -> Circuit: # noqa: PLR0912 """ For a passed circuit, appends a leakage detection circuit for each end of circuit measurement using spare device qubits. diff --git a/pytket/extensions/quantinuum/backends/quantinuum.py b/pytket/extensions/quantinuum/backends/quantinuum.py index 1c6d389e..b3d10c3e 100644 --- a/pytket/extensions/quantinuum/backends/quantinuum.py +++ b/pytket/extensions/quantinuum/backends/quantinuum.py @@ -26,7 +26,7 @@ from enum import Enum from functools import cache, cached_property from http import HTTPStatus -from typing import TYPE_CHECKING, Any, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Union, cast from uuid import uuid1 import numpy as np @@ -143,7 +143,7 @@ def _get_gateset(gates: list[str]) -> set[OpType]: gs = _ADDITIONAL_GATES.copy() for gate in gates: if gate not in _GATE_MAP: - warnings.warn(f"Gate {gate} not recognized.") + warnings.warn(f"Gate {gate} not recognized.") # noqa: B028 else: gs.add(_GATE_MAP[gate]) return gs @@ -206,8 +206,7 @@ def _language2str(language: Language) -> str: """returns matching string for Language enum""" if language == Language.QASM: return "OPENQASM 2.0" - else: - return "QIR 1.0" + return "QIR 1.0" # DEFAULT_CREDENTIALS_STORAGE for use with the DEFAULT_API_HANDLER. @@ -220,7 +219,7 @@ def _language2str(language: Language) -> str: # without requiring them to acquire new tokens. DEFAULT_API_HANDLER = QuantinuumAPI(DEFAULT_CREDENTIALS_STORAGE) -QuumKwargTypes = Union[KwargTypes, WasmFileHandler, dict[str, Any], OpType, bool] +QuumKwargTypes = Union[KwargTypes, WasmFileHandler, dict[str, Any], OpType, bool] # noqa: UP007 @dataclass @@ -235,7 +234,7 @@ class QuantinuumBackendCompilationConfig: """ allow_implicit_swaps: bool = True - target_2qb_gate: Optional[OpType] = None + target_2qb_gate: OpType | None = None @cache @@ -253,9 +252,9 @@ class _LocalEmulatorConfiguration: """Options stored internally when running circuits on the local emulator.""" circuit: Circuit - wasm_fh: Optional[WasmFileHandler] + wasm_fh: WasmFileHandler | None n_shots: int - seed: Optional[int] + seed: int | None multithreading: bool noisy_simulation: bool @@ -272,16 +271,16 @@ class QuantinuumBackend(Backend): _supports_contextual_optimisation = True _persistent_handles = True - def __init__( + def __init__( # noqa: PLR0913 self, device_name: str, - label: Optional[str] = "job", + label: str | None = "job", simulator: str = "state-vector", - group: Optional[str] = None, - provider: Optional[str] = None, + group: str | None = None, + provider: str | None = None, machine_debug: bool = False, api_handler: QuantinuumAPI = DEFAULT_API_HANDLER, - compilation_config: Optional[QuantinuumBackendCompilationConfig] = None, + compilation_config: QuantinuumBackendCompilationConfig | None = None, **kwargs: QuumKwargTypes, ): """Construct a new Quantinuum backend. @@ -308,7 +307,7 @@ def __init__( self._label = label self._group = group - self._backend_info: Optional[BackendInfo] = None + self._backend_info: BackendInfo | None = None self._MACHINE_DEBUG = machine_debug self.simulator_type = simulator @@ -324,7 +323,7 @@ def __init__( self._local_emulator_handles: dict[ ResultHandle, _LocalEmulatorConfiguration, - ] = dict() + ] = dict() # noqa: C408 if compilation_config is None: self._compilation_config = QuantinuumBackendCompilationConfig() @@ -384,7 +383,7 @@ def _dict_to_backendinfo( dct1 = copy(dct) name: str = dct1.pop("name") n_qubits: int = dct1.pop("n_qubits") - n_cl_reg: Optional[int] = None + n_cl_reg: int | None = None if "n_classical_registers" in dct: n_cl_reg = dct1.pop("n_classical_registers") gate_set: list[str] = dct1.pop("gateset", []) @@ -433,7 +432,7 @@ def _retrieve_backendinfo(self, machine: str) -> BackendInfo: try: info = next(entry for entry in infos if entry.device_name == machine) except StopIteration: - raise DeviceNotAvailable(machine) + raise DeviceNotAvailable(machine) # noqa: B904 info.misc["options"] = self._process_circuits_options return info @@ -456,14 +455,14 @@ def device_state( try: info = next(entry for entry in infos if entry.device_name == device_name) except StopIteration: - raise DeviceNotAvailable(device_name) + raise DeviceNotAvailable(device_name) # noqa: B904 if info.get_misc("system_type") == "local_emulator": return "online" res = requests.get( f"{api_handler.url}machine/{device_name}", headers={"Authorization": api_handler.login()}, ) - api_handler._response_check(res, "get machine status") + api_handler._response_check(res, "get machine status") # noqa: SLF001 return str(res.json()["state"]) def get_calendar( @@ -589,18 +588,18 @@ def view_calendar( qntm_calendar = QuantinuumCalendar( year=year, month=month, title_prefix=self._device_name ) - end_day = max(qntm_calendar._cal[-1]) - dt_start = datetime.datetime(year=year, month=month, day=1) - dt_end = datetime.datetime(year=year, month=month, day=end_day) + end_day = max(qntm_calendar._cal[-1]) # noqa: SLF001 + dt_start = datetime.datetime(year=year, month=month, day=1) # noqa: DTZ001 + dt_end = datetime.datetime(year=year, month=month, day=end_day) # noqa: DTZ001 data = self.get_calendar(dt_start, dt_end, localise=True) qntm_calendar.add_events(data) calendar_figure = qntm_calendar.build_calendar( figsize=figsize, fontsize=fontsize, titlesize=titlesize ) - return calendar_figure + return calendar_figure # noqa: RET504 @property - def backend_info(self) -> Optional[BackendInfo]: + def backend_info(self) -> BackendInfo | None: if self._backend_info is None and not self._MACHINE_DEBUG: self._backend_info = self._retrieve_backendinfo(self._device_name) return self._backend_info @@ -636,7 +635,7 @@ def default_two_qubit_gate(self) -> OpType: if default_2q_gate in self.two_qubit_gate_set: pass elif len(self.two_qubit_gate_set) > 0: - default_2q_gate = list(self.two_qubit_gate_set)[0] + default_2q_gate = list(self.two_qubit_gate_set)[0] # noqa: RUF015 else: raise ValueError("The device is not supporting any two qubit gates") @@ -648,7 +647,7 @@ def two_qubit_gate_set(self) -> set[OpType]: Submitted circuits must contain only one of these. """ - return self._gate_set & set([OpType.ZZPhase, OpType.ZZMax, OpType.TK2]) + return self._gate_set & set([OpType.ZZPhase, OpType.ZZMax, OpType.TK2]) # noqa: C405 @property def is_local_emulator(self) -> bool: @@ -728,7 +727,7 @@ def default_compilation_pass( RemoveRedundancies(), ] ) - elif optimisation_level == 2: + elif optimisation_level == 2: # noqa: PLR2004 passlist.append( FullPeepholeOptimise( allow_swaps=self.compilation_config.allow_implicit_swaps, @@ -830,8 +829,8 @@ def get_compiled_circuit( :rtype: Circuit """ return_circuit = circuit.copy() - if optimisation_level == 3 and circuit.n_gates_of_type(OpType.Barrier) > 0: - warnings.warn( + if optimisation_level == 3 and circuit.n_gates_of_type(OpType.Barrier) > 0: # noqa: PLR2004 + warnings.warn( # noqa: B028 "Barrier operations in this circuit will be removed when using " "optimisation level 3." ) @@ -882,17 +881,16 @@ def get_compiled_circuits( @property def _result_id_type(self) -> _ResultIdTuple: - return tuple((str, str, int, str)) + return tuple((str, str, int, str)) # noqa: C409 @staticmethod def _update_result_handle(handle: ResultHandle) -> ResultHandle: """Update a legacy handle to be compatible with current format.""" - if len(handle) == 2: + if len(handle) == 2: # noqa: PLR2004 return ResultHandle(handle[0], handle[1], -1, "") - elif len(handle) == 3: + if len(handle) == 3: # noqa: PLR2004 return ResultHandle(handle[0], handle[1], handle[2], "") - else: - return handle + return handle @staticmethod def get_jobid(handle: ResultHandle) -> str: @@ -914,7 +912,7 @@ def get_ppcirc_rep(handle: ResultHandle) -> Any: return json.loads(cast("str", handle[1])) @staticmethod - def get_results_width(handle: ResultHandle) -> Optional[int]: + def get_results_width(handle: ResultHandle) -> int | None: """Return the truncation width of the results, if any. :param handle: result handle @@ -923,9 +921,8 @@ def get_results_width(handle: ResultHandle) -> Optional[int]: n = cast("int", handle[2]) if n == -1: return None - else: - assert n >= 0 - return n + assert n >= 0 + return n @staticmethod def get_results_selection(handle: ResultHandle) -> Any: @@ -942,20 +939,20 @@ def get_results_selection(handle: ResultHandle) -> Any: assert all(isinstance(name, str) and isinstance(idx, int) for name, idx in bits) return bits - def submit_program( + def submit_program( # noqa: PLR0912, PLR0913 self, language: Language, program: str, n_shots: int, - name: Optional[str] = None, + name: str | None = None, noisy_simulation: bool = True, - group: Optional[str] = None, - wasm_file_handler: Optional[WasmFileHandler] = None, - pytket_pass: Optional[BasePass] = None, - max_cost: Optional[int] = None, - options: Optional[dict[str, Any]] = None, - request_options: Optional[dict[str, Any]] = None, - results_selection: Optional[list[tuple[str, int]]] = None, + group: str | None = None, + wasm_file_handler: WasmFileHandler | None = None, + pytket_pass: BasePass | None = None, + max_cost: int | None = None, + options: dict[str, Any] | None = None, + request_options: dict[str, Any] | None = None, + results_selection: list[tuple[str, int]] | None = None, ) -> ResultHandle: """Submit a program directly to the backend. @@ -1010,7 +1007,7 @@ def submit_program( "no-opt": True, "noreduce": True, "error-model": noisy_simulation, - "tket": dict(), + "tket": dict(), # noqa: C408 }, } @@ -1037,12 +1034,12 @@ def submit_program( body.update(request_options or {}) try: - res = self.api_handler._submit_job(body) + res = self.api_handler._submit_job(body) # noqa: SLF001 if self.api_handler.online: jobdict = res.json() if res.status_code != HTTPStatus.OK: raise QuantinuumAPIError( - f'HTTP error submitting job, {jobdict["error"]}' + f"HTTP error submitting job, {jobdict['error']}" ) else: return ResultHandle( @@ -1052,7 +1049,7 @@ def submit_program( "" if results_selection is None else json.dumps(results_selection), ) except ConnectionError: - raise ConnectionError( + raise ConnectionError( # noqa: B904 f"{self._label} Connection Error: Error during submit..." ) @@ -1064,10 +1061,10 @@ def submit_program( json.dumps(results_selection), ) - def process_circuits( + def process_circuits( # noqa: PLR0912, PLR0915 self, circuits: Sequence[Circuit], - n_shots: Union[None, int, Sequence[Optional[int]]] = None, + n_shots: None | int | Sequence[int | None] = None, valid_check: bool = True, **kwargs: QuumKwargTypes, ) -> list[ResultHandle]: @@ -1108,7 +1105,7 @@ def process_circuits( Ignored for local emulator. """ circuits = list(circuits) - n_shots_list = Backend._get_n_shots_as_list( + n_shots_list = Backend._get_n_shots_as_list( # noqa: SLF001 n_shots, len(circuits), optional=False, @@ -1139,15 +1136,15 @@ def process_circuits( simplify_initial = kwargs.get("simplify_initial", False) noisy_simulation = cast("bool", kwargs.get("noisy_simulation", True)) - group = cast("Optional[str]", kwargs.get("group", self._group)) + group = cast("str | None", kwargs.get("group", self._group)) - wasm_fh = cast("Optional[WasmFileHandler]", kwargs.get("wasm_file_handler")) + wasm_fh = cast("WasmFileHandler | None", kwargs.get("wasm_file_handler")) - pytket_pass = cast("Optional[BasePass]", kwargs.get("pytketpass")) + pytket_pass = cast("BasePass | None", kwargs.get("pytketpass")) language: Language = cast("Language", kwargs.get("language", Language.QIR)) - max_cost = cast("Optional[int]", kwargs.get("max_cost")) + max_cost = cast("int | None", kwargs.get("max_cost")) handle_list = [] @@ -1156,7 +1153,7 @@ def process_circuits( if seed is not None and not isinstance(seed, int): raise ValueError("seed must be an integer or None") multithreading = bool(kwargs.get("multithreading")) - for circ, n_shots in zip(circuits, n_shots_list): + for circ, n_shots in zip(circuits, n_shots_list, strict=False): # noqa: PLR1704 if max_shots is not None and n_shots > max_shots: raise MaxShotsExceeded( f"Number of shots {n_shots} exceeds maximum {max_shots}" @@ -1175,7 +1172,7 @@ def process_circuits( results_selection = [] for name, count in Counter(bit.reg_name for bit in c0.bits).items(): for i in range(count): - results_selection.append((name, i)) + results_selection.append((name, i)) # noqa: PERF401 handle = ResultHandle( jobid, json.dumps(ppcirc_rep), @@ -1218,7 +1215,7 @@ def process_circuits( results_selection.append((name, i)) else: - assert language == Language.QIR or language == Language.PQIR + assert language == Language.QIR or language == Language.PQIR # noqa: PLR1714 if language == Language.QIR: profile = QIRProfile.PYTKET else: @@ -1274,19 +1271,19 @@ def process_circuits( json.dumps(results_selection), ) handle_list.append(handle) - self._cache[handle] = dict() + self._cache[handle] = dict() # noqa: C408 return handle_list def _check_batchable(self) -> None: if self.backend_info and not self.backend_info.misc.get("batching", False): - raise BatchingUnsupported() + raise BatchingUnsupported def start_batch( self, max_batch_cost: int, circuit: Circuit, - n_shots: Union[None, int] = None, + n_shots: None | int = None, valid_check: bool = True, **kwargs: QuumKwargTypes, ) -> ResultHandle: @@ -1317,7 +1314,7 @@ def add_to_batch( self, batch_start_job: ResultHandle, circuit: Circuit, - n_shots: Union[None, int] = None, + n_shots: None | int = None, batch_end: bool = False, valid_check: bool = True, **kwargs: QuumKwargTypes, @@ -1348,9 +1345,9 @@ def add_to_batch( def _retrieve_job( self, jobid: str, - timeout: Optional[int] = None, - wait: Optional[int] = None, - use_websocket: Optional[bool] = True, + timeout: int | None = None, + wait: int | None = None, + use_websocket: bool | None = True, ) -> dict: if not self.api_handler: raise RuntimeError("API handler not set") @@ -1418,7 +1415,7 @@ def circuit_status( def get_partial_result( self, handle: ResultHandle - ) -> tuple[Optional[BackendResult], CircuitStatus]: + ) -> tuple[BackendResult | None, CircuitStatus]: """ Retrieve partial results for a given job, regardless of its current state. @@ -1471,7 +1468,7 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul ) if self.is_local_emulator: if not have_pecos(): - raise RuntimeError( + raise RuntimeError( # noqa: B904 "Local emulator not available: \ try installing with the `pecos` option." ) @@ -1507,7 +1504,7 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul wait = kwargs.get("wait") if wait is not None: wait = int(wait) - use_websocket = cast("Optional[bool]", kwargs.get("use_websocket")) + use_websocket = cast("bool | None", kwargs.get("use_websocket")) job_retrieve = self._retrieve_job(jobid, timeout, wait, use_websocket) circ_status = _parse_status(job_retrieve) @@ -1515,14 +1512,14 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul StatusEnum.COMPLETED, StatusEnum.CANCELLED, ): - raise GetResultFailed( + raise GetResultFailed( # noqa: B904 f"Cannot retrieve result; job status is {circ_status}, \ jobid is {jobid}" ) try: res = job_retrieve["results"] except KeyError: - raise GetResultFailed( + raise GetResultFailed( # noqa: B904 f"Results missing in device return data, jobid is {jobid}" ) @@ -1530,10 +1527,10 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul self._update_cache_result(handle, backres) return backres - def cost_estimate(self, circuit: Circuit, n_shots: int) -> Optional[float]: + def cost_estimate(self, circuit: Circuit, n_shots: int) -> float | None: """Deprecated, use ``cost``.""" - warnings.warn( + warnings.warn( # noqa: B028 "cost_estimate is deprecated, use cost instead", DeprecationWarning ) @@ -1543,10 +1540,10 @@ def cost( self, circuit: Circuit, n_shots: int, - syntax_checker: Optional[str] = None, - use_websocket: Optional[bool] = None, + syntax_checker: str | None = None, + use_websocket: bool | None = None, **kwargs: QuumKwargTypes, - ) -> Optional[float]: + ) -> float | None: """ Return the cost in HQC to process this `circuit` with `n_shots` repeats on this backend. @@ -1572,7 +1569,7 @@ def cost( """ if not self.valid_circuit(circuit): raise ValueError( - "Circuit does not satisfy predicates of backend." + "Circuit does not satisfy predicates of backend." # noqa: ISC003 + " Try running `backend.get_compiled_circuit` first" ) @@ -1600,7 +1597,7 @@ def cost( if syntax_checker is not None: syntax_checker_name = syntax_checker else: - raise NoSyntaxChecker( + raise NoSyntaxChecker( # noqa: B904 "Could not find syntax checker for this backend, " "try setting one explicitly with the ``syntax_checker`` " "parameter (it will normally have a name ending in 'SC')." @@ -1649,13 +1646,12 @@ def logout(self) -> None: def _convert_result( resultdict: dict[str, list[str]], - ppcirc: Optional[Circuit] = None, - n_bits: Optional[int] = None, - results_selection: Optional[list[tuple[str, int]]] = None, + ppcirc: Circuit | None = None, + n_bits: int | None = None, + results_selection: list[tuple[str, int]] | None = None, ) -> BackendResult: - for creg, reslist in resultdict.items(): - if any(["-" in res for res in reslist]): + if any(["-" in res for res in reslist]): # noqa: C419 raise ValueError( f"found negative value for creg: {creg}. \ This could indicate a problem with the circuit submitted" @@ -1773,6 +1769,6 @@ def _convert_datetime_string(datetime_string: str) -> datetime.datetime: hour=hour, minute=minute, second=second, - tzinfo=datetime.timezone.utc, + tzinfo=datetime.UTC, ) - return dt + return dt # noqa: RET504 diff --git a/ruff.toml b/ruff.toml index 2a77dad5..2634dc83 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,7 +1,9 @@ exclude = [ "docs/pytket-docs-theming", + "docs/examples/*" ] + target-version = "py312" lint.select = [ diff --git a/tests/conftest.py b/tests/conftest.py index 8c2e3e4e..2b3413d7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ # limitations under the License. import os -from typing import Any, Optional +from typing import Any import jwt import pytest @@ -28,7 +28,7 @@ ALL_QUANTUM_HARDWARE_NAMES = [] -if not os.getenv("PYTKET_REMOTE_QUANTINUUM_EMULATORS_ONLY", 0): +if not os.getenv("PYTKET_REMOTE_QUANTINUUM_EMULATORS_ONLY", 0): # noqa: PLW1508 ALL_QUANTUM_HARDWARE_NAMES.extend( [ "H1-1", @@ -64,7 +64,6 @@ def pytest_configure() -> None: Note: we need to do this as part of the pytest_configure as these symbols are used while parametrizing the tests and not as fixtures.""" - # pytest.ALL_DEVICE_NAMES = ALL_DEVICE_NAMES # type: ignore pytest.ALL_SYNTAX_CHECKER_NAMES = ALL_SYNTAX_CHECKER_NAMES # type: ignore pytest.ALL_SIMULATOR_NAMES = ALL_SIMULATOR_NAMES # type: ignore @@ -74,10 +73,10 @@ def pytest_configure() -> None: def pytest_make_parametrize_id( config: pytest.Config, val: object, argname: str -) -> Optional[str]: +) -> str | None: """Custom ids for the parametrized tests.""" if isinstance(val, QuantinuumBackend): - return val._device_name + return val._device_name # noqa: SLF001 if isinstance(val, dict): return val.get("device_name") return None @@ -353,13 +352,13 @@ def fixture_mock_quum_api_handler( cred_store = MemoryCredentialStorage() cred_store.save_user_name(username) - cred_store._password = pwd + cred_store._password = pwd # noqa: SLF001 # Construct QuantinuumQAPI and login api_handler = QuantinuumAPI() # Add the credential storage seperately in line with fixture parameters - api_handler._cred_store = cred_store + api_handler._cred_store = cred_store # noqa: SLF001 api_handler.login() return api_handler @@ -399,7 +398,7 @@ def fixture_authenticated_quum_backend_prod( api_handler=authenticated_quum_handler, **request.param ) # In case machine_debug was specified by mistake in the params - backend._MACHINE_DEBUG = False + backend._MACHINE_DEBUG = False # noqa: SLF001 return backend @@ -438,6 +437,6 @@ def fixture_authenticated_quum_backend_qa( api_handler=authenticated_quum_handler_qa, **request.param ) # In case machine_debug was specified by mistake in the params - backend._MACHINE_DEBUG = False + backend._MACHINE_DEBUG = False # noqa: SLF001 return backend diff --git a/tests/integration/backend_test.py b/tests/integration/backend_test.py index d7f659f6..91d663d2 100644 --- a/tests/integration/backend_test.py +++ b/tests/integration/backend_test.py @@ -19,8 +19,9 @@ import time from base64 import b64encode from collections import Counter +from collections.abc import Callable # pylint: disable=unused-import from pathlib import Path -from typing import Any, Callable, cast # pylint: disable=unused-import +from typing import Any, cast import hypothesis.strategies as st import numpy as np @@ -134,16 +135,16 @@ def test_max_classical_register( c.CX(0, 1) c.measure_all() c = backend.get_compiled_circuit(c) - assert backend._check_all_circuits([c]) + assert backend._check_all_circuits([c]) # noqa: SLF001 for i in range(n_cl_reg - 1): c.add_c_register(f"creg-{i}", 32) - assert backend._check_all_circuits([c]) + assert backend._check_all_circuits([c]) # noqa: SLF001 c.add_c_register("creg-extra", 32) with pytest.raises(CircuitNotValidError): - backend._check_all_circuits([c]) + backend._check_all_circuits([c]) # noqa: SLF001 @pytest.mark.skipif(skip_remote_tests, reason=REASON) @@ -273,8 +274,8 @@ def test_cancel( @st.composite def circuits( draw: Callable[[SearchStrategy[Any]], Any], - n_qubits: SearchStrategy[int] = st.integers(min_value=2, max_value=6), - depth: SearchStrategy[int] = st.integers(min_value=1, max_value=100), + n_qubits: SearchStrategy[int] = st.integers(min_value=2, max_value=6), # noqa: B008 + depth: SearchStrategy[int] = st.integers(min_value=1, max_value=100), # noqa: B008 ) -> Circuit: total_qubits = draw(n_qubits) circuit = Circuit(total_qubits, total_qubits) @@ -284,7 +285,7 @@ def circuits( if gate == OpType.ZZMax: target = draw( st.integers(min_value=0, max_value=total_qubits - 1).filter( - lambda x: x != control + lambda x: x != control # noqa: B023 ) ) circuit.add_gate(gate, [control, target]) @@ -333,7 +334,7 @@ def test_cost_estimate( b = authenticated_quum_backend_prod c = b.get_compiled_circuit(c) estimate = None - if b._device_name.endswith("SC"): + if b._device_name.endswith("SC"): # noqa: SLF001 estimate = b.cost(c, n_shots) assert estimate == 0.0 else: @@ -986,7 +987,7 @@ def test_qir_submission(authenticated_quum_backend_qa: QuantinuumBackend) -> Non ir = module.as_bitcode() h = b.submit_program(Language.QIR, b64encode(ir).decode("utf-8"), n_shots=10) r = b.get_result(h) - assert set(r.get_bitlist()) == set([Bit("0_t0", 0), Bit("0_t1", 0)]) + assert set(r.get_bitlist()) == set([Bit("0_t0", 0), Bit("0_t1", 0)]) # noqa: C405 assert len(r.get_shots()) == 10 @@ -1009,7 +1010,7 @@ def test_qir_entrypoints(authenticated_quum_backend_prod: QuantinuumBackend) -> ir = module.as_bitcode() h = b.submit_program(Language.QIR, b64encode(ir).decode("utf-8"), n_shots=10) r = b.get_result(h) - assert set(r.get_bitlist()) == set([Bit("0_t0", 0), Bit("0_t1", 0)]) + assert set(r.get_bitlist()) == set([Bit("0_t0", 0), Bit("0_t1", 0)]) # noqa: C405 assert len(r.get_shots()) == 10 @@ -1032,7 +1033,7 @@ def test_qir_entrypoints_qa(authenticated_quum_backend_qa: QuantinuumBackend) -> ir = module.as_bitcode() h = b.submit_program(Language.QIR, b64encode(ir).decode("utf-8"), n_shots=10) r = b.get_result(h) - assert set(r.get_bitlist()) == set([Bit("0_t0", 0), Bit("0_t1", 0)]) + assert set(r.get_bitlist()) == set([Bit("0_t0", 0), Bit("0_t1", 0)]) # noqa: C405 assert len(r.get_shots()) == 10 @@ -1508,8 +1509,8 @@ def test_get_calendar( backend = QuantinuumBackend( api_handler=authenticated_quum_handler, device_name=machine ) - start_date = datetime.datetime(2024, 1, 8) - end_date = datetime.datetime(2024, 2, 16) + start_date = datetime.datetime(2024, 1, 8) # noqa: DTZ001 + end_date = datetime.datetime(2024, 2, 16) # noqa: DTZ001 calendar_data = backend.get_calendar(start_date, end_date) assert all(isinstance(a, dict) for a in calendar_data) assert all( @@ -1526,8 +1527,8 @@ def test_get_calendar_raises_error( authenticated_quum_backend_qa: QuantinuumBackend, ) -> None: backend = authenticated_quum_backend_qa - start_date = datetime.datetime(2024, 2, 8) - end_date = datetime.datetime(2024, 2, 16) + start_date = datetime.datetime(2024, 2, 8) # noqa: DTZ001 + end_date = datetime.datetime(2024, 2, 16) # noqa: DTZ001 with pytest.raises(RuntimeError): backend.get_calendar(start_date, end_date) diff --git a/tests/integration/local_emulator_test.py b/tests/integration/local_emulator_test.py index 6761b75e..09c75992 100644 --- a/tests/integration/local_emulator_test.py +++ b/tests/integration/local_emulator_test.py @@ -282,7 +282,7 @@ def test_classical_3(authenticated_quum_backend_prod: QuantinuumBackend) -> None cc = backend.get_compiled_circuit(circ) counts = backend.run_circuit(cc, n_shots=10).get_counts() assert len(counts.keys()) == 1 - result = list(counts.keys())[0] + result = list(counts.keys())[0] # noqa: RUF015 assert result == (0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0) diff --git a/tests/unit/api1_test.py b/tests/unit/api1_test.py index c286787d..4e83df0c 100644 --- a/tests/unit/api1_test.py +++ b/tests/unit/api1_test.py @@ -241,7 +241,7 @@ def test_custom_login_flow( backend_3.api_handler.delete_authentication() -def test_mfa_login_flow( +def test_mfa_login_flow( # noqa: PLR0913 requests_mock: Mocker, mock_credentials: tuple[str, str], mock_token: str, @@ -296,12 +296,12 @@ def match_normal_request(request: requests.PreparedRequest) -> bool: assert normal_login_route.called_once # Check that the mfa login has been invoked assert mfa_login_route.called_once - assert backend.api_handler._cred_store.id_token is not None - assert backend.api_handler._cred_store.refresh_token is not None + assert backend.api_handler._cred_store.id_token is not None # noqa: SLF001 + assert backend.api_handler._cred_store.refresh_token is not None # noqa: SLF001 @patch("pytket.extensions.quantinuum.backends.api_wrappers.microsoft_login") -def test_federated_login( +def test_federated_login( # noqa: PLR0913 mock_microsoft_login: MagicMock, requests_mock: Mocker, mock_credentials: tuple[str, str], @@ -332,8 +332,8 @@ def test_federated_login( mock_microsoft_login.assert_called_once() assert login_route.called_once - assert backend.api_handler._cred_store.id_token is not None - assert backend.api_handler._cred_store.refresh_token is not None + assert backend.api_handler._cred_store.id_token is not None # noqa: SLF001 + assert backend.api_handler._cred_store.refresh_token is not None # noqa: SLF001 def test_federated_login_wrong_provider( diff --git a/tests/unit/api_test.py b/tests/unit/api_test.py index 09c13a06..693336cb 100644 --- a/tests/unit/api_test.py +++ b/tests/unit/api_test.py @@ -34,17 +34,17 @@ def test_quum_login( _, pwd = mock_credentials - assert isinstance(mock_quum_api_handler._cred_store, MemoryCredentialStorage) + assert isinstance(mock_quum_api_handler._cred_store, MemoryCredentialStorage) # noqa: SLF001 # Check credentials are retrievable - assert mock_quum_api_handler._cred_store._password == pwd - assert mock_quum_api_handler._cred_store.refresh_token == mock_token - assert mock_quum_api_handler._cred_store.id_token == mock_token + assert mock_quum_api_handler._cred_store._password == pwd # noqa: SLF001 + assert mock_quum_api_handler._cred_store.refresh_token == mock_token # noqa: SLF001 + assert mock_quum_api_handler._cred_store.id_token == mock_token # noqa: SLF001 # Delete authentication and verify mock_quum_api_handler.delete_authentication() - assert mock_quum_api_handler._cred_store.id_token is None - assert mock_quum_api_handler._cred_store._password is None # type: ignore - assert mock_quum_api_handler._cred_store.refresh_token is None + assert mock_quum_api_handler._cred_store.id_token is None # noqa: SLF001 + assert mock_quum_api_handler._cred_store._password is None # type: ignore # noqa: SLF001 + assert mock_quum_api_handler._cred_store.refresh_token is None # noqa: SLF001 def test_machine_status( @@ -100,24 +100,24 @@ def test_full_login( # emulate no pytket config stored email address api_handler.full_login() - assert isinstance(api_handler._cred_store, MemoryCredentialStorage) - assert api_handler._cred_store.id_token == mock_token - assert api_handler._cred_store.refresh_token == "refresh" + mock_token - assert api_handler._cred_store._id_token_timeout is not None - assert api_handler._cred_store._refresh_token_timeout is not None + assert isinstance(api_handler._cred_store, MemoryCredentialStorage) # noqa: SLF001 + assert api_handler._cred_store.id_token == mock_token # noqa: SLF001 + assert api_handler._cred_store.refresh_token == "refresh" + mock_token # noqa: SLF001 + assert api_handler._cred_store._id_token_timeout is not None # noqa: SLF001 + assert api_handler._cred_store._refresh_token_timeout is not None # noqa: SLF001 - assert api_handler._cred_store._password is None - assert api_handler._cred_store._user_name is None + assert api_handler._cred_store._password is None # noqa: SLF001 + assert api_handler._cred_store._user_name is None # noqa: SLF001 api_handler.delete_authentication() assert all( val is None for val in ( - api_handler._cred_store.id_token, - api_handler._cred_store.refresh_token, - api_handler._cred_store._id_token_timeout, - api_handler._cred_store._refresh_token_timeout, + api_handler._cred_store.id_token, # noqa: SLF001 + api_handler._cred_store.refresh_token, # noqa: SLF001 + api_handler._cred_store._id_token_timeout, # noqa: SLF001 + api_handler._cred_store._refresh_token_timeout, # noqa: SLF001 ) ) diff --git a/tests/unit/convert_test.py b/tests/unit/convert_test.py index 75c7720b..0ad2341d 100644 --- a/tests/unit/convert_test.py +++ b/tests/unit/convert_test.py @@ -61,7 +61,7 @@ def test_convert_rzz() -> None: ) -def test_implicit_swap_removal() -> None: +def test_implicit_swap_removal() -> None: # noqa: PLR0915 b = QuantinuumBackend("", machine_debug=True) c = Circuit(2).ISWAPMax(0, 1) b.set_compilation_config_target_2qb_gate(OpType.ZZMax) @@ -165,16 +165,16 @@ def test_switch_target_2qb_gate() -> None: b.set_compilation_config_target_2qb_gate(OpType.ISWAPMax) # Confirming that if ZZPhase is added to gate set that it functions - b._MACHINE_DEBUG = False - b._backend_info = BackendInfo( + b._MACHINE_DEBUG = False # noqa: SLF001 + b._backend_info = BackendInfo( # noqa: SLF001 name="test", device_name="test", version="test", architecture=FullyConnected(1), gate_set={OpType.ZZPhase, OpType.ZZMax, OpType.PhasedX, OpType.Rz}, ) - assert OpType.ZZMax in b._gate_set - assert OpType.ZZPhase in b._gate_set + assert OpType.ZZMax in b._gate_set # noqa: SLF001 + assert OpType.ZZPhase in b._gate_set # noqa: SLF001 b.set_compilation_config_allow_implicit_swaps(True) b.set_compilation_config_target_2qb_gate(OpType.ZZPhase) compiled = b.get_compiled_circuit(c, 0) diff --git a/tests/unit/offline_backend_test.py b/tests/unit/offline_backend_test.py index f5c88b15..374804ea 100644 --- a/tests/unit/offline_backend_test.py +++ b/tests/unit/offline_backend_test.py @@ -37,7 +37,9 @@ def test_quantinuum_offline(language: Language) -> None: qapioffline = QuantinuumAPIOffline() backend = QuantinuumBackend( - device_name="H1-1", machine_debug=False, api_handler=qapioffline # type: ignore + device_name="H1-1", + machine_debug=False, + api_handler=qapioffline, # type: ignore ) c = Circuit(4, 4, "test 1") c.H(0) @@ -74,7 +76,9 @@ def test_quantinuum_offline(language: Language) -> None: def test_max_classical_register_ii() -> None: qapioffline = QuantinuumAPIOffline() backend = QuantinuumBackend( - device_name="H1-1", machine_debug=False, api_handler=qapioffline # type: ignore + device_name="H1-1", + machine_debug=False, + api_handler=qapioffline, # type: ignore ) c = Circuit(4, 4, "test 1") @@ -82,17 +86,17 @@ def test_max_classical_register_ii() -> None: c.CX(0, 1) c.measure_all() c = backend.get_compiled_circuit(c) - assert backend._check_all_circuits([c]) - for i in range(0, 20): + assert backend._check_all_circuits([c]) # noqa: SLF001 + for i in range(20): c.add_c_register(f"creg-{i}", 32) - assert backend._check_all_circuits([c]) + assert backend._check_all_circuits([c]) # noqa: SLF001 for i in range(20, 5000): c.add_c_register(f"creg-{i}", 32) with pytest.raises(CircuitNotValidError): - backend._check_all_circuits([c]) + backend._check_all_circuits([c]) # noqa: SLF001 @pytest.mark.parametrize("language", [Language.QASM, Language.QIR, Language.PQIR]) @@ -170,4 +174,4 @@ def test_custom_api_handler(device_name: str) -> None: backend_2 = QuantinuumBackend(device_name, api_handler=handler_2) assert backend_1.api_handler is not backend_2.api_handler - assert backend_1.api_handler._cred_store is not backend_2.api_handler._cred_store + assert backend_1.api_handler._cred_store is not backend_2.api_handler._cred_store # noqa: SLF001 From 3337a843de3b6e53bd534199b94ea5107655fc05 Mon Sep 17 00:00:00 2001 From: Melf Date: Tue, 13 May 2025 15:43:03 +0100 Subject: [PATCH 4/5] fix --- pytket/extensions/quantinuum/backends/credential_storage.py | 2 +- pytket/extensions/quantinuum/backends/quantinuum.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pytket/extensions/quantinuum/backends/credential_storage.py b/pytket/extensions/quantinuum/backends/credential_storage.py index 23424cc8..5035c41d 100644 --- a/pytket/extensions/quantinuum/backends/credential_storage.py +++ b/pytket/extensions/quantinuum/backends/credential_storage.py @@ -13,7 +13,7 @@ # limitations under the License. from abc import ABC, abstractmethod -from datetime import UTC, datetime, timedelta +from datetime import UTC, datetime, timedelta # type: ignore import jwt diff --git a/pytket/extensions/quantinuum/backends/quantinuum.py b/pytket/extensions/quantinuum/backends/quantinuum.py index b3d10c3e..1ccc9db9 100644 --- a/pytket/extensions/quantinuum/backends/quantinuum.py +++ b/pytket/extensions/quantinuum/backends/quantinuum.py @@ -1769,6 +1769,6 @@ def _convert_datetime_string(datetime_string: str) -> datetime.datetime: hour=hour, minute=minute, second=second, - tzinfo=datetime.UTC, + tzinfo=datetime.UTC, # type: ignore ) return dt # noqa: RET504 From 56444023ca9dbceab0d8f50161ff5fb5a70abd7e Mon Sep 17 00:00:00 2001 From: Melf Date: Tue, 13 May 2025 17:41:56 +0100 Subject: [PATCH 5/5] update pytket version --- _metadata.py | 2 +- docs/changelog.md | 5 +++++ setup.py | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/_metadata.py b/_metadata.py index f214120d..ece592c3 100644 --- a/_metadata.py +++ b/_metadata.py @@ -1,2 +1,2 @@ -__extension_version__ = "0.47.0" +__extension_version__ = "0.48.0" __extension_name__ = "pytket-quantinuum" diff --git a/docs/changelog.md b/docs/changelog.md index b3b3564e..70acbc6f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,11 @@ # Changelog +## 0.48.0 (unreleased) + +- Update pytket minimum version requirement to 2.4.1. +- Update pytket-qir minimum version requirement to 0.23. + ## 0.47.0 (May 2025) - Add `max_cost` parameter to `process_circuits()` and `submit_program()`. diff --git a/setup.py b/setup.py index 18e74df5..3280d8b8 100644 --- a/setup.py +++ b/setup.py @@ -45,8 +45,8 @@ packages=find_namespace_packages(include=["pytket.*"]), include_package_data=True, install_requires=[ - "pytket >= 2.2.0", - "pytket-qir >= 0.22.0", + "pytket >= 2.4.1", + "pytket-qir >= 0.23.0", "requests >= 2.32.2", "types-requests", "websockets >= 13.1",