Please ask questions as I go along ... I've written this in a hurry so if you're confused, you're probably not alone
Everything! (and hopefully nothing for you):
all validation is offloaded to another library - pydantic-core which I'm building now
"You can't make a Tomlette without breaking a few Gregs!" (Succession S2E9) - best TV pun ever?
I'm going to have to upset some people to get V2 out:
class MyModel(BaseModel):
    appointment_time: datetime | None
    
    @validator('appointment_time', mode='wrap')
    def validate_appointment_time(cls, v, handler):
        if v == 'now':
            return datetime.now()
        
        try:
            return handler(v)
        except ValidationError:
            # we don't want to fail, so just use None
            return None
(Implemented, but not with this nice syntactic sugar)
AKA "The onion" - like middleware
class StrictModel(BaseModel, strict=True):
    a_string: str
    an_int: int
    set_of_ints: Set[int]
class LaxModel(BaseModel):
    lax_string: str
    strict_string1: str = Field(..., strict=True)
    strict_string2: StrictStr
(Implemented, but not with this nice syntactic sugar)
(Does not apply when "downcasting" from JSON)
class MyModel(BaseModel):
    int_or_bool: int | bool
    bool_or_int: bool | int
print(MyModel(int_or_bool=1))  #> 1, 1
print(MyModel(int_or_bool=True))  #> True, True
print(MyModel(int_or_bool='1'))  #> 1, True :-( ?(Implemented, but not with this nice syntactic sugar)
(Using strict mode)
(Also works with models and model instances)
class MyModel1(BaseModel):
    none_allowed_required: str | None
    none_allowed_not_required: str | None = None
      
from typing_extentions import Required, NotRequired
class MyModel2(BaseModel):  # ... maybe, do you want this?
    none_allowed_required: Required[str]
    none_allowed_not_required: NotRequired[str](Implemented, but not with this nice syntactic sugar)
(I'm no longer scared of the word "optional")
from pydantic import validate
validate(List[int], [1, 2, '3'])  #> [1, 2, 3]
validate(List[int], [1, 2, 3], strict=True)  #> [1, 2, 3]
validate(List[int], [1, 2, '3'], strict=True)
#> raises ValidationError(Implemented, but not with this nice syntactic sugar)
No intermediate model required (unlike parse_obj in V1)
class MyModel(BaseModel):
    name: str
    age: int
    friends: List[int]
    settings: Dict[str, float]
MyModel.validate_json('{...}')(Implemented, but not with this nice syntactic sugar)
No json.loads - just rust JSON parsing straight into validation
| Benchmark | Speed up | 
|---|---|
| Simple model (str, int, List[int], Dict[str, float]) | 15.97x | 
| Simple model - JSON | 11.56x | 
| A bool (single value) | 3.46x | 
| Recursive model, 50 deep | 3.99x | 
| list of typed dicts, length 100 | 12.14x | 
| list of ints, length 1000 | 25.49x | 
from pydantic_core import SchemaValidator
schema_validator = SchemaValidator({'type': 'bool'})
print(repr(schema_validator))(This code actually runs now!)
Let's start simple
SchemaValidator(name="bool", validator=BoolValidator)print(schema_validator.validate_python(True)) -> True
print(schema_validator.validate_python(1))    -> True
print(schema_validator.validate_json('true')) -> Truefrom pydantic_core import SchemaValidator
# Equivalent to: Dict[str, Optional[int]]
schema_validator = SchemaValidator({
    'type': 'dict',
    'keys': {'type': 'str'},
    'values': {'type': 'optional', 'schema': {'type': 'int'}}
})Let's get a bit more complicated
SchemaValidator(name="dict", validator=DictValidator {
    strict: false,
    key_validator: Some(StrValidator),
    value_validator: Some(
        OptionalValidator {validator: IntValidator},
    ),
    min_items: None,
    max_items: None,
    try_instance_as_dict: false,
})class MyModel(BaseModel):
    name: str
    age: int | None = 42
    settings: dict[str, float]
    friends: list[int | str]And finally...
(you don't need to read all this)
SchemaValidator(name="MyCoreModel", 
  validator=ModelClassValidator {
    strict: false,
    class: Py(0x12fe7e7c0), (MyCoreModel)
    new_method: Py(0x00101054130), (MyCoreModel.__new__)
    validator: ModelValidator {
        name: "Model",
        fields: [
            ModelField {
                name: "name",
                default: None,
                validator: StrValidator,
            },
            ModelField {
                name: "age",
                default: 42,
                validator: OptionalValidator { 
                  validator: IntValidator 
                },
            },
            ModelField {
                name: "settings",
                default: None,
                validator: DictValidator { ... },
            },
            ModelField {
                name: "friends",
                default: None,
                validator: ListValidator {
                    strict: false,
                    item_validator: Some(
                        UnionValidator {
                            choices: [
                                IntValidator,
                                StrValidator,
                            ],
                        },
                    ),
                    min_items: None,
                    max_items: None,
                },
            },
        ],
        extra_behavior: Ignore,
        extra_validator: None,
    },
})Many other people have said all this, there are many (much better) talks about it.
But for completeness, the good:
The bad:
The ugly:
#[derive(Debug, Clone)]
pub struct OptionalValidator {
    validator: Box<dyn Validator>,
}
impl OptionalValidator {
    pub const EXPECTED_TYPE: &'static str = "optional";
}
impl Validator for OptionalValidator {
    fn build(schema: &PyDict, config: Option<&PyDict>) -> PyResult<Box<dyn Validator>> {
        let schema: &PyAny = schema.get_as_req("schema")?;
        Ok(Box::new(Self {
            validator: build_validator(schema, config)?.0,
        }))
    }
    fn validate<'s, 'data>(
        &'s self,
        py: Python<'data>,
        input: &'data dyn Input,
        extra: &Extra,
    ) -> ValResult<'data, PyObject> {
        match input.is_none() {
            true => Ok(py.None()),
            false => self.validator.validate(py, input, extra),
        }
    }
    fn validate_strict<'s, 'data>(
        &'s self,
        py: Python<'data>,
        input: &'data dyn Input,
        extra: &Extra,
    ) -> ValResult<'data, PyObject> {
        match input.is_none() {
            true => Ok(py.None()),
            false => self.validator.validate_strict(py, input, extra),
        }
    }
    fn set_ref(&mut self, name: &str, validator_arc: &ValidatorArc) -> PyResult<()> {
        self.validator.set_ref(name, validator_arc)
    }
    validator_boilerplate!(Self::EXPECTED_TYPE);
}
mod optional;
...
lots of code...
...
    validator_match!(
        type_,
        dict,
        config,
        ... all the other validators
        // unions
        self::union::UnionValidator,
        self::optional::OptionalValidator,
        ...
    )Checkout: github.com/samuelcolvin/pydantic-core
And: github.com/samuelcolvin/pydantic
Follow me on twitter: @samuel_colvin