blob: 94ac15a6805dc59b4c7b9388522f7e2dd5552d46 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 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.
//! Types that wrap the Python API.
//!
//! Because mutability, aliasing, etc is all hidden behind Python, the normal Rust rules about
//! only one mutable reference to one piece of memory, etc, may not hold since using `&mut self`
//! instead of `&self` is only guided by inspection of the Python source, not the compiler.
//!
//! The modules are generally structured to mirror the Python equivalents.
// Re-exported to make it easy for users to depend on the same `PyObject`, etc
pub use pyo3;
use pyo3::{
prelude::*,
types::{PyDict, PyTuple},
};
pub use pyo3_asyncio;
pub mod assigned_numbers;
pub mod core;
pub mod device;
pub mod drivers;
pub mod gatt_client;
pub mod hci;
pub mod host;
pub mod l2cap;
pub mod logging;
pub mod profile;
pub mod transport;
/// Convenience extensions to [PyObject]
pub trait PyObjectExt: Sized {
/// Get a GIL-bound reference
fn gil_ref<'py>(&'py self, py: Python<'py>) -> &'py PyAny;
/// Extract any [FromPyObject] implementation from this value
fn extract_with_gil<T>(&self) -> PyResult<T>
where
T: for<'a> FromPyObject<'a>,
{
Python::with_gil(|py| self.gil_ref(py).extract::<T>())
}
/// If the Python object is a Python `None`, return a Rust `None`, otherwise `Some` with the mapped type
fn into_option<T>(self, map_obj: impl Fn(Self) -> T) -> Option<T> {
Python::with_gil(|py| {
if self.gil_ref(py).is_none() {
None
} else {
Some(map_obj(self))
}
})
}
}
impl PyObjectExt for PyObject {
fn gil_ref<'py>(&'py self, py: Python<'py>) -> &'py PyAny {
self.as_ref(py)
}
}
/// Convenience extensions to [PyDict]
pub trait PyDictExt {
/// Set item in dict only if value is Some, otherwise do nothing.
fn set_opt_item<K: ToPyObject, V: ToPyObject>(&self, key: K, value: Option<V>) -> PyResult<()>;
}
impl PyDictExt for PyDict {
fn set_opt_item<K: ToPyObject, V: ToPyObject>(&self, key: K, value: Option<V>) -> PyResult<()> {
if let Some(value) = value {
self.set_item(key, value)?
}
Ok(())
}
}
/// Wrapper to make Rust closures ([Fn] implementations) callable from Python.
///
/// The Python callable form returns a Python `None`.
#[pyclass(name = "SubscribeCallback")]
pub(crate) struct ClosureCallback {
// can't use generics in a pyclass, so have to box
#[allow(clippy::type_complexity)]
callback: Box<dyn Fn(Python, &PyTuple, Option<&PyDict>) -> PyResult<()> + Send + 'static>,
}
impl ClosureCallback {
/// Create a new callback around the provided closure
pub fn new(
callback: impl Fn(Python, &PyTuple, Option<&PyDict>) -> PyResult<()> + Send + 'static,
) -> Self {
Self {
callback: Box::new(callback),
}
}
}
#[pymethods]
impl ClosureCallback {
#[pyo3(signature = (*args, **kwargs))]
fn __call__(
&self,
py: Python<'_>,
args: &PyTuple,
kwargs: Option<&PyDict>,
) -> PyResult<Py<PyAny>> {
(self.callback)(py, args, kwargs).map(|_| py.None())
}
}