Skip to content

Commit d8b9b3e

Browse files
committed
working on uuid type
1 parent 4767abf commit d8b9b3e

File tree

11 files changed

+221
-0
lines changed

11 files changed

+221
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ url = "2.3.1"
4040
# idna is already required by url, added here to be explicit
4141
idna = "0.3.0"
4242
base64 = "0.13.1"
43+
uuid = "1.3.2"
4344

4445
[lib]
4546
name = "_pydantic_core"

pydantic_core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
SchemaSerializer,
1414
SchemaValidator,
1515
Url,
16+
Uuid,
1617
ValidationError,
1718
__version__,
1819
to_json,
@@ -38,6 +39,7 @@
3839
'SchemaValidator',
3940
'SchemaSerializer',
4041
'Url',
42+
'Uuid',
4143
'MultiHostUrl',
4244
'ArgsKwargs',
4345
'SchemaError',

pydantic_core/_pydantic_core.pyi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ __all__ = (
2424
'SchemaSerializer',
2525
'Url',
2626
'MultiHostUrl',
27+
'Uuid',
2728
'SchemaError',
2829
'ValidationError',
2930
'PydanticCustomError',
@@ -174,6 +175,17 @@ class MultiHostUrl(SupportsAllComparisons):
174175
def __str__(self) -> str: ...
175176
def __repr__(self) -> str: ...
176177

178+
class Uuid(SupportsAllComparisons):
179+
@property
180+
def urn(self) -> str: ...
181+
@property
182+
def variant(self) -> str: ...
183+
@property
184+
def version(self) -> str: ...
185+
def __init__(self, uuid: str) -> None: ...
186+
def __str__(self) -> str: ...
187+
def __repr__(self) -> str: ...
188+
177189
class SchemaError(Exception):
178190
def error_count(self) -> int: ...
179191
def errors(self) -> 'list[ErrorDetails]': ...

pydantic_core/core_schema.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3503,6 +3503,38 @@ def multi_host_url_schema(
35033503
)
35043504

35053505

3506+
class UuidSchema(TypedDict, total=False):
3507+
type: Required[Literal['uuid']]
3508+
version: int
3509+
ref: str
3510+
metadata: Any
3511+
serialization: SerSchema
3512+
3513+
3514+
def uuid_schema(
3515+
*, version: int | None = None, ref: str | None = None, metadata: Any = None, serialization: SerSchema | None = None
3516+
) -> UuidSchema:
3517+
"""
3518+
Returns a schema that matches a UUID value, e.g.:
3519+
3520+
```py
3521+
from pydantic_core import SchemaValidator, core_schema
3522+
3523+
schema = core_schema.uuid_schema()
3524+
v = SchemaValidator(schema)
3525+
print(v.validate_python('12345678-1234-5678-1234-567812345678'))
3526+
#> 12345678-1234-5678-1234-567812345678
3527+
```
3528+
3529+
Args:
3530+
version: The UUID version number as per RFC 4122
3531+
ref: optional unique identifier of the schema, used to reference the schema in other places
3532+
metadata: Any other information you want to include with the schema, not used by pydantic-core
3533+
serialization: Custom serialization schema
3534+
"""
3535+
return dict_not_none(type='uuid', version=version, ref=ref, metadata=metadata, serialization=serialization)
3536+
3537+
35063538
class DefinitionsSchema(TypedDict, total=False):
35073539
type: Required[Literal['definitions']]
35083540
schema: Required[CoreSchema]
@@ -3613,6 +3645,7 @@ def definition_reference_schema(
36133645
JsonSchema,
36143646
UrlSchema,
36153647
MultiHostUrlSchema,
3648+
UuidSchema,
36163649
DefinitionsSchema,
36173650
DefinitionReferenceSchema,
36183651
]
@@ -3665,6 +3698,7 @@ def definition_reference_schema(
36653698
'json',
36663699
'url',
36673700
'multi-host-url',
3701+
'uuid',
36683702
'definitions',
36693703
'definition-ref',
36703704
]
@@ -3757,4 +3791,5 @@ def definition_reference_schema(
37573791
'url_syntax_violation',
37583792
'url_too_long',
37593793
'url_scheme',
3794+
# TODO(martinabeleda): add error types
37603795
]

src/errors/types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ pub enum ErrorType {
294294
UrlScheme {
295295
expected_schemes: String,
296296
},
297+
// ---------------------
298+
// UUID errors
299+
// TODO(martinabeleda): define errors
297300
}
298301

299302
macro_rules! render {

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ mod lookup_key;
1919
mod recursion_guard;
2020
mod serializers;
2121
mod url;
22+
mod uuid;
2223
mod validators;
2324

2425
// required for benchmarks
2526
pub use self::url::{PyMultiHostUrl, PyUrl};
27+
pub use self::uuid::PyUuid;
2628
pub use argument_markers::ArgsKwargs;
2729
pub use build_tools::SchemaError;
2830
pub use errors::{list_all_errors, PydanticCustomError, PydanticKnownError, PydanticOmit, ValidationError};
@@ -55,6 +57,7 @@ fn _pydantic_core(_py: Python, m: &PyModule) -> PyResult<()> {
5557
m.add_class::<PydanticSerializationUnexpectedValue>()?;
5658
m.add_class::<PyUrl>()?;
5759
m.add_class::<PyMultiHostUrl>()?;
60+
m.add_class::<PyUuid>()?;
5861
m.add_class::<ArgsKwargs>()?;
5962
m.add_class::<SchemaSerializer>()?;
6063
m.add_function(wrap_pyfunction!(to_json, m)?)?;

src/uuid.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use std::collections::hash_map::DefaultHasher;
2+
use std::hash::{Hash, Hasher};
3+
4+
use pyo3::once_cell::GILOnceCell;
5+
use pyo3::prelude::*;
6+
use pyo3::pyclass::CompareOp;
7+
use pyo3::types::PyDict;
8+
use uuid::Uuid;
9+
10+
use crate::SchemaValidator;
11+
12+
static SCHEMA_DEFINITION_UUID: GILOnceCell<SchemaValidator> = GILOnceCell::new();
13+
14+
#[pyclass(name = "Uuid", module = "pydantic_core._pydantic_core")]
15+
#[derive(Clone)]
16+
#[cfg_attr(debug_assertions, derive(Debug))]
17+
pub struct PyUuid {
18+
lib_uuid: Uuid,
19+
}
20+
21+
impl PyUuid {
22+
pub fn new(lib_uuid: Uuid) -> Self {
23+
Self { lib_uuid }
24+
}
25+
26+
pub fn into_uuid(self) -> Uuid {
27+
self.lib_uuid
28+
}
29+
}
30+
31+
fn build_schema_validator(py: Python, schema_type: &str) -> SchemaValidator {
32+
let schema: &PyDict = PyDict::new(py);
33+
schema.set_item("type", schema_type).unwrap();
34+
SchemaValidator::py_new(py, schema, None).unwrap()
35+
}
36+
37+
#[pymethods]
38+
impl PyUuid {
39+
#[new]
40+
pub fn py_new(py: Python, uuid: &PyAny) -> PyResult<Self> {
41+
let schema_obj = SCHEMA_DEFINITION_UUID
42+
.get_or_init(py, || build_schema_validator(py, "uuid"))
43+
.validate_python(py, uuid, None, None, None)?;
44+
schema_obj.extract(py)
45+
}
46+
47+
#[getter]
48+
pub fn urn(&self) -> &str {
49+
// self.lib_uuid.urn().encode_lower(&mut Uuid::encode_buffer())
50+
"foo"
51+
}
52+
53+
#[getter]
54+
pub fn variant(&self) -> &str {
55+
// self.lib_uuid.get_variant().to_string().as_str()
56+
"bar"
57+
}
58+
59+
#[getter]
60+
pub fn version(&self) -> usize {
61+
self.lib_uuid.get_version_num()
62+
}
63+
64+
pub fn __str__(&self) -> &str {
65+
// self.lib_uuid.hyphenated().to_string().as_str()
66+
"baz"
67+
}
68+
69+
pub fn __repr__(&self) -> String {
70+
format!("Uuid('{}')", self.lib_uuid)
71+
}
72+
73+
fn __richcmp__(&self, other: &Self, op: CompareOp) -> PyResult<bool> {
74+
match op {
75+
CompareOp::Lt => Ok(self.lib_uuid < other.lib_uuid),
76+
CompareOp::Le => Ok(self.lib_uuid <= other.lib_uuid),
77+
CompareOp::Eq => Ok(self.lib_uuid == other.lib_uuid),
78+
CompareOp::Ne => Ok(self.lib_uuid != other.lib_uuid),
79+
CompareOp::Gt => Ok(self.lib_uuid > other.lib_uuid),
80+
CompareOp::Ge => Ok(self.lib_uuid >= other.lib_uuid),
81+
}
82+
}
83+
84+
fn __hash__(&self) -> u64 {
85+
let mut s = DefaultHasher::new();
86+
self.lib_uuid.to_string().hash(&mut s);
87+
s.finish()
88+
}
89+
90+
fn __bool__(&self) -> bool {
91+
true // an empty string is not a valid UUID
92+
}
93+
94+
pub fn __deepcopy__(&self, py: Python, _memo: &PyDict) -> Py<PyAny> {
95+
self.clone().into_py(py)
96+
}
97+
}

src/validators/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ mod tuple;
5050
mod typed_dict;
5151
mod union;
5252
mod url;
53+
mod uuid;
5354
mod with_default;
5455

5556
pub use with_default::DefaultType;
@@ -433,6 +434,8 @@ pub fn build_validator<'a>(
433434
// url types
434435
url::UrlValidator,
435436
url::MultiHostUrlValidator,
437+
// uuid types
438+
uuid::UuidValidator,
436439
// recursive (self-referencing) models
437440
definitions::DefinitionRefValidator,
438441
definitions::DefinitionsValidatorBuilder,
@@ -565,6 +568,8 @@ pub enum CombinedValidator {
565568
// url types
566569
Url(url::UrlValidator),
567570
MultiHostUrl(url::MultiHostUrlValidator),
571+
// uuid types
572+
Uuid(uuid::UuidValidator),
568573
// reference to definition, useful for recursive (self-referencing) models
569574
DefinitionRef(definitions::DefinitionRefValidator),
570575
}

src/validators/uuid.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use pyo3::prelude::*;
2+
use pyo3::types::PyDict;
3+
4+
use crate::errors::ValResult;
5+
use crate::input::Input;
6+
use crate::recursion_guard::RecursionGuard;
7+
8+
use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator};
9+
10+
#[derive(Debug, Clone)]
11+
pub struct UuidValidator;
12+
13+
impl BuildValidator for UuidValidator {
14+
const EXPECTED_TYPE: &'static str = "uuid";
15+
16+
fn build(
17+
_schema: &PyDict,
18+
_config: Option<&PyDict>,
19+
_definitions: &mut DefinitionsBuilder<CombinedValidator>,
20+
) -> PyResult<CombinedValidator> {
21+
Ok(Self.into())
22+
}
23+
}
24+
25+
impl Validator for UuidValidator {
26+
fn validate<'s, 'data>(
27+
&'s self,
28+
py: Python<'data>,
29+
input: &'data impl Input<'data>,
30+
_extra: &Extra,
31+
_definitions: &'data Definitions<CombinedValidator>,
32+
_recursion_guard: &'s mut RecursionGuard,
33+
) -> ValResult<'data, PyObject> {
34+
// Ok(input.clone().into_py(py))
35+
Ok(input.to_object(py))
36+
}
37+
38+
fn different_strict_behavior(
39+
&self,
40+
_definitions: Option<&DefinitionsBuilder<CombinedValidator>>,
41+
_ultra_strict: bool,
42+
) -> bool {
43+
false
44+
}
45+
46+
fn get_name(&self) -> &str {
47+
Self::EXPECTED_TYPE
48+
}
49+
50+
fn complete(&mut self, _definitions: &DefinitionsBuilder<CombinedValidator>) -> PyResult<()> {
51+
Ok(())
52+
}
53+
}

tests/test_schema_functions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ def test_all_schema_functions_used():
279279
types_used.remove('typed-dict-field')
280280
types_used.remove('model-field')
281281

282+
# TODO(martinabeleda): use this schema
283+
all_types.remove('uuid')
284+
282285
assert all_types == types_used
283286

284287

0 commit comments

Comments
 (0)