Skip to content

Commit 352d40f

Browse files
authored
implement Input for str (#1229)
1 parent b92a69b commit 352d40f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+461
-443
lines changed

src/errors/line_error.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,27 @@ use pyo3::DowncastIntoError;
55

66
use jiter::JsonValue;
77

8+
use crate::input::BorrowInput;
89
use crate::input::Input;
910

1011
use super::location::{LocItem, Location};
1112
use super::types::ErrorType;
1213

1314
pub type ValResult<T> = Result<T, ValError>;
1415

15-
pub trait AsErrorValue {
16-
fn as_error_value(&self) -> InputValue;
16+
pub trait ToErrorValue {
17+
fn to_error_value(&self) -> InputValue;
1718
}
1819

19-
impl<'a, T: Input<'a>> AsErrorValue for T {
20-
fn as_error_value(&self) -> InputValue {
21-
Input::as_error_value(self)
20+
impl<'a, T: BorrowInput<'a>> ToErrorValue for T {
21+
fn to_error_value(&self) -> InputValue {
22+
Input::as_error_value(self.borrow_input())
23+
}
24+
}
25+
26+
impl ToErrorValue for &'_ dyn ToErrorValue {
27+
fn to_error_value(&self) -> InputValue {
28+
(**self).to_error_value()
2229
}
2330
}
2431

@@ -55,11 +62,11 @@ impl From<Vec<ValLineError>> for ValError {
5562
}
5663

5764
impl ValError {
58-
pub fn new(error_type: ErrorType, input: &impl AsErrorValue) -> ValError {
65+
pub fn new(error_type: ErrorType, input: impl ToErrorValue) -> ValError {
5966
Self::LineErrors(vec![ValLineError::new(error_type, input)])
6067
}
6168

62-
pub fn new_with_loc(error_type: ErrorType, input: &impl AsErrorValue, loc: impl Into<LocItem>) -> ValError {
69+
pub fn new_with_loc(error_type: ErrorType, input: impl ToErrorValue, loc: impl Into<LocItem>) -> ValError {
6370
Self::LineErrors(vec![ValLineError::new_with_loc(error_type, input, loc)])
6471
}
6572

@@ -94,26 +101,26 @@ pub struct ValLineError {
94101
}
95102

96103
impl ValLineError {
97-
pub fn new(error_type: ErrorType, input: &impl AsErrorValue) -> ValLineError {
104+
pub fn new(error_type: ErrorType, input: impl ToErrorValue) -> ValLineError {
98105
Self {
99106
error_type,
100-
input_value: input.as_error_value(),
107+
input_value: input.to_error_value(),
101108
location: Location::default(),
102109
}
103110
}
104111

105-
pub fn new_with_loc(error_type: ErrorType, input: &impl AsErrorValue, loc: impl Into<LocItem>) -> ValLineError {
112+
pub fn new_with_loc(error_type: ErrorType, input: impl ToErrorValue, loc: impl Into<LocItem>) -> ValLineError {
106113
Self {
107114
error_type,
108-
input_value: input.as_error_value(),
115+
input_value: input.to_error_value(),
109116
location: Location::new_some(loc.into()),
110117
}
111118
}
112119

113-
pub fn new_with_full_loc(error_type: ErrorType, input: &impl AsErrorValue, location: Location) -> ValLineError {
120+
pub fn new_with_full_loc(error_type: ErrorType, input: impl ToErrorValue, location: Location) -> ValLineError {
114121
Self {
115122
error_type,
116-
input_value: input.as_error_value(),
123+
input_value: input.to_error_value(),
117124
location,
118125
}
119126
}

src/errors/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod types;
66
mod validation_exception;
77
mod value_exception;
88

9-
pub use self::line_error::{AsErrorValue, InputValue, ValError, ValLineError, ValResult};
9+
pub use self::line_error::{InputValue, ToErrorValue, ValError, ValLineError, ValResult};
1010
pub use self::location::LocItem;
1111
pub use self::types::{list_all_errors, ErrorType, ErrorTypeDefaults, Number};
1212
pub use self::validation_exception::ValidationError;

src/errors/value_exception.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use pyo3::types::{PyDict, PyString};
55
use crate::input::InputType;
66
use crate::tools::extract_i64;
77

8-
use super::line_error::AsErrorValue;
8+
use super::line_error::ToErrorValue;
99
use super::{ErrorType, ValError};
1010

1111
#[pyclass(extends=PyException, module="pydantic_core._pydantic_core")]
@@ -106,7 +106,7 @@ impl PydanticCustomError {
106106
}
107107

108108
impl PydanticCustomError {
109-
pub fn into_val_error(self, input: &impl AsErrorValue) -> ValError {
109+
pub fn into_val_error(self, input: impl ToErrorValue) -> ValError {
110110
let error_type = ErrorType::CustomError {
111111
error_type: self.error_type,
112112
message_template: self.message_template,
@@ -181,7 +181,7 @@ impl PydanticKnownError {
181181
}
182182

183183
impl PydanticKnownError {
184-
pub fn into_val_error(self, input: &impl AsErrorValue) -> ValError {
184+
pub fn into_val_error(self, input: impl ToErrorValue) -> ValError {
185185
ValError::new(self.error_type, input)
186186
}
187187
}

src/input/datetime.rs

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::hash::Hasher;
1414
use strum::EnumMessage;
1515

1616
use super::Input;
17+
use crate::errors::ToErrorValue;
1718
use crate::errors::{ErrorType, ValError, ValResult};
1819
use crate::tools::py_err;
1920

@@ -285,7 +286,7 @@ impl<'a> EitherDateTime<'a> {
285286
}
286287
}
287288

288-
pub fn bytes_as_date<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<EitherDate<'a>> {
289+
pub fn bytes_as_date<'py>(input: &(impl Input<'py> + ?Sized), bytes: &[u8]) -> ValResult<EitherDate<'py>> {
289290
match Date::parse_bytes(bytes) {
290291
Ok(date) => Ok(date.into()),
291292
Err(err) => Err(ValError::new(
@@ -298,11 +299,11 @@ pub fn bytes_as_date<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<E
298299
}
299300
}
300301

301-
pub fn bytes_as_time<'a>(
302-
input: &'a impl Input<'a>,
302+
pub fn bytes_as_time<'py>(
303+
input: &(impl Input<'py> + ?Sized),
303304
bytes: &[u8],
304305
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
305-
) -> ValResult<EitherTime<'a>> {
306+
) -> ValResult<EitherTime<'py>> {
306307
match Time::parse_bytes_with_config(
307308
bytes,
308309
&TimeConfig {
@@ -321,11 +322,11 @@ pub fn bytes_as_time<'a>(
321322
}
322323
}
323324

324-
pub fn bytes_as_datetime<'a, 'b>(
325-
input: &'a impl Input<'a>,
326-
bytes: &'b [u8],
325+
pub fn bytes_as_datetime<'py>(
326+
input: &(impl Input<'py> + ?Sized),
327+
bytes: &[u8],
327328
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
328-
) -> ValResult<EitherDateTime<'a>> {
329+
) -> ValResult<EitherDateTime<'py>> {
329330
match DateTime::parse_bytes_with_config(
330331
bytes,
331332
&TimeConfig {
@@ -344,11 +345,11 @@ pub fn bytes_as_datetime<'a, 'b>(
344345
}
345346
}
346347

347-
pub fn int_as_datetime<'a>(
348-
input: &'a impl Input<'a>,
348+
pub fn int_as_datetime<'py>(
349+
input: &(impl Input<'py> + ?Sized),
349350
timestamp: i64,
350351
timestamp_microseconds: u32,
351-
) -> ValResult<EitherDateTime> {
352+
) -> ValResult<EitherDateTime<'py>> {
352353
match DateTime::from_timestamp_with_config(
353354
timestamp,
354355
timestamp_microseconds,
@@ -382,7 +383,7 @@ macro_rules! nan_check {
382383
};
383384
}
384385

385-
pub fn float_as_datetime<'a>(input: &'a impl Input<'a>, timestamp: f64) -> ValResult<EitherDateTime> {
386+
pub fn float_as_datetime<'py>(input: &(impl Input<'py> + ?Sized), timestamp: f64) -> ValResult<EitherDateTime<'py>> {
386387
nan_check!(input, timestamp, DatetimeParsing);
387388
let microseconds = timestamp.fract().abs() * 1_000_000.0;
388389
// checking for extra digits in microseconds is unreliable with large floats,
@@ -408,11 +409,11 @@ pub fn date_as_datetime<'py>(date: &Bound<'py, PyDate>) -> PyResult<EitherDateTi
408409

409410
const MAX_U32: i64 = u32::MAX as i64;
410411

411-
pub fn int_as_time<'a>(
412-
input: &'a impl Input<'a>,
412+
pub fn int_as_time<'py>(
413+
input: &(impl Input<'py> + ?Sized),
413414
timestamp: i64,
414415
timestamp_microseconds: u32,
415-
) -> ValResult<EitherTime> {
416+
) -> ValResult<EitherTime<'py>> {
416417
let time_timestamp: u32 = match timestamp {
417418
t if t < 0_i64 => {
418419
return Err(ValError::new(
@@ -447,14 +448,14 @@ pub fn int_as_time<'a>(
447448
}
448449
}
449450

450-
pub fn float_as_time<'a>(input: &'a impl Input<'a>, timestamp: f64) -> ValResult<EitherTime> {
451+
pub fn float_as_time<'py>(input: &(impl Input<'py> + ?Sized), timestamp: f64) -> ValResult<EitherTime<'py>> {
451452
nan_check!(input, timestamp, TimeParsing);
452453
let microseconds = timestamp.fract().abs() * 1_000_000.0;
453454
// round for same reason as above
454455
int_as_time(input, timestamp.floor() as i64, microseconds.round() as u32)
455456
}
456457

457-
fn map_timedelta_err<'a>(input: &'a impl Input<'a>, err: ParseError) -> ValError {
458+
fn map_timedelta_err(input: impl ToErrorValue, err: ParseError) -> ValError {
458459
ValError::new(
459460
ErrorType::TimeDeltaParsing {
460461
error: Cow::Borrowed(err.get_documentation().unwrap_or_default()),
@@ -464,11 +465,11 @@ fn map_timedelta_err<'a>(input: &'a impl Input<'a>, err: ParseError) -> ValError
464465
)
465466
}
466467

467-
pub fn bytes_as_timedelta<'a, 'b>(
468-
input: &'a impl Input<'a>,
469-
bytes: &'b [u8],
468+
pub fn bytes_as_timedelta<'py>(
469+
input: &(impl Input<'py> + ?Sized),
470+
bytes: &[u8],
470471
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
471-
) -> ValResult<EitherTimedelta<'a>> {
472+
) -> ValResult<EitherTimedelta<'py>> {
472473
match Duration::parse_bytes_with_config(
473474
bytes,
474475
&TimeConfig {
@@ -481,7 +482,7 @@ pub fn bytes_as_timedelta<'a, 'b>(
481482
}
482483
}
483484

484-
pub fn int_as_duration<'a>(input: &'a impl Input<'a>, total_seconds: i64) -> ValResult<Duration> {
485+
pub fn int_as_duration(input: impl ToErrorValue, total_seconds: i64) -> ValResult<Duration> {
485486
let positive = total_seconds >= 0;
486487
let total_seconds = total_seconds.unsigned_abs();
487488
// we can safely unwrap here since we've guaranteed seconds and microseconds can't cause overflow
@@ -490,7 +491,7 @@ pub fn int_as_duration<'a>(input: &'a impl Input<'a>, total_seconds: i64) -> Val
490491
Duration::new(positive, days, seconds, 0).map_err(|err| map_timedelta_err(input, err))
491492
}
492493

493-
pub fn float_as_duration<'a>(input: &'a impl Input<'a>, total_seconds: f64) -> ValResult<Duration> {
494+
pub fn float_as_duration(input: impl ToErrorValue, total_seconds: f64) -> ValResult<Duration> {
494495
nan_check!(input, total_seconds, TimeDeltaParsing);
495496
let positive = total_seconds >= 0_f64;
496497
let total_seconds = total_seconds.abs();

src/input/input_abstract.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use pyo3::exceptions::PyValueError;
44
use pyo3::types::{PyDict, PyType};
55
use pyo3::{intern, prelude::*};
66

7-
use crate::errors::{ErrorTypeDefaults, InputValue, LocItem, ValError, ValResult};
7+
use crate::errors::{ErrorTypeDefaults, InputValue, ValError, ValResult};
88
use crate::tools::py_err;
99
use crate::{PyMultiHostUrl, PyUrl};
1010

@@ -46,7 +46,7 @@ impl TryFrom<&str> for InputType {
4646
/// the convention is to either implement:
4747
/// * `strict_*` & `lax_*` if they have different behavior
4848
/// * or, `validate_*` and `strict_*` to just call `validate_*` if the behavior for strict and lax is the same
49-
pub trait Input<'py>: fmt::Debug + ToPyObject + Into<LocItem> + Sized {
49+
pub trait Input<'py>: fmt::Debug + ToPyObject {
5050
fn as_error_value(&self) -> InputValue;
5151

5252
fn identity(&self) -> Option<usize> {
@@ -83,9 +83,9 @@ pub trait Input<'py>: fmt::Debug + ToPyObject + Into<LocItem> + Sized {
8383
false
8484
}
8585

86-
fn validate_args(&self) -> ValResult<GenericArguments<'_>>;
86+
fn validate_args(&self) -> ValResult<GenericArguments<'_, 'py>>;
8787

88-
fn validate_dataclass_args<'a>(&'a self, dataclass_name: &str) -> ValResult<GenericArguments<'a>>;
88+
fn validate_dataclass_args<'a>(&'a self, dataclass_name: &str) -> ValResult<GenericArguments<'a, 'py>>;
8989

9090
fn validate_str(&self, strict: bool, coerce_numbers_to_str: bool) -> ValResult<ValidationMatch<EitherString<'_>>>;
9191

@@ -201,25 +201,25 @@ pub trait Input<'py>: fmt::Debug + ToPyObject + Into<LocItem> + Sized {
201201

202202
fn validate_iter(&self) -> ValResult<GenericIterator>;
203203

204-
fn validate_date(&self, strict: bool) -> ValResult<ValidationMatch<EitherDate>>;
204+
fn validate_date(&self, strict: bool) -> ValResult<ValidationMatch<EitherDate<'py>>>;
205205

206206
fn validate_time(
207207
&self,
208208
strict: bool,
209209
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
210-
) -> ValResult<ValidationMatch<EitherTime>>;
210+
) -> ValResult<ValidationMatch<EitherTime<'py>>>;
211211

212212
fn validate_datetime(
213213
&self,
214214
strict: bool,
215215
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
216-
) -> ValResult<ValidationMatch<EitherDateTime>>;
216+
) -> ValResult<ValidationMatch<EitherDateTime<'py>>>;
217217

218218
fn validate_timedelta(
219219
&self,
220220
strict: bool,
221221
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
222-
) -> ValResult<ValidationMatch<EitherTimedelta>>;
222+
) -> ValResult<ValidationMatch<EitherTimedelta<'py>>>;
223223
}
224224

225225
/// The problem to solve here is that iterating collections often returns owned
@@ -228,6 +228,13 @@ pub trait Input<'py>: fmt::Debug + ToPyObject + Into<LocItem> + Sized {
228228
/// or borrowed; all we care about is that we can borrow it again with `borrow_input`
229229
/// for some lifetime 'a.
230230
pub trait BorrowInput<'py> {
231-
type Input: Input<'py>;
231+
type Input: Input<'py> + ?Sized;
232232
fn borrow_input(&self) -> &Self::Input;
233233
}
234+
235+
impl<'py, T: Input<'py> + ?Sized> BorrowInput<'py> for &'_ T {
236+
type Input = T;
237+
fn borrow_input(&self) -> &Self::Input {
238+
self
239+
}
240+
}

0 commit comments

Comments
 (0)