Skip to content

Commit

Permalink
Update speedate and truncate microseconds by default (#762)
Browse files Browse the repository at this point in the history
  • Loading branch information
adriangb authored Jul 11, 2023
1 parent 5f660f0 commit bf0bce0
Show file tree
Hide file tree
Showing 17 changed files with 392 additions and 193 deletions.
213 changes: 116 additions & 97 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pydantic-core"
version = "2.2.0"
version = "2.3.0"
edition = "2021"
license = "MIT"
homepage = "https://github.com/pydantic/pydantic-core"
Expand Down Expand Up @@ -35,7 +35,7 @@ enum_dispatch = "0.3.8"
serde = { version = "1.0.147", features = ["derive"] }
# disabled for benchmarks since it makes microbenchmark performance more flakey
mimalloc = { version = "0.1.30", optional = true, default-features = false, features = ["local_dynamic_tls"] }
speedate = "0.9.1"
speedate = "0.10.0"
ahash = "0.8.0"
url = "2.3.1"
# idna is already required by url, added here to be explicit
Expand Down
12 changes: 12 additions & 0 deletions python/pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ class TimeSchema(TypedDict, total=False):
lt: time
gt: time
tz_constraint: Union[Literal['aware', 'naive'], int]
microseconds_precision: Literal['truncate', 'error']
ref: str
metadata: Any
serialization: SerSchema
Expand All @@ -861,6 +862,7 @@ def time_schema(
lt: time | None = None,
gt: time | None = None,
tz_constraint: Literal['aware', 'naive'] | int | None = None,
microseconds_precision: Literal['truncate', 'error'] = 'truncate',
ref: str | None = None,
metadata: Any = None,
serialization: SerSchema | None = None,
Expand All @@ -884,6 +886,7 @@ def time_schema(
lt: The value must be strictly less than this time
gt: The value must be strictly greater than this time
tz_constraint: The value must be timezone aware or naive, or an int to indicate required tz offset
microseconds_precision: The behavior when seconds have more than 6 digits or microseconds is too large
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
serialization: Custom serialization schema
Expand All @@ -896,6 +899,7 @@ def time_schema(
lt=lt,
gt=gt,
tz_constraint=tz_constraint,
microseconds_precision=microseconds_precision,
ref=ref,
metadata=metadata,
serialization=serialization,
Expand All @@ -914,6 +918,7 @@ class DatetimeSchema(TypedDict, total=False):
# defaults to current local utc offset from `time.localtime().tm_gmtoff`
# value is restricted to -86_400 < offset < 86_400 by bounds in generate_self_schema.py
now_utc_offset: int
microseconds_precision: Literal['truncate', 'error'] = ('truncate',)
ref: str
metadata: Any
serialization: SerSchema
Expand All @@ -929,6 +934,7 @@ def datetime_schema(
now_op: Literal['past', 'future'] | None = None,
tz_constraint: Literal['aware', 'naive'] | int | None = None,
now_utc_offset: int | None = None,
microseconds_precision: Literal['truncate', 'error'] = 'truncate',
ref: str | None = None,
metadata: Any = None,
serialization: SerSchema | None = None,
Expand Down Expand Up @@ -956,6 +962,7 @@ def datetime_schema(
tz_constraint: The value must be timezone aware or naive, or an int to indicate required tz offset
TODO: use of a tzinfo where offset changes based on the datetime is not yet supported
now_utc_offset: The value must be in the past or future relative to the current datetime with this utc offset
microseconds_precision: The behavior when seconds have more than 6 digits or microseconds is too large
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
serialization: Custom serialization schema
Expand All @@ -970,6 +977,7 @@ def datetime_schema(
now_op=now_op,
tz_constraint=tz_constraint,
now_utc_offset=now_utc_offset,
microseconds_precision=microseconds_precision,
ref=ref,
metadata=metadata,
serialization=serialization,
Expand All @@ -983,6 +991,7 @@ class TimedeltaSchema(TypedDict, total=False):
ge: timedelta
lt: timedelta
gt: timedelta
microseconds_precision: Literal['truncate', 'error']
ref: str
metadata: Any
serialization: SerSchema
Expand All @@ -995,6 +1004,7 @@ def timedelta_schema(
ge: timedelta | None = None,
lt: timedelta | None = None,
gt: timedelta | None = None,
microseconds_precision: Literal['truncate', 'error'] = 'truncate',
ref: str | None = None,
metadata: Any = None,
serialization: SerSchema | None = None,
Expand All @@ -1017,6 +1027,7 @@ def timedelta_schema(
ge: The value must be greater than or equal to this timedelta
lt: The value must be strictly less than this timedelta
gt: The value must be strictly greater than this timedelta
microseconds_precision: The behavior when seconds have more than 6 digits or microseconds is too large
ref: optional unique identifier of the schema, used to reference the schema in other places
metadata: Any other information you want to include with the schema, not used by pydantic-core
serialization: Custom serialization schema
Expand All @@ -1028,6 +1039,7 @@ def timedelta_schema(
ge=ge,
lt=lt,
gt=gt,
microseconds_precision=microseconds_precision,
ref=ref,
metadata=metadata,
serialization=serialization,
Expand Down
42 changes: 35 additions & 7 deletions src/input/datetime.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::{PyDate, PyDateTime, PyDelta, PyDeltaAccess, PyDict, PyTime, PyTzInfo};
use speedate::{Date, DateTime, Duration, ParseError, Time};
use speedate::MicrosecondsPrecisionOverflowBehavior;
use speedate::{Date, DateTime, Duration, ParseError, Time, TimeConfig};
use std::borrow::Cow;
use strum::EnumMessage;

Expand Down Expand Up @@ -264,8 +265,17 @@ pub fn bytes_as_date<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<'
}
}

pub fn bytes_as_time<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<'a, EitherTime<'a>> {
match Time::parse_bytes(bytes) {
pub fn bytes_as_time<'a>(
input: &'a impl Input<'a>,
bytes: &[u8],
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<'a, EitherTime<'a>> {
match Time::parse_bytes_with_config(
bytes,
TimeConfig {
microseconds_precision_overflow_behavior: microseconds_overflow_behavior,
},
) {
Ok(date) => Ok(date.into()),
Err(err) => Err(ValError::new(
ErrorType::TimeParsing {
Expand All @@ -276,8 +286,17 @@ pub fn bytes_as_time<'a>(input: &'a impl Input<'a>, bytes: &[u8]) -> ValResult<'
}
}

pub fn bytes_as_datetime<'a, 'b>(input: &'a impl Input<'a>, bytes: &'b [u8]) -> ValResult<'a, EitherDateTime<'a>> {
match DateTime::parse_bytes(bytes) {
pub fn bytes_as_datetime<'a, 'b>(
input: &'a impl Input<'a>,
bytes: &'b [u8],
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<'a, EitherDateTime<'a>> {
match DateTime::parse_bytes_with_config(
bytes,
TimeConfig {
microseconds_precision_overflow_behavior: microseconds_overflow_behavior,
},
) {
Ok(dt) => Ok(dt.into()),
Err(err) => Err(ValError::new(
ErrorType::DatetimeParsing {
Expand Down Expand Up @@ -389,8 +408,17 @@ fn map_timedelta_err<'a>(input: &'a impl Input<'a>, err: ParseError) -> ValError
)
}

pub fn bytes_as_timedelta<'a, 'b>(input: &'a impl Input<'a>, bytes: &'b [u8]) -> ValResult<'a, EitherTimedelta<'a>> {
match Duration::parse_bytes(bytes) {
pub fn bytes_as_timedelta<'a, 'b>(
input: &'a impl Input<'a>,
bytes: &'b [u8],
microseconds_overflow_behavior: MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<'a, EitherTimedelta<'a>> {
match Duration::parse_bytes_with_config(
bytes,
TimeConfig {
microseconds_precision_overflow_behavior: microseconds_overflow_behavior,
},
) {
Ok(dt) => Ok(dt.into()),
Err(err) => Err(map_timedelta_err(input, err)),
}
Expand Down
74 changes: 52 additions & 22 deletions src/input/input_abstract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,42 +238,72 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
self.strict_date()
}

fn validate_time(&self, strict: bool) -> ValResult<EitherTime> {
fn validate_time(
&self,
strict: bool,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherTime> {
if strict {
self.strict_time()
self.strict_time(microseconds_overflow_behavior)
} else {
self.lax_time()
self.lax_time(microseconds_overflow_behavior)
}
}
fn strict_time(&self) -> ValResult<EitherTime>;
fn strict_time(
&self,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherTime>;
#[cfg_attr(has_no_coverage, no_coverage)]
fn lax_time(&self) -> ValResult<EitherTime> {
self.strict_time()
}

fn validate_datetime(&self, strict: bool) -> ValResult<EitherDateTime> {
fn lax_time(
&self,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherTime> {
self.strict_time(microseconds_overflow_behavior)
}

fn validate_datetime(
&self,
strict: bool,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherDateTime> {
if strict {
self.strict_datetime()
self.strict_datetime(microseconds_overflow_behavior)
} else {
self.lax_datetime()
self.lax_datetime(microseconds_overflow_behavior)
}
}
fn strict_datetime(&self) -> ValResult<EitherDateTime>;
fn strict_datetime(
&self,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherDateTime>;
#[cfg_attr(has_no_coverage, no_coverage)]
fn lax_datetime(&self) -> ValResult<EitherDateTime> {
self.strict_datetime()
}

fn validate_timedelta(&self, strict: bool) -> ValResult<EitherTimedelta> {
fn lax_datetime(
&self,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherDateTime> {
self.strict_datetime(microseconds_overflow_behavior)
}

fn validate_timedelta(
&self,
strict: bool,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherTimedelta> {
if strict {
self.strict_timedelta()
self.strict_timedelta(microseconds_overflow_behavior)
} else {
self.lax_timedelta()
self.lax_timedelta(microseconds_overflow_behavior)
}
}
fn strict_timedelta(&self) -> ValResult<EitherTimedelta>;
fn strict_timedelta(
&self,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherTimedelta>;
#[cfg_attr(has_no_coverage, no_coverage)]
fn lax_timedelta(&self) -> ValResult<EitherTimedelta> {
self.strict_timedelta()
fn lax_timedelta(
&self,
microseconds_overflow_behavior: speedate::MicrosecondsPrecisionOverflowBehavior,
) -> ValResult<EitherTimedelta> {
self.strict_timedelta(microseconds_overflow_behavior)
}
}
Loading

0 comments on commit bf0bce0

Please sign in to comment.