Skip to content

Commit b80f4d6

Browse files
committed
refactor: serializer and validators
1 parent 326fdb8 commit b80f4d6

File tree

15 files changed

+353
-97
lines changed

15 files changed

+353
-97
lines changed

python/pydantic_core/core_schema.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,7 @@ def callable_schema(
11921192
class UuidSchema(TypedDict, total=False):
11931193
type: Required[Literal['uuid']]
11941194
version: Literal[1, 3, 4, 5]
1195+
strict: bool
11951196
ref: str
11961197
metadata: Any
11971198
serialization: SerSchema
@@ -1200,11 +1201,14 @@ class UuidSchema(TypedDict, total=False):
12001201
def uuid_schema(
12011202
*,
12021203
version: Literal[1, 3, 4, 5] | None = None,
1204+
strict: bool | None = None,
12031205
ref: str | None = None,
12041206
metadata: Any = None,
12051207
serialization: SerSchema | None = None,
12061208
) -> UuidSchema:
1207-
return _dict_not_none(type='uuid', ref=ref, version=version, metadata=metadata, serialization=serialization)
1209+
return _dict_not_none(
1210+
type='uuid', version=version, strict=strict, ref=ref, metadata=metadata, serialization=serialization
1211+
)
12081212

12091213

12101214
class IncExSeqSerSchema(TypedDict, total=False):
@@ -3708,7 +3712,6 @@ def definition_reference_schema(
37083712
IsInstanceSchema,
37093713
IsSubclassSchema,
37103714
CallableSchema,
3711-
UuidSchema,
37123715
ListSchema,
37133716
TuplePositionalSchema,
37143717
TupleVariableSchema,
@@ -3740,6 +3743,7 @@ def definition_reference_schema(
37403743
MultiHostUrlSchema,
37413744
DefinitionsSchema,
37423745
DefinitionReferenceSchema,
3746+
UuidSchema,
37433747
]
37443748
elif False:
37453749
CoreSchema: TypeAlias = Mapping[str, Any]
@@ -3890,6 +3894,7 @@ def definition_reference_schema(
38903894
'url_too_long',
38913895
'url_scheme',
38923896
'uuid_type',
3897+
'uuid_exact_type',
38933898
'uuid_parsing',
38943899
'uuid_version_mismatch',
38953900
]

src/errors/types.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,9 +573,9 @@ impl ErrorType {
573573
Self::UrlTooLong {..} => "URL should have at most {max_length} characters",
574574
Self::UrlScheme {..} => "URL scheme should be {expected_schemes}",
575575
Self::UuidExactType { .. } => "Input should be an instance of {class_name}",
576-
Self::UuidType => "UUID input should be a string or UUID object",
576+
Self::UuidType => "UUID input should be a string, bytes, integer or UUID object",
577577
Self::UuidParsing { .. } => "Input should be a valid UUID, {error}",
578-
Self::UuidVersionMismatch { .. } => "UUID version {version} doest not match expected version: {schema_version}"
578+
Self::UuidVersionMismatch { .. } => "UUID version {version} does not match expected version: {schema_version}"
579579

580580
}
581581
}

src/input/input_abstract.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::fmt;
2-
use uuid::Uuid;
32

43
use pyo3::types::{PyDict, PyType};
54
use pyo3::{intern, prelude::*};

src/input/input_python.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::borrow::Cow;
22
use std::str::from_utf8;
3-
use uuid::Uuid;
43

54
use pyo3::prelude::*;
65
use pyo3::types::{
@@ -347,7 +346,6 @@ impl<'a> Input<'a> for PyAny {
347346
Err(ValError::new(ErrorType::FloatType, self))
348347
}
349348
}
350-
351349
fn strict_dict(&'a self) -> ValResult<GenericMapping<'a>> {
352350
if let Ok(dict) = self.downcast::<PyDict>() {
353351
Ok(dict.into())

src/input/return_enums.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use std::slice::Iter as SliceIter;
55
use std::str::FromStr;
66

77
use num_bigint::BigInt;
8-
use uuid::Uuid;
98

109
use pyo3::exceptions::PyTypeError;
1110
use pyo3::prelude::*;
@@ -854,6 +853,27 @@ impl<'a> EitherInt<'a> {
854853
}
855854
}
856855

856+
pub fn into_u128(self, py: Python<'a>) -> ValResult<'a, u128> {
857+
match self {
858+
EitherInt::I64(i) => match u128::try_from(i) {
859+
Ok(u) => Ok(u),
860+
Err(_) => Err(ValError::new(ErrorType::IntParsingSize, i.into_py(py).into_ref(py))),
861+
},
862+
EitherInt::U64(u) => match u128::try_from(u) {
863+
Ok(u) => Ok(u),
864+
Err(_) => Err(ValError::new(ErrorType::IntParsingSize, u.into_py(py).into_ref(py))),
865+
},
866+
EitherInt::BigInt(u) => match u128::try_from(u) {
867+
Ok(u) => Ok(u),
868+
Err(e) => Err(ValError::new(
869+
ErrorType::IntParsingSize,
870+
e.into_original().into_py(py).into_ref(py),
871+
)),
872+
},
873+
EitherInt::Py(i) => i.extract().map_err(|_| ValError::new(ErrorType::IntParsingSize, i)),
874+
}
875+
}
876+
857877
pub fn as_int(&self) -> ValResult<'a, Int> {
858878
match self {
859879
EitherInt::I64(i) => Ok(Int::I64(*i)),

src/serializers/infer.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use serde::ser::{Error, Serialize, SerializeMap, SerializeSeq, Serializer};
1313
use crate::input::Int;
1414
use crate::serializers::errors::SERIALIZATION_ERR_MARKER;
1515
use crate::serializers::filter::SchemaFilter;
16+
use crate::serializers::shared::uuid_to_dict;
1617
use crate::serializers::shared::{PydanticSerializer, TypeSerializer};
1718
use crate::serializers::SchemaSerializer;
1819
use crate::tools::{extract_i64, py_err, safe_repr};
@@ -187,10 +188,7 @@ pub(crate) fn infer_to_python_known(
187188
let py_url: PyMultiHostUrl = value.extract()?;
188189
py_url.__str__().into_py(py)
189190
}
190-
ObType::Uuid => {
191-
let v = value.getattr(intern!(py, "value"))?;
192-
infer_to_python(v, include, exclude, extra)?.into_py(py)
193-
}
191+
ObType::Uuid => serialize_dict(uuid_to_dict(value)?)?,
194192
ObType::PydanticSerializable => serialize_with_serializer()?,
195193
ObType::Dataclass => serialize_dict(dataclass_to_dict(value)?)?,
196194
ObType::Enum => {
@@ -487,6 +485,7 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
487485
pydantic_serializer.serialize(serializer)
488486
}
489487
ObType::Dataclass => serialize_dict!(dataclass_to_dict(value).map_err(py_err_se_err)?),
488+
ObType::Uuid => serialize_dict!(uuid_to_dict(value).map_err(py_err_se_err)?),
490489
ObType::Enum => {
491490
let v = value.getattr(intern!(value.py(), "value")).map_err(py_err_se_err)?;
492491
infer_serialize(v, serializer, include, exclude, extra)
@@ -507,10 +506,6 @@ pub(crate) fn infer_serialize_known<S: Serializer>(
507506
}
508507
seq.end()
509508
}
510-
ObType::Uuid => {
511-
let s = value.str().map_err(py_err_se_err)?.to_str().map_err(py_err_se_err)?;
512-
serializer.serialize_str(s)
513-
}
514509
ObType::Path => {
515510
let s = value.str().map_err(py_err_se_err)?.to_str().map_err(py_err_se_err)?;
516511
serializer.serialize_str(s)

src/serializers/ob_type.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ impl ObTypeLookup {
8585
enum_type: py.import("enum").unwrap().getattr("Enum").unwrap().get_type_ptr() as usize,
8686
generator: py.import("types").unwrap().getattr("GeneratorType").unwrap().as_ptr() as usize,
8787
path: py.import("pathlib").unwrap().getattr("Path").unwrap().as_ptr() as usize,
88-
uuid: py.import("uuid").unwrap().getattr("UUID").unwrap().get_type_ptr() as usize,
88+
uuid: py.import("uuid").unwrap().getattr("UUID").unwrap().as_ptr() as usize,
8989
}
9090
}
9191

@@ -133,7 +133,7 @@ impl ObTypeLookup {
133133
ObType::Enum => self.enum_type == ob_type,
134134
ObType::Generator => self.generator == ob_type,
135135
ObType::Path => self.path == ob_type,
136-
ObType::Uuid => self.uuid == ob_type,
136+
ObType::Uuid => is_uuid(op_value),
137137
ObType::Unknown => false,
138138
};
139139

@@ -222,7 +222,7 @@ impl ObTypeLookup {
222222
ObType::Generator
223223
} else if ob_type == self.path {
224224
ObType::Path
225-
} else if ob_type == self.uuid {
225+
} else if ob_type == self.uuid || is_uuid(op_value) {
226226
ObType::Uuid
227227
} else {
228228
// this allows for subtypes of the supported class types,
@@ -262,6 +262,13 @@ fn is_dataclass(op_value: Option<&PyAny>) -> bool {
262262
}
263263
}
264264

265+
fn is_uuid(op_value: Option<&PyAny>) -> bool {
266+
if let Some(value) = op_value {
267+
value.hasattr(intern!(value.py(), "int")).unwrap_or(false)
268+
} else {
269+
false
270+
}
271+
}
265272
fn is_pydantic_serializable(op_value: Option<&PyAny>) -> bool {
266273
if let Some(value) = op_value {
267274
value

src/serializers/shared.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,15 @@ pub(super) fn dataclass_to_dict(dc: &PyAny) -> PyResult<&PyDict> {
357357
}
358358
Ok(dict)
359359
}
360+
361+
pub(super) fn uuid_to_dict(dc: &PyAny) -> PyResult<&PyDict> {
362+
let py = dc.py();
363+
let dc_slots: &PyDict = dc.getattr(intern!(py, "__slots__"))?.downcast()?;
364+
let dict = PyDict::new(py);
365+
366+
for (field_name, _) in dc_slots.iter() {
367+
let field_name: &PyString = field_name.downcast()?;
368+
dict.set_item(field_name, dc.getattr(field_name)?)?;
369+
}
370+
Ok(dict)
371+
}

src/serializers/type_serializers/string.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ impl TypeSerializer for StrSerializer {
3434
extra: &Extra,
3535
) -> PyResult<PyObject> {
3636
let py = value.py();
37-
dbg!("to python");
3837
match extra.ob_type_lookup.is_type(value, ObType::Str) {
3938
IsType::Exact => Ok(value.into_py(py)),
4039
IsType::Subclass => match extra.mode {

src/serializers/type_serializers/uuid.rs

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::borrow::Cow;
22

3-
use pyo3::prelude::*;
4-
use pyo3::types::{PyDict, PyString};
3+
use pyo3::types::PyDict;
4+
use pyo3::{intern, prelude::*};
55

66
use crate::definitions::DefinitionsBuilder;
77

@@ -10,6 +10,20 @@ use super::{
1010
IsType, ObType, SerMode, TypeSerializer,
1111
};
1212

13+
pub(crate) fn uuid_to_string(py_uuid: &PyAny) -> PyResult<String> {
14+
let py = py_uuid.py();
15+
let int: i128 = py_uuid.getattr(intern!(py, "int"))?.extract()?;
16+
let hex = format!("{int:032x}");
17+
Ok(format!(
18+
"{}-{}-{}-{}-{}",
19+
&hex[0..8],
20+
&hex[8..12],
21+
&hex[12..16],
22+
&hex[16..20],
23+
&hex[20..]
24+
))
25+
}
26+
1327
#[derive(Debug, Clone)]
1428
pub struct UuidSerializer;
1529

@@ -34,10 +48,9 @@ impl TypeSerializer for UuidSerializer {
3448
extra: &Extra,
3549
) -> PyResult<PyObject> {
3650
let py = value.py();
37-
match extra.ob_type_lookup.is_type(value, ObType::Str) {
38-
IsType::Exact => Ok(value.into_py(py)),
39-
IsType::Subclass => match extra.mode {
40-
SerMode::Json => Ok(value.extract::<&str>()?.into_py(py)),
51+
match extra.ob_type_lookup.is_type(value, ObType::Uuid) {
52+
IsType::Exact | IsType::Subclass => match extra.mode {
53+
SerMode::Json => Ok(uuid_to_string(value)?.into_py(py)),
4154
_ => Ok(value.into_py(py)),
4255
},
4356
IsType::False => {
@@ -48,11 +61,15 @@ impl TypeSerializer for UuidSerializer {
4861
}
4962

5063
fn json_key<'py>(&self, key: &'py PyAny, extra: &Extra) -> PyResult<Cow<'py, str>> {
51-
if let Ok(py_str) = key.downcast::<PyString>() {
52-
Ok(py_str.to_string_lossy())
53-
} else {
54-
extra.warnings.on_fallback_py(self.get_name(), key, extra)?;
55-
infer_json_key(key, extra)
64+
match extra.ob_type_lookup.is_type(key, ObType::Uuid) {
65+
IsType::Exact | IsType::Subclass => {
66+
let str = uuid_to_string(key)?;
67+
Ok(Cow::Owned(str))
68+
}
69+
IsType::False => {
70+
extra.warnings.on_fallback_py(self.get_name(), key, extra)?;
71+
infer_json_key(key, extra)
72+
}
5673
}
5774
}
5875

@@ -64,9 +81,12 @@ impl TypeSerializer for UuidSerializer {
6481
exclude: Option<&PyAny>,
6582
extra: &Extra,
6683
) -> Result<S::Ok, S::Error> {
67-
match value.downcast::<PyString>() {
68-
Ok(py_str) => serialize_py_str(py_str, serializer),
69-
Err(_) => {
84+
match extra.ob_type_lookup.is_type(value, ObType::Uuid) {
85+
IsType::Exact | IsType::Subclass => {
86+
let s = uuid_to_string(value).map_err(py_err_se_err)?;
87+
serializer.serialize_str(&s)
88+
}
89+
IsType::False => {
7090
extra.warnings.on_fallback_ser::<S>(self.get_name(), value, extra)?;
7191
infer_serialize(value, serializer, include, exclude, extra)
7292
}
@@ -77,8 +97,3 @@ impl TypeSerializer for UuidSerializer {
7797
Self::EXPECTED_TYPE
7898
}
7999
}
80-
81-
pub fn serialize_py_str<S: serde::ser::Serializer>(py_str: &PyString, serializer: S) -> Result<S::Ok, S::Error> {
82-
let s = py_str.to_str().map_err(py_err_se_err)?;
83-
serializer.serialize_str(s)
84-
}

0 commit comments

Comments
 (0)