pyo3/conversions/
uuid.rs

1#![cfg(feature = "uuid")]
2
3//! Conversions to and from [uuid](https://docs.rs/uuid/latest/uuid/)'s [`Uuid`] type.
4//!
5//! This is useful for converting Python's uuid.UUID into and from a native Rust type.
6//!
7//! # Setup
8//!
9//! To use this feature, add to your **`Cargo.toml`**:
10//!
11//! ```toml
12//! [dependencies]
13#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"),  "\", features = [\"uuid\"] }")]
14//! uuid = "1.11.0"
15//! ```
16//!
17//! Note that you must use a compatible version of uuid and PyO3.
18//! The required uuid version may vary based on the version of PyO3.
19//!
20//! # Example
21//!
22//! Rust code to create a function that parses a UUID string and returns it as a `Uuid`:
23//!
24//! ```rust,no_run
25//! use pyo3::prelude::*;
26//! use pyo3::exceptions::PyValueError;
27//! use uuid::Uuid;
28//!
29//! /// Parse a UUID from a string.
30//! #[pyfunction]
31//! fn get_uuid_from_str(s: &str) -> PyResult<Uuid> {
32//!     Uuid::parse_str(s).map_err(|e| PyValueError::new_err(e.to_string()))
33//! }
34//!
35//! /// Passing a Python uuid.UUID directly to Rust.
36//! #[pyfunction]
37//! fn get_uuid(u: Uuid) -> Uuid {
38//!     u
39//! }
40//!
41//! #[pymodule]
42//! fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
43//!     m.add_function(wrap_pyfunction!(get_uuid_from_str, m)?)?;
44//!     m.add_function(wrap_pyfunction!(get_uuid, m)?)?;
45//!     Ok(())
46//! }
47//! ```
48//!
49//! Python code that validates the functionality
50//!
51//!
52//! ```python
53//! from my_module import get_uuid_from_str, get_uuid
54//! import uuid
55//!
56//! py_uuid = uuid.uuid4()
57//!
58//! # Convert string to Rust Uuid
59//! rust_uuid = get_uuid_from_str(str(py_uuid))
60//! assert py_uuid == rust_uuid
61//!
62//! # Pass Python UUID directly to Rust
63//! returned_uuid = get_uuid(py_uuid)
64//! assert py_uuid == returned_uuid
65//! ```
66use uuid::Uuid;
67
68use crate::conversion::IntoPyObject;
69use crate::exceptions::PyTypeError;
70use crate::instance::Bound;
71use crate::sync::PyOnceLock;
72use crate::types::any::PyAnyMethods;
73use crate::types::PyType;
74use crate::{intern, Borrowed, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
75
76fn get_uuid_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
77    static UUID_CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
78    UUID_CLS.import(py, "uuid", "UUID")
79}
80
81impl FromPyObject<'_, '_> for Uuid {
82    type Error = PyErr;
83
84    fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
85        let py = obj.py();
86        let uuid_cls = get_uuid_cls(py)?;
87
88        if obj.is_instance(uuid_cls)? {
89            let uuid_int: u128 = obj.getattr(intern!(py, "int"))?.extract()?;
90            Ok(Uuid::from_u128(uuid_int))
91        } else {
92            Err(PyTypeError::new_err("Expected a `uuid.UUID` instance."))
93        }
94    }
95}
96
97impl<'py> IntoPyObject<'py> for Uuid {
98    type Target = PyAny;
99    type Output = Bound<'py, Self::Target>;
100    type Error = PyErr;
101
102    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
103        let uuid_cls = get_uuid_cls(py)?;
104
105        uuid_cls.call1((py.None(), py.None(), py.None(), py.None(), self.as_u128()))
106    }
107}
108
109impl<'py> IntoPyObject<'py> for &Uuid {
110    type Target = PyAny;
111    type Output = Bound<'py, Self::Target>;
112    type Error = PyErr;
113
114    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
115        (*self).into_pyobject(py)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::types::dict::PyDictMethods;
123    use crate::types::PyDict;
124    use std::ffi::CString;
125    use uuid::Uuid;
126
127    macro_rules! convert_constants {
128        ($name:ident, $rs:expr, $py:literal) => {
129            #[test]
130            fn $name() -> PyResult<()> {
131                Python::attach(|py| {
132                    let rs_orig = $rs;
133                    let rs_uuid = rs_orig.into_pyobject(py).unwrap();
134                    let locals = PyDict::new(py);
135                    locals.set_item("rs_uuid", &rs_uuid).unwrap();
136
137                    py.run(
138                        &CString::new(format!(
139                            "import uuid\npy_uuid = uuid.UUID('{}')\nassert py_uuid == rs_uuid",
140                            $py
141                        ))
142                        .unwrap(),
143                        None,
144                        Some(&locals),
145                    )
146                    .unwrap();
147
148                    let py_uuid = locals.get_item("py_uuid").unwrap().unwrap();
149                    let py_result: Uuid = py_uuid.extract().unwrap();
150                    assert_eq!(rs_orig, py_result);
151
152                    Ok(())
153                })
154            }
155        };
156    }
157
158    convert_constants!(
159        convert_nil,
160        Uuid::nil(),
161        "00000000-0000-0000-0000-000000000000"
162    );
163    convert_constants!(
164        convert_max,
165        Uuid::max(),
166        "ffffffff-ffff-ffff-ffff-ffffffffffff"
167    );
168
169    convert_constants!(
170        convert_uuid_v4,
171        Uuid::parse_str("a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00").unwrap(),
172        "a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00"
173    );
174
175    convert_constants!(
176        convert_uuid_v3,
177        Uuid::parse_str("6fa459ea-ee8a-3ca4-894e-db77e160355e").unwrap(),
178        "6fa459ea-ee8a-3ca4-894e-db77e160355e"
179    );
180
181    convert_constants!(
182        convert_uuid_v1,
183        Uuid::parse_str("a6cc5730-2261-11ee-9c43-2eb5a363657c").unwrap(),
184        "a6cc5730-2261-11ee-9c43-2eb5a363657c"
185    );
186}