qcs 0.27.1-rc.0

High level interface for running Quil on a QPU
Documentation
//! Python bindings for the qcs-sdk crate.
//!
//! While this package can be used directly,
//! [PyQuil](https://pypi.org/project/pyquil/) offers more functionality
//! and a higher-level interface for building and executing Quil programs.

use std::sync::OnceLock;

use pyo3::prelude::*;
use rigetti_pyo3::{create_init_submodule, py_sync};

#[cfg(feature = "stubs")]
use pyo3_stub_gen::derive::gen_stub_pyfunction;

use crate::{
    compiler, diagnostics,
    python::{
        executable::{ExeParameter, PyExecutable, PyJobHandle},
        execution_data::{
            PyRegisterMatrix, RegisterMapItemsIter, RegisterMapKeysIter, RegisterMapValuesIter,
        },
    },
    qpu, qvm, ExecutionData, RegisterData, RegisterMap, Service,
};

pub(crate) mod client;
pub(crate) mod errors;
pub(crate) mod executable;
pub(crate) mod execution_data;
pub(crate) mod nonzero;
pub(crate) mod register_data;

pub(crate) use nonzero::NonZeroU16;

static PY_RESET_LOGGING_HANDLE: OnceLock<pyo3_log::ResetHandle> = OnceLock::new();

/// The docstring for the `qcs_sdk` Python package.
///
/// The `__init__.py` files in the package are generated by `pyo3_stub_gen`, which does not
/// write docstrings into them, so this is the only definition of the package docstring:
/// [`init_module`] assigns it to the compiled module, from which the generated
/// `qcs_sdk/__init__.py` re-exports it, and `module_doc!` copies it into the type stubs.
const PACKAGE_DOC: &str =
    "An interface to Rigetti [Quantum Cloud Services](https://docs.rigetti.com/qcs/) (QCS).

These APIs allow users to compile and run Quil programs on Rigetti quantum processors.
Internally, it is powered by the [QCS Rust SDK](https://github.com/rigetti/qcs-sdk-rust).

This package is still in development and breaking changes should be expected between minor versions.
";

create_init_submodule! {
    classes: [
        ExeParameter,
        ExecutionData,
        PyExecutable,
        PyJobHandle,
        RegisterMap,
        RegisterMapItemsIter,
        RegisterMapKeysIter,
        RegisterMapValuesIter,
        Service
    ],

    complex_enums: [ PyRegisterMatrix, RegisterData ],

    errors: [
        errors::QcsSdkError,
        errors::ExecutionError,
        errors::RegisterMatrixConversionError
    ],

    funcs: [ reset_logging, gather_diagnostics ],

    submodules: [
        "client": client::init_submodule,
        "compiler": compiler::python::init_submodule,
        "qpu": qpu::python::init_submodule,
        "qvm": qvm::python::init_submodule,
        "diagnostics": diagnostics::python::init_submodule
    ],
}

#[pymodule]
#[pyo3(name = "_qcs_sdk")]
fn init_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
    match pyo3_log::try_init() {
        Ok(reset_handle) => {
            // Ignore the error if the handle is already set.
            drop(PY_RESET_LOGGING_HANDLE.set(reset_handle));
        }
        Err(e) => eprintln!("Failed to initialize the qcs_sdk logger: {e}"),
    }

    let py = m.py();
    init_submodule("qcs_sdk._qcs_sdk", py, m)?;

    m.add("__version__", env!("CARGO_PKG_VERSION"))?;
    m.setattr("__doc__", PACKAGE_DOC)?;

    // Registered under the public `qcs_sdk` name rather than `qcs_sdk._qcs_sdk`: the
    // corresponding Python source is generated into `python/qcs_sdk/_tracing_subscriber` by
    // `pyo3_tracing_subscriber_build`, so `qcs_sdk._tracing_subscriber` is its import path.
    pyo3_tracing_subscriber::add_submodule("qcs_sdk", "_tracing_subscriber", py, m)?;

    Ok(())
}

#[cfg(feature = "stubs")]
mod stubs {
    use pyo3_stub_gen::{
        define_stub_info_gatherer, export_verbatim, exclude_from_all,
        derive::gen_type_alias_from_python,
        generate::Module, module_doc, reexport_module_members, Result,
        StubInfo,
    };
    use std::path::Path;

    #[derive(pyo3::IntoPyObject)]
    struct Final<T>(T);

    impl<T> pyo3_stub_gen::PyStubType for Final<T>
    where
        T: pyo3_stub_gen::PyStubType,
    {
        fn type_output() -> pyo3_stub_gen::TypeInfo {
            let mut inner = T::type_output();
            inner.import.insert("typing".into());
            inner.name = format!("typing.Final[{}]", inner.name);
            inner
        }
    }

    pyo3_stub_gen::module_variable!("qcs_sdk._qcs_sdk", "__version__", Final<&str>);

    pyo3_stub_gen::module_variable!(
        "qcs_sdk._qcs_sdk.compiler.quilc",
        "DEFAULT_COMPILER_TIMEOUT",
        Final<f64>
    );

    pyo3_stub_gen::module_doc!("qcs_sdk._qcs_sdk", "{}", super::PACKAGE_DOC);

