1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Types that wrap the Python API.
16 //!
17 //! Because mutability, aliasing, etc is all hidden behind Python, the normal Rust rules about
18 //! only one mutable reference to one piece of memory, etc, may not hold since using `&mut self`
19 //! instead of `&self` is only guided by inspection of the Python source, not the compiler.
20 //!
21 //! The modules are generally structured to mirror the Python equivalents.
22 
23 // Re-exported to make it easy for users to depend on the same `PyObject`, etc
24 pub use pyo3;
25 pub use pyo3_asyncio;
26 
27 use pyo3::{
28     intern,
29     prelude::*,
30     types::{PyDict, PyTuple},
31 };
32 
33 pub mod assigned_numbers;
34 pub mod common;
35 pub mod controller;
36 pub mod core;
37 pub mod device;
38 pub mod drivers;
39 pub mod gatt_client;
40 pub mod hci;
41 pub mod host;
42 pub mod l2cap;
43 pub mod link;
44 pub mod logging;
45 pub mod profile;
46 pub mod transport;
47 
48 /// Convenience extensions to [PyObject]
49 pub trait PyObjectExt: Sized {
50     /// Get a GIL-bound reference
gil_ref<'py>(&'py self, py: Python<'py>) -> &'py PyAny51     fn gil_ref<'py>(&'py self, py: Python<'py>) -> &'py PyAny;
52 
53     /// Extract any [FromPyObject] implementation from this value
extract_with_gil<T>(&self) -> PyResult<T> where T: for<'a> FromPyObject<'a>,54     fn extract_with_gil<T>(&self) -> PyResult<T>
55     where
56         T: for<'a> FromPyObject<'a>,
57     {
58         Python::with_gil(|py| self.gil_ref(py).extract::<T>())
59     }
60 
61     /// If the Python object is a Python `None`, return a Rust `None`, otherwise `Some` with the mapped type
into_option<T>(self, map_obj: impl Fn(Self) -> T) -> Option<T>62     fn into_option<T>(self, map_obj: impl Fn(Self) -> T) -> Option<T> {
63         Python::with_gil(|py| {
64             if self.gil_ref(py).is_none() {
65                 None
66             } else {
67                 Some(map_obj(self))
68             }
69         })
70     }
71 }
72 
73 impl PyObjectExt for PyObject {
gil_ref<'py>(&'py self, py: Python<'py>) -> &'py PyAny74     fn gil_ref<'py>(&'py self, py: Python<'py>) -> &'py PyAny {
75         self.as_ref(py)
76     }
77 }
78 
79 /// Convenience extensions to [PyDict]
80 pub trait PyDictExt {
81     /// Set item in dict only if value is Some, otherwise do nothing.
set_opt_item<K: ToPyObject, V: ToPyObject>(&self, key: K, value: Option<V>) -> PyResult<()>82     fn set_opt_item<K: ToPyObject, V: ToPyObject>(&self, key: K, value: Option<V>) -> PyResult<()>;
83 }
84 
85 impl PyDictExt for PyDict {
set_opt_item<K: ToPyObject, V: ToPyObject>(&self, key: K, value: Option<V>) -> PyResult<()>86     fn set_opt_item<K: ToPyObject, V: ToPyObject>(&self, key: K, value: Option<V>) -> PyResult<()> {
87         if let Some(value) = value {
88             self.set_item(key, value)?
89         }
90         Ok(())
91     }
92 }
93 
94 /// Wrapper to make Rust closures ([Fn] implementations) callable from Python.
95 ///
96 /// The Python callable form returns a Python `None`.
97 #[pyclass(name = "SubscribeCallback")]
98 pub(crate) struct ClosureCallback {
99     // can't use generics in a pyclass, so have to box
100     #[allow(clippy::type_complexity)]
101     callback: Box<dyn Fn(Python, &PyTuple, Option<&PyDict>) -> PyResult<()> + Send + 'static>,
102 }
103 
104 impl ClosureCallback {
105     /// Create a new callback around the provided closure
new( callback: impl Fn(Python, &PyTuple, Option<&PyDict>) -> PyResult<()> + Send + 'static, ) -> Self106     pub fn new(
107         callback: impl Fn(Python, &PyTuple, Option<&PyDict>) -> PyResult<()> + Send + 'static,
108     ) -> Self {
109         Self {
110             callback: Box::new(callback),
111         }
112     }
113 }
114 
115 #[pymethods]
116 impl ClosureCallback {
117     #[pyo3(signature = (*args, **kwargs))]
__call__( &self, py: Python<'_>, args: &PyTuple, kwargs: Option<&PyDict>, ) -> PyResult<Py<PyAny>>118     fn __call__(
119         &self,
120         py: Python<'_>,
121         args: &PyTuple,
122         kwargs: Option<&PyDict>,
123     ) -> PyResult<Py<PyAny>> {
124         (self.callback)(py, args, kwargs).map(|_| py.None())
125     }
126 }
127 
128 /// Wraps the Python function in a Python async function. `pyo3_asyncio` needs functions to be
129 /// marked async to properly inject a running loop.
wrap_python_async<'a>(py: Python<'a>, function: &'a PyAny) -> PyResult<&'a PyAny>130 pub(crate) fn wrap_python_async<'a>(py: Python<'a>, function: &'a PyAny) -> PyResult<&'a PyAny> {
131     PyModule::import(py, intern!(py, "bumble.utils"))?
132         .getattr(intern!(py, "wrap_async"))?
133         .call1((function,))
134 }
135 
136 /// Represents the two major kinds of errors that can occur when converting between Rust and Python.
137 pub enum ConversionError<T> {
138     /// Occurs across the Python/native boundary.
139     Python(PyErr),
140     /// Occurs within the native ecosystem, such as when performing more transformations before
141     /// finally converting to the native type.
142     Native(T),
143 }
144