Skip to content

Commit d1ae0fb

Browse files
committed
custom_init and remove expect_fields_set
1 parent cb42663 commit d1ae0fb

29 files changed

+219
-335
lines changed

pydantic_core/core_schema.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2737,6 +2737,7 @@ class ModelSchema(TypedDict, total=False):
27372737
revalidate_instances: Literal['always', 'never', 'subclass-instances'] # default: 'never'
27382738
strict: bool
27392739
frozen: bool
2740+
custom_init: bool
27402741
config: CoreConfig
27412742
ref: str
27422743
metadata: Any
@@ -2751,6 +2752,7 @@ def model_schema(
27512752
revalidate_instances: Literal['always', 'never', 'subclass-instances'] | None = None,
27522753
strict: bool | None = None,
27532754
frozen: bool | None = None,
2755+
custom_init: bool | None = None,
27542756
config: CoreConfig | None = None,
27552757
ref: str | None = None,
27562758
metadata: Any = None,
@@ -2775,6 +2777,7 @@ class MyModel:
27752777
config=CoreConfig(str_max_length=5),
27762778
schema=core_schema.typed_dict_schema(
27772779
fields={'a': core_schema.typed_dict_field(core_schema.str_schema())},
2780+
return_fields_set=True,
27782781
),
27792782
)
27802783
v = SchemaValidator(schema)
@@ -2790,6 +2793,7 @@ class MyModel:
27902793
should re-validate defaults to config.revalidate_instances, else 'never'
27912794
strict: Whether the model is strict
27922795
frozen: Whether the model is frozen
2796+
custom_init: Whether the model has a custom init method
27932797
config: The config to use for the model
27942798
ref: optional unique identifier of the schema, used to reference the schema in other places
27952799
metadata: Any other information you want to include with the schema, not used by pydantic-core
@@ -2803,6 +2807,7 @@ class MyModel:
28032807
revalidate_instances=revalidate_instances,
28042808
strict=strict,
28052809
frozen=frozen,
2810+
custom_init=custom_init,
28062811
config=config,
28072812
ref=ref,
28082813
metadata=metadata,
@@ -3239,7 +3244,8 @@ def json_schema(
32393244
{
32403245
'field_a': core_schema.typed_dict_field(core_schema.str_schema()),
32413246
'field_b': core_schema.typed_dict_field(core_schema.bool_schema()),
3242-
}
3247+
},
3248+
return_fields_set=True,
32433249
)
32443250
32453251
class MyModel:

src/args_kwargs.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,30 @@ impl ArgsKwargs {
6060
}
6161
}
6262
}
63+
64+
pub(crate) const VALIDATED_DATA_KEY: &str = "__validated_data__";
65+
66+
#[pyclass(module = "pydantic_core._pydantic_core", frozen, get_all, freelist = 100)]
67+
#[derive(Debug, Clone)]
68+
pub struct ValidatedData {
69+
pub model_dict: PyObject,
70+
pub fields_set: PyObject,
71+
}
72+
73+
impl ValidatedData {
74+
pub(crate) fn new(model_dict: &PyAny, fields_set: &PyAny) -> Self {
75+
Self {
76+
model_dict: model_dict.to_object(model_dict.py()),
77+
fields_set: fields_set.to_object(model_dict.py()),
78+
}
79+
}
80+
}
81+
82+
#[pymethods]
83+
impl ValidatedData {
84+
fn __repr__(&self, py: Python) -> String {
85+
let model_dict = safe_repr(self.model_dict.as_ref(py));
86+
let fields_set = safe_repr(self.fields_set.as_ref(py));
87+
format!("ValidatedData(model_dict={model_dict}, fields_set={fields_set})")
88+
}
89+
}

src/build_context.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@ use pyo3::types::{PyDict, PyList};
55
use ahash::{AHashMap, AHashSet};
66

77
use crate::build_tools::{py_err, py_error_type, SchemaDict};
8-
use crate::questions::Answers;
98
use crate::serializers::CombinedSerializer;
109
use crate::validators::{CombinedValidator, Validator};
1110

