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 IntoPyObjectExt,
24 prelude::*,
25 pyclass::CompareOp,
26 types::{PyBytes, PyTuple},
27};
28
29use super::{IntoPyObjectNautilusExt, to_pyvalue_err};
30use crate::uuid::{UUID4, UUID4_LEN};
31
32#[pymethods]
33impl UUID4 {
34 /// Creates a new [`UUID4`] instance.
35 ///
36 /// If a string value is provided, it attempts to parse it into a UUID.
37 /// If no value is provided, a new random UUID is generated.
38 #[new]
39 fn py_new() -> Self {
40 Self::new()
41 }
42
43 /// Sets the state of the `UUID4` instance during unpickling.
44 fn __setstate__(&mut self, py: Python<'_>, state: PyObject) -> PyResult<()> {
45 let bytes: &Bound<'_, PyBytes> = state.downcast_bound::<PyBytes>(py)?;
46 let slice = bytes.as_bytes();
47
48 if slice.len() != UUID4_LEN {
49 return Err(to_pyvalue_err(
50 "Invalid state for deserialzing, incorrect bytes length",
51 ));
52 }
53
54 self.value.copy_from_slice(slice);
55 Ok(())
56 }
57
58 /// Gets the state of the `UUID4` instance for pickling.
59 fn __getstate__(&self, py: Python<'_>) -> PyResult<PyObject> {
60 PyBytes::new(py, &self.value).into_py_any(py)
61 }
62
63 /// Reduces the `UUID4` instance for pickling.
64 fn __reduce__(&self, py: Python<'_>) -> PyResult<PyObject> {
65 let safe_constructor = py.get_type::<Self>().getattr("_safe_constructor")?;
66 let state = self.__getstate__(py)?;
67 (safe_constructor, PyTuple::empty(py), state).into_py_any(py)
68 }
69
70 /// A safe constructor used during unpickling to ensure the correct initialization of `UUID4`.
71 #[staticmethod]
72 fn _safe_constructor() -> PyResult<Self> {
73 Ok(Self::new()) // Safe default
74 }
75
76 /// Compares two `UUID4` instances for equality
77 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
78 match op {
79 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
80 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
81 _ => py.NotImplemented(),
82 }
83 }
84
85 /// Returns a hash value for the `UUID4` instance.
86 fn __hash__(&self) -> isize {
87 let mut h = DefaultHasher::new();
88 self.hash(&mut h);
89 h.finish() as isize
90 }
91
92 /// Returns a detailed string representation of the `UUID4` instance.
93 fn __repr__(&self) -> String {
94 format!("{self:?}")
95 }
96
97 /// Returns the `UUID4` as a string.
98 fn __str__(&self) -> String {
99 self.to_string()
100 }
101
102 /// Gets the `UUID4` value as a string.
103 #[getter]
104 #[pyo3(name = "value")]
105 fn py_value(&self) -> String {
106 self.to_string()
107 }
108
109 /// Creates a new `UUID4` from a string representation.
110 #[staticmethod]
111 #[pyo3(name = "from_str")]
112 fn py_from_str(value: &str) -> PyResult<Self> {
113 Self::from_str(value).map_err(to_pyvalue_err)
114 }
115}