Skip to content

Commit f9b49eb

Browse files
authored
support deque input to list, tuple etc., change how lengths are validated (#275)
* support deque input to list * fix iter check macros * tests for sequence types * add allow_iter * rename to "allow_any_iter" * remove dict case from benchmarks * fix collecting generators, improve coverage * better lengths checks for collections * fixing length constraints * remove plurals from error context * remove print * dfix compatibility with #283
1 parent f403300 commit f9b49eb

21 files changed

+891
-300
lines changed

pydantic_core/core_schema.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ class ListSchema(TypedDict, total=False):
310310
min_length: int
311311
max_length: int
312312
strict: bool
313+
allow_any_iter: bool
313314
ref: str
314315

315316

@@ -319,10 +320,17 @@ def list_schema(
319320
min_length: int | None = None,
320321
max_length: int | None = None,
321322
strict: bool | None = None,
323+
allow_any_iter: bool | None = None,
322324
ref: str | None = None,
323325
) -> ListSchema:
324326
return dict_not_none(
325-
type='list', items_schema=items_schema, min_length=min_length, max_length=max_length, strict=strict, ref=ref
327+
type='list',
328+
items_schema=items_schema,
329+
min_length=min_length,
330+
max_length=max_length,
331+
strict=strict,
332+
allow_any_iter=allow_any_iter,
333+
ref=ref,
326334
)
327335

328336

@@ -380,6 +388,7 @@ class SetSchema(TypedDict, total=False):
380388
items_schema: CoreSchema
381389
min_length: int
382390
max_length: int
391+
generator_max_length: int
383392
strict: bool
384393
ref: str
385394

@@ -389,11 +398,18 @@ def set_schema(
389398
*,
390399
min_length: int | None = None,
391400
max_length: int | None = None,
401+
generator_max_length: int | None = None,
392402
strict: bool | None = None,
393403
ref: str | None = None,
394404
) -> SetSchema:
395405
return dict_not_none(
396-
type='set', items_schema=items_schema, min_length=min_length, max_length=max_length, strict=strict, ref=ref
406+
type='set',
407+
items_schema=items_schema,
408+
min_length=min_length,
409+
max_length=max_length,
410+
generator_max_length=generator_max_length,
411+
strict=strict,
412+
ref=ref,
397413
)
398414

399415

@@ -402,6 +418,7 @@ class FrozenSetSchema(TypedDict, total=False):
402418
items_schema: CoreSchema
403419
min_length: int
404420
max_length: int
421+
generator_max_length: int
405422
strict: bool
406423
ref: str
407424

@@ -411,6 +428,7 @@ def frozenset_schema(
411428
*,
412429
min_length: int | None = None,
413430
max_length: int | None = None,
431+
generator_max_length: int | None = None,
414432
strict: bool | None = None,
415433
ref: str | None = None,
416434
) -> FrozenSetSchema:
@@ -419,6 +437,7 @@ def frozenset_schema(
419437
items_schema=items_schema,
420438
min_length=min_length,
421439
max_length=max_length,
440+
generator_max_length=generator_max_length,
422441
strict=strict,
423442
ref=ref,
424443
)

src/errors/kinds.rs

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,18 +77,28 @@ pub enum ErrorKind {
7777
// ---------------------
7878
// generic length errors - used for everything with a length except strings and bytes which need custom messages
7979
#[strum(
80-
message = "Input should have at least {min_length} item{expected_plural}, got {input_length} item{input_plural}"
80+
message = "{field_type} should have at least {min_length} item{expected_plural} after validation, not {actual_length}"
8181
)]
8282
TooShort {
83+
field_type: String,
8384
min_length: usize,
84-
input_length: usize,
85+
actual_length: usize,
8586
},
8687
#[strum(
87-
message = "Input should have at most {max_length} item{expected_plural}, got {input_length} item{input_plural}"
88+
message = "{field_type} should have at most {max_length} item{expected_plural} after validation, not {actual_length}"
8889
)]
8990
TooLong {
91+
field_type: String,
9092
max_length: usize,
91-
input_length: usize,
93+
actual_length: usize,
94+
},
95+
// ---------------------
96+
// generic collection and iteration errors
97+
#[strum(message = "Input should be iterable")]
98+
IterableType,
99+
#[strum(message = "Error iterating over object, error: {error}")]
100+
IterationError {
101+
error: String,
92102
},
93103
// ---------------------
94104
// string errors
@@ -123,12 +133,6 @@ pub enum ErrorKind {
123133
error: String,
124134
},
125135
// ---------------------
126-
// generic collection and iteration errors
127-
#[strum(message = "Input should be iterable")]
128-
IterableType,
129-
#[strum(message = "Error iterating over object")]
130-
IterationError,
131-
// ---------------------
132136
// list errors
133137
#[strum(message = "Input should be a valid list/array")]
134138
ListType,
@@ -501,21 +505,22 @@ impl ErrorKind {
501505
Self::LessThan { lt } => render!(self, lt),
502506
Self::LessThanEqual { le } => render!(self, le),
503507
Self::TooShort {
508+
field_type,
504509
min_length,
505-
input_length,
510+
actual_length,
506511
} => {
507512
let expected_plural = plural_s(min_length);
508-
let input_plural = plural_s(input_length);
509-
to_string_render!(self, min_length, input_length, expected_plural, input_plural)
513+
to_string_render!(self, field_type, min_length, actual_length, expected_plural)
510514
}
511515
Self::TooLong {
516+
field_type,
512517
max_length,
513-
input_length,
518+
actual_length,
514519
} => {
515520
let expected_plural = plural_s(max_length);
516-
let input_plural = plural_s(input_length);
517-
to_string_render!(self, max_length, input_length, expected_plural, input_plural)
521+
to_string_render!(self, field_type, max_length, actual_length, expected_plural)
518522
}
523+
Self::IterationError { error } => render!(self, error),
519524
Self::StrTooShort { min_length } => to_string_render!(self, min_length),
520525
Self::StrTooLong { max_length } => to_string_render!(self, max_length),
521526
Self::StrPatternMismatch { pattern } => render!(self, pattern),
@@ -565,13 +570,16 @@ impl ErrorKind {
565570
Self::LessThan { lt } => py_dict!(py, lt),
566571
Self::LessThanEqual { le } => py_dict!(py, le),
567572
Self::TooShort {
573+
field_type,
568574
min_length,
569-
input_length,
570-
} => py_dict!(py, min_length, input_length),
575+
actual_length,
576+
} => py_dict!(py, field_type, min_length, actual_length),
571577
Self::TooLong {
578+
field_type,
572579
max_length,
573-
input_length,
574-
} => py_dict!(py, max_length, input_length),
580+
actual_length,
581+
} => py_dict!(py, field_type, max_length, actual_length),
582+
Self::IterationError { error } => py_dict!(py, error),
575583
Self::StrTooShort { min_length } => py_dict!(py, min_length),
576584
Self::StrTooLong { max_length } => py_dict!(py, max_length),
577585
Self::StrPatternMismatch { pattern } => py_dict!(py, pattern),