1211
#[derive(Clone, Debug)]
1312
struct Slot<T> {
1413
slot_ref: String,
1514
op_val_ser: Option<T>,
16-
answers: Option<Answers>,
1715
}
1816

1917
pub enum ThingOrId<T> {
@@ -83,12 +81,11 @@ impl<T: Clone + std::fmt::Debug> BuildContext<T> {
8381
/// First of two part process to add a new validator/serializer slot, we add the `slot_ref` to the array,
8482
/// but not the actual `validator`/`serializer`, we can't add that until it's build.
8583
/// But we need the `id` to build it, hence this two-step process.
86-
pub fn prepare_slot(&mut self, slot_ref: String, answers: Option<Answers>) -> PyResult<usize> {
84+
pub fn prepare_slot(&mut self, slot_ref: String) -> PyResult<usize> {
8785
let id = self.slots.len();
8886
let slot = Slot {
8987
slot_ref,
9088
op_val_ser: None,
91-
answers,
9289
};
9390
self.slots.push(slot);
9491
Ok(id)
@@ -101,7 +98,6 @@ impl<T: Clone + std::fmt::Debug> BuildContext<T> {
10198
self.slots[slot_id] = Slot {
10299
slot_ref: slot.slot_ref.clone(),
103100
op_val_ser: Some(val_ser),
104-
answers: slot.answers.clone(),
105101
};
106102
Ok(())
107103
}
@@ -123,14 +119,6 @@ impl<T: Clone + std::fmt::Debug> BuildContext<T> {
123119
}
124120
}
125121

126-
/// get a slot answer by `id`
127-
pub fn get_slot_answer(&self, slot_id: usize) -> PyResult<Option<Answers>> {
128-
match self.slots.get(slot_id) {
129-
Some(slot) => Ok(slot.answers.clone()),
130-
None => py_err!("Slots Error: slot {} not found", slot_id),
131-
}
132-
}
133-
134122
/// find a validator/serializer by `slot_id` - this used in `Validator.complete`,
135123
/// specifically `DefinitionRefValidator` to set its name
136124
pub fn find_validator(&self, slot_id: usize) -> PyResult<&T> {

src/input/input_abstract.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::fmt;
33
use pyo3::prelude::*;
44
use pyo3::types::{PyString, PyType};
55

6+
use crate::args_kwargs::ValidatedData;
67
use crate::errors::{InputValue, LocItem, ValResult};
78
use crate::{PyMultiHostUrl, PyUrl};
89

@@ -44,6 +45,11 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
4445
None
4546
}
4647

48+
#[cfg_attr(has_no_coverage, no_coverage)]
49+
fn validated_data(&self) -> Option<ValidatedData> {
50+
None
51+
}
52+
4753
// input_ prefix to differentiate from the function on PyAny
4854
fn input_is_instance(&self, class: &PyAny, json_mask: u8) -> PyResult<bool>;
4955

src/input/input_python.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use pyo3::types::{
1111
use pyo3::types::{PyDictItems, PyDictKeys, PyDictValues};
1212
use pyo3::{ffi, intern, AsPyPointer, PyTypeInfo};
1313

14+
use crate::args_kwargs::{ValidatedData, VALIDATED_DATA_KEY};
1415
use crate::build_tools::safe_repr;
1516
use crate::errors::{ErrorType, InputValue, LocItem, ValError, ValResult};
1617
use crate::{ArgsKwargs, PyMultiHostUrl, PyUrl};
@@ -102,6 +103,16 @@ impl<'a> Input<'a> for PyAny {
102103
Some(self.getattr(name))
103104
}
104105

106+
#[cfg_attr(has_no_coverage, no_coverage)]
107+
fn validated_data(&self) -> Option<ValidatedData> {
108+
if let Ok(v) = self.get_item(intern!(self.py(), VALIDATED_DATA_KEY)) {
109+
if let Ok(validated_data) = v.extract::<ValidatedData>() {
110+
return Some(validated_data);
111+
}
112+
}
113+
None
114+
}
115+
105116
fn input_is_instance(&self, class: &PyAny, _json_mask: u8) -> PyResult<bool> {
106117
// See PyO3/pyo3#2694 - we can't use `is_instance` here since it requires PyType,
107118
// and some check objects are not types, this logic is lifted from `is_instance` in PyO3

src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ mod errors;
1616
mod input;
1717
mod lazy_index_map;
1818
mod lookup_key;
19-
mod questions;
2019
mod recursion_guard;
2120
mod serializers;
2221
mod url;

src/questions.rs

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/serializers/shared.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ impl BuildSerializer for CombinedSerializer {
214214
return if build_context.ref_used_within(schema, &schema_ref)? {
215215
// the ref is used within itself, so we have to store the serializer in slots
216216
// and return a DefinitionRefSerializer
217-
let slot_id = build_context.prepare_slot(schema_ref, None)?;
217+
let slot_id = build_context.prepare_slot(schema_ref)?;
218218
let inner_ser = Self::_build(schema, config, build_context)?;
219219
build_context.complete_slot(slot_id, inner_ser)?;
220220
Ok(super::type_serializers::definitions::DefinitionRefSerializer::from_id(

src/validators/chain.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use pyo3::types::{PyDict, PyList};
55
use crate::build_tools::{py_err, SchemaDict};
66
use crate::errors::ValResult;
77
use crate::input::Input;
8-
use crate::questions::Question;
98
use crate::recursion_guard::RecursionGuard;
109

1110
use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator};
@@ -99,12 +98,6 @@ impl Validator for ChainValidator {
9998
&self.name
10099
}
101100

102-
fn ask(&self, question: &Question) -> bool {
103-
// any makes more sense since at the moment we only use ask for "return_fields_set", might need
104-
// more complex logic in future
105-
self.steps.iter().any(|v| v.ask(question))
106-
}
107-
108101
fn complete(&mut self, build_context: &BuildContext<CombinedValidator>) -> PyResult<()> {
109102
self.steps.iter_mut().try_for_each(|v| v.complete(build_context))
110103
}

src/validators/custom_error.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use pyo3::types::PyDict;
55
use crate::build_tools::{py_err, SchemaDict};
66
use crate::errors::{ErrorType, PydanticCustomError, PydanticKnownError, ValError, ValResult};
77
use crate::input::Input;
8-
use crate::questions::Question;
98
use crate::recursion_guard::RecursionGuard;
109

1110
use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator};
@@ -109,10 +108,6 @@ impl Validator for CustomErrorValidator {
109108
&self.name
110109
}
111110

112-
fn ask(&self, question: &Question) -> bool {
113-
self.validator.ask(question)
114-
}
115-
116111
fn complete(&mut self, build_context: &BuildContext<CombinedValidator>) -> PyResult<()> {
117112
self.validator.complete(build_context)
118113
}

src/validators/definitions.rs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::build_context::{BuildContext, ThingOrId};
66
use crate::build_tools::SchemaDict;
77
use crate::errors::{ErrorType, ValError, ValResult};
88
use crate::input::Input;
9-
use crate::questions::{Answers, Question};
109
use crate::recursion_guard::RecursionGuard;
1110

1211
use super::{build_validator, BuildValidator, CombinedValidator, Extra, Validator};
@@ -41,15 +40,13 @@ pub struct DefinitionRefValidator {
4140
validator_id: usize,
4241
inner_name: String,
4342
// we have to record the answers to `Question`s as we can't access the validator when `ask()` is called
44-
answers: Answers,
4543
}
4644

4745
impl DefinitionRefValidator {
48-
pub fn from_id(validator_id: usize, inner_name: String, answers: Answers) -> CombinedValidator {
46+
pub fn from_id(validator_id: usize, inner_name: String) -> CombinedValidator {
4947
Self {
5048
validator_id,
5149
inner_name,
52-
answers,
5350
}
5451
.into()
5552
}
@@ -67,15 +64,11 @@ impl BuildValidator for DefinitionRefValidator {
6764

6865
match build_context.find(&schema_ref)? {
6966
ThingOrId::Thing(validator) => Ok(validator),
70-
ThingOrId::Id(validator_id) => {
71-
let answers = build_context.get_slot_answer(validator_id)?;
72-
Ok(Self {
73-
validator_id,
74-
inner_name: "...".to_string(),
75-
answers: answers.unwrap(),
76-
}
77-
.into())
67+
ThingOrId::Id(validator_id) => Ok(Self {
68+
validator_id,
69+
inner_name: "...".to_string(),
7870
}
71+
.into()),
7972
}
8073
}
8174
}
@@ -125,10 +118,6 @@ impl Validator for DefinitionRefValidator {
125118
&self.inner_name
126119
}
127120

128-
fn ask(&self, question: &Question) -> bool {
129-
self.answers.ask(question)
130-
}
131-
132121
/// don't need to call complete on the inner validator here, complete_validators takes care of that.
133122
fn complete(&mut self, build_context: &BuildContext<CombinedValidator>) -> PyResult<()> {
134123
let validator = build_context.find_validator(self.validator_id)?;

src/validators/function.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use crate::errors::{
88
ErrorType, LocItem, PydanticCustomError, PydanticKnownError, PydanticOmit, ValError, ValResult, ValidationError,
99
};
1010
use crate::input::Input;
11-
use crate::questions::Question;
1211
use crate::recursion_guard::RecursionGuard;
1312

1413
use super::generator::InternalValidator;
@@ -111,10 +110,6 @@ macro_rules! impl_validator {
111110
&self.name
112111
}
113112

114-
fn ask(&self, question: &Question) -> bool {
115-
self.validator.ask(question)
116-
}
117-
118113
fn complete(&mut self, build_context: &BuildContext<CombinedValidator>) -> PyResult<()> {
119114
self.validator.complete(build_context)
120115
}
@@ -341,10 +336,6 @@ impl Validator for FunctionWrapValidator {
341336
&self.name
342337
}
343338

344-
fn ask(&self, question: &Question) -> bool {
345-
self.validator.ask(question)
346-
}
347-
348339
fn complete(&mut self, build_context: &BuildContext<CombinedValidator>) -> PyResult<()> {
349340
self.validator.complete(build_context)
350341
}

src/validators/generator.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use pyo3::types::PyDict;
66
use crate::build_tools::SchemaDict;
77
use crate::errors::{ErrorMode, ErrorType, LocItem, ValError, ValResult};
88
use crate::input::{GenericIterator, Input};
9-
use crate::questions::Question;
109
use crate::recursion_guard::RecursionGuard;
1110
use crate::ValidationError;
1211

@@ -84,13 +83,6 @@ impl Validator for GeneratorValidator {
8483
&self.name
8584
}
8685

87-
fn ask(&self, question: &Question) -> bool {
88-
match self.item_validator {
89-
Some(ref v) => v.ask(question),
90-
None => false,
91-
}
92-
}
93-
9486
fn complete(&mut self, build_context: &BuildContext<CombinedValidator>) -> PyResult<()> {
9587
match self.item_validator {
9688
Some(ref mut v) => v.complete(build_context),

src/validators/json.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use pyo3::types::PyDict;
55
use crate::build_tools::SchemaDict;
66
use crate::errors::ValResult;
77
use crate::input::Input;
8-
use crate::questions::Question;
98
use crate::recursion_guard::RecursionGuard;
109

1110
use super::{build_validator, BuildContext, BuildValidator, CombinedValidator, Extra, Validator};
@@ -78,10 +77,6 @@ impl Validator for JsonValidator {
7877
&self.name
7978
}
8079

81-
fn ask(&self, question: &Question) -> bool {
82-
self.validator.as_ref().map(|v| v.ask(question)).unwrap_or(false)
83-
}
84-
8580
fn complete(&mut self, build_context: &BuildContext<CombinedValidator>) -> PyResult<()> {
8681
match self.validator {
8782
Some(ref mut v) => v.complete(build_context),

0 commit comments

Comments
 (0)