    gen_type_alias_from_python!(
        "qcs_sdk._qcs_sdk.client",
        r#"
        AuthServer: TypeAlias = configuration.AuthServer
        ClientCredentials: TypeAlias = configuration.ClientCredentials
        ExternallyManaged: TypeAlias = configuration.ExternallyManaged
        OAuthSession: TypeAlias = configuration.OAuthSession
        RefreshToken: TypeAlias = configuration.RefreshToken
        SecretRefreshToken: TypeAlias = configuration.SecretRefreshToken
        "#
    );

    reexport_module_members!("qcs_sdk" from "qcs_sdk._qcs_sdk"; *, "__doc__", "__version__");
    reexport_module_members!("qcs_sdk.client" from "qcs_sdk._qcs_sdk.client");
    reexport_module_members!("qcs_sdk.compiler" from "qcs_sdk._qcs_sdk.compiler");
    reexport_module_members!("qcs_sdk.compiler.quilc" from "qcs_sdk._qcs_sdk.compiler.quilc");
    reexport_module_members!("qcs_sdk.diagnostics" from "qcs_sdk._qcs_sdk.diagnostics");
    reexport_module_members!("qcs_sdk.qpu" from "qcs_sdk._qcs_sdk.qpu");
    reexport_module_members!("qcs_sdk.qpu.api" from "qcs_sdk._qcs_sdk.qpu.api");
    reexport_module_members!("qcs_sdk.qpu.experimental" from "qcs_sdk._qcs_sdk.qpu.experimental");
    reexport_module_members!("qcs_sdk.qpu.experimental.random" from "qcs_sdk._qcs_sdk.qpu.experimental.random");
    reexport_module_members!("qcs_sdk.qpu.isa" from "qcs_sdk._qcs_sdk.qpu.isa");
    reexport_module_members!("qcs_sdk.qpu.translation" from "qcs_sdk._qcs_sdk.qpu.translation");
    reexport_module_members!("qcs_sdk.qvm" from "qcs_sdk._qcs_sdk.qvm");
    reexport_module_members!("qcs_sdk.qvm.api" from "qcs_sdk._qcs_sdk.qvm.api");

    reexport_module_members!("qcs_sdk._qcs_sdk.client" from "qcs_api_client_common._qcs_api_client_common"; "configuration");
    reexport_module_members!("qcs_sdk._qcs_sdk.compiler.quilc" from "quil._quil"; "program");
    exclude_from_all!("qcs_sdk._qcs_sdk.client", "configuration");
    exclude_from_all!("qcs_sdk._qcs_sdk.compiler.quilc", "program");

    reexport_module_members!("qcs_sdk" from "qcs_sdk._qcs_sdk.client"; "QCSClient");

    reexport_module_members!("qcs_sdk" from "qcs_sdk"; "_tracing_subscriber");
    reexport_module_members!("qcs_sdk._qcs_sdk" from "qcs_sdk"; "_tracing_subscriber");
    export_verbatim!("qcs_sdk._qcs_sdk", "_tracing_subscriber");
    export_verbatim!("qcs_sdk._qcs_sdk", "_gather_diagnostics");
    export_verbatim!("qcs_sdk._qcs_sdk", "__version__");

    module_doc!("quil._quil.validation", "");

    define_stub_info_gatherer!(internal_stub_info);

    /// Gather stub information to generate stub files.
    pub fn stub_info() -> Result<StubInfo> {
        let manifest_dir: &Path = env!("CARGO_MANIFEST_DIR").as_ref();
        let mut stubs = StubInfo::from_pyproject_toml(manifest_dir.join("pyproject.toml"))?;

        // `pyo3_stub_gen` gathers declarations from every linked crate, so dependencies that
        // define their own Python packages (`quil-rs` and `qcs-api-client-common`) contribute
        // their modules here too. Generation rejects any non-empty module outside this
        // package's module path, and writing files for them would shadow the real, installed
        // packages, so drop them. The stubs refer to foreign types by their private paths
        // (`quil._quil.program.Program`), since resolving the public alias needs the foreign
        // module to still be present at generation time.
        stubs
            .modules
            .retain(|name, _| name == "qcs_sdk" || name.starts_with("qcs_sdk."));

        Ok(stubs)
    }
}

#[cfg(feature = "stubs")]
pub use stubs::stub_info;

#[cfg_attr(feature = "stubs", gen_stub_pyfunction(module = "qcs_sdk._qcs_sdk"))]
#[pyfunction]
/// Reset all caches for logging configuration within this library,
/// allowing the most recent Python-side changes to be applied.
///
/// See <https://docs.rs/pyo3-log/latest/pyo3_log/> for more information.
fn reset_logging() {
    if let Some(handle) = PY_RESET_LOGGING_HANDLE.get() {
        handle.reset();
    }
}

#[cfg_attr(feature = "stubs", gen_stub_pyfunction(module = "qcs_sdk._qcs_sdk"))]
#[pyfunction]
#[pyo3(name = "_gather_diagnostics")]
fn gather_diagnostics(py: Python<'_>) -> PyResult<String> {
    py_sync!(py, async { Ok(diagnostics::get_report().await) })
}