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
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::GILOnceCell;
72use crate::types::any::PyAnyMethods;
73use crate::types::PyType;
74use crate::{intern, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
75
76fn get_uuid_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
77    static UUID_CLS: GILOnceCell<Py<PyType>> = GILOnceCell::new();
78    UUID_CLS.import(py, "uuid", "UUID")
79}
80
81impl FromPyObject<'_> for Uuid {
82    fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
83        let py = obj.py();
84        let uuid_cls = get_uuid_cls(py)?;
85
86        if obj.is_instance(uuid_cls)? {
87            let uuid_int: u128 = obj.getattr(intern!(py, "int"))?.extract()?;
88            Ok(Uuid::from_u128(uuid_int.to_le()))
89        } else {
90            Err(PyTypeError::new_err("Expected a `uuid.UUID` instance."))
91        }
92    }
93}
94
95impl<'py> IntoPyObject<'py> for Uuid {
96    type Target = PyAny;
97    type Output = Bound<'py, Self::Target>;
98    type Error = PyErr;
99
100    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
101        let uuid_cls = get_uuid_cls(py)?;
102
103        uuid_cls.call1((py.None(), py.None(), py.None(), py.None(), self.as_u128()))
104    }
105}
106
107impl<'py> IntoPyObject<'py> for &Uuid {
108    type Target = PyAny;
109    type Output = Bound<'py, Self::Target>;
110    type Error = PyErr;
111
112    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
113        (*self).into_pyobject(py)
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::types::dict::PyDictMethods;
121    use crate::types::PyDict;
122    use std::ffi::CString;
123    use uuid::Uuid;
124
125    macro_rules! convert_constants {
126        ($name:ident, $rs:expr, $py:literal) => {
127            #[test]
128            fn $name() -> PyResult<()> {
129                Python::with_gil(|py| {
130                    let rs_orig = $rs;
131                    let rs_uuid = rs_orig.into_pyobject(py).unwrap();
132                    let locals = PyDict::new(py);
133                    locals.set_item("rs_uuid", &rs_uuid).unwrap();
134
135                    py.run(
136                        &CString::new(format!(
137                            "import uuid\npy_uuid = uuid.UUID('{}')\nassert py_uuid == rs_uuid",
138                            $py
139                        ))
140                        .unwrap(),
141                        None,
142                        Some(&locals),
143                    )
144                    .unwrap();
145
146                    let py_uuid = locals.get_item("py_uuid").unwrap().unwrap();
147                    let py_result: Uuid = py_uuid.extract().unwrap();
148                    assert_eq!(rs_orig, py_result);
149
150                    Ok(())
151                })
152            }
153        };
154    }
155
156    convert_constants!(
157        convert_nil,
158        Uuid::nil(),
159        "00000000-0000-0000-0000-000000000000"
160    );
161    convert_constants!(
162        convert_max,
163        Uuid::max(),
164        "ffffffff-ffff-ffff-ffff-ffffffffffff"
165    );
166
167    convert_constants!(
168        convert_uuid_v4,
169        Uuid::parse_str("a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00").unwrap(),
170        "a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00"
171    );
172
173    convert_constants!(
174        convert_uuid_v3,
175        Uuid::parse_str("6fa459ea-ee8a-3ca4-894e-db77e160355e").unwrap(),
176        "6fa459ea-ee8a-3ca4-894e-db77e160355e"
177    );
178
179    convert_constants!(
180        convert_uuid_v1,
181        Uuid::parse_str("a6cc5730-2261-11ee-9c43-2eb5a363657c").unwrap(),
182        "a6cc5730-2261-11ee-9c43-2eb5a363657c"
183    );
184}
⚠️ Internal Docs ⚠️ Not Public API 👉 Official Docs Here