1#![cfg(feature = "uuid")]
2
3#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"uuid\"] }")]
14use 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}