#![cfg(feature = "uuid")]
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"uuid\"] }")]
use uuid::{NonNilUuid, Uuid};
use crate::conversion::IntoPyObject;
use crate::exceptions::{PyTypeError, PyValueError};
use crate::instance::Bound;
use crate::sync::PyOnceLock;
use crate::types::any::PyAnyMethods;
use crate::types::PyType;
use crate::{intern, Borrowed, FromPyObject, Py, PyAny, PyErr, PyResult, Python};
fn get_uuid_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static UUID_CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
UUID_CLS.import(py, "uuid", "UUID")
}
impl FromPyObject<'_, '_> for Uuid {
type Error = PyErr;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
let py = obj.py();
let uuid_cls = get_uuid_cls(py)?;
if obj.is_instance(uuid_cls)? {
let uuid_int: u128 = obj.getattr(intern!(py, "int"))?.extract()?;
Ok(Uuid::from_u128(uuid_int))
} else {
Err(PyTypeError::new_err("Expected a `uuid.UUID` instance."))
}
}
}
impl<'py> IntoPyObject<'py> for Uuid {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
let uuid_cls = get_uuid_cls(py)?;
uuid_cls.call1((py.None(), py.None(), py.None(), py.None(), self.as_u128()))
}
}
impl<'py> IntoPyObject<'py> for &Uuid {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
impl FromPyObject<'_, '_> for NonNilUuid {
type Error = PyErr;
fn extract(obj: Borrowed<'_, '_, PyAny>) -> PyResult<Self> {
let uuid: Uuid = obj.extract()?;
NonNilUuid::new(uuid).ok_or_else(|| PyValueError::new_err("UUID is nil"))
}
}
impl<'py> IntoPyObject<'py> for NonNilUuid {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Uuid::from(self).into_pyobject(py)
}
}
impl<'py> IntoPyObject<'py> for &NonNilUuid {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
(*self).into_pyobject(py)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::dict::PyDictMethods;
use crate::types::PyDict;
use std::ffi::CString;
use uuid::Uuid;
macro_rules! convert_constants {
($name:ident, $rs:expr, $py:literal) => {
#[test]
fn $name() -> PyResult<()> {
Python::attach(|py| {
let rs_orig = $rs;
let rs_uuid = rs_orig.into_pyobject(py).unwrap();
let locals = PyDict::new(py);
locals.set_item("rs_uuid", &rs_uuid).unwrap();
py.run(
&CString::new(format!(
"import uuid\npy_uuid = uuid.UUID('{}')\nassert py_uuid == rs_uuid",
$py
))
.unwrap(),
None,
Some(&locals),
)
.unwrap();
let py_uuid = locals.get_item("py_uuid").unwrap().unwrap();
let py_result: Uuid = py_uuid.extract().unwrap();
assert_eq!(rs_orig, py_result);
Ok(())
})
}
};
}
convert_constants!(
convert_nil,
Uuid::nil(),
"00000000-0000-0000-0000-000000000000"
);
convert_constants!(
convert_max,
Uuid::max(),
"ffffffff-ffff-ffff-ffff-ffffffffffff"
);
convert_constants!(
convert_uuid_v4,
Uuid::parse_str("a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00").unwrap(),
"a4f6d1b9-1898-418f-b11d-ecc6fe1e1f00"
);
convert_constants!(
convert_uuid_v3,
Uuid::parse_str("6fa459ea-ee8a-3ca4-894e-db77e160355e").unwrap(),
"6fa459ea-ee8a-3ca4-894e-db77e160355e"
);
convert_constants!(
convert_uuid_v1,
Uuid::parse_str("a6cc5730-2261-11ee-9c43-2eb5a363657c").unwrap(),
"a6cc5730-2261-11ee-9c43-2eb5a363657c"
);
#[test]
fn test_non_nil_uuid() {
Python::attach(|py| {
let rs_uuid = NonNilUuid::new(Uuid::max()).unwrap();
let py_uuid = rs_uuid.into_pyobject(py).unwrap();
let extract_uuid: NonNilUuid = py_uuid.extract().unwrap();
assert_eq!(extract_uuid, rs_uuid);
let nil_uuid = Uuid::nil().into_pyobject(py).unwrap();
let extract_nil: PyResult<NonNilUuid> = nil_uuid.extract();
assert!(extract_nil.is_err());
})
}
}