1#![cfg(feature = "uuid")]
2
3#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"uuid\"] }")]
14use uuid::{NonNilUuid, Uuid};
67
68use crate::conversion::IntoPyObject;
69use crate::exceptions::{PyTypeError, PyValueError};
70#[cfg(feature = "experimental-inspect")]
71use crate::inspect::PyStaticExpr;
72use crate::instance::Bound;
73use crate::sync::PyOnceLock;
74#[cfg(feature = "experimental-inspect")]
75use crate::type_hint_identifier;
76use crate::types::any::PyAnyMethods;
77use crate::types::PyType;
78use crate::{intern, Borrowed, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
79
80fn get_uuid_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
81 static UUID_CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
82 UUID_CLS.import(py, "uuid", "UUID")
83}
84
85impl FromPyObject<'_, '_> for Uuid {
86 type Error = PyErr;
87
88 #[cfg(feature = "experimental-inspect")]
89 const INPUT_TYPE: PyStaticExpr = type_hint_identifier!("uuid", "UUID");
90
91 fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
92 let py = obj.py();
93 let uuid_cls = get_uuid_cls(py)?;
94
95 if obj.is_instance(uuid_cls)? {
96 let uuid_int: u128 = obj.getattr(intern!(py, "int"))?.extract()?;
97 Ok(Uuid::from_u128(uuid_int))
98 } else {
99 Err(PyTypeError::new_err("Expected a `uuid.UUID` instance."))
100 }
101 }
102}
103
104impl<'py> IntoPyObject<'py> for Uuid {
105 type Target = PyAny;
106 type Output = Bound<'py, Self::Target>;
107 type Error = PyErr;
108
109 #[cfg(feature = "experimental-inspect")]
110 const OUTPUT_TYPE: PyStaticExpr = type_hint_identifier!("uuid", "UUID");
111
112 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
113 let uuid_cls = get_uuid_cls(py)?;
114
115 uuid_cls.call1((py.None(), py.None(), py.None(), py.None(), self.as_u128()))
116 }
117}
118
119impl<'py> IntoPyObject<'py> for &Uuid {
120 type Target = PyAny;
121 type Output = Bound<'py, Self::Target>;
122 type Error = PyErr;
123
124 #[cfg(feature = "experimental-inspect")]
125 const OUTPUT_TYPE: PyStaticExpr = Uuid::OUTPUT_TYPE;
126
127 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
128 (*self).into_pyobject(py)
129 }
130}
131
132impl FromPyObject<'_, '_> for NonNilUuid {
133 type Error = PyErr;
134
135 #[cfg(feature = "experimental-inspect")]
136 const INPUT_TYPE: PyStaticExpr = Uuid::INPUT_TYPE;
137
138 fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
139 let uuid: Uuid = obj.extract()?;
140 NonNilUuid::new(uuid).ok_or_else(|| PyValueError::new_err("UUID is nil"))
141 }
142}
143
144impl<'py> IntoPyObject<'py> for NonNilUuid {
145 type Target = PyAny;
146 type Output = Bound<'py, Self::Target>;
147 type Error = PyErr;
148
149 #[cfg(feature = "experimental-inspect")]
150 const OUTPUT_TYPE: PyStaticExpr = Uuid::OUTPUT_TYPE;
151
152 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
153 Uuid::from(self).into_pyobject(py)
154 }
155}
156
157impl<'py> IntoPyObject<'py> for &NonNilUuid {
158 type Target = PyAny;
159 type Output = Bound<'py, Self::Target>;
160 type Error = PyErr;
161
162 #[cfg(feature = "experimental-inspect")]
163 const OUTPUT_TYPE: PyStaticExpr = NonNilUuid::OUTPUT_TYPE;
164
165 fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
166 (*self).into_pyobject(py)
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::types::dict::PyDictMethods;
174 use crate::types::PyDict;
175 use std::ffi::CString;
176 use uuid::Uuid;
177
178 macro_rules! convert_constants {
179 ($name:ident, $rs:expr, $py:literal) => {
180 #[test]
181 fn $name() -> PyResult<()> {
182 Python::attach(|py| {
183 let rs_orig = $rs;
184 let rs_uuid = rs_orig.into_pyobject(py).unwrap();
185 let locals = PyDict::new(py);
186 locals.set_item("rs_uuid", &rs_uuid).unwrap();
187
188 py.run(
189 &CString::new(format!(
190 "import uuid\npy_uuid = uuid.UUID('{}')\nassert py_uuid == rs_uuid",
191 $py
192 ))
193 .unwrap(),
194 None,
195 Some(&locals),
196 )
197 .unwrap();
198
199 let py_uuid = locals.get_item("py_uuid").unwrap().unwrap();
200 let py_result: Uuid = py_uuid.extract().unwrap();
201 assert_eq!(rs_orig, py_result);
202
203 Ok(())
204 })
205 }
206 };
207 }
208
209 convert_constants!(
210 convert_nil,
211 Uuid::nil(),
212 "00000000-0000-0000-0000-000000000000"
213 );
214 convert_constants!(
215 convert_max,
216 Uuid::max(),
217 "ffffffff-ffff-ffff-ffff-ffffffffffff"
218 );
219
220 convert_constants!(
221 convert_uuid_v4,
222 Uuid::parse_str("a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00").unwrap(),
223 "a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00"
224 );
225
226 convert_constants!(
227 convert_uuid_v3,
228 Uuid::parse_str("6fa459ea-ee8a-3ca4-894e-db77e160355e").unwrap(),
229 "6fa459ea-ee8a-3ca4-894e-db77e160355e"
230 );
231
232 convert_constants!(
233 convert_uuid_v1,
234 Uuid::parse_str("a6cc5730-2261-11ee-9c43-2eb5a363657c").unwrap(),
235 "a6cc5730-2261-11ee-9c43-2eb5a363657c"
236 );
237
238 #[test]
239 fn test_non_nil_uuid() {
240 Python::attach(|py| {
241 let rs_uuid = NonNilUuid::new(Uuid::max()).unwrap();
242 let py_uuid = rs_uuid.into_pyobject(py).unwrap();
243
244 let extract_uuid: NonNilUuid = py_uuid.extract().unwrap();
245 assert_eq!(extract_uuid, rs_uuid);
246
247 let nil_uuid = Uuid::nil().into_pyobject(py).unwrap();
248 let extract_nil: PyResult<NonNilUuid> = nil_uuid.extract();
249 assert!(extract_nil.is_err());
250 })
251 }
252}