From 1d55899cb8c14b99b3f3a104d1c4191c317d80eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erc=C3=BCment=20Kaya?= <49598189+kayaercument@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:18:01 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Implementation=20of=20C++=20ver?= =?UTF-8?q?sion=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ initial C interface implementation * ✨ initial signatures * ✨ job implemented * ✨ rest client implementation * ✨ rabbitmq client implementation * 🎨 refactoring and new features * 🎨 Unit tests and refactoring * 🎨 pre-commit run * 🎨 hide implementation details * 🎨 refactoring and naming fixes * 🎨 splitting the header * 🎨 constructor rework and source split * 🎨 removing the fromJson methods * 🎨 improve code qualitiy and read me --- .devcontainer/Dockerfile | 11 + .devcontainer/devcontainer.json | 9 + .pre-commit-config.yaml | 16 - CMakeLists.txt | 57 ++ README.md | 32 +- include/mqss/client.h | 54 ++ include/mqss/job.h | 110 ++++ include/mqss/resource.h | 34 ++ mqss_client/__init__.py | 14 - mqss_client/base_client.py | 22 - mqss_client/comm/rmq_client.py | 162 ------ mqss_client/hpc_client.py | 107 ---- mqss_client/job.py | 76 --- mqss_client/mqss_client.py | 161 ------ mqss_client/resource_info.py | 64 --- mqss_client/rest_client.py | 87 --- mqss_client/settings.py | 5 - noxfile.py | 7 - pyproject.toml | 44 -- src/CMakeLists.txt | 36 ++ src/client.cpp | 120 ++++ src/clients/hpc_client.cpp | 72 +++ src/clients/hpc_client.h | 29 + src/clients/rabbitmq_client.cpp | 77 +++ src/clients/rabbitmq_client.h | 40 ++ src/clients/rest_client.cpp | 105 ++++ src/clients/rest_client.h | 24 + src/job.cpp | 60 ++ src/resource.cpp | 66 +++ test/__init__.py | 0 test/config.py | 45 -- test/example.qasm | 11 - test/mocks.py | 101 ---- test/mqss_client_tests_base.py | 128 ----- test/test_mqss_client_live.py | 75 --- test/test_mqss_client_mock.py | 85 --- tests/CMakeLists.txt | 2 + tests/unit_tests/CMakeLists.txt | 25 + tests/unit_tests/job_unit_test.cpp | 166 ++++++ tests/unit_tests/resource_unit_test.cpp | 123 ++++ uv.lock | 725 ------------------------ 41 files changed, 1240 insertions(+), 1947 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 CMakeLists.txt create mode 100644 include/mqss/client.h create mode 100644 include/mqss/job.h create mode 100644 include/mqss/resource.h delete mode 100644 mqss_client/__init__.py delete mode 100644 mqss_client/base_client.py delete mode 100644 mqss_client/comm/rmq_client.py delete mode 100644 mqss_client/hpc_client.py delete mode 100644 mqss_client/job.py delete mode 100644 mqss_client/mqss_client.py delete mode 100644 mqss_client/resource_info.py delete mode 100644 mqss_client/rest_client.py delete mode 100644 mqss_client/settings.py delete mode 100644 noxfile.py delete mode 100644 pyproject.toml create mode 100644 src/CMakeLists.txt create mode 100644 src/client.cpp create mode 100644 src/clients/hpc_client.cpp create mode 100644 src/clients/hpc_client.h create mode 100644 src/clients/rabbitmq_client.cpp create mode 100644 src/clients/rabbitmq_client.h create mode 100644 src/clients/rest_client.cpp create mode 100644 src/clients/rest_client.h create mode 100644 src/job.cpp create mode 100644 src/resource.cpp delete mode 100644 test/__init__.py delete mode 100644 test/config.py delete mode 100644 test/example.qasm delete mode 100644 test/mocks.py delete mode 100644 test/mqss_client_tests_base.py delete mode 100644 test/test_mqss_client_live.py delete mode 100644 test/test_mqss_client_mock.py create mode 100644 tests/CMakeLists.txt create mode 100644 tests/unit_tests/CMakeLists.txt create mode 100644 tests/unit_tests/job_unit_test.cpp create mode 100644 tests/unit_tests/resource_unit_test.cpp delete mode 100644 uv.lock diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..4721738 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,11 @@ +FROM --platform=x86_64 python:3.11-bookworm + +ENV TZ="Europe/Berlin" + +RUN apt-get update -y && \ + apt-get autoremove -y +RUN apt-get install -y software-properties-common bash-completion cmake build-essential iputils-ping rsync p7zip-full libuv1-dev git nlohmann-json3-dev librabbitmq-dev libboost-dev libgtest-dev + +WORKDIR /home + +CMD ["bash"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..fdff3ce --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,9 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/debian +{ + "name": "MQSS Client Development", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "build": { + "dockerfile": "Dockerfile" + } +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index baef90e..e5e41f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,19 +60,3 @@ repos: # Run the formatter. - id: ruff-format types_or: [python, pyi] - - - repo: local - hooks: - - id: isort - name: isort - entry: uv run isort mqss_client/ test/ - language: system - always_run: true - pass_filenames: false - - - id: mypy - name: mypy - entry: uv run python -m mypy mqss_client/ test/ - language: system - always_run: true - pass_filenames: false diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5f3f073 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +# ------------------------------------------------------------------------------ +# Copyright 2024 Munich Quantum Software Stack Project +# +# Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Munich-Quantum-Software-Stack/QDMI-Devices/blob/develop/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# ------------------------------------------------------------------------------ + +# set required cmake version +cmake_minimum_required(VERSION 3.19...3.30) + +project( + MQSS-Client + VERSION 0.1 + DESCRIPTION "MQSS Client" + LANGUAGES CXX) + +# Generate compile_commands.json to make it easier to work with clang based +# tools +set(CMAKE_EXPORT_COMPILE_COMMANDS + ON + CACHE BOOL "Export compile commands" FORCE) + +option(BUILD_UNIT_TESTS "Build the unit tests" ON) +option(ENABLE_COVERAGE "Enabling coverage" ON) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +if(ENABLE_COVERAGE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -O0 -g") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -O0 -g") +endif() + + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED True) +set(CMAKE_C_EXTENSIONS OFF) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_subdirectory(src) + +if(BUILD_UNIT_TESTS) + add_subdirectory(tests) +endif() diff --git a/README.md b/README.md index 5bc4dc1..f26ece3 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,29 @@ -## Installation +## Building and Requirements + +The requirements of the MQSSClient are as follows: ```bash -pip install mqss-client +1) cmake >= 3.19 +2) CURL +3) nlohmann_json +4) rabbitmq-c ``` -## Usage +To build the MQSSClient, the commands need to be executable: -```python -from mqss_client import MQSSClient +```bash +cmake -S . -B build +cmake --build build +``` -URL = "" -TOKEN = "" +## Usage -# create a client instance -client = MQSSClient(token=TOKEN, url=URL) -``` +```cpp +#include +URL_OR_QUEUE_NAME = "" +TOKEN = "" +IS_HPC = false -## Changelog +client = mqss::client::MQSSClient(TOKEN, URL_OR_QUEUE_NAME, IS_HPC); -See the [CHANGELOG](CHANGELOG.md) for details on changes in each version. +``` diff --git a/include/mqss/client.h b/include/mqss/client.h new file mode 100644 index 0000000..21430e1 --- /dev/null +++ b/include/mqss/client.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "job.h" +#include "resource.h" +#include + +#define MQP_DEFAULT_URL "https://portal.quantum.lrz.de:4000/v1/" + +namespace mqss::client { + +class MQSSBaseClient { +public: + virtual ~MQSSBaseClient() = default; + virtual std::string get(const std::string &path) = 0; + virtual std::string post(const std::string &path, + const nlohmann::json &data) = 0; + virtual void del(const std::string &path) = 0; +}; + +class MQSSClient { + +private: + std::unique_ptr waitForJobResult(const JobRequest &job, size_t poll_seconds); + +public: + MQSSClient(const std::string &token = "", + const std::string &url_or_queue = MQP_DEFAULT_URL, + bool is_hpc = false); + + // Resources + std::vector getAllResources() const; + std::optional getResourceInfo(const std::string &resource) const; + + // Jobs + std::optional submitJob(JobRequest &job); + void cancelJob(JobRequest &job); + std::string getJobStatus(const JobRequest &job); + std::unique_ptr + getJobResult(const JobRequest &job, bool wait = false, size_t timeout = 100); + + int getNumberPendingJobs(const std::string &resource) const; + +private: + std::unique_ptr mClient; +}; + +} // namespace mqss::client diff --git a/include/mqss/job.h b/include/mqss/job.h new file mode 100644 index 0000000..5aa1a6b --- /dev/null +++ b/include/mqss/job.h @@ -0,0 +1,110 @@ +#include +#include +#include + +#include + +namespace mqss::client { +class JobRequest { + +private: + std::string mUuid; + +public: + virtual ~JobRequest() = default; + virtual nlohmann::json toJson() const = 0; + virtual std::string getPath() const = 0; + std::string getUuid() const { return mUuid; } + void setUuid(const std::string &uuid) { mUuid = uuid; } +}; + +class CircuitJobRequest : public JobRequest { +private: + std::string mCircuit; + std::string mCircuitFormat; + std::string mResourceName; + unsigned int mShots; + bool mNoModify; + bool mQueued; + +public: + CircuitJobRequest(){}; + + CircuitJobRequest(std::string circuit, std::string circuitFormat, + std::string resourceName, unsigned int shots, bool noModify, + bool queued); + + void setCircuit(std::string circuit) { mCircuit = circuit; } + std::string getCircuit() const { return mCircuit; } + void setCircuitFormat(std::string circuitFormat) { + mCircuitFormat = circuitFormat; + } + std::string getCircuitFormat() const { return mCircuitFormat; } + void setResourceName(const std::string &resourceName) { + mResourceName = resourceName; + } + std::string getResourceName() const { return mResourceName; } + void setShots(unsigned int shots) { mShots = shots; } + unsigned int getShots() const { return mShots; } + void setNoModify(bool noModify) { mNoModify = noModify; } + bool isNoModify() const { return mNoModify; } + void setQueued(bool queued) { mQueued = queued; } + bool isQueued() const { return mQueued; } + + nlohmann::json toJson() const; + + std::string getPath() const { return "job"; } +}; + +class HamiltonianJobRequest : public JobRequest { +private: + std::string mResourceName; + std::string mInteractionStr; + std::string mCoefficientsStr; + +public: + HamiltonianJobRequest(std::string resourceName, std::string interactionStr, + std::string coefficientsStr); + + HamiltonianJobRequest(){}; + + void setResourceName(std::string resourceName) { + mResourceName = resourceName; + } + std::string getResourceName() const { return mResourceName; } + + void setInteractionString(std::string interactionStr) { + mInteractionStr = interactionStr; + } + std::string getInteractionString() const { return mInteractionStr; } + + void setCoefficientsString(std::string coefficientsStr) { + mCoefficientsStr = coefficientsStr; + } + std::string getCoefficientsString() const { return mCoefficientsStr; } + + nlohmann::json toJson() const; + + std::string getPath() const { return "hamiltonian_job"; } +}; + +class JobResult { + + std::map mResults; + std::string mTimestampCompleted; + std::string mTimestampSubmitted; + std::string mTimestampScheduled; + +public: + JobResult(std::map results, + std::string timestampCompleted, std::string timestampSubmitted, + std::string timestampScheduled); + + JobResult(const nlohmann::json &parsed); + + std::map getResults() const { return mResults; } + std::string getTimestampCompleted() const { return mTimestampCompleted; } + std::string getTimestampSubmitted() const { return mTimestampSubmitted; } + std::string getTimestampScheduled() const { return mTimestampScheduled; } +}; +} // namespace mqss::client diff --git a/include/mqss/resource.h b/include/mqss/resource.h new file mode 100644 index 0000000..b38a7b0 --- /dev/null +++ b/include/mqss/resource.h @@ -0,0 +1,34 @@ +#include +#include +#include + +#include +namespace mqss::client { +class Resource { +public: + Resource(std::string name, unsigned qubitCount, bool online, + std::vector> couplingMap, + std::vector nativeGateset); + + Resource(const nlohmann::json &json); + + const std::string &getName() const noexcept { return mName; } + unsigned getQubitCount() const noexcept { return mQubitCount; } + bool isOnline() const noexcept { return mOnline; } + + const std::vector> &getCouplingMap() const noexcept { + return mCouplingMap; + } + + const std::vector &getNativeGateset() const noexcept { + return mNativeGateset; + } + +private: + std::string mName; + unsigned mQubitCount; + bool mOnline; + std::vector> mCouplingMap; + std::vector mNativeGateset; +}; +} // namespace mqss::client diff --git a/mqss_client/__init__.py b/mqss_client/__init__.py deleted file mode 100644 index 974202c..0000000 --- a/mqss_client/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -"""MQSS Client package""" - -from .job import CircuitJobRequest, HamiltonianJobRequest, JobStatus, Result -from .mqss_client import MQSSClient -from .resource_info import ResourceInfo - -__all__ = [ - "MQSSClient", - "ResourceInfo", - "CircuitJobRequest", - "HamiltonianJobRequest", - "JobStatus", - "Result", -] diff --git a/mqss_client/base_client.py b/mqss_client/base_client.py deleted file mode 100644 index 3e33597..0000000 --- a/mqss_client/base_client.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Base Client for underlying communication clients of the MQSS Client""" - -from abc import ABC, abstractmethod - - -class BaseClient(ABC): - """Abstract base class for MQSS clients""" - - @abstractmethod - def get(self, path: str) -> dict: - """GET request to the specified path""" - return {} - - @abstractmethod - def post(self, path: str, data: dict) -> dict: - """POST request to the specified path""" - return {} - - @abstractmethod - def delete(self, path: str) -> None: - """DELETE request to the specified path""" - return None diff --git a/mqss_client/comm/rmq_client.py b/mqss_client/comm/rmq_client.py deleted file mode 100644 index cfed25f..0000000 --- a/mqss_client/comm/rmq_client.py +++ /dev/null @@ -1,162 +0,0 @@ -"""RabbitMQ Client for HPCQC""" - -import os -from dataclasses import dataclass -from json import load -from typing import Any, Dict, Optional -from warnings import warn - -import pika.exceptions as pikaExceptions # type: ignore -from pika import ConnectionParameters # type: ignore -from pika import BlockingConnection, PlainCredentials -from pika.exceptions import AMQPError # type: ignore - - -class RabbitMQClient: - """RabbitMQ Client for HPCQC""" - - def __init__(self, token: str) -> None: - self.token = token - self.connection = None - self.channel = None - self.conn_config = ( - ConnectionConfiguration() - if os.environ.get("MQSS_CLIENT_RMQ_CONN_CONFIG_FILE") is None - else ConnectionConfiguration.from_json_file( - os.environ["MQSS_CLIENT_RMQ_CONN_CONFIG_FILE"] - ) - ) - - def connect(self) -> None: - """Connect to RabbitMQ server""" - self.connection = create_rmq_connection(self.conn_config) - assert self.connection is not None - self.channel = self.connection.channel() - - def close(self) -> None: - """Close the connection to RabbitMQ server""" - if self.channel: - self.channel.close() - if self.connection: - self.connection.close() - - def send(self, data: str, destination: str) -> bool: - """Send data to RabbitMQ server""" - if not self.channel: - return False - - if self.channel.is_closed: - self.channel = self.connection.channel() - - try: - self.channel.queue_declare(queue=destination) - self.channel.basic_publish( - exchange="", routing_key=destination, body=data, mandatory=True - ) - except AMQPError: - return False - return True - - def receive( - self, source: str - ) -> Optional[str]: # Updated return type to Optional[str] - """Receive data from RabbitMQ server""" - if not self.channel: - return None - - if self.channel.is_closed: - self.channel = self.connection.channel() - - response = None - self.channel.queue_declare(queue=source) - for method_frame, _, body in self.channel.consume( - queue=source, - inactivity_timeout=1, - auto_ack=True, - ): - if body is not None: - response = body.decode() - break - - return response - - def declare_queue(self, queue_name: str) -> None: - """Declare a queue""" - if not self.channel: - return - self.channel.queue_declare(queue=queue_name) - - def delete_queue(self, queue_name: str) -> None: - """Delete a queue""" - if not self.channel: - return - self.channel.queue_delete(queue=queue_name) - - def __enter__(self) -> "RabbitMQClient": - self.connect() - return self - - def __exit__(self, exc_type, exc_value, traceback) -> None: - self.close() - - -@dataclass -class ConnectionConfiguration: - """Configuration required for connection to RabbitMQ""" - - host: str = "localhost" - port: int = 5672 - user: str = "guest" - password: str = "guest" - - @classmethod - def from_json_file(cls, config_file_path: str, conn_config_key: str = ""): - """Read Connection Configuration from a file""" - - try: - with open(file=config_file_path, encoding="utf-8") as json_config_file: - config_data: Dict[str, Any] = load(json_config_file) - config_dict: Dict[str, Any] = {} - if conn_config_key != "" and conn_config_key in config_data: - config_dict = config_data[conn_config_key] - else: - config_dict = config_data - - conn_config = cls(**config_dict) - except (FileExistsError, FileNotFoundError): - warn(f"Configuration file not found: {config_file_path}") - return cls() - - return conn_config - - -def create_rmq_connection( - conn_config: ConnectionConfiguration, raise_exceptions: bool = False -) -> Optional[ - BlockingConnection -]: # Updated return type to Optional[BlockingConnection] - """Connect to RabbitMQ and return a blocking connection handle""" - - _rmq_connection = None - try: - _rmq_connection = BlockingConnection( - ConnectionParameters( - host=conn_config.host, - port=conn_config.port, - heartbeat=0, - credentials=PlainCredentials( - username=conn_config.user, - password=conn_config.password, - ), - ) - ) - assert _rmq_connection is not None - - except ( - pikaExceptions.ChannelWrongStateError, - pikaExceptions.AMQPConnectionError, - ) as _exception: - if raise_exceptions: - raise _exception - - return _rmq_connection diff --git a/mqss_client/hpc_client.py b/mqss_client/hpc_client.py deleted file mode 100644 index 0128a26..0000000 --- a/mqss_client/hpc_client.py +++ /dev/null @@ -1,107 +0,0 @@ -"""Client for HPCQC Offloading -It needs to interact with the following API endpoints: -- `resources`: Get information about all resources. -- `resources/{resource_name}`: Get information about a specific resource. -- `resources/{resource_name}/num_pending_jobs`: Get the number of pending jobs for a resource. -- `job`: Submit a job. -- `job/{uuid}/status`: Get the status of a job. -- `job/{uuid}/result`: Get the result of a job. -- `job/{uuid}/cancel_reason`: Get the cancel reason of a job. -- `hamiltonian_job`: Submit a Hamiltonian job. -- `hamiltonian_job/{uuid}/status`: Get the status of a Hamiltonian job. -- `hamiltonian_job/{uuid}/result`: Get the result of a Hamiltonian job. -- `hamiltonian_job/{uuid}/cancel_reason`: Get the cancel reason of a Hamiltonian job. -The client can be initialized with a token and a base URL. -It supports both REST and HPC Offload modes. -""" - -import json -import socket -import uuid -from dataclasses import dataclass - -from .base_client import BaseClient -from .comm.rmq_client import RabbitMQClient - -HOSTNAME = socket.gethostname().replace(" ", "_") - - -class HPCOffloadClient(BaseClient): - """HPC Client for HPC Offload Listener""" - - def __init__( - self, - token: str, - offload_listener_queue_name: str = f"qoffload_api_request_reception_queue_{HOSTNAME}", - ) -> None: - """Initialize the HPC Offload Client""" - self.token = token - self.offload_listener_queue_name = offload_listener_queue_name - unique_id = str(uuid.uuid4())[:8] - self.response_queue_name = f"response_queue_{HOSTNAME}_{unique_id}" - - def get(self, path: str) -> dict: - """GET request to the specified path""" - # Implement GET request logic here - with RabbitMQClient(self.token) as client: - request = OffloadRequest( - authorization=self.token, - method="GET", - request=path, - data={}, - response_queue=self.response_queue_name, - ) - client.declare_queue(self.response_queue_name) - client.send(request.request_str(), self.offload_listener_queue_name) - response = client.receive(request.response_queue) - client.delete_queue(self.response_queue_name) - if response is not None: - return json.loads(response) - return {} - - def post(self, path: str, data: dict) -> dict: - """POST request to the specified path""" - # Implement POST request logic here - with RabbitMQClient(self.token) as client: - request = OffloadRequest( - authorization=self.token, - method="POST", - request=path, - data=data, - response_queue=self.response_queue_name, - ) - client.declare_queue(self.response_queue_name) - client.send(request.request_str(), self.offload_listener_queue_name) - response = client.receive(request.response_queue) - client.delete_queue(self.response_queue_name) - if response is not None: - return json.loads(response) - return {} - - def delete(self, path: str) -> None: - """DELETE request to the specified path""" - # Implement DELETE request logic here - with RabbitMQClient(self.token) as client: - request = OffloadRequest( - authorization=self.token, - method="DELETE", - request=path, - data={}, - response_queue="", - ) - client.send(request.request_str(), self.offload_listener_queue_name) - - -@dataclass -class OffloadRequest: - """Data class for offload requests""" - - authorization: str - method: str - request: str - data: dict - response_queue: str - - def request_str(self) -> str: - """Convert the request to a JSON string""" - return json.dumps(self.__dict__) diff --git a/mqss_client/job.py b/mqss_client/job.py deleted file mode 100644 index 7df49b0..0000000 --- a/mqss_client/job.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Job module for MQSS Client""" - -from abc import ABC -from dataclasses import dataclass -from datetime import datetime -from enum import Enum -from typing import Dict, Optional - - -class JobStatus(str, Enum): - """Status enumeration for the job status""" - - PENDING = "PENDING" - # NOTE: We need an intermediate status before COMPLETED for the job runner to work - WAITING = "WAITING" - FAILED = "FAILED" - COMPLETED = "COMPLETED" - CANCELLED = "CANCELLED" - - -class JobRequest(ABC): # pylint: disable=too-few-public-methods - """Base class for job requests""" - - def to_json_dict(self) -> dict: - """Convert JobRequest to JSON dictionary""" - raise NotImplementedError("Subclasses must implement this method") - - -@dataclass -class CircuitJobRequest(JobRequest): - """Class to hold job information""" - - circuits: str - circuit_format: str - resource_name: str - shots: int - no_modify: bool - queued: bool - - def to_json_dict(self) -> dict: - """Convert CircuitJobRequest to JSON dictionary""" - return { - "circuit": self.circuits, - "circuit_format": self.circuit_format, - "resource_name": self.resource_name, - "shots": self.shots, - "no_modify": self.no_modify, - "queued": self.queued, - } - - -@dataclass -class HamiltonianJobRequest(JobRequest): - """Class to hold Hamiltonian job information""" - - resource_name: str - interaction_str: str - coefficients_str: str - - def to_json_dict(self) -> dict: - """Convert HamiltonianJobRequest to JSON dictionary""" - return { - "resource_name": self.resource_name, - "interaction_str": self.interaction_str, - "coefficients_str": self.coefficients_str, - } - - -@dataclass -class Result: - """Result Class to hold counts""" - - counts: Dict[str, int] - timestamp_submitted: datetime - timestamp_scheduled: datetime - timestamp_completed: Optional[datetime] = None diff --git a/mqss_client/mqss_client.py b/mqss_client/mqss_client.py deleted file mode 100644 index 1a78d3e..0000000 --- a/mqss_client/mqss_client.py +++ /dev/null @@ -1,161 +0,0 @@ -""" -This module provides a client for the MQP API and HPC Offload Listener. -""" - -import json -import os -import socket -import time -from datetime import datetime -from typing import Dict, Optional, Union - -from .hpc_client import HPCOffloadClient -from .job import CircuitJobRequest, HamiltonianJobRequest, JobRequest, JobStatus, Result -from .resource_info import ResourceInfo -from .rest_client import RESTClient - - -class MQSSClient: - """MQSS Client class for interacting with MQP REST API / HPC Offload Listener""" - - def __init__(self, token: str, base_url: str, is_hpc: bool = False): - self.token = token - self.base_url = base_url - self.client: Union[HPCOffloadClient, RESTClient] - if is_hpc: - HOSTNAME = socket.gethostname().replace(" ", "_") - _offload_queue_name = os.environ.get( - "MQSS_OFFLOAD_LISTENER_QUEUE_NAME", - f"qoffload_api_request_reception_queue_{HOSTNAME}", - ) - self.client = HPCOffloadClient( - token, offload_listener_queue_name=_offload_queue_name - ) - else: - self.client = RESTClient(token, base_url) - - def get_all_resources(self) -> Dict[str, ResourceInfo]: - """Get resource info about all resources""" - - _resources = {} - rsp_json = self.client.get("resources") - for name, resource_json in rsp_json.items(): - _resources[name] = ResourceInfo.from_json_dict(resource_json) - - return _resources - - def get_resource_info(self, resource_name: str) -> Optional[ResourceInfo]: - """Get resource info about specific resource""" - rsp_json = self.client.get(f"resources/{resource_name}") - - if not rsp_json: - return None - - return ResourceInfo.from_json_dict(rsp_json) - - def submit_job(self, job_request: JobRequest) -> str: - """Submit a circuit job""" - if isinstance(job_request, CircuitJobRequest): - rsp_json = self.client.post( - "job", - job_request.to_json_dict(), - ) - elif isinstance(job_request, HamiltonianJobRequest): - rsp_json = self.client.post( - "hamiltonian_job", - job_request.to_json_dict(), - ) - else: - raise ValueError("Invalid job request type") - - if not rsp_json: - raise ValueError("Invalid response from server") - - if "uuid" not in rsp_json: - raise ValueError("UUID not found in response") - - return rsp_json["uuid"] - - def cancel_job(self, uuid: str, job_type: JobRequest) -> None: - """Cancel a job""" - if isinstance(job_type, CircuitJobRequest): - self.client.delete(f"job/{uuid}") - elif isinstance(job_type, HamiltonianJobRequest): - self.client.delete(f"hamiltonian_job/{uuid}") - else: - raise ValueError("Invalid job request type") - - def job_status(self, uuid: str, job_type: JobRequest) -> JobStatus: - """Get job status""" - if isinstance(job_type, CircuitJobRequest): - rsp_json = self.client.get(f"job/{uuid}/status") - elif isinstance(job_type, HamiltonianJobRequest): - rsp_json = self.client.get(f"hamiltonian_job/{uuid}/status") - else: - raise ValueError("Invalid job request type") - - if not rsp_json: - raise ValueError("Invalid response from server") - if "status" not in rsp_json: - raise ValueError("Status not found in response") - - return JobStatus(rsp_json["status"]) - - def job_result(self, uuid: str, job_type: JobRequest) -> Optional[Result]: - """Get job result as JSON""" - if isinstance(job_type, CircuitJobRequest): - result_json = self.client.get(f"job/{uuid}/result") - elif isinstance(job_type, HamiltonianJobRequest): - result_json = self.client.get(f"hamiltonian_job/{uuid}/result") - else: - raise ValueError("Invalid job request type") - - if not result_json: - return None - - return Result( - counts=json.loads(result_json["result"]), - timestamp_completed=( - datetime.strptime( - result_json["timestamp_completed"], "%Y-%m-%d %H:%M:%S.%f" - ) - if result_json["timestamp_completed"] != "" - else None - ), - timestamp_submitted=datetime.strptime( - result_json["timestamp_submitted"], "%Y-%m-%d %H:%M:%S.%f" - ), - timestamp_scheduled=datetime.strptime( - result_json["timestamp_scheduled"], "%Y-%m-%d %H:%M:%S.%f" - ), - ) - - def wait_for_job_result(self, uuid: str, job_type: JobRequest) -> Optional[Result]: - """Wait for a job to complete and return the result""" - timeout = 2.0 - end_status = self.job_status(uuid, job_type) - while end_status in (JobStatus.PENDING, JobStatus.WAITING): - time.sleep(timeout) - end_status = self.job_status(uuid, job_type) - - if end_status == JobStatus.COMPLETED: - return self.job_result(uuid, job_type) - if end_status == JobStatus.FAILED: - raise RuntimeError("Job failed") - if end_status == JobStatus.CANCELLED: - if isinstance(job_type, CircuitJobRequest): - cancel_reason = self.client.get(f"job/{uuid}/cancel_reason") - else: - cancel_reason = self.client.get(f"hamiltonian_job/{uuid}/cancel_reason") - raise RuntimeError(f"Job cancelled: {cancel_reason}") - - raise RuntimeError("Unknown status") - - def get_num_pending_jobs(self, resource_name: str) -> int: - """Get the number of pending jobs for a resource""" - rsp_json = self.client.get(f"resources/{resource_name}/num_pending_jobs") - - if not rsp_json or "num_pending_jobs" not in rsp_json: - raise ValueError("Invalid response from server") - - return rsp_json["num_pending_jobs"] diff --git a/mqss_client/resource_info.py b/mqss_client/resource_info.py deleted file mode 100644 index 1f7f059..0000000 --- a/mqss_client/resource_info.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Module for ResourceInfo class""" - -import ast -from dataclasses import dataclass -from typing import Any, Dict, List, Optional, Tuple - - -@dataclass -class ResourceInfo: - """Hold information about resource needed for transpilation""" - - name: str - qubits: int - online: bool = False - connectivity: Optional[List[List[int]]] = None - instructions: Optional[List[Tuple[str, Optional[Dict[Any, Any]]]]] = None - - def __eq__(self, other) -> bool: - return isinstance(other, ResourceInfo) and self.qubits == other.qubits - - @classmethod - def from_json_dict(cls, resource_json: dict): - """Return ResourceInfo object from json string""" - - if "name" not in resource_json: - raise ValueError("Resource name not found") - _name = resource_json["name"] - - _online = False - _connectivity = None - _instructions = None - - if "online" in resource_json: - _online = resource_json["online"] - try: - _connectivity = ast.literal_eval(resource_json["connectivity"]) - except ( - ValueError, - TypeError, - SyntaxError, - MemoryError, - RecursionError, - KeyError, - ): - pass - try: - _instructions = ast.literal_eval(resource_json["instructions"]) - except ( - ValueError, - TypeError, - SyntaxError, - MemoryError, - RecursionError, - KeyError, - ): - pass - - return cls( - name=_name, - qubits=resource_json["qubits"], - online=_online, - connectivity=_connectivity, - instructions=_instructions, - ) diff --git a/mqss_client/rest_client.py b/mqss_client/rest_client.py deleted file mode 100644 index 2e6ae9e..0000000 --- a/mqss_client/rest_client.py +++ /dev/null @@ -1,87 +0,0 @@ -"""MQP REST API Client""" - -from posixpath import join -from typing import Dict, Optional - -import requests # type: ignore -from decouple import config # type: ignore - -from .base_client import BaseClient - -MQP_API_VERSION: str = "v1" -REQUEST_TIMEOUT: int = 10 - - -def _fetch_hardcoded_url() -> str: - """Default URL for MQP REST API""" - return "https://portal.quantum.lrz.de:4000" - - -# pylint: disable=too-few-public-methods -class RESTClient(BaseClient): - """REST API Client Base Class for basic REST functions""" - - def __init__(self, token: str, url: Optional[str] = None) -> None: - self.token = token - - self.url = url or config("MQP_URL", default=None) or _fetch_hardcoded_url() - if not self.url: - raise RuntimeError("No URL provided for MQP.") - - def _headers(self) -> Dict[str, str]: - return {"Authorization": f"Bearer {self.token}"} - - def get(self, path: str) -> dict: - """GET request to the specified path""" - assert isinstance(self.url, str) - try: - response = requests.get( - join(self.url, MQP_API_VERSION, path), - headers=self._headers(), - timeout=REQUEST_TIMEOUT, - ) - response.raise_for_status() - except requests.exceptions.JSONDecodeError: - print(f"Error decoding JSON response from {path}") - return {} - except requests.exceptions.Timeout: - print(f"Request to {path} timed out") - return {} - except requests.exceptions.RequestException as e: - print(f"Error fetching data from {path}: {e}") - return {} - return response.json() - - def post(self, path: str, data: dict) -> dict: - """POST request to the specified path""" - assert isinstance(self.url, str) - try: - response = requests.post( - join(self.url, MQP_API_VERSION, path), - json=data, - headers=self._headers(), - timeout=REQUEST_TIMEOUT, - ) - response.raise_for_status() - except requests.exceptions.Timeout: - print(f"Request to {path} timed out") - return {} - except requests.exceptions.RequestException as e: - print(f"Error posting data to {path}: {e}") - return {} - return response.json() - - def delete(self, path: str) -> None: - """DELETE request to the specified path""" - assert isinstance(self.url, str) - try: - response = requests.delete( - join(self.url, MQP_API_VERSION, path), - headers=self._headers(), - timeout=REQUEST_TIMEOUT, - ) - response.raise_for_status() - except requests.exceptions.Timeout: - print(f"Request to {path} timed out") - except requests.exceptions.RequestException as e: - print(f"Error deleting data from {path}: {e}") diff --git a/mqss_client/settings.py b/mqss_client/settings.py deleted file mode 100644 index 50a959e..0000000 --- a/mqss_client/settings.py +++ /dev/null @@ -1,5 +0,0 @@ -"""MQP Client settings""" - -from decouple import config # type: ignore - -MQP_URL = config("MQP_URL") diff --git a/noxfile.py b/noxfile.py deleted file mode 100644 index 57ca12c..0000000 --- a/noxfile.py +++ /dev/null @@ -1,7 +0,0 @@ -import nox - - -@nox.session() -def test(session): - session.run("uv", "sync", external=True) - session.run("pytest", "-m", "mock") diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 7c112d9..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,44 +0,0 @@ - -[project] -name = "mqss-client" -version = "0.2.1" -description = "A client for MQP and HPCQC" -authors = [ - {name = "mqss", email = "mqss@munich-quantum-valley.de"}, -] -dependencies = [ - "requests>=2.31.0", - "python-decouple>=3.8", - "pika>=1.3.2", -] -requires-python = ">=3.8" -readme = "README.md" -license = {text = "Apache-2.0"} - -[build-system] -requires = ["pdm-backend"] -build-backend = "pdm.backend" - -[tool.pdm.build] -includes = ["mqss_client"] - -[tool.isort] -profile = "black" -src_paths = ["mqss_client", "test"] -known_first_party = ["mqss_client"] - -[tool.pytest.ini_options] -testpaths = ["test"] -addopts = ["-v", "-s"] -markers = [ - "mock: mark a test as a mock test", - "live: mark a test as a live test",] - -[dependency-groups] -dev = [ - "isort>=5.13.2", - "mypy>=1.14.1", - "nox>=2025.2.9", - "pre-commit>=3.5.0", - "pytest>=8.3.5", -] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..d10c0bb --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright 2024 Munich Quantum Software Stack Project +# +# Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Munich-Quantum-Software-Stack/QDMI-Devices/blob/develop/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# ------------------------------------------------------------------------------ + +find_package(CURL REQUIRED) +find_package(nlohmann_json REQUIRED) +find_package(rabbitmq-c REQUIRED) + +file(GLOB CLIENT_SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/clients/*.cpp") +add_library(mqss_client SHARED client.cpp resource.cpp job.cpp ${CLIENT_SRC_FILES}) + +target_include_directories( + mqss_client PUBLIC ${CMAKE_SOURCE_DIR}/include) + +target_link_libraries(mqss_client PUBLIC curl nlohmann_json rabbitmq) +include(GNUInstallDirs) + +install(TARGETS mqss_client + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib) + +install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/src/client.cpp b/src/client.cpp new file mode 100644 index 0000000..214c908 --- /dev/null +++ b/src/client.cpp @@ -0,0 +1,120 @@ +#include "mqss/client.h" +#include "clients/hpc_client.h" +#include "clients/rest_client.h" +#include +#include +#include + +using namespace mqss::client; + +MQSSClient::MQSSClient(const std::string &token, + const std::string &url_or_queue, bool is_hpc) { + if (is_hpc) { + mClient = std::make_unique(token, url_or_queue); + } else { + mClient = std::make_unique(token, url_or_queue); + } +} + +std::vector MQSSClient::getAllResources() const { + std::vector Resources; + std::string resp = mClient->get("resources"); + if (!nlohmann::json::accept(resp)) + return {}; + nlohmann::json parsed = nlohmann::json::parse(resp); + for (auto &item : parsed) { + Resources.push_back(Resource(item)); + } + return Resources; +} + +std::optional +MQSSClient::getResourceInfo(const std::string &resource) const { + std::string resp = mClient->get("resources/" + resource); + if (resp.find("RESOURCE NOT FOUND") != std::string::npos) + return std::nullopt; + + if (!nlohmann::json::accept(resp)) + return std::nullopt; + + nlohmann::json parsed = nlohmann::json::parse(resp); + if (parsed.contains("ERROR")) + return std::nullopt; + return Resource(parsed); +} +std::optional MQSSClient::submitJob(JobRequest &job) { + std::string path = job.getPath(); + std::string result = mClient->post(path, job.toJson()); + + if (result.empty() || !nlohmann::json::accept(result)) + return std::nullopt; + + nlohmann::json parsed = nlohmann::json::parse(result); + if (!parsed.contains("uuid")) + return std::nullopt; + + std::string uuid = parsed["uuid"].get(); + job.setUuid(uuid); + return uuid; +} + +void MQSSClient::cancelJob(JobRequest &job) { + std::string path = job.getPath() + "/" + job.getUuid(); + mClient->del(path); +} + +std::string MQSSClient::getJobStatus(const JobRequest &job) { + std::string path = job.getPath() + "/" + job.getUuid() + "/status"; + std::string resp = mClient->get(path); + if (resp.empty()) + return ""; + + if (!nlohmann::json::accept(resp)) + return ""; + + nlohmann::json parsed = nlohmann::json::parse(resp); + return parsed.value("status", ""); +} + +std::unique_ptr MQSSClient::getJobResult(const JobRequest &job, + bool wait, size_t timeout) { + + if (wait) { + return waitForJobResult(job, timeout); + } + + std::string path = job.getPath() + "/" + job.getUuid() + "/result"; + std::string resp = mClient->get(path); + if (resp.empty() || !nlohmann::json::accept(resp)) + return nullptr; + + nlohmann::json parsed = nlohmann::json::parse(resp); + return std::make_unique(JobResult(parsed)); +} + +std::unique_ptr MQSSClient::waitForJobResult(const JobRequest &job, + size_t timeout) { + size_t poll_seconds = 2; + + while (timeout > 0) { + std::string status = getJobStatus(job); + if (status == "COMPLETED") + break; + if (status == "FAILED" || status == "CANCELLED" || status.empty()) + return nullptr; + std::this_thread::sleep_for(std::chrono::seconds(poll_seconds)); + timeout -= poll_seconds; + } + return getJobResult(job); +} + +int MQSSClient::getNumberPendingJobs(const std::string &resource) const { + std::string resp = + mClient->get("resources/" + resource + "/num_pending_jobs"); + if (resp.empty() || !nlohmann::json::accept(resp)) + return -1; + + nlohmann::json parsed = nlohmann::json::parse(resp); + + return parsed.value("num_pending_jobs", -1); +} diff --git a/src/clients/hpc_client.cpp b/src/clients/hpc_client.cpp new file mode 100644 index 0000000..456880e --- /dev/null +++ b/src/clients/hpc_client.cpp @@ -0,0 +1,72 @@ +#include "hpc_client.h" + +inline std::string getHostName() { + size_t max_hostname_size = sysconf(_SC_HOST_NAME_MAX); + if (max_hostname_size == -1) { + max_hostname_size = 256; + } + char *hostname = new char[max_hostname_size]; + if (gethostname(hostname, max_hostname_size) == 0) { + std::string result(hostname); + delete[] hostname; + return result; + } else { + delete[] hostname; + return ""; + } +} + +MQSSHPCClient::MQSSHPCClient(std::string token, + std::string offloadListenerQueueName) + : mOffloadListenerQueueName(offloadListenerQueueName.empty() + ? "qoffload_api_request_reception_queue_" + + getHostName() + : offloadListenerQueueName) { + mResponseQueueName = + "response_queue_" + getHostName() + "_" + + boost::uuids::to_string(boost::uuids::random_generator()()).substr(0, 8); + int isErr = mRabbitmqClient.connect(); + if (isErr) { + throw std::runtime_error("Error: Failed to connect RabbiitMQ"); + } + mRabbitmqClient.declareQueue(mOffloadListenerQueueName); + mRabbitmqClient.declareQueue(mResponseQueueName); +} + +std::string MQSSHPCClient::get(const std::string &path) { + std::string request, response; + nlohmann::json request_json = { + {"authorization", ""}, + {"method", "GET"}, + {"request", path}, + {"data", ""}, + {"response_queue", mResponseQueueName}, + }; + + int err = + mRabbitmqClient.send(mOffloadListenerQueueName, request_json.dump()); + response = mRabbitmqClient.receive(mResponseQueueName); + return response; +} + +std::string MQSSHPCClient::post(const std::string &path, + const nlohmann::json &data) { + nlohmann::json request_json = { + {"authorization", ""}, + {"method", "POST"}, + {"request", path}, + {"data", data}, + {"response_queue", mResponseQueueName}, + }; + + mRabbitmqClient.send(mOffloadListenerQueueName, request_json.dump()); + std::string response = mRabbitmqClient.receive(mResponseQueueName); + + return response; +} + +void MQSSHPCClient::del(const std::string &path) { + std::string request, response; + mRabbitmqClient.send(mOffloadListenerQueueName, request); + response = mRabbitmqClient.receive(mResponseQueueName); +} diff --git a/src/clients/hpc_client.h b/src/clients/hpc_client.h new file mode 100644 index 0000000..a886068 --- /dev/null +++ b/src/clients/hpc_client.h @@ -0,0 +1,29 @@ +#pragma once + +#include "mqss/client.h" +#include "rabbitmq_client.h" + +#include +#include +#include + +using namespace mqss::client; + +class MQSSHPCClient : public MQSSBaseClient { +private: + std::string token; + std::string mOffloadListenerQueueName; + std::string mResponseQueueName; + + MQSSRabbitMQClient mRabbitmqClient; + +public: + MQSSHPCClient(std::string token, std::string offloadListenerQueueName); + + std::string get(const std::string &path) override; + + std::string post(const std::string &path, + const nlohmann::json &data) override; + + void del(const std::string &path) override; +}; diff --git a/src/clients/rabbitmq_client.cpp b/src/clients/rabbitmq_client.cpp new file mode 100644 index 0000000..670b7bc --- /dev/null +++ b/src/clients/rabbitmq_client.cpp @@ -0,0 +1,77 @@ +#include "rabbitmq_client.h" + +int MQSSRabbitMQClient::connect() { + + mConnection = amqp_new_connection(); + mpSocket = amqp_tcp_socket_new(mConnection); + if (!mpSocket) + return 1; + + if (amqp_socket_open(mpSocket, mHostname.c_str(), mPort)) + return 1; + + amqp_rpc_reply_t reply = + amqp_login(mConnection, "/", 0, AMQP_DEFAULT_FRAME_SIZE, 60, + AMQP_SASL_METHOD_PLAIN, mUser.c_str(), mPassword.c_str()); + + if (reply.reply_type != AMQP_RESPONSE_NORMAL) + return 1; + + amqp_channel_open(mConnection, 1); + reply = amqp_get_rpc_reply(mConnection); + + if (reply.reply_type != AMQP_RESPONSE_NORMAL) + return 1; + + return 0; +} + +void MQSSRabbitMQClient::disconnect() { + amqp_channel_close(mConnection, 1, AMQP_REPLY_SUCCESS); + amqp_connection_close(mConnection, AMQP_REPLY_SUCCESS); + amqp_destroy_connection(mConnection); +} + +int MQSSRabbitMQClient::send(const std::string &queue, + const std::string &data) { + amqp_basic_properties_t props; + props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG; + props.content_type = amqp_cstring_bytes("text/plain"); + props.delivery_mode = 2; + return amqp_basic_publish(mConnection, 1, amqp_empty_bytes, + amqp_cstring_bytes(queue.c_str()), 0, 0, &props, + amqp_cstring_bytes(data.c_str())); +} + +std::string MQSSRabbitMQClient::receive(const std::string &queue) { + declareQueue(queue); + + amqp_basic_consume(mConnection, 1, amqp_cstring_bytes(queue.c_str()), + amqp_empty_bytes, 0, 0, 0, amqp_empty_table); + + amqp_envelope_t envelope; + amqp_rpc_reply_t reply = + amqp_consume_message(mConnection, &envelope, NULL, 0); + + if (reply.reply_type != AMQP_RESPONSE_NORMAL) { + return amqp_error_string2(reply.library_error); + } + + std::string body(static_cast(envelope.message.body.bytes), + envelope.message.body.len); + + amqp_basic_ack(mConnection, 1, envelope.delivery_tag, false); + amqp_destroy_envelope(&envelope); + + return body; +} + +int MQSSRabbitMQClient::declareQueue(const std::string &queueName) { + if (std::find(mQueues.begin(), mQueues.end(), queueName) != mQueues.end()) + return 0; + + amqp_queue_declare(mConnection, 1, amqp_cstring_bytes(queueName.c_str()), 0, + 0, 0, 0, amqp_empty_table); + mQueues.push_back(queueName); + return 0; +} diff --git a/src/clients/rabbitmq_client.h b/src/clients/rabbitmq_client.h new file mode 100644 index 0000000..1eb9e86 --- /dev/null +++ b/src/clients/rabbitmq_client.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include +#include +#include + +class MQSSRabbitMQClient { + +private: + amqp_connection_state_t mConnection; + amqp_socket_t *mpSocket; + + std::string mHostname; + std::string mUser; + std::string mPassword; + int mPort; + std::vector mQueues; + +public: + MQSSRabbitMQClient(std::string user = "guest", + std::string password = "guest", + std::string hostname = "host.docker.internal", + int port = 5672) + : mUser(user), mPassword(password), mHostname(hostname), mPort(port){}; + + ~MQSSRabbitMQClient() { disconnect(); } + + int connect(); + + void disconnect(); + + int send(const std::string& queue, const std::string& data); + + std::string receive(const std::string& queue); + + int declareQueue(const std::string& queueName); +}; diff --git a/src/clients/rest_client.cpp b/src/clients/rest_client.cpp new file mode 100644 index 0000000..5d5eec0 --- /dev/null +++ b/src/clients/rest_client.cpp @@ -0,0 +1,105 @@ + +#include "rest_client.h" + +typedef struct MQSSRestResponse_d { + char *pResponse; + size_t mSize; +} MQSSRestResponse; + +static size_t writeCallback(char *data, size_t size, size_t nmemb, + void *clientp) { + size_t realsize = size * nmemb; + MQSSRestResponse *pResponse = (MQSSRestResponse *)clientp; + char *pTempResponse = + (char *)realloc(pResponse->pResponse, pResponse->mSize + realsize + 1); + if (!pTempResponse) + return 0; + + pResponse->pResponse = pTempResponse; + void *dest = + memcpy(&(pResponse->pResponse[pResponse->mSize]), data, realsize); + if (dest == NULL) + fprintf(stderr, "Memory error"); + pResponse->mSize += realsize; + pResponse->pResponse[pResponse->mSize] = 0; + + return realsize; +} + +MQSSRestClient::MQSSRestClient(std::string token, std::string url) + : mToken(token), mUrl(url), MQSSBaseClient() { + pHeaders = nullptr; + std::string auth_header = "Authorization: Bearer " + token; + pHeaders = curl_slist_append(pHeaders, auth_header.c_str()); + pHeaders = curl_slist_append(pHeaders, "Content-Type: application/json"); +} + +std::string MQSSRestClient::get(const std::string &path) { + CURL *curl = curl_easy_init(); + if (!curl) + return "1"; + + MQSSRestResponse response = {0}; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response); + curl_easy_setopt(curl, CURLOPT_URL, (mUrl + path).c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pHeaders); + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) + return "2"; + + std::string result(response.pResponse, response.mSize); + if (response.pResponse) + free(response.pResponse); + + return result; +} + +std::string MQSSRestClient::post(const std::string &path, + const nlohmann::json &data) { + + CURL *curl = curl_easy_init(); + if (!curl) + return ""; + + MQSSRestResponse response = {0}; + + std::string payload = data.dump(); + + curl_easy_setopt(curl, CURLOPT_URL, (mUrl + path).c_str()); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pHeaders); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + + CURLcode res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + if (res != CURLE_OK || response.pResponse == NULL) { + return ""; + } + + std::string result(response.pResponse, response.mSize); + return result; +} + +void MQSSRestClient::del(const std::string &path) { + + CURL *curl = curl_easy_init(); + if (!curl) + return; + long response_code; + MQSSRestResponse response = {0}; + + curl_easy_setopt(curl, CURLOPT_URL, (mUrl + path).c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pHeaders); + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) + return; +} diff --git a/src/clients/rest_client.h b/src/clients/rest_client.h new file mode 100644 index 0000000..731afa2 --- /dev/null +++ b/src/clients/rest_client.h @@ -0,0 +1,24 @@ +#pragma once + +#include "mqss/client.h" +#include +#include + +using namespace mqss::client; + +class MQSSRestClient : public MQSSBaseClient { +private: + std::string mUrl; + std::string mToken; + struct curl_slist *pHeaders; + +public: + MQSSRestClient(std::string token, std::string url = MQP_DEFAULT_URL); + + std::string get(const std::string &path) override; + + std::string post(const std::string &path, + const nlohmann::json &data) override; + + void del(const std::string &path) override; +}; diff --git a/src/job.cpp b/src/job.cpp new file mode 100644 index 0000000..7109f91 --- /dev/null +++ b/src/job.cpp @@ -0,0 +1,60 @@ +#include "mqss/job.h" + +using namespace mqss::client; + +nlohmann::json CircuitJobRequest::toJson() const { + + return {{"circuit", mCircuit}, + {"circuit_format", mCircuitFormat}, + {"resource_name", mResourceName}, + {"shots", mShots}, + {"no_modify", mNoModify}, + {"queued", mQueued}}; +} + +CircuitJobRequest::CircuitJobRequest(std::string circuit, + std::string circuitFormat, + std::string resourceName, + unsigned int shots, bool noModify, + bool queued) + : mCircuit(std::move(circuit)), mCircuitFormat(std::move(circuitFormat)), + mResourceName(std::move(resourceName)), mShots(shots), + mNoModify(noModify), mQueued(queued) {} + +HamiltonianJobRequest::HamiltonianJobRequest(std::string resourceName, + std::string interactionStr, + std::string coefficientsStr) + : mResourceName(std::move(resourceName)), + mInteractionStr(std::move(interactionStr)), + mCoefficientsStr(std::move(coefficientsStr)) {} + +nlohmann::json HamiltonianJobRequest::toJson() const { + + return {{"resource_name", mResourceName}, + {"interaction_str", mInteractionStr}, + {"coefficients_str", mCoefficientsStr}}; +} + +JobResult::JobResult(std::map results, + std::string timestampCompleted, + std::string timestampSubmitted, + std::string timestampScheduled) + : mResults(std::move(results)), + mTimestampCompleted(std::move(timestampCompleted)), + mTimestampSubmitted(std::move(timestampSubmitted)), + mTimestampScheduled(std::move(timestampScheduled)) {} + +JobResult::JobResult(const nlohmann::json &parsed) { + + const auto &rResultStr = parsed.at("result").get_ref(); + nlohmann::json resultMap = nlohmann::json::parse(rResultStr); + + for (auto &[key, value] : resultMap.items()) { + resultMap[key] = value.get(); + } + + mResults = std::move(resultMap); + mTimestampCompleted = parsed.at("timestamp_completed").get(); + mTimestampSubmitted = parsed.at("timestamp_submitted").get(); + mTimestampScheduled = parsed.at("timestamp_scheduled").get(); +} diff --git a/src/resource.cpp b/src/resource.cpp new file mode 100644 index 0000000..33d0370 --- /dev/null +++ b/src/resource.cpp @@ -0,0 +1,66 @@ +#include "mqss/Resource.h" +#include + +using namespace mqss::client; + +namespace { + +const std::regex COUPLING_PATTERN(R"(\(\s*(\d+)\s*,\s*(\d+)\s*\))"); + +const std::regex GATE_PATTERN(R"('([^']+)')"); + +template +std::vector extractFromStringField(const nlohmann::json &response, const std::string &key, + const std::regex &pattern, Converter &&convert) { + std::vector result; + + const auto it = response.find(key); + if (it == response.end() || !it->is_string()) + return result; + + const std::string &text = it->get_ref(); + if (text == "None") + return result; + + std::smatch match; + auto begin = text.cbegin(); + auto end = text.cend(); + + while (std::regex_search(begin, end, match, pattern)) { + result.push_back(convert(match)); + begin = match.suffix().first; + } + + return result; +} + +} // namespace + +Resource::Resource(std::string name, unsigned qubitCount, bool online, + std::vector> couplingMap, + std::vector nativeGateset) + : mName(std::move(name)), mQubitCount(qubitCount), mOnline(online), + mCouplingMap(std::move(couplingMap)), + mNativeGateset(std::move(nativeGateset)) {} + +Resource::Resource(const nlohmann::json &json) { + + std::string name = json.value("name", ""); + unsigned qubitCount = json.value("qubits", 0); + bool online = json.value("online", false); + + auto couplingMap = extractFromStringField>( + json, "connectivity", COUPLING_PATTERN, [](const std::smatch &m) { + return std::make_pair(std::stoi(m[1]), std::stoi(m[2])); + }); + + auto nativeGateset = + extractFromStringField(json, "instructions", GATE_PATTERN, + [](const std::smatch &m) { return m[1].str(); }); + + mName = std::move(name); + mQubitCount = qubitCount; + mOnline = online; + mCouplingMap = std::move(couplingMap); + mNativeGateset = std::move(nativeGateset); +} diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/config.py b/test/config.py deleted file mode 100644 index 47524b1..0000000 --- a/test/config.py +++ /dev/null @@ -1,45 +0,0 @@ -import os - -from mqss_client.resource_info import ResourceInfo - -# TODO: change this line to the API endpoint you want to test -URL = "https://portal.quantum.lrz.de:4000/" -# TODO: change this to your own token, obviously remove this line in the future - -TOKEN = os.getenv("MQP_TOKEN", None) -# TODO: change this to all the currently available resources -CURRENT_RESOURCES = { - "Q5": ResourceInfo( - name="Q5", - qubits=5, - online=True, - connectivity=None, - instructions=None, - ), - "Q20": ResourceInfo( - name="Q20", - qubits=20, - online=True, - connectivity=None, - instructions=None, - ), - "QExa20": ResourceInfo( - name="QExa20", qubits=20, online=True, connectivity=None, instructions=None - ), - "AQT20": ResourceInfo( - name="AQT20", qubits=12, online=True, connectivity=None, instructions=None - ), - "WMI3": ResourceInfo( - name="WMI3", qubits=3, online=True, connectivity=None, instructions=None - ), - "QLM": ResourceInfo( - name="QLM", qubits=38, online=True, connectivity=None, instructions=None - ), -} -# TODO: change this to the the qasm you want to test -QASM_FILE = "test/example.qasm" - - -def get_qasm() -> str: - with open(QASM_FILE, "r") as f: - return f.read() diff --git a/test/example.qasm b/test/example.qasm deleted file mode 100644 index 6fb6aa4..0000000 --- a/test/example.qasm +++ /dev/null @@ -1,11 +0,0 @@ -OPENQASM 2.0; -include "qelib1.inc"; - -qreg q[2]; -creg c[2]; - -h q[0]; -cx q[0],q[1]; - -measure q[0] -> c[0]; -measure q[1] -> c[1]; diff --git a/test/mocks.py b/test/mocks.py deleted file mode 100644 index 1684a33..0000000 --- a/test/mocks.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Common mock objects and fixtures for MQSS Client tests.""" - -import json -from unittest.mock import MagicMock, patch - -from mqss_client.rest_client import RESTClient - -from .config import CURRENT_RESOURCES - -# =================== MOCK DATA =================== -MOCK_RESOURCES = { - key: CURRENT_RESOURCES[key].__dict__ for key in CURRENT_RESOURCES.keys() -} - -MOCK_JOB_DATA = { - # For resource endpoints - "resources": MOCK_RESOURCES, - "resources/Q5": CURRENT_RESOURCES["Q5"].__dict__, - "resources/Q5/num_pending_jobs": {"num_pending_jobs": 3}, - # For job endpoints - "job": {"jobs": ["mock-uuid-12345"]}, - "hamiltonian_job": {"jobs": ["mock-uuid-12345"]}, - # Status endpoints - "job/mock-uuid-12345/status": {"status": "PENDING"}, - "hamiltonian_job/mock-uuid-12345/status": {"status": "PENDING"}, - # Result endpoints - "job/mock-uuid-12345/result": { - "result": '{"00": 500, "11": 500}', - "timestamp_completed": "2023-04-14 10:15:30.123456", - "timestamp_submitted": "2023-04-14 10:00:00.123456", - "timestamp_scheduled": "2023-04-14 10:05:00.123456", - }, - "hamiltonian_job/mock-uuid-12345/result": { - "result": '{"00": 500, "11": 500}', - "timestamp_completed": "2023-04-14 10:15:30.123456", - "timestamp_submitted": "2023-04-14 10:00:00.123456", - "timestamp_scheduled": "2023-04-14 10:05:00.123456", - }, -} - - -# =================== REST CLIENT MOCKS =================== -def create_rest_mock(): - """Create and configure a mock REST client""" - mock = MagicMock(spec=RESTClient) - mock.post.return_value = {"uuid": "mock-uuid-12345"} - mock.get.side_effect = lambda path: MOCK_JOB_DATA.get(path, {}) - return mock - - -# =================== RABBITMQ CLIENT MOCKS =================== -def create_rabbitmq_mock(): - """Create and configure a mock RabbitMQ client""" - mock = MagicMock() - mock.__enter__.return_value = mock - mock.__exit__.return_value = None - mock.last_message = "{}" - - def mock_receive(queue_name): - try: - message_data = json.loads(mock.last_message) - request_path = message_data.get("request", "") - method = message_data.get("method", "") - - # Handle POST requests for job creation - if (request_path in ["job", "hamiltonian_job"]) and method == "POST": - return json.dumps({"uuid": "mock-uuid-12345"}) - - # Handle GET requests using the shared data - if method == "GET" and request_path in MOCK_JOB_DATA: - return json.dumps(MOCK_JOB_DATA[request_path]) - - # Special handling for resource paths with names - if "resources/" in request_path and "/num_pending_jobs" not in request_path: - resource_name = request_path.split("/")[-1] - if resource_name in CURRENT_RESOURCES: - return json.dumps(CURRENT_RESOURCES[resource_name].__dict__) - except Exception as e: - print(f"Error in mock_receive: {e}") - return None - - def mock_send(message, queue): - mock.last_message = message - return True - - mock.receive.side_effect = mock_receive - mock.send.side_effect = mock_send - return mock - - -# =================== PATCHING HELPERS =================== -def patch_mqss_rest_client(): - """Patch the RESTClient within MQSSClient with a mock""" - return patch("mqss_client.mqss_client.RESTClient", return_value=create_rest_mock()) - - -def patch_rabbitmq_client(): - """Patch the RabbitMQClient with a mock""" - return patch( - "mqss_client.hpc_client.RabbitMQClient", return_value=create_rabbitmq_mock() - ) diff --git a/test/mqss_client_tests_base.py b/test/mqss_client_tests_base.py deleted file mode 100644 index 2000c96..0000000 --- a/test/mqss_client_tests_base.py +++ /dev/null @@ -1,128 +0,0 @@ -import pytest - -from mqss_client import ( - CircuitJobRequest, - HamiltonianJobRequest, - JobStatus, - MQSSClient, - ResourceInfo, - Result, -) - -from .config import CURRENT_RESOURCES, TOKEN, URL, get_qasm - - -@pytest.fixture -def resource_name(): - """Fixture to provide a valid resource name for tests""" - return list(CURRENT_RESOURCES.keys())[0] - - -@pytest.fixture -def circuit_job_request(resource_name): - """Fixture to create a circuit job request""" - return CircuitJobRequest( - circuits=get_qasm(), - circuit_format="qasm", - resource_name=resource_name, - shots=1000, - no_modify=False, - queued=False, - ) - - -@pytest.fixture -def hamiltonian_job_request(resource_name): - """Fixture to create a hamiltonian job request""" - # Simple example Hamiltonian - interaction_str = "Z0 Z1\nX0 X1" - coefficients_str = "0.5\n0.3" - return HamiltonianJobRequest( - resource_name=resource_name, - interaction_str=interaction_str, - coefficients_str=coefficients_str, - ) - - -class BaseMQSSClientTests: - """Base class for MQSS client tests.""" - - @pytest.fixture - def client_args(self) -> tuple: - """Fixture to provide client arguments for tests.""" - return TOKEN, URL, False - - @pytest.fixture - def client(self, client_args) -> MQSSClient: - """Get the MQSS client instance.""" - return MQSSClient( - token=client_args[0], base_url=client_args[1], is_hpc=client_args[2] - ) - - def test_get_all_resources(self, client: MQSSClient) -> None: - """Test getting all resources.""" - resources = client.get_all_resources() - assert isinstance(resources, dict) - assert len(resources) > 0 - assert all( - isinstance(resource_info, ResourceInfo) - for resource_info in resources.values() - ) - assert any( - resource_name in list(CURRENT_RESOURCES.keys()) - for resource_name in resources.keys() - ) - - def test_get_resource_info(self, client: MQSSClient) -> None: - """Test getting resource info.""" - resource_name = list(CURRENT_RESOURCES.keys())[0] - resource_info = client.get_resource_info(resource_name) - assert isinstance(resource_info, ResourceInfo) - assert resource_info.name == resource_name - - def test_get_resource_info_invalid(self, client: MQSSClient) -> None: - """Test getting resource info with invalid resource name.""" - resource_info = client.get_resource_info("invalid_resource") - assert resource_info is None - - def test_get_num_pending_jobs(self, client: MQSSClient, resource_name) -> None: - """Test getting the number of pending jobs.""" - num_pending_jobs = client.get_num_pending_jobs(resource_name) - assert isinstance(num_pending_jobs, int) - assert num_pending_jobs >= 0 - - def test_submit_circuit_job(self, client: MQSSClient, circuit_job_request) -> None: - """Test submitting a circuit job.""" - job_id = client.submit_job(circuit_job_request) - assert job_id is not None - client.cancel_job(job_id, circuit_job_request) - - def test_submit_hamiltonian_job( - self, client: MQSSClient, hamiltonian_job_request - ) -> None: - """Test submitting a Hamiltonian job.""" - job_id = client.submit_job(hamiltonian_job_request) - assert job_id is not None - client.cancel_job(job_id, hamiltonian_job_request) - - def test_job_status( - self, client: MQSSClient, circuit_job_request, monkeypatch - ) -> None: - """Test getting job status.""" - job_id = client.submit_job(circuit_job_request) - status = client.job_status(job_id, circuit_job_request) - assert status in [JobStatus.PENDING, JobStatus.WAITING] - client.cancel_job(job_id, circuit_job_request) - status = client.job_status(job_id, circuit_job_request) - assert status == JobStatus.CANCELLED - - def test_wait_for_job_result( - self, client: MQSSClient, circuit_job_request, monkeypatch - ) -> None: - """Test waiting for job result.""" - job_id = client.submit_job(circuit_job_request) - result = client.wait_for_job_result(job_id, circuit_job_request) - assert result is not None - assert isinstance(result, Result) - assert result.counts["00"] > 0 - assert result.counts["11"] > 0 diff --git a/test/test_mqss_client_live.py b/test/test_mqss_client_live.py deleted file mode 100644 index 64be5f0..0000000 --- a/test/test_mqss_client_live.py +++ /dev/null @@ -1,75 +0,0 @@ -import pytest - -from mqss_client.mqss_client import MQSSClient - -from .config import CURRENT_RESOURCES, TOKEN, URL -from .mqss_client_tests_base import ( - BaseMQSSClientTests, - circuit_job_request, - hamiltonian_job_request, - resource_name, -) - -__all__ = ["circuit_job_request", "hamiltonian_job_request", "resource_name"] - - -def online_resource_name(client): - """Get the name of an online resource""" - online_resources_names = [ - res - for res in list(CURRENT_RESOURCES.keys()) - if client.get_resource_info(res).online - ] - return online_resources_names[0] if online_resources_names else None - - -@pytest.mark.live -@pytest.mark.parametrize( - "client_type", - [ - "MQP", - ], - ids=["MQP"], -) -class TestMQSSClientLive(BaseMQSSClientTests): - """Test class for MQSSClient with live REST and HPC clients.""" - - @pytest.fixture(autouse=True) - def setup_client(self, client_type): - """Setup the proper client based on the parameterized client type.""" - self.client_type = client_type - - yield - - @pytest.fixture - def client_args(self) -> tuple: - """Fixture to provide client arguments for tests.""" - return TOKEN, URL, "HPC" in self.client_type - - def test_submit_circuit_job(self, client: MQSSClient, circuit_job_request) -> None: - """Test submitting a circuit job.""" - circuit_job_request.resource_name = online_resource_name(client) - return super().test_submit_circuit_job(client, circuit_job_request) - - def test_submit_hamiltonian_job( - self, client: MQSSClient, hamiltonian_job_request - ) -> None: - """Test submitting a Hamiltonian job.""" - hamiltonian_job_request.resource_name = online_resource_name(client) - return super().test_submit_hamiltonian_job(client, hamiltonian_job_request) - - def test_job_status( - self, client: MQSSClient, circuit_job_request, monkeypatch - ) -> None: - """Test job status.""" - circuit_job_request.resource_name = online_resource_name(client) - return super().test_job_status(client, circuit_job_request, monkeypatch) - - def test_wait_for_job_result( - self, client: MQSSClient, circuit_job_request, monkeypatch - ) -> None: - """Test waiting for job result.""" - circuit_job_request.resource_name = online_resource_name(client) - return super().test_wait_for_job_result( - client, circuit_job_request, monkeypatch - ) diff --git a/test/test_mqss_client_mock.py b/test/test_mqss_client_mock.py deleted file mode 100644 index afec0f6..0000000 --- a/test/test_mqss_client_mock.py +++ /dev/null @@ -1,85 +0,0 @@ -import time - -import pytest - -from mqss_client import JobStatus, MQSSClient, Result - -from .mocks import patch_mqss_rest_client, patch_rabbitmq_client -from .mqss_client_tests_base import ( - BaseMQSSClientTests, - circuit_job_request, - hamiltonian_job_request, - resource_name, -) - -__all__ = ["circuit_job_request", "hamiltonian_job_request", "resource_name"] - - -@pytest.mark.mock -@pytest.mark.parametrize( - "client_type", - [ - "MQP", - "HPC", - ], - ids=["MQP", "HPC"], -) -class TestMQSSClientMock(BaseMQSSClientTests): - """Test class for MQSSClient with mocked REST and HPC clients.""" - - @pytest.fixture(autouse=True) - def setup_client(self, client_type): - """Setup the proper client patch based on the parameterized client type.""" - self.client_type = client_type - - # Start patch based on client type - if "HPC" in client_type: - self.patch = patch_rabbitmq_client() - else: - self.patch = patch_mqss_rest_client() - self.patch.start() - - yield - - # Stop the patch after test - self.patch.stop() - - @pytest.fixture - def client_args(self) -> tuple: - return "mock-token", "http://mock-url", "HPC" in self.client_type - - def test_job_status( - self, client: MQSSClient, circuit_job_request, monkeypatch - ) -> None: - """Test job status.""" - job_id = client.submit_job(circuit_job_request) - status = client.job_status(job_id, circuit_job_request) - assert status in [JobStatus.PENDING, JobStatus.WAITING] - - def mock_job_status(job_id, job_request): - """Mock job status to return a predefined status.""" - return JobStatus.CANCELLED - - monkeypatch.setattr(client, "job_status", mock_job_status) - - client.cancel_job(job_id, circuit_job_request) - status = client.job_status(job_id, circuit_job_request) - assert status == JobStatus.CANCELLED - - def test_wait_for_job_result( - self, client: MQSSClient, circuit_job_request, monkeypatch - ) -> None: - """Test waiting for job result.""" - job_id = client.submit_job(circuit_job_request) - - def mock_job_status(job_id, job_request): - """Mock job status to return a predefined status.""" - return JobStatus.COMPLETED - - monkeypatch.setattr(client, "job_status", mock_job_status) - monkeypatch.setattr(time, "sleep", lambda x: None) - - result = client.wait_for_job_result(job_id, circuit_job_request) - assert result is not None - assert isinstance(result, Result) - assert result.counts == {"00": 500, "11": 500} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..6a419f5 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,2 @@ +enable_testing() +add_subdirectory(unit_tests) diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt new file mode 100644 index 0000000..118303d --- /dev/null +++ b/tests/unit_tests/CMakeLists.txt @@ -0,0 +1,25 @@ +include(GoogleTest) + +add_executable(resource_unit_test resource_unit_test.cpp) + +target_link_libraries( + resource_unit_test PRIVATE gtest gtest_main mqss_client) +gtest_discover_tests(resource_unit_test) + + +add_executable(job_unit_test job_unit_test.cpp) + +target_link_libraries( + job_unit_test PRIVATE gtest gtest_main mqss_client) +gtest_discover_tests(job_unit_test) + + +if(ENABLE_COVERAGE) + target_compile_options(resource_unit_test INTERFACE --coverage -fprofile-arcs + -ftest-coverage -O0) + target_link_libraries(resource_unit_test INTERFACE gcov --coverage) + + target_compile_options(job_unit_test INTERFACE --coverage -fprofile-arcs + -ftest-coverage -O0) + target_link_libraries(job_unit_test INTERFACE gcov --coverage) +endif() diff --git a/tests/unit_tests/job_unit_test.cpp b/tests/unit_tests/job_unit_test.cpp new file mode 100644 index 0000000..3e7162c --- /dev/null +++ b/tests/unit_tests/job_unit_test.cpp @@ -0,0 +1,166 @@ +/*------------------------------------------------------------------------------ +Copyright 2024 Munich Quantum Software Stack Project + +Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/develop/LICENSE + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT> +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. + +SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +------------------------------------------------------------------------------*/ + +#include "mqss/client.h" +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include + +#define MQSS_HPC_QUEUENAME std::getenv("MQSS_HPC_QUEUENAME") +#define MQSS_API_TOKEN std::getenv("MQSS_API_TOKEN") +#define MQSS_API_URL "https://portal-test.quantum.lrz.de:4000/v1" + +struct ClientCtorParam { + std::string token; + std::string url_or_queue; + bool isHPC = false; +}; + +class MQSSClientJobTest : public ::testing::TestWithParam { +protected: + void SetUp() override { + const auto &p = GetParam(); + + client = mqss::client::MQSSClient{p.token, p.url_or_queue, p.isHPC}; + } + + mqss::client::MQSSClient client; +}; + +INSTANTIATE_TEST_SUITE_P(MQSS_Test_Instantiation, MQSSClientJobTest, + ::testing::Values( + // ClientCtorParam{CtorKind::Empty}, + ClientCtorParam{"", MQSS_HPC_QUEUENAME, true}, + ClientCtorParam{MQSS_API_TOKEN, MQSS_API_URL, + false})); + + +static const std::string TEST_CIRCUIT = R"( +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +creg c[2]; +h q[0]; +cx q[0], q[1]; +measure q -> c;)"; + +TEST_P(MQSSClientJobTest, ClientSubmitJob) { + mqss::client::CircuitJobRequest job = + mqss::client::CircuitJobRequest(TEST_CIRCUIT, "qasm", "QLM", 100, 0, 0); + auto uuid = client.submitJob(job); + ASSERT_TRUE(uuid.has_value()); +} + +TEST_P(MQSSClientJobTest, ClientCancelJob) { + if (GetParam().isHPC) + GTEST_SKIP(); + mqss::client::CircuitJobRequest job = + mqss::client::CircuitJobRequest(TEST_CIRCUIT, "qasm", "QLM", 100, 0, 0); + auto uuid = client.submitJob(job); + ASSERT_TRUE(uuid.has_value()); + client.cancelJob(job); + ASSERT_STREQ(client.getJobStatus(job).c_str(), "CANCELLED"); +} + +TEST_P(MQSSClientJobTest, ClientSubmitHamiltonianJob) { + if (GetParam().isHPC) + GTEST_SKIP(); + mqss::client::HamiltonianJobRequest job = mqss::client::HamiltonianJobRequest( + "QLM", "0 1; 1 2; 0 2; 0 3;", "0.5 0.1 0.8 1;"); + auto uuid = client.submitJob(job); + ASSERT_TRUE(uuid.has_value()); +} + +TEST_P(MQSSClientJobTest, ClientCheckJobStatus) { + mqss::client::CircuitJobRequest job = + mqss::client::CircuitJobRequest(TEST_CIRCUIT, "qasm", "QLM", 100, 0, 0); + auto uuid = client.submitJob(job); + ASSERT_TRUE(uuid.has_value()); + std::string status = client.getJobStatus(job); + ASSERT_STRNE(status.c_str(), ""); +} + +TEST_P(MQSSClientJobTest, ClientCheckJobSetterAndGetter) { + mqss::client::CircuitJobRequest job = mqss::client::CircuitJobRequest(); + std::string circuitFormat("qasm"); + std::string resourceName("AQT20"); + unsigned int shots = 10; + bool isQueued = false; + bool isNoModify = false; + + job.setCircuit(TEST_CIRCUIT); + ASSERT_STREQ(job.getCircuit().c_str(), TEST_CIRCUIT.c_str()); + + job.setCircuitFormat(circuitFormat); + ASSERT_STREQ(job.getCircuitFormat().c_str(), circuitFormat.c_str()); + + job.setResourceName(resourceName); + ASSERT_STREQ(job.getResourceName().c_str(), resourceName.c_str()); + + job.setShots(shots); + ASSERT_EQ(job.getShots(), shots); + + job.setNoModify(isNoModify); + ASSERT_EQ(job.isNoModify(), isNoModify); + + job.setQueued(isQueued); + ASSERT_EQ(job.isQueued(), isQueued); +} + +TEST_P(MQSSClientJobTest, ClientCheckHamiltonianJobSetterAndGetter) { + mqss::client::HamiltonianJobRequest job = + mqss::client::HamiltonianJobRequest(); + std::string coefficientsString("0.5 0.1 0.8 1;"); + std::string interactionString("0 1; 1 2; 0 2; 0 3;"); + + job.setCoefficientsString(coefficientsString); + ASSERT_STREQ(job.getCoefficientsString().c_str(), coefficientsString.c_str()); + + job.setInteractionString(interactionString); + ASSERT_STREQ(job.getInteractionString().c_str(), interactionString.c_str()); +} + +TEST_P(MQSSClientJobTest, ClientWaitForResult) { + if (GetParam().isHPC) + GTEST_SKIP(); + mqss::client::CircuitJobRequest job = + mqss::client::CircuitJobRequest(TEST_CIRCUIT, "qasm", "QLM", 100, 0, 0); + auto uuid_or_null = client.submitJob(job); + ASSERT_TRUE(uuid_or_null.has_value()); + auto result = client.getJobResult(job, true, 50); + ASSERT_NE(result, nullptr); + ASSERT_NE(result->getResults().size(), 0); +} + +TEST_P(MQSSClientJobTest, ClientGetNumPendingJobs) { + mqss::client::CircuitJobRequest job = + mqss::client::CircuitJobRequest(TEST_CIRCUIT, "qasm", "QLM", 100, 0, 0); + auto uuid_or_null = client.submitJob(job); + ASSERT_TRUE(uuid_or_null.has_value()); + int n_job = client.getNumberPendingJobs("QLM"); + ASSERT_GE(n_job, 0); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/unit_tests/resource_unit_test.cpp b/tests/unit_tests/resource_unit_test.cpp new file mode 100644 index 0000000..078235e --- /dev/null +++ b/tests/unit_tests/resource_unit_test.cpp @@ -0,0 +1,123 @@ +/*------------------------------------------------------------------------------ +Copyright 2024 Munich Quantum Software Stack Project + +Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/develop/LICENSE + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. + +SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +------------------------------------------------------------------------------*/ + +#include "mqss/client.h" +#include "gtest/gtest.h" +#include +#include +#include +#include + +#define MQSS_HPC_QUEUENAME std::getenv("MQSS_HPC_QUEUENAME") +#define MQSS_API_TOKEN std::getenv("MQSS_API_TOKEN") +#define MQSS_API_URL "https://portal.quantum.lrz.de:4000/v1/" + +struct ClientCtorParam { + std::string token; + std::string url_or_queue; + bool isHPC = false; +}; + +class MQSSClientResourceTest : public ::testing::TestWithParam { +protected: + void SetUp() override { + const auto &p = GetParam(); + client = mqss::client::MQSSClient{p.token, p.url_or_queue, p.isHPC}; + } + + mqss::client::MQSSClient client; +}; + +INSTANTIATE_TEST_SUITE_P(MQSS_Test_Instantiation, MQSSClientResourceTest, + ::testing::Values( + // ClientCtorParam{CtorKind::Empty}, + ClientCtorParam{"", MQSS_HPC_QUEUENAME, true}, + ClientCtorParam{MQSS_API_TOKEN, MQSS_API_URL, + false})); + +TEST_P(MQSSClientResourceTest, ClientGetAllResources) { + std::vector resources = client.getAllResources(); + ASSERT_GE(resources.size(), 0); +} + +TEST_P(MQSSClientResourceTest, ClientGetAResource) { + std::vector resourceNames = {"QLM", "Q5", "Q20", "AQT20", + "QExa20"}; + for (auto resourceName : resourceNames) { + std::optional resource = + client.getResourceInfo(resourceName); + ASSERT_TRUE(resource.has_value()); + } +} + +TEST_P(MQSSClientResourceTest, ClientGetAResourceFalse) { + std::vector resourceNames = {"Eviden", "IQM5", "IQM20", "AQT", + "QExa120"}; + for (auto resourceName : resourceNames) { + std::optional resource = + client.getResourceInfo(resourceName); + ASSERT_FALSE(resource.has_value()); + } +} + +TEST_P(MQSSClientResourceTest, ClientCheckResourceName) { + std::string goldenResourceName = "QLM"; + std::optional resource = + client.getResourceInfo(goldenResourceName); + ASSERT_TRUE(resource.has_value()); + ASSERT_EQ((*resource).getName(), goldenResourceName); +} + +TEST_P(MQSSClientResourceTest, ClientCheckQubitCount) { + std::string resourceName = "Q5"; + std::optional resource = + client.getResourceInfo(resourceName); + ASSERT_TRUE(resource.has_value()); + ASSERT_GE((*resource).getQubitCount(), 0); +} + +TEST_P(MQSSClientResourceTest, ClientCheckIfOnline) { + /* If the resource is under maintenance, this test might fail.*/ + std::string resourceName = "AQT20"; + std::optional resource = + client.getResourceInfo(resourceName); + ASSERT_TRUE(resource.has_value()); + ASSERT_EQ((*resource).isOnline(), true); +} + +TEST_P(MQSSClientResourceTest, ClientCheckCouplingMap) { + + std::string resourceName = "AQT20"; + std::optional resource = + client.getResourceInfo(resourceName); + ASSERT_TRUE(resource.has_value()); + ASSERT_GE((*resource).getCouplingMap().size(), 0); +} + +TEST_P(MQSSClientResourceTest, ClientCheckNativeGateset) { + std::string resourceName = "Q20"; + std::optional resource = + client.getResourceInfo(resourceName); + ASSERT_TRUE(resource.has_value()); + ASSERT_GE((*resource).getNativeGateset().size(), 0); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/uv.lock b/uv.lock deleted file mode 100644 index 96947ea..0000000 --- a/uv.lock +++ /dev/null @@ -1,725 +0,0 @@ -version = 1 -revision = 2 -requires-python = ">=3.8" -resolution-markers = [ - "python_full_version >= '3.9'", - "python_full_version < '3.9'", -] - -[[package]] -name = "argcomplete" -version = "3.6.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload_time = "2025-04-03T04:57:03.52Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload_time = "2025-04-03T04:57:01.591Z" }, -] - -[[package]] -name = "attrs" -version = "25.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload_time = "2025-03-13T11:10:22.779Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload_time = "2025-03-13T11:10:21.14Z" }, -] - -[[package]] -name = "certifi" -version = "2025.1.31" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload_time = "2025-01-31T02:16:47.166Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload_time = "2025-01-31T02:16:45.015Z" }, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload_time = "2023-08-12T20:38:17.776Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload_time = "2023-08-12T20:38:16.269Z" }, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload_time = "2024-12-24T18:12:35.43Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013, upload_time = "2024-12-24T18:09:43.671Z" }, - { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285, upload_time = "2024-12-24T18:09:48.113Z" }, - { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449, upload_time = "2024-12-24T18:09:50.845Z" }, - { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892, upload_time = "2024-12-24T18:09:52.078Z" }, - { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123, upload_time = "2024-12-24T18:09:54.575Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943, upload_time = "2024-12-24T18:09:57.324Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063, upload_time = "2024-12-24T18:09:59.794Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578, upload_time = "2024-12-24T18:10:02.357Z" }, - { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629, upload_time = "2024-12-24T18:10:03.678Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778, upload_time = "2024-12-24T18:10:06.197Z" }, - { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453, upload_time = "2024-12-24T18:10:08.848Z" }, - { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479, upload_time = "2024-12-24T18:10:10.044Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790, upload_time = "2024-12-24T18:10:11.323Z" }, - { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995, upload_time = "2024-12-24T18:10:12.838Z" }, - { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471, upload_time = "2024-12-24T18:10:14.101Z" }, - { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831, upload_time = "2024-12-24T18:10:15.512Z" }, - { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335, upload_time = "2024-12-24T18:10:18.369Z" }, - { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862, upload_time = "2024-12-24T18:10:19.743Z" }, - { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673, upload_time = "2024-12-24T18:10:21.139Z" }, - { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211, upload_time = "2024-12-24T18:10:22.382Z" }, - { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039, upload_time = "2024-12-24T18:10:24.802Z" }, - { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939, upload_time = "2024-12-24T18:10:26.124Z" }, - { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075, upload_time = "2024-12-24T18:10:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340, upload_time = "2024-12-24T18:10:32.679Z" }, - { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205, upload_time = "2024-12-24T18:10:34.724Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441, upload_time = "2024-12-24T18:10:37.574Z" }, - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload_time = "2024-12-24T18:10:38.83Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload_time = "2024-12-24T18:10:44.272Z" }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload_time = "2024-12-24T18:10:45.492Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload_time = "2024-12-24T18:10:47.898Z" }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload_time = "2024-12-24T18:10:50.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload_time = "2024-12-24T18:10:52.541Z" }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload_time = "2024-12-24T18:10:53.789Z" }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload_time = "2024-12-24T18:10:55.048Z" }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload_time = "2024-12-24T18:10:57.647Z" }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload_time = "2024-12-24T18:10:59.43Z" }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload_time = "2024-12-24T18:11:00.676Z" }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload_time = "2024-12-24T18:11:01.952Z" }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload_time = "2024-12-24T18:11:03.142Z" }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload_time = "2024-12-24T18:11:05.834Z" }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload_time = "2024-12-24T18:11:07.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload_time = "2024-12-24T18:11:08.374Z" }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload_time = "2024-12-24T18:11:09.831Z" }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload_time = "2024-12-24T18:11:12.03Z" }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload_time = "2024-12-24T18:11:13.372Z" }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload_time = "2024-12-24T18:11:14.628Z" }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload_time = "2024-12-24T18:11:17.672Z" }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload_time = "2024-12-24T18:11:18.989Z" }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload_time = "2024-12-24T18:11:21.507Z" }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload_time = "2024-12-24T18:11:22.774Z" }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload_time = "2024-12-24T18:11:24.139Z" }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload_time = "2024-12-24T18:11:26.535Z" }, - { url = "https://files.pythonhosted.org/packages/10/bd/6517ea94f2672e801011d50b5d06be2a0deaf566aea27bcdcd47e5195357/charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", size = 195653, upload_time = "2024-12-24T18:11:45.568Z" }, - { url = "https://files.pythonhosted.org/packages/e5/0d/815a2ba3f283b4eeaa5ece57acade365c5b4135f65a807a083c818716582/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", size = 140701, upload_time = "2024-12-24T18:11:46.968Z" }, - { url = "https://files.pythonhosted.org/packages/aa/17/c94be7ee0d142687e047fe1de72060f6d6837f40eedc26e87e6e124a3fc6/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", size = 150495, upload_time = "2024-12-24T18:11:48.375Z" }, - { url = "https://files.pythonhosted.org/packages/f7/33/557ac796c47165fc141e4fb71d7b0310f67e05cb420756f3a82e0a0068e0/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", size = 142946, upload_time = "2024-12-24T18:11:53.619Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0d/38ef4ae41e9248d63fc4998d933cae22473b1b2ac4122cf908d0f5eb32aa/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", size = 144737, upload_time = "2024-12-24T18:11:54.993Z" }, - { url = "https://files.pythonhosted.org/packages/43/01/754cdb29dd0560f58290aaaa284d43eea343ad0512e6ad3b8b5c11f08592/charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", size = 147471, upload_time = "2024-12-24T18:11:58.169Z" }, - { url = "https://files.pythonhosted.org/packages/ba/cd/861883ba5160c7a9bd242c30b2c71074cda2aefcc0addc91118e0d4e0765/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", size = 140801, upload_time = "2024-12-24T18:12:01.02Z" }, - { url = "https://files.pythonhosted.org/packages/6f/7f/0c0dad447819e90b93f8ed238cc8f11b91353c23c19e70fa80483a155bed/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", size = 149312, upload_time = "2024-12-24T18:12:02.267Z" }, - { url = "https://files.pythonhosted.org/packages/8e/09/9f8abcc6fff60fb727268b63c376c8c79cc37b833c2dfe1f535dfb59523b/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", size = 152347, upload_time = "2024-12-24T18:12:04.145Z" }, - { url = "https://files.pythonhosted.org/packages/be/e5/3f363dad2e24378f88ccf63ecc39e817c29f32e308ef21a7a6d9c1201165/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", size = 149888, upload_time = "2024-12-24T18:12:05.673Z" }, - { url = "https://files.pythonhosted.org/packages/e4/10/a78c0e91f487b4ad0ef7480ac765e15b774f83de2597f1b6ef0eaf7a2f99/charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", size = 145169, upload_time = "2024-12-24T18:12:06.846Z" }, - { url = "https://files.pythonhosted.org/packages/d3/81/396e7d7f5d7420da8273c91175d2e9a3f569288e3611d521685e4b9ac9cc/charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", size = 95094, upload_time = "2024-12-24T18:12:08.048Z" }, - { url = "https://files.pythonhosted.org/packages/40/bb/20affbbd9ea29c71ea123769dc568a6d42052ff5089c5fe23e21e21084a6/charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", size = 102139, upload_time = "2024-12-24T18:12:09.161Z" }, - { url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867, upload_time = "2024-12-24T18:12:10.438Z" }, - { url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385, upload_time = "2024-12-24T18:12:11.847Z" }, - { url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367, upload_time = "2024-12-24T18:12:13.177Z" }, - { url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928, upload_time = "2024-12-24T18:12:14.497Z" }, - { url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203, upload_time = "2024-12-24T18:12:15.731Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082, upload_time = "2024-12-24T18:12:18.641Z" }, - { url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053, upload_time = "2024-12-24T18:12:20.036Z" }, - { url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625, upload_time = "2024-12-24T18:12:22.804Z" }, - { url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549, upload_time = "2024-12-24T18:12:24.163Z" }, - { url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945, upload_time = "2024-12-24T18:12:25.415Z" }, - { url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595, upload_time = "2024-12-24T18:12:28.03Z" }, - { url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453, upload_time = "2024-12-24T18:12:29.569Z" }, - { url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811, upload_time = "2024-12-24T18:12:30.83Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload_time = "2024-12-24T18:12:32.852Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload_time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "colorlog" -version = "6.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624, upload_time = "2024-10-29T18:34:51.011Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424, upload_time = "2024-10-29T18:34:49.815Z" }, -] - -[[package]] -name = "dependency-groups" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b4/57/cd53c3e335eafbb0894449af078e2b71db47e9939ce2b45013e5a9fe89b7/dependency_groups-1.3.0.tar.gz", hash = "sha256:5b9751d5d98fbd6dfd038a560a69c8382e41afcbf7ffdbcc28a2a3f85498830f", size = 9832, upload_time = "2024-11-01T00:31:56.828Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/2c/3e3afb1df3dc8a8deeb143f6ac41acbfdfae4f03a54c760871c56832a554/dependency_groups-1.3.0-py3-none-any.whl", hash = "sha256:1abf34d712deda5581e80d507512664d52b35d1c2d7caf16c85e58ca508547e0", size = 8597, upload_time = "2024-11-01T00:31:55.488Z" }, -] - -[[package]] -name = "distlib" -version = "0.3.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload_time = "2024-10-09T18:35:47.551Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload_time = "2024-10-09T18:35:44.272Z" }, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883, upload_time = "2024-07-12T22:26:00.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453, upload_time = "2024-07-12T22:25:58.476Z" }, -] - -[[package]] -name = "filelock" -version = "3.16.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037, upload_time = "2024-09-17T19:02:01.779Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163, upload_time = "2024-09-17T19:02:00.268Z" }, -] - -[[package]] -name = "filelock" -version = "3.18.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload_time = "2025-03-14T07:11:40.47Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload_time = "2025-03-14T07:11:39.145Z" }, -] - -[[package]] -name = "identify" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/29/bb/25024dbcc93516c492b75919e76f389bac754a3e4248682fba32b250c880/identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98", size = 99097, upload_time = "2024-09-14T23:50:32.513Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/0c/4ef72754c050979fdcc06c744715ae70ea37e734816bb6514f79df77a42f/identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0", size = 98972, upload_time = "2024-09-14T23:50:30.747Z" }, -] - -[[package]] -name = "identify" -version = "2.6.9" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/98/a71ab060daec766acc30fb47dfca219d03de34a70d616a79a38c6066c5bf/identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf", size = 99249, upload_time = "2025-03-08T15:54:13.632Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/ce/0845144ed1f0e25db5e7a79c2354c1da4b5ce392b8966449d5db8dca18f1/identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150", size = 99101, upload_time = "2025-03-08T15:54:12.026Z" }, -] - -[[package]] -name = "idna" -version = "3.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload_time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload_time = "2025-03-19T20:10:01.071Z" }, -] - -[[package]] -name = "isort" -version = "5.13.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303, upload_time = "2023-12-13T20:37:26.124Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload_time = "2023-12-13T20:37:23.244Z" }, -] - -[[package]] -name = "isort" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955, upload_time = "2025-02-26T21:13:16.955Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186, upload_time = "2025-02-26T21:13:14.911Z" }, -] - -[[package]] -name = "mqss-client" -version = "0.2.1" -source = { editable = "." } -dependencies = [ - { name = "pika" }, - { name = "python-decouple" }, - { name = "requests" }, -] - -[package.dev-dependencies] -dev = [ - { name = "isort", version = "5.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "isort", version = "6.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "mypy", version = "1.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "mypy", version = "1.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "nox" }, - { name = "pre-commit", version = "3.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "pre-commit", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "pytest" }, -] - -[package.metadata] -requires-dist = [ - { name = "pika", specifier = ">=1.3.2" }, - { name = "python-decouple", specifier = ">=3.8" }, - { name = "requests", specifier = ">=2.31.0" }, -] - -[package.metadata.requires-dev] -dev = [ - { name = "isort", specifier = ">=5.13.2" }, - { name = "mypy", specifier = ">=1.14.1" }, - { name = "nox", specifier = ">=2025.2.9" }, - { name = "pre-commit", specifier = ">=3.5.0" }, - { name = "pytest", specifier = ">=8.3.5" }, -] - -[[package]] -name = "mypy" -version = "1.14.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "mypy-extensions", marker = "python_full_version < '3.9'" }, - { name = "tomli", marker = "python_full_version < '3.9'" }, - { name = "typing-extensions", marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051, upload_time = "2024-12-30T16:39:07.335Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002, upload_time = "2024-12-30T16:37:22.435Z" }, - { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400, upload_time = "2024-12-30T16:37:53.526Z" }, - { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172, upload_time = "2024-12-30T16:37:50.332Z" }, - { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732, upload_time = "2024-12-30T16:37:29.96Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197, upload_time = "2024-12-30T16:38:05.037Z" }, - { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836, upload_time = "2024-12-30T16:37:19.726Z" }, - { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432, upload_time = "2024-12-30T16:37:11.533Z" }, - { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515, upload_time = "2024-12-30T16:37:40.724Z" }, - { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791, upload_time = "2024-12-30T16:36:58.73Z" }, - { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203, upload_time = "2024-12-30T16:37:03.741Z" }, - { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900, upload_time = "2024-12-30T16:37:57.948Z" }, - { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869, upload_time = "2024-12-30T16:37:33.428Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668, upload_time = "2024-12-30T16:38:02.211Z" }, - { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060, upload_time = "2024-12-30T16:37:46.131Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167, upload_time = "2024-12-30T16:37:43.534Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341, upload_time = "2024-12-30T16:37:36.249Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991, upload_time = "2024-12-30T16:37:06.743Z" }, - { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016, upload_time = "2024-12-30T16:37:15.02Z" }, - { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097, upload_time = "2024-12-30T16:37:25.144Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728, upload_time = "2024-12-30T16:38:08.634Z" }, - { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965, upload_time = "2024-12-30T16:38:12.132Z" }, - { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660, upload_time = "2024-12-30T16:38:17.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198, upload_time = "2024-12-30T16:38:32.839Z" }, - { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276, upload_time = "2024-12-30T16:38:20.828Z" }, - { url = "https://files.pythonhosted.org/packages/39/02/1817328c1372be57c16148ce7d2bfcfa4a796bedaed897381b1aad9b267c/mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31", size = 11143050, upload_time = "2024-12-30T16:38:29.743Z" }, - { url = "https://files.pythonhosted.org/packages/b9/07/99db9a95ece5e58eee1dd87ca456a7e7b5ced6798fd78182c59c35a7587b/mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6", size = 10321087, upload_time = "2024-12-30T16:38:14.739Z" }, - { url = "https://files.pythonhosted.org/packages/9a/eb/85ea6086227b84bce79b3baf7f465b4732e0785830726ce4a51528173b71/mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319", size = 12066766, upload_time = "2024-12-30T16:38:47.038Z" }, - { url = "https://files.pythonhosted.org/packages/4b/bb/f01bebf76811475d66359c259eabe40766d2f8ac8b8250d4e224bb6df379/mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac", size = 12787111, upload_time = "2024-12-30T16:39:02.444Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c9/84837ff891edcb6dcc3c27d85ea52aab0c4a34740ff5f0ccc0eb87c56139/mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b", size = 12974331, upload_time = "2024-12-30T16:38:23.849Z" }, - { url = "https://files.pythonhosted.org/packages/84/5f/901e18464e6a13f8949b4909535be3fa7f823291b8ab4e4b36cfe57d6769/mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837", size = 9763210, upload_time = "2024-12-30T16:38:36.299Z" }, - { url = "https://files.pythonhosted.org/packages/ca/1f/186d133ae2514633f8558e78cd658070ba686c0e9275c5a5c24a1e1f0d67/mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35", size = 11200493, upload_time = "2024-12-30T16:38:26.935Z" }, - { url = "https://files.pythonhosted.org/packages/af/fc/4842485d034e38a4646cccd1369f6b1ccd7bc86989c52770d75d719a9941/mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc", size = 10357702, upload_time = "2024-12-30T16:38:50.623Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e6/457b83f2d701e23869cfec013a48a12638f75b9d37612a9ddf99072c1051/mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9", size = 12091104, upload_time = "2024-12-30T16:38:53.735Z" }, - { url = "https://files.pythonhosted.org/packages/f1/bf/76a569158db678fee59f4fd30b8e7a0d75bcbaeef49edd882a0d63af6d66/mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb", size = 12830167, upload_time = "2024-12-30T16:38:56.437Z" }, - { url = "https://files.pythonhosted.org/packages/43/bc/0bc6b694b3103de9fed61867f1c8bd33336b913d16831431e7cb48ef1c92/mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60", size = 13013834, upload_time = "2024-12-30T16:38:59.204Z" }, - { url = "https://files.pythonhosted.org/packages/b0/79/5f5ec47849b6df1e6943d5fd8e6632fbfc04b4fd4acfa5a5a9535d11b4e2/mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c", size = 9781231, upload_time = "2024-12-30T16:39:05.124Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905, upload_time = "2024-12-30T16:38:42.021Z" }, -] - -[[package]] -name = "mypy" -version = "1.15.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.9'", -] -dependencies = [ - { name = "mypy-extensions", marker = "python_full_version >= '3.9'" }, - { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version >= '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload_time = "2025-02-05T03:50:34.655Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433, upload_time = "2025-02-05T03:49:29.145Z" }, - { url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472, upload_time = "2025-02-05T03:49:16.986Z" }, - { url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424, upload_time = "2025-02-05T03:49:46.908Z" }, - { url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450, upload_time = "2025-02-05T03:50:05.89Z" }, - { url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765, upload_time = "2025-02-05T03:49:33.56Z" }, - { url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701, upload_time = "2025-02-05T03:49:38.981Z" }, - { url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload_time = "2025-02-05T03:50:17.287Z" }, - { url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload_time = "2025-02-05T03:49:51.21Z" }, - { url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload_time = "2025-02-05T03:50:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload_time = "2025-02-05T03:49:42.408Z" }, - { url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload_time = "2025-02-05T03:49:07.707Z" }, - { url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload_time = "2025-02-05T03:49:54.581Z" }, - { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload_time = "2025-02-05T03:50:28.25Z" }, - { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload_time = "2025-02-05T03:50:13.411Z" }, - { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload_time = "2025-02-05T03:50:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload_time = "2025-02-05T03:48:48.705Z" }, - { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload_time = "2025-02-05T03:49:03.628Z" }, - { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload_time = "2025-02-05T03:50:00.313Z" }, - { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload_time = "2025-02-05T03:48:55.789Z" }, - { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload_time = "2025-02-05T03:48:44.581Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload_time = "2025-02-05T03:49:25.514Z" }, - { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload_time = "2025-02-05T03:49:57.623Z" }, - { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload_time = "2025-02-05T03:48:52.361Z" }, - { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload_time = "2025-02-05T03:49:11.395Z" }, - { url = "https://files.pythonhosted.org/packages/5a/fa/79cf41a55b682794abe71372151dbbf856e3008f6767057229e6649d294a/mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078", size = 10737129, upload_time = "2025-02-05T03:50:24.509Z" }, - { url = "https://files.pythonhosted.org/packages/d3/33/dd8feb2597d648de29e3da0a8bf4e1afbda472964d2a4a0052203a6f3594/mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba", size = 9856335, upload_time = "2025-02-05T03:49:36.398Z" }, - { url = "https://files.pythonhosted.org/packages/e4/b5/74508959c1b06b96674b364ffeb7ae5802646b32929b7701fc6b18447592/mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5", size = 11611935, upload_time = "2025-02-05T03:49:14.154Z" }, - { url = "https://files.pythonhosted.org/packages/6c/53/da61b9d9973efcd6507183fdad96606996191657fe79701b2c818714d573/mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b", size = 12365827, upload_time = "2025-02-05T03:48:59.458Z" }, - { url = "https://files.pythonhosted.org/packages/c1/72/965bd9ee89540c79a25778cc080c7e6ef40aa1eeac4d52cec7eae6eb5228/mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2", size = 12541924, upload_time = "2025-02-05T03:50:03.12Z" }, - { url = "https://files.pythonhosted.org/packages/46/d0/f41645c2eb263e6c77ada7d76f894c580c9ddb20d77f0c24d34273a4dab2/mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980", size = 9271176, upload_time = "2025-02-05T03:50:10.86Z" }, - { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload_time = "2025-02-05T03:50:08.348Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433, upload_time = "2023-02-04T12:11:27.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695, upload_time = "2023-02-04T12:11:25.002Z" }, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload_time = "2024-06-04T18:44:11.171Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload_time = "2024-06-04T18:44:08.352Z" }, -] - -[[package]] -name = "nox" -version = "2025.2.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "argcomplete" }, - { name = "attrs" }, - { name = "colorlog" }, - { name = "dependency-groups" }, - { name = "packaging" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0d/22/84a2d3442cb33e6fb1af18172a15deb1eea3f970417f1f4c5fa1600143e8/nox-2025.2.9.tar.gz", hash = "sha256:d50cd4ca568bd7621c2e6cbbc4845b3b7f7697f25d5fb0190ce8f4600be79768", size = 4021103, upload_time = "2025-02-09T19:02:06.556Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/ca/64e634c056cba463cac743735660a772ab78eb26ec9759e88de735f2cd27/nox-2025.2.9-py3-none-any.whl", hash = "sha256:7d1e92d1918c6980d70aee9cf1c1d19d16faa71c4afe338fffd39e8a460e2067", size = 71315, upload_time = "2025-02-09T19:02:04.624Z" }, -] - -[[package]] -name = "packaging" -version = "24.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload_time = "2024-11-08T09:47:47.202Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload_time = "2024-11-08T09:47:44.722Z" }, -] - -[[package]] -name = "pika" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/db/d4102f356af18f316c67f2cead8ece307f731dd63140e2c71f170ddacf9b/pika-1.3.2.tar.gz", hash = "sha256:b2a327ddddf8570b4965b3576ac77091b850262d34ce8c1d8cb4e4146aa4145f", size = 145029, upload_time = "2023-05-05T14:25:43.368Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/f3/f412836ec714d36f0f4ab581b84c491e3f42c6b5b97a6c6ed1817f3c16d0/pika-1.3.2-py3-none-any.whl", hash = "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f", size = 155415, upload_time = "2023-05-05T14:25:41.484Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.3.6" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302, upload_time = "2024-09-17T19:06:50.688Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439, upload_time = "2024-09-17T19:06:49.212Z" }, -] - -[[package]] -name = "platformdirs" -version = "4.3.7" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291, upload_time = "2025-03-19T20:36:10.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499, upload_time = "2025-03-19T20:36:09.038Z" }, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload_time = "2024-04-20T21:34:42.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload_time = "2024-04-20T21:34:40.434Z" }, -] - -[[package]] -name = "pre-commit" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -dependencies = [ - { name = "cfgv", marker = "python_full_version < '3.9'" }, - { name = "identify", version = "2.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "nodeenv", marker = "python_full_version < '3.9'" }, - { name = "pyyaml", marker = "python_full_version < '3.9'" }, - { name = "virtualenv", marker = "python_full_version < '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/b3/4ae08d21eb097162f5aad37f4585f8069a86402ed7f5362cc9ae097f9572/pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32", size = 177079, upload_time = "2023-10-13T15:57:48.334Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/75/526915fedf462e05eeb1c75ceaf7e3f9cde7b5ce6f62740fe5f7f19a0050/pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660", size = 203698, upload_time = "2023-10-13T15:57:46.378Z" }, -] - -[[package]] -name = "pre-commit" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.9'", -] -dependencies = [ - { name = "cfgv", marker = "python_full_version >= '3.9'" }, - { name = "identify", version = "2.6.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "nodeenv", marker = "python_full_version >= '3.9'" }, - { name = "pyyaml", marker = "python_full_version >= '3.9'" }, - { name = "virtualenv", marker = "python_full_version >= '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload_time = "2025-03-18T21:35:20.987Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload_time = "2025-03-18T21:35:19.343Z" }, -] - -[[package]] -name = "pytest" -version = "8.3.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload_time = "2025-03-02T12:54:54.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload_time = "2025-03-02T12:54:52.069Z" }, -] - -[[package]] -name = "python-decouple" -version = "3.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/97/373dcd5844ec0ea5893e13c39a2c67e7537987ad8de3842fe078db4582fa/python-decouple-3.8.tar.gz", hash = "sha256:ba6e2657d4f376ecc46f77a3a615e058d93ba5e465c01bbe57289bfb7cce680f", size = 9612, upload_time = "2023-03-01T19:38:38.143Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/d4/9193206c4563ec771faf2ccf54815ca7918529fe81f6adb22ee6d0e06622/python_decouple-3.8-py3-none-any.whl", hash = "sha256:d0d45340815b25f4de59c974b855bb38d03151d81b037d9e3f463b0c9f8cbd66", size = 9947, upload_time = "2023-03-01T19:38:36.015Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload_time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload_time = "2024-08-06T20:31:40.178Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload_time = "2024-08-06T20:31:42.173Z" }, - { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload_time = "2024-08-06T20:31:44.263Z" }, - { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload_time = "2024-08-06T20:31:50.199Z" }, - { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload_time = "2024-08-06T20:31:52.292Z" }, - { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload_time = "2024-08-06T20:31:53.836Z" }, - { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload_time = "2024-08-06T20:31:55.565Z" }, - { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload_time = "2024-08-06T20:31:56.914Z" }, - { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload_time = "2024-08-06T20:31:58.304Z" }, - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload_time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload_time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload_time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload_time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload_time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload_time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload_time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload_time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload_time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload_time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload_time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload_time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload_time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload_time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload_time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload_time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload_time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload_time = "2024-08-06T20:32:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload_time = "2024-08-06T20:32:43.4Z" }, - { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload_time = "2024-08-06T20:32:44.801Z" }, - { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload_time = "2024-08-06T20:32:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload_time = "2024-08-06T20:32:51.188Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload_time = "2024-08-06T20:32:53.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload_time = "2024-08-06T20:32:54.708Z" }, - { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload_time = "2024-08-06T20:32:56.985Z" }, - { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload_time = "2024-08-06T20:33:03.001Z" }, - { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload_time = "2024-08-06T20:33:04.33Z" }, - { url = "https://files.pythonhosted.org/packages/74/d9/323a59d506f12f498c2097488d80d16f4cf965cee1791eab58b56b19f47a/PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", size = 183218, upload_time = "2024-08-06T20:33:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/74/cc/20c34d00f04d785f2028737e2e2a8254e1425102e730fee1d6396f832577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", size = 728067, upload_time = "2024-08-06T20:33:07.879Z" }, - { url = "https://files.pythonhosted.org/packages/20/52/551c69ca1501d21c0de51ddafa8c23a0191ef296ff098e98358f69080577/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", size = 757812, upload_time = "2024-08-06T20:33:12.542Z" }, - { url = "https://files.pythonhosted.org/packages/fd/7f/2c3697bba5d4aa5cc2afe81826d73dfae5f049458e44732c7a0938baa673/PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", size = 746531, upload_time = "2024-08-06T20:33:14.391Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ab/6226d3df99900e580091bb44258fde77a8433511a86883bd4681ea19a858/PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", size = 800820, upload_time = "2024-08-06T20:33:16.586Z" }, - { url = "https://files.pythonhosted.org/packages/a0/99/a9eb0f3e710c06c5d922026f6736e920d431812ace24aae38228d0d64b04/PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", size = 145514, upload_time = "2024-08-06T20:33:22.414Z" }, - { url = "https://files.pythonhosted.org/packages/75/8a/ee831ad5fafa4431099aa4e078d4c8efd43cd5e48fbc774641d233b683a9/PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", size = 162702, upload_time = "2024-08-06T20:33:23.813Z" }, - { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777, upload_time = "2024-08-06T20:33:25.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318, upload_time = "2024-08-06T20:33:27.212Z" }, - { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891, upload_time = "2024-08-06T20:33:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614, upload_time = "2024-08-06T20:33:34.157Z" }, - { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360, upload_time = "2024-08-06T20:33:35.84Z" }, - { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006, upload_time = "2024-08-06T20:33:37.501Z" }, - { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577, upload_time = "2024-08-06T20:33:39.389Z" }, - { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593, upload_time = "2024-08-06T20:33:46.63Z" }, - { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312, upload_time = "2024-08-06T20:33:49.073Z" }, -] - -[[package]] -name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3", version = "2.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "urllib3", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload_time = "2024-05-29T15:37:49.536Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload_time = "2024-05-29T15:37:47.027Z" }, -] - -[[package]] -name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload_time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload_time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload_time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload_time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload_time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload_time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload_time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload_time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload_time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload_time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload_time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload_time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload_time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload_time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload_time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload_time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload_time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload_time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload_time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload_time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload_time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload_time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload_time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload_time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload_time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload_time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload_time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload_time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload_time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload_time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload_time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload_time = "2024-11-27T22:38:35.385Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload_time = "2025-04-10T14:19:05.416Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload_time = "2025-04-10T14:19:03.967Z" }, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677, upload_time = "2024-09-12T10:52:18.401Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338, upload_time = "2024-09-12T10:52:16.589Z" }, -] - -[[package]] -name = "urllib3" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.9'", -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload_time = "2025-04-10T15:23:39.232Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload_time = "2025-04-10T15:23:37.377Z" }, -] - -[[package]] -name = "virtualenv" -version = "20.30.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib" }, - { name = "filelock", version = "3.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "filelock", version = "3.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, - { name = "platformdirs", version = "4.3.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, - { name = "platformdirs", version = "4.3.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945, upload_time = "2025-03-31T16:33:29.185Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461, upload_time = "2025-03-31T16:33:26.758Z" }, -] From f33a8eb346bd6b7a46698c5fea930e4783ce6606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erc=C3=BCment=20Kaya?= <49598189+kayaercument@users.noreply.github.com> Date: Wed, 29 Apr 2026 11:46:40 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20=20Implantation=20of=20Python?= =?UTF-8?q?=20Bindings=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ python bindings * 🎨 refactoring the python tests * 🎨 minor improvments --- .devcontainer/Dockerfile | 2 +- src/CMakeLists.txt | 2 + src/client.cpp | 7 +- src/job.cpp | 8 +- src/python/CMakeLists.txt | 5 + src/python/bindings.cpp | 110 ++++++++++++++++ src/resource.cpp | 2 +- tests/unit_tests/python/test_job.py | 152 +++++++++++++++++++++++ tests/unit_tests/python/test_resource.py | 115 +++++++++++++++++ 9 files changed, 396 insertions(+), 7 deletions(-) create mode 100644 src/python/CMakeLists.txt create mode 100644 src/python/bindings.cpp create mode 100644 tests/unit_tests/python/test_job.py create mode 100644 tests/unit_tests/python/test_resource.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 4721738..701f733 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -4,7 +4,7 @@ ENV TZ="Europe/Berlin" RUN apt-get update -y && \ apt-get autoremove -y -RUN apt-get install -y software-properties-common bash-completion cmake build-essential iputils-ping rsync p7zip-full libuv1-dev git nlohmann-json3-dev librabbitmq-dev libboost-dev libgtest-dev +RUN apt-get install -y software-properties-common bash-completion cmake build-essential iputils-ping rsync p7zip-full libuv1-dev git nlohmann-json3-dev librabbitmq-dev libboost-dev libgtest-dev pybind11-dev libgmock-dev WORKDIR /home diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d10c0bb..13aa989 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,6 +26,8 @@ target_include_directories( mqss_client PUBLIC ${CMAKE_SOURCE_DIR}/include) target_link_libraries(mqss_client PUBLIC curl nlohmann_json rabbitmq) + +add_subdirectory(python) include(GNUInstallDirs) install(TARGETS mqss_client diff --git a/src/client.cpp b/src/client.cpp index 214c908..df60a88 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -45,7 +45,6 @@ MQSSClient::getResourceInfo(const std::string &resource) const { std::optional MQSSClient::submitJob(JobRequest &job) { std::string path = job.getPath(); std::string result = mClient->post(path, job.toJson()); - if (result.empty() || !nlohmann::json::accept(result)) return std::nullopt; @@ -98,14 +97,14 @@ std::unique_ptr MQSSClient::waitForJobResult(const JobRequest &job, while (timeout > 0) { std::string status = getJobStatus(job); - if (status == "COMPLETED") + if (status == "COMPLETED" || status == "CANCELLED") break; - if (status == "FAILED" || status == "CANCELLED" || status.empty()) + if (status == "FAILED" || status.empty()) return nullptr; std::this_thread::sleep_for(std::chrono::seconds(poll_seconds)); timeout -= poll_seconds; } - return getJobResult(job); + return timeout <= 0 ? nullptr : getJobResult(job); } int MQSSClient::getNumberPendingJobs(const std::string &resource) const { diff --git a/src/job.cpp b/src/job.cpp index 7109f91..867c7a6 100644 --- a/src/job.cpp +++ b/src/job.cpp @@ -4,7 +4,7 @@ using namespace mqss::client; nlohmann::json CircuitJobRequest::toJson() const { - return {{"circuit", mCircuit}, + return {{"circuit", {mCircuit}}, {"circuit_format", mCircuitFormat}, {"resource_name", mResourceName}, {"shots", mShots}, @@ -47,7 +47,13 @@ JobResult::JobResult(std::map results, JobResult::JobResult(const nlohmann::json &parsed) { const auto &rResultStr = parsed.at("result").get_ref(); + + if(!nlohmann::json::accept(rResultStr)) + return; + nlohmann::json resultMap = nlohmann::json::parse(rResultStr); + if(resultMap.is_array()) + resultMap = resultMap[0]; for (auto &[key, value] : resultMap.items()) { resultMap[key] = value.get(); diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt new file mode 100644 index 0000000..42a176e --- /dev/null +++ b/src/python/CMakeLists.txt @@ -0,0 +1,5 @@ +find_package(pybind11 REQUIRED) +pybind11_add_module(mqss THIN_LTO OPT_SIZE bindings.cpp) +target_include_directories(mqss PRIVATE ${CMAKE_SOURCE_DIR}/include) +target_link_libraries(mqss PRIVATE mqss_client) +set_target_properties(mqss PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp new file mode 100644 index 0000000..57e180b --- /dev/null +++ b/src/python/bindings.cpp @@ -0,0 +1,110 @@ +/*------------------------------------------------------------------------------ +Copyright 2024 Munich Quantum Software Stack Project + +Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the +"License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/develop/LICENSE + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations under +the License. + +SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +------------------------------------------------------------------------------*/ + +#include "mqss/client.h" + +#include +#include +#include + +namespace py = pybind11; + +PYBIND11_MODULE(mqss, m) { + m.doc() = "Python bindings for the MQSS client"; + + py::module_ clientModule = m.def_submodule("client", "Submodule Client"); + + py::class_(clientModule, "MQSSClient") + .def(py::init(), + py::arg("token") = "", py::arg("url_or_queue") = MQP_DEFAULT_URL, + py::arg("is_hpc") = false) + .def_property_readonly("resources", + &mqss::client::MQSSClient::getAllResources) + .def("resource", &mqss::client::MQSSClient::getResourceInfo, + py::arg("resource")) + .def("submit_job", &mqss::client::MQSSClient::submitJob, + py::arg("job_request")) + .def("cancel_job", &mqss::client::MQSSClient::cancelJob, py::arg("job")) + .def("job_status", &mqss::client::MQSSClient::getJobStatus, + py::arg("job")) + .def("job_results", &mqss::client::MQSSClient::getJobResult, + py::arg("job"), py::arg("wait") = false, py::arg("timeout") = 100) + .def("pending_job_count", &mqss::client::MQSSClient::getNumberPendingJobs, + py::arg("resource")); + + py::class_(clientModule, "Resource") + .def_property_readonly("name", &mqss::client::Resource::getName) + .def_property_readonly("qubit_count", + &mqss::client::Resource::getQubitCount) + .def_property_readonly("online", &mqss::client::Resource::isOnline) + .def_property_readonly("coupling_map", + &mqss::client::Resource::getCouplingMap) + .def_property_readonly("native_gateset", + &mqss::client::Resource::getNativeGateset); + + py::class_(m, "JobRequest").doc(); + + py::class_( + clientModule, "CircuitJobRequest") + .def(py::init<>()) + .def(py::init(), + py::arg("circuit"), py::arg("circuit_format"), + py::arg("resource_name"), py::arg("shots"), py::arg("no_modify"), + py::arg("queued")) + .def_property("circuit", &mqss::client::CircuitJobRequest::getCircuit, + &mqss::client::CircuitJobRequest::setCircuit) + .def_property("circuit_format", + &mqss::client::CircuitJobRequest::getCircuitFormat, + &mqss::client::CircuitJobRequest::setCircuitFormat) + .def_property("shots", &mqss::client::CircuitJobRequest::getShots, + &mqss::client::CircuitJobRequest::setShots) + .def_property("resource_name", + &mqss::client::CircuitJobRequest::getResourceName, + &mqss::client::CircuitJobRequest::setResourceName) + .def_property("no_modify", &mqss::client::CircuitJobRequest::isNoModify, + &mqss::client::CircuitJobRequest::setNoModify) + .def_property("queued", &mqss::client::CircuitJobRequest::isQueued, + &mqss::client::CircuitJobRequest::setQueued); + + py::class_( + clientModule, "HamiltonianJobRequest") + .def(py::init<>()) + .def(py::init(), + py::arg("resource_name"), py::arg("interaction"), + py::arg("coefficients")) + .def_property("resource_name", + &mqss::client::HamiltonianJobRequest::getResourceName, + &mqss::client::HamiltonianJobRequest::setResourceName) + .def_property("interaction", + &mqss::client::HamiltonianJobRequest::getInteractionString, + &mqss::client::HamiltonianJobRequest::setInteractionString) + .def_property( + "coefficients", + &mqss::client::HamiltonianJobRequest::getCoefficientsString, + &mqss::client::HamiltonianJobRequest::setCoefficientsString); + + py::class_(clientModule, "JobResult") + .def_property_readonly("results", &mqss::client::JobResult::getResults) + .def_property_readonly("completion_timestamp", + &mqss::client::JobResult::getTimestampCompleted) + .def_property_readonly("submission_timestamp", + &mqss::client::JobResult::getTimestampSubmitted) + .def_property_readonly("scheduled_timestamp", + &mqss::client::JobResult::getTimestampScheduled); +} diff --git a/src/resource.cpp b/src/resource.cpp index 33d0370..80fa9da 100644 --- a/src/resource.cpp +++ b/src/resource.cpp @@ -1,4 +1,4 @@ -#include "mqss/Resource.h" +#include "mqss/resource.h" #include using namespace mqss::client; diff --git a/tests/unit_tests/python/test_job.py b/tests/unit_tests/python/test_job.py new file mode 100644 index 0000000..3f66ee8 --- /dev/null +++ b/tests/unit_tests/python/test_job.py @@ -0,0 +1,152 @@ +# ------------------------------------------------------------------------------ +# Copyright 2024 Munich Quantum Software Stack Project +# +# Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/develop/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# ------------------------------------------------------------------------------ + +from mqss.client import MQSSClient +import pytest +import os +from mqss.client import CircuitJobRequest, HamiltonianJobRequest + +MQSS_API_URL = "https://portal.quantum.lrz.de:4000/v1/" + +TEST_CIRCUIT = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +creg c[2]; +h q[0]; +cx q[0], q[1]; +measure q -> c; +""" + + +@pytest.fixture(params=["hpc", "api"]) +def client_with_mode(request): + url_or_queue, is_hpc = ( + (os.getenv("MQSS_HPC_QUEUENAME"), True) + if request.param == "hpc" + else (MQSS_API_URL, False) + ) + return MQSSClient( + token=os.getenv("MQSS_API_TOKEN"), url_or_queue=url_or_queue, is_hpc=is_hpc + ), is_hpc + + +@pytest.fixture +def circuit_job(): + return CircuitJobRequest( + TEST_CIRCUIT, + "qasm", + "QLM", + 100, + 0, + 0, + ) + + +def test_client_submit_job(client_with_mode, circuit_job): + client, is_hpc = client_with_mode + job_id = client.submit_job(circuit_job) + assert job_id is not None + + +def test_client_submit_hamiltonian_job(client_with_mode): + client, is_hpc = client_with_mode + if is_hpc: + pytest.skip("Hamiltonian job not supported in HPC mode") + + job = HamiltonianJobRequest( + "QLM", + "0 1; 1 2; 0 2; 0 3;", + "0.5 0.1 0.8 1;", + ) + + job_id = client.submit_job(job) + assert job_id is not None + + +def test_client_check_job_status(client_with_mode, circuit_job): + client, is_hpc = client_with_mode + job_id = client.submit_job(circuit_job) + assert job_id is not None + + status = client.job_status(circuit_job) + assert status != "" + + +def test_client_check_job_setter_and_getter(): + job = CircuitJobRequest() + + circuit_format = "qasm" + resource_name = "AQT20" + shots = 10 + is_queued = False + is_no_modify = False + + job.circuit = TEST_CIRCUIT + assert job.circuit == TEST_CIRCUIT + + job.circuit_format = circuit_format + assert job.circuit_format == circuit_format + + job.resource_name = resource_name + assert job.resource_name == resource_name + + job.shots = shots + assert job.shots == shots + + job.no_modify = is_no_modify + assert job.no_modify == is_no_modify + + job.queued = is_queued + assert job.queued == is_queued + + +def test_client_check_hamiltonian_job_setter_and_getter(): + job = HamiltonianJobRequest() + + coefficients = "0.5 0.1 0.8 1;" + interaction = "0 1; 1 2; 0 2; 0 3;" + + job.coefficients = coefficients + assert job.coefficients == coefficients + + job.interaction = interaction + assert job.interaction == interaction + + +def test_client_wait_for_result(client_with_mode, circuit_job): + client, is_hpc = client_with_mode + if is_hpc: + pytest.skip("Wait for result not supported in HPC mode") + + job_id = client.submit_job(circuit_job) + assert job_id is not None + + result = client.job_results(circuit_job, wait=True, timeout=50) + + assert result is not None + assert len(result.results) != 0 + + +def test_client_get_num_pending_jobs(client_with_mode, circuit_job): + client, is_hpc = client_with_mode + job_id = client.submit_job(circuit_job) + assert job_id is not None + + n_jobs = client.pending_job_count("QLM") + assert n_jobs >= 0 diff --git a/tests/unit_tests/python/test_resource.py b/tests/unit_tests/python/test_resource.py new file mode 100644 index 0000000..3d94e0d --- /dev/null +++ b/tests/unit_tests/python/test_resource.py @@ -0,0 +1,115 @@ +# ------------------------------------------------------------------------------ +# Copyright 2024 Munich Quantum Software Stack Project +# +# Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/develop/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# ------------------------------------------------------------------------------ + +from mqss.client import MQSSClient +import pytest +import os + + +MQSS_API_URL = "https://portal.quantum.lrz.de:4000/v1/" + +TEST_CIRCUIT = """ +OPENQASM 2.0; +include "qelib1.inc"; +qreg q[2]; +creg c[2]; +h q[0]; +cx q[0], q[1]; +measure q -> c; +""" + + +@pytest.fixture(params=["hpc", "api"]) +def client(request): + url_or_queue, is_hpc = ( + (os.getenv("MQSS_HPC_QUEUENAME"), True) + if request.param == "hpc" + else (MQSS_API_URL, False) + ) + return MQSSClient( + token=os.getenv("MQSS_API_TOKEN"), url_or_queue=url_or_queue, is_hpc=is_hpc + ) + + +def test_client_get_all_resources(client): + resources = client.resources + assert len(resources) >= 0 + + +@pytest.mark.parametrize( + "resource_name", + ["QLM", "Q5", "Q20", "AQT20", "QExa20"], +) +def test_client_get_a_resource(client, resource_name): + resource = client.resource(resource_name) + assert resource is not None + + +# --------------------------------------------------------------------- +# 3️⃣ ClientGetAResourceFalse +# --------------------------------------------------------------------- +@pytest.mark.parametrize( + "resource_name", + ["Eviden", "IQM5", "IQM20", "AQT", "QExa120"], +) +def test_client_get_a_resource_false(client, resource_name): + resource = client.resource(resource_name) + assert resource is None + + +def test_client_check_resource_name(client): + golden_resource_name = "QLM" + + resource = client.resource(golden_resource_name) + assert resource is not None + assert resource.name == golden_resource_name + + +def test_client_check_qubit_count(client): + resource_name = "Q5" + + resource = client.resource(resource_name) + assert resource is not None + assert resource.qubit_count >= 0 + + +def test_client_check_if_online(client): + """ + If the resource is under maintenance, this test might fail. + """ + resource_name = "AQT20" + + resource = client.resource(resource_name) + assert resource is not None + assert resource.online is True + + +def test_client_check_coupling_map(client): + resource_name = "AQT20" + + resource = client.resource(resource_name) + assert resource is not None + assert len(resource.coupling_map) >= 0 + + +def test_client_check_native_gateset(client): + resource_name = "Q20" + + resource = client.resource(resource_name) + assert resource is not None + assert len(resource.native_gateset) >= 0 From 725292b2e2d3018363023658fc9d717cca9ece20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erc=C3=BCment=20Kaya?= <49598189+kayaercument@users.noreply.github.com> Date: Thu, 7 May 2026 20:42:02 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=93=9D=20Documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 initial documentation * ⏪ Reverse deleted files * 📝 adding new documentation * 📝 Improving the documentation * 🎨 minor improvement --- .github/contributing.md | 116 + CMakeLists.txt | 8 +- README.md | 113 +- README_INTERNAL.md | 59 - cmake/ExternalDependencies.cmake | 38 + docs/CMakeLists.txt | 112 + docs/Doxyfile.in | 2909 +++++++++++++++++++++ docs/_static/mqss-client-classdiagram.svg | 4 + docs/_static/mqss_client_structure.png | Bin 0 -> 667013 bytes docs/_static/mqss_logo.svg | 87 + docs/_static/mqss_logo_dark.svg | 240 ++ docs/ai_usage.md | 144 + docs/build.md | 86 + docs/contributing.md | 11 + docs/examples.md | 281 ++ docs/faq.md | 27 + docs/header.html | 197 ++ docs/implementation.md | 280 ++ docs/index.md | 42 + docs/layout.xml | 277 ++ docs/style.css | 49 + docs/support.md | 23 + 22 files changed, 5022 insertions(+), 81 deletions(-) create mode 100644 .github/contributing.md delete mode 100644 README_INTERNAL.md create mode 100644 cmake/ExternalDependencies.cmake create mode 100644 docs/CMakeLists.txt create mode 100644 docs/Doxyfile.in create mode 100644 docs/_static/mqss-client-classdiagram.svg create mode 100644 docs/_static/mqss_client_structure.png create mode 100644 docs/_static/mqss_logo.svg create mode 100644 docs/_static/mqss_logo_dark.svg create mode 100644 docs/ai_usage.md create mode 100644 docs/build.md create mode 100644 docs/contributing.md create mode 100644 docs/examples.md create mode 100644 docs/faq.md create mode 100644 docs/header.html create mode 100644 docs/implementation.md create mode 100644 docs/index.md create mode 100644 docs/layout.xml create mode 100644 docs/style.css create mode 100644 docs/support.md diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000..e4f9b10 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,116 @@ +# Contributing {#contributing} + + + +Thank you for your interest in contributing to **MQSS Client**. We value contributions from people +with all levels of experience. + +We use GitHub to [host code](https://github.com/Munich-Quantum-Software-Stack/MQSS Client), to +[track issues and feature requests][issues], as well as accept [pull +requests](https://github.com/Munich-Quantum-Software-Stack/MQSS Client/pulls). See + for a general introduction to working with +GitHub and contributing to projects. + +## Types of Contributions {#types-of-contributions} + +Pick the path that fits your time and interests: + +- 🐛 Report bugs: + + Use the _🐛 Bug report_ template at + . Include steps to reproduce, + expected vs. actual behavior, environment, and a minimal example. + +- 🛠️ Fix bugs: + + Browse [issues][issues], especially those labeled "bug", "help wanted", or "good first issue". + Open a draft PR early to get feedback. + +- 💡 Propose features: + + Use the _✨ Feature request_ template at + . Describe the motivation, + alternatives considered, and (optionally) a small API sketch. + +- ✨ Implement features: + + Pick items labeled "feature" or "enhancement". Coordinate in the issue first if the change is + substantial; start with a draft PR. + +- 📝 Improve documentation: + + Add or refine docstrings, tutorials, and examples; fix typos; clarify explanations. Small + documentation-only PRs are very welcome. + +- ⚡️ Performance and reliability: + + Profile hot paths, add benchmarks, reduce allocations, deflake tests, and improve error messages. + +- 📦 Packaging and tooling: + + Improve build configuration, type hints/stubs, CI workflows, and platform wheels. Incremental + tooling fixes have a big impact. + +- 🙌 Community support: + + Triage issues, reproduce reports, and answer questions in Discussions: + . + +## Guidelines {#guidelines} + +Please adhere to the following guidelines to help the project grow sustainably. Contributions that +do not comply with these guidelines or violate our [AI Usage Guidelines][ai_usage] may be rejected +without further review. + +### Core Guidelines {#core-guidelines} + +- ["Commit early and push often"](https://www.worklytics.co/blog/commit-early-push-often). +- Write meaningful commit messages, preferably using [gitmoji](https://gitmoji.dev) for additional + context. +- Focus on a single feature or bug at a time and only touch relevant files. Split multiple features + into separate contributions. +- Add tests for new features to ensure they work as intended. +- Document new features. For user-facing changes, add a changelog entry; for breaking changes, + update the upgrade guide. +- Add tests for bug fixes to demonstrate the fix. +- Document your code thoroughly and ensure it is readable. +- Keep your code clean by removing debug statements, leftover comments, and unrelated code. +- Check your code for style and linting errors before committing. +- Follow the project's coding standards and conventions. +- Be open to feedback and willing to make necessary changes based on code reviews. + +### AI-assisted contributions {#ai-assisted-contributions} + +We acknowledge the utility of AI-based coding assistants (e.g., GitHub Copilot, ChatGPT) in modern +software development. However, their use requires a high degree of responsibility and transparency +to maintain code quality and licensing compliance. + +Please carefully read and follow our dedicated [AI Usage Guidelines][ai_usage] before submitting any +AI-assisted contribution. In short: **You are responsible for every line of code you submit**, and a +**human must always be in the loop**. We require disclosure of AI tool usage in your PR description. + +### Pull Request Workflow {#pull-request-workflow} + +- Create PRs early. Work-in-progress PRs are welcome; mark them as drafts on GitHub. +- Use a clear title, reference related issues by number, and describe the changes. Follow the PR + template; only omit the issue reference if not applicable. +- CI runs on all supported platforms and Python versions to build, test, format, and lint. All + checks must pass before merging. +- When ready, convert the draft to a regular PR and request a review from a maintainer. If unsure, + ask in PR comments. If you are a first-time contributor, mention a maintainer in a comment to + request a review. +- If your PR gets a "Changes requested" review, address the feedback and push updates to the same + branch. Do not close and reopen a new PR. Respond to comments to signal that you have addressed + the feedback. Do not resolve review comments yourself; the reviewer will do so once satisfied. +- If the reviewer suggested changes with explicit code suggestions as part of the comments, apply + these directly using the GitHub UI. This attributes the changes to the reviewer and automatically + resolves the respective comments (this is an exception to the rule above). If there are multiple + suggestions that you want to apply at once, you can batch them into a single commit. Go to the + "Files changed" tab of the PR, and then click "Add suggestion to batch" for each suggestion you + want to include. Once you are done selecting suggestions, click "Commit suggestions". Only apply + suggestions manually if using the GitHub UI is not feasible. +- Re-request a review after pushing changes that address feedback. +- Do not squash commits locally; maintainers typically squash on merge. Avoid rebasing or + force-pushing before reviews; you may rebase after addressing feedback if desired. + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f3f073..360ed08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,8 +50,14 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +include(cmake/ExternalDependencies.cmake) + add_subdirectory(src) -if(BUILD_UNIT_TESTS) +if(BUILD_TESTS) add_subdirectory(tests) endif() + +if(BUILD_DOCUMENTATION) + add_subdirectory(docs) +endif() diff --git a/README.md b/README.md index f26ece3..0e19b3b 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,100 @@ -## Building and Requirements + -## Usage +

+ + + + +

-```cpp -#include -URL_OR_QUEUE_NAME = "" -TOKEN = "" -IS_HPC = false +# MQSS Client -client = mqss::client::MQSSClient(TOKEN, URL_OR_QUEUE_NAME, IS_HPC); +

+ + Documentation + +

+ -``` +The **MQSS Client** is a unifying, context-aware access layer and programming library that separates +programming interfaces from the underlying compiler and runtime stacks. The **MQSS Client** is +integrated into the _Munich Quantum Software Stack (MQSS)_ to pass quantum jobs to the underlying +compiler infrastructure and query the properties of available quantum resources. It supports +multiple programming models, such as gate-based circuits and Hamiltonians. The **MQSS Client** +serves as an abstract layer between programming interfaces and the underlying compiler +infrastructure. + + + +## FAQ + + + +### What is MQSS? + +_MQSS_ stands for _Munich Quantum Software Stack_ and is a project of the _Munich Quantum Valley_ +initiative. It is jointly developed by the _Munich Quantum Valley (MQV) gGmbH_, _Leibniz +Supercomputing Centre (LRZ)_, the _Chair for Design Automation (CDA)_, and the _Chair of Computer +Architecture and Parallel Systems (CAPS)_ at TUM. It provides a comprehensive compilation and +runtime infrastructure for on-premise and remote quantum devices, support for modern compilation and +optimization techniques, and enables both current and future high-level abstractions for quantum +programming. This stack is designed to be capable of deployment in a variety of scenarios via +flexible configuration options. This includes stand-alone scenarios for individual systems, cloud +access to a variety of devices, as well as tight integration into HPC environments supporting +quantum acceleration. Concrete instances of the _MQSS_ are deployed at the LRZ and MQV gGmbH, +providing unified access to all of their quantum devices through multiple compatible access paths. +This includes a web portal, command line access via web credentials, as well as the option for +hybrid access with tight integration with HPC systems.It facilitates the connection between +end-users and quantum computing platforms by its integration within HPC infrastructures, such as +those found at the LRZ. + +### What is the MQSS Client? + +The **MQSS Client** acts as an abstraction layer for the front-end programming interfaces of _MQSS_. +Its purpose is to provide a unified interface for communication with the underlying middle-end. + +### Where is the code? + +The code is publicly available and hosted on GitHub at +[github.com/Munich-Quantum-Software-Stack/MQSS-Client](https://github.com/Munich-Quantum-Software-Stack/MQSS-Client). + +### Under which license is MQSS Client released? + +**MQSS Client** is released under the Apache License v2.0 with LLVM Exceptions. See +[LICENSE](https://github.com/Munich-Quantum-Software-Stack/MQSS-Client/blob/develop/LICENSE.md) for +more information. Any contribution to the project is assumed to be under the same license. + +### In which languages ​​can the MQSS Client be used? + +We use C++ to write the core of **MQSS Client**. With the provided Python bindings and C-API, **MQSS +Client** can be used with Python and C-based applications. + + + +## 📬 Contact + +The development of this project is led by the QCT department at the LRZ and the QSI department at +MQV gGmbH. You can also always reach us at +[mqss@munich-quantum-valley.de](mailto:mqss@munich-quantum-valley.de). + +Please try to use the publicly accessible GitHub channels +([issues](https://github.com/Munich-Quantum-Software-Stack/MQSS-Client/issues), +[discussions](https://github.com/Munich-Quantum-Software-Stack/MQSS-Client/discussions), +[pull requests](https://github.com/Munich-Quantum-Software-Stack/MQSS-Client/pulls)) to allow for a +transparent and open discussion as much as possible. diff --git a/README_INTERNAL.md b/README_INTERNAL.md deleted file mode 100644 index 42ab849..0000000 --- a/README_INTERNAL.md +++ /dev/null @@ -1,59 +0,0 @@ -## Installation - -```bash -pip install mqss-client -``` - -## Usage - -```python -from mqss_client import MQSSClient, CircuitJobRequest - -URL = "" -TOKEN = "" - -# create a client instance -# set is_hpc=True if running on HPC Cluster -client = MQSSClient(token=TOKEN, base_url=URL, is_hpc=False) - -# check out all the resources -resources = client.get_all_resources() -# get information about one specific resource -resource = client.get_resource_info(resource_name=",doxygen,> + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_CONFIG_FILE_OUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM) + +# Create a custom target +add_custom_target(mqss-client-docs ALL DEPENDS ${DOXYGEN_OUTPUT_DIR}/html/index.html) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in new file mode 100644 index 0000000..8cb4c23 --- /dev/null +++ b/docs/Doxyfile.in @@ -0,0 +1,2909 @@ +# ------------------------------------------------------------------------------ +# Copyright 2024 Munich Quantum Software Stack Project +# +# Licensed under the Apache License, Version 2.0 with LLVM Exceptions (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/develop/LICENSE +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# ------------------------------------------------------------------------------ + +# Doxyfile 1.12.0 + +# This file describes the settings to be used by the documentation system +# Doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "MQSS Client" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = v@PROJECT_VERSION@ + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = _static/@PROJECT_LOGO@ + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where Doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIR@ + +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding Doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by Doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which Doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where Doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/include/mqss @PROJECT_SOURCE_DIR@/src @PROJECT_SOURCE_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by Doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make Doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by Doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = py=Python + +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 6. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled Doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let Doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# Doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then Doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = YES + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run Doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = YES + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = NO + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = NO + +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# Doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by Doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by Doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents Doxygen's defaults, run Doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = layout.xml + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by Doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = YES + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = YES + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @DOXYGEN_INPUT_DIRS@ + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by Doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cxxm \ + *.cpp \ + *.cppm \ + *.ccm \ + *.c++ \ + *.c++m \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.ixx \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which Doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = @DOXYGEN_EXAMPLE_DIRS@ + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = _static + +# The INPUT_FILTER tag can be used to specify a program that Doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the Doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# multi-line macros, enums or list initialized variables directly into the +# documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by Doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank Doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that Doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that Doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = header.html + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank Doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that Doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank Doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that Doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by Doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = @AWESOME_CSS_DIR@/doxygen-awesome.css \ + @AWESOME_CSS_DIR@/doxygen-awesome-sidebar-only.css \ + @AWESOME_CSS_DIR@/doxygen-awesome-sidebar-only-darkmode-toggle.css \ + style.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = @AWESOME_CSS_DIR@/doxygen-awesome-darkmode-toggle.js \ + @AWESOME_CSS_DIR@/doxygen-awesome-fragment-copy-button.js \ + @AWESOME_CSS_DIR@/doxygen-awesome-paragraph-link.js \ + @AWESOME_CSS_DIR@/doxygen-awesome-interactive-toc.js \ + @AWESOME_CSS_DIR@/doxygen-awesome-tabs.js + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = NO + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, Doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# Doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# Doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of ONGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# Doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the JavaScript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /