|
| 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 | +//! ``` |
| 66 | +use uuid::Uuid; |
| 67 | + |
| 68 | +use crate::conversion::IntoPyObject; |
| 69 | +use crate::exceptions::PyTypeError; |
| 70 | +use crate::instance::Bound; |
| 71 | +use crate::sync::GILOnceCell; |
| 72 | +use crate::types::any::PyAnyMethods; |
| 73 | +use crate::types::PyType; |
| 74 | +use crate::{intern, FromPyObject, Py, PyAny, PyErr, PyResult, Python}; |
| 75 | + |
| 76 | +fn 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 | + |
| 81 | +impl 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 | + |
| 95 | +impl<'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 | + |
| 107 | +impl<'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)] |
| 118 | +mod 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 | +} |
0 commit comments