// 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; pub use pyo3_asyncio; use pyo3::{ intern, prelude::*, types::{PyDict, PyTuple}, }; pub mod assigned_numbers; pub mod common; pub mod controller; pub mod core; pub mod device; pub mod drivers; pub mod gatt_client; pub mod hci; pub mod host; pub mod l2cap; pub mod link; 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(&self) -> PyResult where T: for<'a> FromPyObject<'a>, { Python::with_gil(|py| self.gil_ref(py).extract::()) } /// If the Python object is a Python `None`, return a Rust `None`, otherwise `Some` with the mapped type fn into_option(self, map_obj: impl Fn(Self) -> T) -> Option { 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(&self, key: K, value: Option) -> PyResult<()>; } impl PyDictExt for PyDict { fn set_opt_item(&self, key: K, value: Option) -> 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) -> 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> { (self.callback)(py, args, kwargs).map(|_| py.None()) } } /// Wraps the Python function in a Python async function. `pyo3_asyncio` needs functions to be /// marked async to properly inject a running loop. pub(crate) fn wrap_python_async<'a>(py: Python<'a>, function: &'a PyAny) -> PyResult<&'a PyAny> { PyModule::import(py, intern!(py, "bumble.utils"))? .getattr(intern!(py, "wrap_async"))? .call1((function,)) } /// Represents the two major kinds of errors that can occur when converting between Rust and Python. pub enum ConversionError { /// Occurs across the Python/native boundary. Python(PyErr), /// Occurs within the native ecosystem, such as when performing more transformations before /// finally converting to the native type. Native(T), }