Skip to content

Commit e2cd996

Browse files
committed
Convert length constraints
1 parent f5ef7af commit e2cd996

28 files changed

+321
-441
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ features = ["pyo3/extension-module"]
5656
[tool.ruff]
5757
line-length = 120
5858
extend-select = ['Q', 'RUF100', 'C90', 'I']
59+
ignore = ['E501']
5960
flake8-quotes = {inline-quotes = 'single', multiline-quotes = 'double'}
6061
mccabe = { max-complexity = 13 }
6162
isort = { known-first-party = ['pydantic_core', 'tests'] }

src/errors/types.rs

Lines changed: 51 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,10 @@ pub enum ErrorType {
129129
// ---------------------
130130
// generic length errors - used for everything with a length except strings and bytes which need custom messages
131131
TooShort {
132-
field_type: String,
133132
min_length: usize,
134133
actual_length: usize,
135134
},
136135
TooLong {
137-
field_type: String,
138136
max_length: usize,
139137
actual_length: usize,
140138
},
@@ -414,14 +412,12 @@ impl ErrorType {
414412
Self::TooShort { .. } => extract_context!(
415413
TooShort,
416414
ctx,
417-
field_type: String,
418415
min_length: usize,
419416
actual_length: usize
420417
),
421418
Self::TooLong { .. } => extract_context!(
422419
TooLong,
423420
ctx,
424-
field_type: String,
425421
max_length: usize,
426422
actual_length: usize
427423
),
@@ -479,40 +475,44 @@ impl ErrorType {
479475

480476
pub fn message_template_python(&self) -> &'static str {
481477
match self {
482-
Self::NoSuchAttribute {..} => "Object has no attribute '{attribute}'",
483-
Self::JsonInvalid {..} => "Invalid JSON: {error}",
478+
Self::NoSuchAttribute { .. } => "Object has no attribute '{attribute}'",
479+
Self::JsonInvalid { .. } => "Invalid JSON: {error}",
484480
Self::JsonType => "JSON input should be string, bytes or bytearray",
485481
Self::RecursionLoop => "Recursion error - cyclic reference detected",
486482
Self::Missing => "Field required",
487483
Self::FrozenField => "Field is frozen",
488484
Self::FrozenInstance => "Instance is frozen",
489485
Self::ExtraForbidden => "Extra inputs are not permitted",
490486
Self::InvalidKey => "Keys should be strings",
491-
Self::GetAttributeError {..} => "Error extracting attribute: {error}",
492-
Self::ModelType {..} => "Input should be a valid dictionary or instance of {class_name}",
487+
Self::GetAttributeError { .. } => "Error extracting attribute: {error}",
488+
Self::ModelType { .. } => "Input should be a valid dictionary or instance of {class_name}",
493489
Self::ModelAttributesType => "Input should be a valid dictionary or object to extract fields from",
494-
Self::DataclassType {..} => "Input should be a dictionary or an instance of {class_name}",
495-
Self::DataclassExactType {..} => "Input should be an instance of {class_name}",
490+
Self::DataclassType { .. } => "Input should be a dictionary or an instance of {class_name}",
491+
Self::DataclassExactType { .. } => "Input should be an instance of {class_name}",
496492
Self::NoneRequired => "Input should be None",
497-
Self::GreaterThan {..} => "Input should be greater than {gt}",
498-
Self::GreaterThanEqual {..} => "Input should be greater than or equal to {ge}",
499-
Self::LessThan {..} => "Input should be less than {lt}",
500-
Self::LessThanEqual {..} => "Input should be less than or equal to {le}",
501-
Self::MultipleOf {..} => "Input should be a multiple of {multiple_of}",
493+
Self::GreaterThan { .. } => "Input should be greater than {gt}",
494+
Self::GreaterThanEqual { .. } => "Input should be greater than or equal to {ge}",
495+
Self::LessThan { .. } => "Input should be less than {lt}",
496+
Self::LessThanEqual { .. } => "Input should be less than or equal to {le}",
497+
Self::MultipleOf { .. } => "Input should be a multiple of {multiple_of}",
502498
Self::FiniteNumber => "Input should be a finite number",
503-
Self::TooShort {..} => "{field_type} should have at least {min_length} item{expected_plural} after validation, not {actual_length}",
504-
Self::TooLong {..} => "{field_type} should have at most {max_length} item{expected_plural} after validation, not {actual_length}",
499+
Self::TooShort { .. } => {
500+
"Data should have at least {min_length} item{expected_plural} after validation, not {actual_length}"
501+
}
502+
Self::TooLong { .. } => {
503+
"Data should have at most {max_length} item{expected_plural} after validation, not {actual_length}"
504+
}
505505
Self::IterableType => "Input should be iterable",
506-
Self::IterationError {..} => "Error iterating over object, error: {error}",
506+
Self::IterationError { .. } => "Error iterating over object, error: {error}",
507507
Self::StringType => "Input should be a valid string",
508508
Self::StringSubType => "Input should be a string, not an instance of a subclass of str",
509509
Self::StringUnicode => "Input should be a valid string, unable to parse raw data as a unicode string",
510-
Self::StringTooShort {..} => "String should have at least {min_length} characters",
511-
Self::StringTooLong {..} => "String should have at most {max_length} characters",
512-
Self::StringPatternMismatch {..} => "String should match pattern '{pattern}'",
513-
Self::Enum {..} => "Input should be {expected}",
510+
Self::StringTooShort { .. } => "String should have at least {min_length} characters",
511+
Self::StringTooLong { .. } => "String should have at most {max_length} characters",
512+
Self::StringPatternMismatch { .. } => "String should match pattern '{pattern}'",
513+
Self::Enum { .. } => "Input should be {expected}",
514514
Self::DictType => "Input should be a valid dictionary",
515-
Self::MappingType {..} => "Input should be a valid mapping, error: {error}",
515+
Self::MappingType { .. } => "Input should be a valid mapping, error: {error}",
516516
Self::ListType => "Input should be a valid list",
517517
Self::TupleType => "Input should be a valid tuple",
518518
Self::SetType => "Input should be a valid set",
@@ -525,36 +525,38 @@ impl ErrorType {
525525
Self::FloatType => "Input should be a valid number",
526526
Self::FloatParsing => "Input should be a valid number, unable to parse string as a number",
527527
Self::BytesType => "Input should be a valid bytes",
528-
Self::BytesTooShort {..} => "Data should have at least {min_length} bytes",
529-
Self::BytesTooLong {..} => "Data should have at most {max_length} bytes",
530-
Self::ValueError {..} => "Value error, {error}",
531-
Self::AssertionError {..} => "Assertion failed, {error}",
532-
Self::CustomError {..} => "", // custom errors are handled separately
533-
Self::LiteralError {..} => "Input should be {expected}",
528+
Self::BytesTooShort { .. } => "Data should have at least {min_length} bytes",
529+
Self::BytesTooLong { .. } => "Data should have at most {max_length} bytes",
530+
Self::ValueError { .. } => "Value error, {error}",
531+
Self::AssertionError { .. } => "Assertion failed, {error}",
532+
Self::CustomError { .. } => "", // custom errors are handled separately
533+
Self::LiteralError { .. } => "Input should be {expected}",
534534
Self::DateType => "Input should be a valid date",
535-
Self::DateParsing {..} => "Input should be a valid date in the format YYYY-MM-DD, {error}",
536-
Self::DateFromDatetimeParsing {..} => "Input should be a valid date or datetime, {error}",
535+
Self::DateParsing { .. } => "Input should be a valid date in the format YYYY-MM-DD, {error}",
536+
Self::DateFromDatetimeParsing { .. } => "Input should be a valid date or datetime, {error}",
537537
Self::DateFromDatetimeInexact => "Datetimes provided to dates should have zero time - e.g. be exact dates",
538538
Self::DatePast => "Date should be in the past",
539539
Self::DateFuture => "Date should be in the future",
540540
Self::TimeType => "Input should be a valid time",
541-
Self::TimeParsing {..} => "Input should be in a valid time format, {error}",
541+
Self::TimeParsing { .. } => "Input should be in a valid time format, {error}",
542542
Self::DatetimeType => "Input should be a valid datetime",
543-
Self::DatetimeParsing {..} => "Input should be a valid datetime, {error}",
544-
Self::DatetimeObjectInvalid {..} => "Invalid datetime object, got {error}",
543+
Self::DatetimeParsing { .. } => "Input should be a valid datetime, {error}",
544+
Self::DatetimeObjectInvalid { .. } => "Invalid datetime object, got {error}",
545545
Self::DatetimePast => "Input should be in the past",
546546
Self::DatetimeFuture => "Input should be in the future",
547547
Self::TimezoneNaive => "Input should not have timezone info",
548548
Self::TimezoneAware => "Input should have timezone info",
549-
Self::TimezoneOffset {..} => "Timezone offset of {tz_expected} required, got {tz_actual}",
549+
Self::TimezoneOffset { .. } => "Timezone offset of {tz_expected} required, got {tz_actual}",
550550
Self::TimeDeltaType => "Input should be a valid timedelta",
551-
Self::TimeDeltaParsing {..} => "Input should be a valid timedelta, {error}",
551+
Self::TimeDeltaParsing { .. } => "Input should be a valid timedelta, {error}",
552552
Self::FrozenSetType => "Input should be a valid frozenset",
553-
Self::IsInstanceOf {..} => "Input should be an instance of {class}",
554-
Self::IsSubclassOf {..} => "Input should be a subclass of {class}",
553+
Self::IsInstanceOf { .. } => "Input should be an instance of {class}",
554+
Self::IsSubclassOf { .. } => "Input should be a subclass of {class}",
555555
Self::CallableType => "Input should be callable",
556-
Self::UnionTagInvalid {..} => "Input tag '{tag}' found using {discriminator} does not match any of the expected tags: {expected_tags}",
557-
Self::UnionTagNotFound {..} => "Unable to extract tag using discriminator {discriminator}",
556+
Self::UnionTagInvalid { .. } => {
557+
"Input tag '{tag}' found using {discriminator} does not match any of the expected tags: {expected_tags}"
558+
}
559+
Self::UnionTagNotFound { .. } => "Unable to extract tag using discriminator {discriminator}",
558560
Self::ArgumentsType => "Arguments must be a tuple, list or a dictionary",
559561
Self::MissingArgument => "Missing required argument",
560562
Self::UnexpectedKeywordArgument => "Unexpected keyword argument",
@@ -563,14 +565,13 @@ impl ErrorType {
563565
Self::MissingPositionalOnlyArgument => "Missing required positional only argument",
564566
Self::MultipleArgumentValues => "Got multiple values for argument",
565567
Self::UrlType => "URL input should be a string or URL",
566-
Self::UrlParsing {..} => "Input should be a valid URL, {error}",
567-
Self::UrlSyntaxViolation {..} => "Input violated strict URL syntax rules, {error}",
568-
Self::UrlTooLong {..} => "URL should have at most {max_length} characters",
569-
Self::UrlScheme {..} => "URL scheme should be {expected_schemes}",
568+
Self::UrlParsing { .. } => "Input should be a valid URL, {error}",
569+
Self::UrlSyntaxViolation { .. } => "Input violated strict URL syntax rules, {error}",
570+
Self::UrlTooLong { .. } => "URL should have at most {max_length} characters",
571+
Self::UrlScheme { .. } => "URL scheme should be {expected_schemes}",
570572
Self::UuidType => "UUID input should be a string, bytes or UUID object",
571573
Self::UuidParsing { .. } => "Input should be a valid UUID, {error}",
572-
Self::UuidVersion { .. } => "UUID version {expected_version} expected"
573-
574+
Self::UuidVersion { .. } => "UUID version {expected_version} expected",
574575
}
575576
}
576577

@@ -632,20 +633,18 @@ impl ErrorType {
632633
Self::LessThanEqual { le } => to_string_render!(tmpl, le),
633634
Self::MultipleOf { multiple_of } => to_string_render!(tmpl, multiple_of),
634635
Self::TooShort {
635-
field_type,
636636
min_length,
637637
actual_length,
638638
} => {
639639
let expected_plural = plural_s(*min_length);
640-
to_string_render!(tmpl, field_type, min_length, actual_length, expected_plural)
640+
to_string_render!(tmpl, min_length, actual_length, expected_plural)
641641
}
642642
Self::TooLong {
643-
field_type,
644643
max_length,
645644
actual_length,
646645
} => {
647646
let expected_plural = plural_s(*max_length);
648-
to_string_render!(tmpl, field_type, max_length, actual_length, expected_plural)
647+
to_string_render!(tmpl, max_length, actual_length, expected_plural)
649648
}
650649
Self::IterationError { error } => render!(tmpl, error),
651650
Self::StringTooShort { min_length } => to_string_render!(tmpl, min_length),
@@ -710,15 +709,13 @@ impl ErrorType {
710709
Self::LessThanEqual { le } => py_dict!(py, le),
711710
Self::MultipleOf { multiple_of } => py_dict!(py, multiple_of),
712711
Self::TooShort {
713-
field_type,
714712
min_length,
715713
actual_length,
716-
} => py_dict!(py, field_type, min_length, actual_length),
714+
} => py_dict!(py, min_length, actual_length),
717715
Self::TooLong {
718-
field_type,
719716
max_length,
720717
actual_length,
721-
} => py_dict!(py, field_type, max_length, actual_length),
718+
} => py_dict!(py, max_length, actual_length),
722719
Self::IterationError { error } => py_dict!(py, error),
723720
Self::StringTooShort { min_length } => py_dict!(py, min_length),
724721
Self::StringTooLong { max_length } => py_dict!(py, max_length),

src/input/input_abstract.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,5 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
306306
) -> ValResult<EitherTimedelta> {
307307
self.strict_timedelta(microseconds_overflow_behavior)
308308
}
309+
fn len(&self, py: Python<'_>) -> PyResult<usize>;
309310
}

src/input/input_json.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,13 @@ impl<'a> Input<'a> for JsonInput {
334334
_ => Err(ValError::new(ErrorType::TimeDeltaType, self)),
335335
}
336336
}
337+
338+
fn len(&self, py: Python<'_>) -> PyResult<usize> {
339+
match self.len() {
340+
Some(len) => Ok(len),
341+
None => self.to_object(py).as_ref(py).len(),
342+
}
343+
}
337344
}
338345

339346
/// Required for Dict keys so the string can behave like an Input
@@ -522,6 +529,10 @@ impl<'a> Input<'a> for String {
522529
) -> ValResult<EitherTimedelta> {
523530
self.validate_timedelta(false, microseconds_overflow_behavior)
524531
}
532+
533+
fn len(&self, _py: Python<'_>) -> PyResult<usize> {
534+
Ok(self.len())
535+
}
525536
}
526537

527538
fn string_to_vec(s: &str) -> JsonArray {

src/input/input_python.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,10 @@ impl<'a> Input<'a> for PyAny {
646646
Err(ValError::new(ErrorType::TimeDeltaType, self))
647647
}
648648
}
649+
650+
fn len(&self, _py: Python) -> PyResult<usize> {
651+
self.len()
652+
}
649653
}
650654

651655
/// Best effort check of whether it's likely to make sense to inspect obj for attributes and iterate over it

src/input/parse_json.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,14 @@ impl<'de> Visitor<'de> for KeyDeserializer {
218218
unreachable!()
219219
}
220220
}
221+
222+
impl JsonInput {
223+
pub fn len(&self) -> Option<usize> {
224+
match self {
225+
Self::Array(v) => Some(v.len()),
226+
Self::Object(o) => Some(o.len()),
227+
Self::String(s) => Some(s.len()),
228+
_ => None,
229+
}
230+
}
231+
}

0 commit comments

Comments
 (0)