src/input/input_abstract.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,16 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
124124
self.validate_dict(strict)
125125
}
126126

127-
fn validate_list(&'a self, strict: bool) -> ValResult<GenericCollection<'a>> {
128-
if strict {
127+
fn validate_list(&'a self, strict: bool, allow_any_iter: bool) -> ValResult<GenericCollection<'a>> {
128+
if strict && !allow_any_iter {
129129
self.strict_list()
130130
} else {
131-
self.lax_list()
131+
self.lax_list(allow_any_iter)
132132
}
133133
}
134134
fn strict_list(&'a self) -> ValResult<GenericCollection<'a>>;
135135
#[cfg_attr(has_no_coverage, no_coverage)]
136-
fn lax_list(&'a self) -> ValResult<GenericCollection<'a>> {
136+
fn lax_list(&'a self, _allow_any_iter: bool) -> ValResult<GenericCollection<'a>> {
137137
self.strict_list()
138138
}
139139

src/input/input_json.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,15 @@ impl<'a> Input<'a> for JsonInput {
171171
self.validate_dict(false)
172172
}
173173

174-
fn validate_list(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
174+
fn validate_list(&'a self, _strict: bool, _allow_any_iter: bool) -> ValResult<GenericCollection<'a>> {
175175
match self {
176176
JsonInput::Array(a) => Ok(a.into()),
177177
_ => Err(ValError::new(ErrorKind::ListType, self)),
178178
}
179179
}
180180
#[cfg_attr(has_no_coverage, no_coverage)]
181181
fn strict_list(&'a self) -> ValResult<GenericCollection<'a>> {
182-
self.validate_list(false)
182+
self.validate_list(false, false)
183183
}
184184

185185
fn validate_tuple(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
@@ -371,12 +371,12 @@ impl<'a> Input<'a> for String {
371371
}
372372

373373
#[cfg_attr(has_no_coverage, no_coverage)]
374-
fn validate_list(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
374+
fn validate_list(&'a self, _strict: bool, _allow_any_iter: bool) -> ValResult<GenericCollection<'a>> {
375375
Err(ValError::new(ErrorKind::ListType, self))
376376
}
377377
#[cfg_attr(has_no_coverage, no_coverage)]
378378
fn strict_list(&'a self) -> ValResult<GenericCollection<'a>> {
379-
self.validate_list(false)
379+
self.validate_list(false, false)
380380
}
381381

382382
#[cfg_attr(has_no_coverage, no_coverage)]

0 commit comments

Comments
 (0)