Skip to content

Commit b6e9baf

Browse files
eager length checks and accept arbitrary iterables for list/sets/tuples (#610)
Co-authored-by: Adrian Garcia Badaracco <[email protected]>
1 parent 084172a commit b6e9baf

File tree

16 files changed

+850
-567
lines changed

16 files changed

+850
-567
lines changed

pydantic_core/core_schema.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,7 +1184,6 @@ class ListSchema(TypedDict, total=False):
11841184
min_length: int
11851185
max_length: int
11861186
strict: bool
1187-
allow_any_iter: bool
11881187
ref: str
11891188
metadata: Any
11901189
serialization: IncExSeqOrElseSerSchema
@@ -1196,7 +1195,6 @@ def list_schema(
11961195
min_length: int | None = None,
11971196
max_length: int | None = None,
11981197
strict: bool | None = None,
1199-
allow_any_iter: bool | None = None,
12001198
ref: str | None = None,
12011199
metadata: Any = None,
12021200
serialization: IncExSeqOrElseSerSchema | None = None,
@@ -1217,7 +1215,6 @@ def list_schema(
12171215
min_length: The value must be a list with at least this many items
12181216
max_length: The value must be a list with at most this many items
12191217
strict: The value must be a list with exactly this many items
1220-
allow_any_iter: Whether the value can be any iterable
12211218
ref: optional unique identifier of the schema, used to reference the schema in other places
12221219
metadata: Any other information you want to include with the schema, not used by pydantic-core
12231220
serialization: Custom serialization schema
@@ -1228,7 +1225,6 @@ def list_schema(
12281225
min_length=min_length,
12291226
max_length=max_length,
12301227
strict=strict,
1231-
allow_any_iter=allow_any_iter,
12321228
ref=ref,
12331229
metadata=metadata,
12341230
serialization=serialization,
@@ -1349,7 +1345,6 @@ class SetSchema(TypedDict, total=False):
13491345
items_schema: CoreSchema
13501346
min_length: int
13511347
max_length: int
1352-
generator_max_length: int
13531348
strict: bool
13541349
ref: str
13551350
metadata: Any
@@ -1361,7 +1356,6 @@ def set_schema(
13611356
*,
13621357
min_length: int | None = None,
13631358
max_length: int | None = None,
1364-
generator_max_length: int | None = None,
13651359
strict: bool | None = None,
13661360
ref: str | None = None,
13671361
metadata: Any = None,
@@ -1384,9 +1378,6 @@ def set_schema(
13841378
items_schema: The value must be a set with items that match this schema
13851379
min_length: The value must be a set with at least this many items
13861380
max_length: The value must be a set with at most this many items
1387-
generator_max_length: At most this many items will be read from a generator before failing validation
1388-
This is important because generators can be infinite, and even with a `max_length` on the set,
1389-
an infinite generator could run forever without producing more than `max_length` distinct items.
13901381
strict: The value must be a set with exactly this many items
13911382
ref: optional unique identifier of the schema, used to reference the schema in other places
13921383
metadata: Any other information you want to include with the schema, not used by pydantic-core
@@ -1397,7 +1388,6 @@ def set_schema(
13971388
items_schema=items_schema,
13981389
min_length=min_length,
13991390
max_length=max_length,
1400-
generator_max_length=generator_max_length,
14011391
strict=strict,
14021392
ref=ref,
14031393
metadata=metadata,
@@ -1410,7 +1400,6 @@ class FrozenSetSchema(TypedDict, total=False):
14101400
items_schema: CoreSchema
14111401
min_length: int
14121402
max_length: int
1413-
generator_max_length: int
14141403
strict: bool
14151404
ref: str
14161405
metadata: Any
@@ -1422,7 +1411,6 @@ def frozenset_schema(
14221411
*,
14231412
min_length: int | None = None,
14241413
max_length: int | None = None,
1425-
generator_max_length: int | None = None,
14261414
strict: bool | None = None,
14271415
ref: str | None = None,
14281416
metadata: Any = None,
@@ -1445,7 +1433,6 @@ def frozenset_schema(
14451433
items_schema: The value must be a frozenset with items that match this schema
14461434
min_length: The value must be a frozenset with at least this many items
14471435
max_length: The value must be a frozenset with at most this many items
1448-
generator_max_length: The value must generate a frozenset with at most this many items
14491436
strict: The value must be a frozenset with exactly this many items
14501437
ref: optional unique identifier of the schema, used to reference the schema in other places
14511438
metadata: Any other information you want to include with the schema, not used by pydantic-core
@@ -1456,7 +1443,6 @@ def frozenset_schema(
14561443
items_schema=items_schema,
14571444
min_length=min_length,
14581445
max_length=max_length,
1459-
generator_max_length=generator_max_length,
14601446
strict=strict,
14611447
ref=ref,
14621448
metadata=metadata,

src/input/input_abstract.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{PyMultiHostUrl, PyUrl};
88

99
use super::datetime::{EitherDate, EitherDateTime, EitherTime, EitherTimedelta};
1010
use super::return_enums::{EitherBytes, EitherString};
11-
use super::{GenericArguments, GenericCollection, GenericIterator, GenericMapping, JsonInput};
11+
use super::{GenericArguments, GenericIterable, GenericIterator, GenericMapping, JsonInput};
1212

1313
#[derive(Debug, Clone, Copy)]
1414
pub enum InputType {
@@ -166,58 +166,60 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
166166
self.validate_dict(strict)
167167
}
168168

169-
fn validate_list(&'a self, strict: bool, allow_any_iter: bool) -> ValResult<GenericCollection<'a>> {
170-
if strict && !allow_any_iter {
169+
fn validate_list(&'a self, strict: bool) -> ValResult<GenericIterable<'a>> {
170+
if strict {
171171
self.strict_list()
172172
} else {
173-
self.lax_list(allow_any_iter)
173+
self.lax_list()
174174
}
175175
}
176-
fn strict_list(&'a self) -> ValResult<GenericCollection<'a>>;
176+
fn strict_list(&'a self) -> ValResult<GenericIterable<'a>>;
177177
#[cfg_attr(has_no_coverage, no_coverage)]
178-
fn lax_list(&'a self, _allow_any_iter: bool) -> ValResult<GenericCollection<'a>> {
178+
fn lax_list(&'a self) -> ValResult<GenericIterable<'a>> {
179179
self.strict_list()
180180
}
181181

182-
fn validate_tuple(&'a self, strict: bool) -> ValResult<GenericCollection<'a>> {
182+
fn validate_tuple(&'a self, strict: bool) -> ValResult<GenericIterable<'a>> {
183183
if strict {
184184
self.strict_tuple()
185185
} else {
186186
self.lax_tuple()
187187
}
188188
}
189-
fn strict_tuple(&'a self) -> ValResult<GenericCollection<'a>>;
189+
fn strict_tuple(&'a self) -> ValResult<GenericIterable<'a>>;
190190
#[cfg_attr(has_no_coverage, no_coverage)]
191-
fn lax_tuple(&'a self) -> ValResult<GenericCollection<'a>> {
191+
fn lax_tuple(&'a self) -> ValResult<GenericIterable<'a>> {
192192
self.strict_tuple()
193193
}
194194

195-
fn validate_set(&'a self, strict: bool) -> ValResult<GenericCollection<'a>> {
195+
fn validate_set(&'a self, strict: bool) -> ValResult<GenericIterable<'a>> {
196196
if strict {
197197
self.strict_set()
198198
} else {
199199
self.lax_set()
200200
}
201201
}
202-
fn strict_set(&'a self) -> ValResult<GenericCollection<'a>>;
202+
fn strict_set(&'a self) -> ValResult<GenericIterable<'a>>;
203203
#[cfg_attr(has_no_coverage, no_coverage)]
204-
fn lax_set(&'a self) -> ValResult<GenericCollection<'a>> {
204+
fn lax_set(&'a self) -> ValResult<GenericIterable<'a>> {
205205
self.strict_set()
206206
}
207207

208-
fn validate_frozenset(&'a self, strict: bool) -> ValResult<GenericCollection<'a>> {
208+
fn validate_frozenset(&'a self, strict: bool) -> ValResult<GenericIterable<'a>> {
209209
if strict {
210210
self.strict_frozenset()
211211
} else {
212212
self.lax_frozenset()
213213
}
214214
}
215-
fn strict_frozenset(&'a self) -> ValResult<GenericCollection<'a>>;
215+
fn strict_frozenset(&'a self) -> ValResult<GenericIterable<'a>>;
216216
#[cfg_attr(has_no_coverage, no_coverage)]
217-
fn lax_frozenset(&'a self) -> ValResult<GenericCollection<'a>> {
217+
fn lax_frozenset(&'a self) -> ValResult<GenericIterable<'a>> {
218218
self.strict_frozenset()
219219
}
220220

221+
fn extract_generic_iterable(&'a self) -> ValResult<GenericIterable<'a>>;
222+
221223
fn validate_iter(&self) -> ValResult<GenericIterator>;
222224

223225
fn validate_date(&self, strict: bool) -> ValResult<EitherDate> {

src/input/input_json.rs

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use super::datetime::{
1010
use super::parse_json::JsonArray;
1111
use super::shared::{float_as_int, int_as_bool, map_json_err, str_as_bool, str_as_int};
1212
use super::{
13-
EitherBytes, EitherString, EitherTimedelta, GenericArguments, GenericCollection, GenericIterator, GenericMapping,
13+
EitherBytes, EitherString, EitherTimedelta, GenericArguments, GenericIterable, GenericIterator, GenericMapping,
1414
Input, JsonArgs, JsonInput,
1515
};
1616

@@ -187,53 +187,62 @@ impl<'a> Input<'a> for JsonInput {
187187
self.validate_dict(false)
188188
}
189189

190-
fn validate_list(&'a self, _strict: bool, _allow_any_iter: bool) -> ValResult<GenericCollection<'a>> {
190+
fn validate_list(&'a self, _strict: bool) -> ValResult<GenericIterable<'a>> {
191191
match self {
192-
JsonInput::Array(a) => Ok(a.into()),
192+
JsonInput::Array(a) => Ok(GenericIterable::JsonArray(a)),
193193
_ => Err(ValError::new(ErrorType::ListType, self)),
194194
}
195195
}
196196
#[cfg_attr(has_no_coverage, no_coverage)]
197-
fn strict_list(&'a self) -> ValResult<GenericCollection<'a>> {
198-
self.validate_list(false, false)
197+
fn strict_list(&'a self) -> ValResult<GenericIterable<'a>> {
198+
self.validate_list(false)
199199
}
200200

201-
fn validate_tuple(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
201+
fn validate_tuple(&'a self, _strict: bool) -> ValResult<GenericIterable<'a>> {
202202
// just as in set's case, List has to be allowed
203203
match self {
204-
JsonInput::Array(a) => Ok(a.into()),
204+
JsonInput::Array(a) => Ok(GenericIterable::JsonArray(a)),
205205
_ => Err(ValError::new(ErrorType::TupleType, self)),
206206
}
207207
}
208208
#[cfg_attr(has_no_coverage, no_coverage)]
209-
fn strict_tuple(&'a self) -> ValResult<GenericCollection<'a>> {
209+
fn strict_tuple(&'a self) -> ValResult<GenericIterable<'a>> {
210210
self.validate_tuple(false)
211211
}
212212

213-
fn validate_set(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
213+
fn validate_set(&'a self, _strict: bool) -> ValResult<GenericIterable<'a>> {
214214
// we allow a list here since otherwise it would be impossible to create a set from JSON
215215
match self {
216-
JsonInput::Array(a) => Ok(a.into()),
216+
JsonInput::Array(a) => Ok(GenericIterable::JsonArray(a)),
217217
_ => Err(ValError::new(ErrorType::SetType, self)),
218218
}
219219
}
220220
#[cfg_attr(has_no_coverage, no_coverage)]
221-
fn strict_set(&'a self) -> ValResult<GenericCollection<'a>> {
221+
fn strict_set(&'a self) -> ValResult<GenericIterable<'a>> {
222222
self.validate_set(false)
223223
}
224224

225-
fn validate_frozenset(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
225+
fn validate_frozenset(&'a self, _strict: bool) -> ValResult<GenericIterable<'a>> {
226226
// we allow a list here since otherwise it would be impossible to create a frozenset from JSON
227227
match self {
228-
JsonInput::Array(a) => Ok(a.into()),
228+
JsonInput::Array(a) => Ok(GenericIterable::JsonArray(a)),
229229
_ => Err(ValError::new(ErrorType::FrozenSetType, self)),
230230
}
231231
}
232232
#[cfg_attr(has_no_coverage, no_coverage)]
233-
fn strict_frozenset(&'a self) -> ValResult<GenericCollection<'a>> {
233+
fn strict_frozenset(&'a self) -> ValResult<GenericIterable<'a>> {
234234
self.validate_frozenset(false)
235235
}
236236

237+
fn extract_generic_iterable(&self) -> ValResult<GenericIterable> {
238+
match self {
239+
JsonInput::Array(a) => Ok(GenericIterable::JsonArray(a)),
240+
JsonInput::String(s) => Ok(GenericIterable::JsonString(s)),
241+
JsonInput::Object(object) => Ok(GenericIterable::JsonObject(object)),
242+
_ => Err(ValError::new(ErrorType::IterableType, self)),
243+
}
244+
}
245+
237246
fn validate_iter(&self) -> ValResult<GenericIterator> {
238247
match self {
239248
JsonInput::Array(a) => Ok(a.clone().into()),
@@ -405,41 +414,45 @@ impl<'a> Input<'a> for String {
405414
}
406415

407416
#[cfg_attr(has_no_coverage, no_coverage)]
408-
fn validate_list(&'a self, _strict: bool, _allow_any_iter: bool) -> ValResult<GenericCollection<'a>> {
417+
fn validate_list(&'a self, _strict: bool) -> ValResult<GenericIterable<'a>> {
409418
Err(ValError::new(ErrorType::ListType, self))
410419
}
411420
#[cfg_attr(has_no_coverage, no_coverage)]
412-
fn strict_list(&'a self) -> ValResult<GenericCollection<'a>> {
413-
self.validate_list(false, false)
421+
fn strict_list(&'a self) -> ValResult<GenericIterable<'a>> {
422+
self.validate_list(false)
414423
}
415424

416425
#[cfg_attr(has_no_coverage, no_coverage)]
417-
fn validate_tuple(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
426+
fn validate_tuple(&'a self, _strict: bool) -> ValResult<GenericIterable<'a>> {
418427
Err(ValError::new(ErrorType::TupleType, self))
419428
}
420429
#[cfg_attr(has_no_coverage, no_coverage)]
421-
fn strict_tuple(&'a self) -> ValResult<GenericCollection<'a>> {
430+
fn strict_tuple(&'a self) -> ValResult<GenericIterable<'a>> {
422431
self.validate_tuple(false)
423432
}
424433

425434
#[cfg_attr(has_no_coverage, no_coverage)]
426-
fn validate_set(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
435+
fn validate_set(&'a self, _strict: bool) -> ValResult<GenericIterable<'a>> {
427436
Err(ValError::new(ErrorType::SetType, self))
428437
}
429438
#[cfg_attr(has_no_coverage, no_coverage)]
430-
fn strict_set(&'a self) -> ValResult<GenericCollection<'a>> {
439+
fn strict_set(&'a self) -> ValResult<GenericIterable<'a>> {
431440
self.validate_set(false)
432441
}
433442

434443
#[cfg_attr(has_no_coverage, no_coverage)]
435-
fn validate_frozenset(&'a self, _strict: bool) -> ValResult<GenericCollection<'a>> {
444+
fn validate_frozenset(&'a self, _strict: bool) -> ValResult<GenericIterable<'a>> {
436445
Err(ValError::new(ErrorType::FrozenSetType, self))
437446
}
438447
#[cfg_attr(has_no_coverage, no_coverage)]
439-
fn strict_frozenset(&'a self) -> ValResult<GenericCollection<'a>> {
448+
fn strict_frozenset(&'a self) -> ValResult<GenericIterable<'a>> {
440449
self.validate_frozenset(false)
441450
}
442451

452+
fn extract_generic_iterable(&'a self) -> ValResult<GenericIterable<'a>> {
453+
Ok(GenericIterable::JsonString(self))
454+
}
455+
443456
fn validate_iter(&self) -> ValResult<GenericIterator> {
444457
Ok(string_to_vec(self).into())
445458
}

0 commit comments

Comments
 (0)