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