nautilus_core/python/uuid.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{
17 collections::hash_map::DefaultHasher,
18 hash::{Hash, Hasher},
19 str::FromStr,
20};
21
22use pyo3::{
23 prelude::*,
24 pyclass::CompareOp,
25 types::{PyBytes, PyTuple},
26};
27
28use super::to_pyvalue_err;
29use crate::uuid::{UUID4, UUID4_LEN};
30
31#[pymethods]
32impl UUID4 {
33 /// Creates a new [`UUID4`] instance.
34 ///
35 /// If a string value is provided, it attempts to parse it into a UUID.
36 /// If no value is provided, a new random UUID is generated.
37 #[new]
38 fn py_new() -> Self {
39 Self::new()
40 }
41
42 /// Sets the state of the `UUID4` instance during unpickling.
43 fn __setstate__(&mut self, py: Python<'_>, state: PyObject) -> PyResult<()> {
44 let bytes: &Bound<'_, PyBytes> = state.downcast_bound::<PyBytes>(py)?;
45 let slice = bytes.as_bytes();
46
47 if slice.len() != UUID4_LEN {
48 return Err(to_pyvalue_err(
49 "Invalid state for deserialzing, incorrect bytes length",
50 ));
51 }
52
53 self.value.copy_from_slice(slice);
54 Ok(())
55 }
56
57 /// Gets the state of the `UUID4` instance for pickling.
58 fn __getstate__(&self, py: Python<'_>) -> PyResult<PyObject> {
59 Ok(PyBytes::new(py, &self.value).to_object(py))
60 }
61
62 /// Reduces the `UUID4` instance for pickling.
63 fn __reduce__(&self, py: Python<'_>) -> PyResult<PyObject> {
64 let safe_constructor = py.get_type::<Self>().getattr("_safe_constructor")?;
65 let state = self.__getstate__(py)?;
66 Ok((safe_constructor, PyTuple::empty(py), state).to_object(py))
67 }
68
69 /// A safe constructor used during unpickling to ensure the correct initialization of `UUID4`.
70 #[staticmethod]
71 fn _safe_constructor() -> PyResult<Self> {
72 Ok(Self::new()) // Safe default
73 }
74
75 /// Compares two `UUID4` instances for equality
76 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
77 match op {
78 CompareOp::Eq => self.eq(other).into_py(py),
79 CompareOp::Ne => self.ne(other).into_py(py),
80 _ => py.NotImplemented(),
81 }
82 }
83
84 /// Returns a hash value for the `UUID4` instance.
85 fn __hash__(&self) -> isize {
86 let mut h = DefaultHasher::new();
87 self.hash(&mut h);
88 h.finish() as isize
89 }
90
91 /// Returns a detailed string representation of the `UUID4` instance.
92 fn __repr__(&self) -> String {
93 format!("{self:?}")
94 }
95
96 /// Returns the `UUID4` as a string.
97 fn __str__(&self) -> String {
98 self.to_string()
99 }
100
101 /// Gets the `UUID4` value as a string.
102 #[getter]
103 #[pyo3(name = "value")]
104 fn py_value(&self) -> String {
105 self.to_string()
106 }
107
108 /// Creates a new `UUID4` from a string representation.
109 #[staticmethod]
110 #[pyo3(name = "from_str")]
111 fn py_from_str(value: &str) -> PyResult<Self> {
112 Self::from_str(value).map_err(to_pyvalue_err)
113 }
114}