pydantic / pydantic-core

Core validation logic for pydantic written in rust
MIT License
1.4k stars 232 forks source link

Introduce a schema variant to reuse Validators, Serializers and CoreSchema #1414

Open BoxyUwU opened 1 month ago

BoxyUwU commented 1 month ago

Introduces a nested CoreSchema variant that reuses the specified model's schema/validator/serializer. As an example, the core schema for the following pydantic models:

class A(BaseModel):
    field3: 'B'

class B(BaseModel):
    field1: list[A]

class Wrapper(BaseModel):
    a: A
    b: B
{
    'type': 'model',
    'cls': <class '__main__.A'>,
    'schema': {
        'type': 'model-fields',
        'fields': {
            'field3': {
                'type': 'model-field',
                'schema': {
                    'type': 'nested',
                    'cls': <class '__main__.B'>,
                    'get_info': <function GenerateSchema._generate_schema_from_property.<locals>.get_model_info at 0x7f9ec650be20>
                }
            }
        },
        'model_name': 'A'
    },
    'ref': '__main__.A:20628480'
}
{
    'type': 'model',
    'cls': <class '__main__.B'>,
    'schema': {
        'type': 'model-fields',
        'fields': {
            'field1': {
                'type': 'model-field',
                'schema': {
                    'type': 'list',
                    'items_schema': {
                        'type': 'nested',
                        'cls': <class '__main__.A'>,
                        'get_info': <function GenerateSchema._generate_schema_from_property.<locals>.get_model_info at 0x7f9ec650ba60>
                    }
                }
            }
        },
        'model_name': 'B'
    },
    'ref': '__main__.B:20632592'
}
{
    'type': 'model',
    'cls': <class '__main__.Wrapper'>,
    'schema': {
        'type': 'model-fields',
        'fields': {
            'a': {
                'type': 'model-field',
                'schema': {
                    'type': 'nested',
                    'cls': <class '__main__.A'>,
                    'get_info': <function GenerateSchema._generate_schema_from_property.<locals>.get_model_info at 0x7f9ec650bec0>
                }
            },
            'b': {
                'type': 'model-field',
                'schema': {
                    'type': 'nested',
                    'cls': <class '__main__.B'>,
                    'get_info': <function GenerateSchema._generate_schema_from_property.<locals>.get_model_info at 0x7f9ec65c8040>
                }
            }
        },
        'model_name': 'Wrapper'
    },
    'ref': '__main__.Wrapper:20635728'
}
Compare to the schema generated on main

``` { 'type': 'definitions', 'schema': {'type': 'definition-ref', 'schema_ref': '__main__.A:37460656'}, 'definitions': [ { 'type': 'model', 'cls': , 'schema': { 'type': 'model-fields', 'fields': {'field3': {'type': 'model-field', 'schema': {'type': 'definition-ref', 'schema_ref': '__main__.B:37433488'}}}, 'model_name': 'A' }, 'ref': '__main__.A:37460656' }, { 'type': 'model', 'cls': , 'schema': { 'type': 'model-fields', 'fields': {'field1': {'type': 'model-field', 'schema': {'type': 'list', 'items_schema': {'type': 'definition-ref', 'schema_ref': '__main__.A:37460656'}}}}, 'model_name': 'B' }, 'ref': '__main__.B:37433488' } ] } { 'type': 'definitions', 'schema': {'type': 'definition-ref', 'schema_ref': '__main__.B:37433488'}, 'definitions': [ { 'type': 'model', 'cls': , 'schema': { 'type': 'model-fields', 'fields': {'field3': {'type': 'model-field', 'schema': {'type': 'definition-ref', 'schema_ref': '__main__.B:37433488'}}}, 'model_name': 'A' }, 'ref': '__main__.A:37460656' }, { 'type': 'model', 'cls': , 'schema': { 'type': 'model-fields', 'fields': {'field1': {'type': 'model-field', 'schema': {'type': 'list', 'items_schema': {'type': 'definition-ref', 'schema_ref': '__main__.A:37460656'}}}}, 'model_name': 'B' }, 'ref': '__main__.B:37433488' } ] } { 'type': 'definitions', 'schema': { 'type': 'model', 'cls': , 'schema': { 'type': 'model-fields', 'fields': { 'a': {'type': 'model-field', 'schema': {'type': 'definition-ref', 'schema_ref': '__main__.A:37460656'}}, 'b': {'type': 'model-field', 'schema': {'type': 'definition-ref', 'schema_ref': '__main__.B:37433488'}} }, 'model_name': 'Wrapper' }, 'ref': '__main__.Wrapper:37440960' }, 'definitions': [ { 'type': 'model', 'cls': , 'schema': { 'type': 'model-fields', 'fields': {'field3': {'type': 'model-field', 'schema': {'type': 'definition-ref', 'schema_ref': '__main__.B:37433488'}}}, 'model_name': 'A' }, 'ref': '__main__.A:37460656' }, { 'type': 'model', 'cls': , 'schema': { 'type': 'model-fields', 'fields': {'field1': {'type': 'model-field', 'schema': {'type': 'list', 'items_schema': {'type': 'definition-ref', 'schema_ref': '__main__.A:37460656'}}}}, 'model_name': 'B' }, 'ref': '__main__.B:37433488' } ] } ```

This is similar to the current definitions-ref setup but it has a number of benefits:

See pydantic/pydantic#10246 for the implementation of generating this new schema variant and also the benchmak results.

Before Merging


There is an issue pydantic/pydantic#10394 tracking future work on the pydantic side to use this schema more

sydney-runkle commented 1 month ago

@adriangb, would love to get your feedback on this as well!

codspeed-hq[bot] commented 1 month ago

CodSpeed Performance Report

Merging #1414 will not alter performance

Comparing boxy/validator_serializer_reuse (f6508d2) with main (ba8eab4)

Summary

✅ 155 untouched benchmarks