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::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}