8000 Switching to `pydantic_core` by samuelcolvin · Pull Request #4516 · pydantic/pydantic · GitHub
[go: up one dir, main page]

Skip to content

Switching to pydantic_core #4516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 70 commits into from
Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
e2e4602
working on core schema generation
samuelcolvin Sep 12, 2022
964bd48
adapting main.py
samuelcolvin Sep 14, 2022
71fd9a6
getting tests to run
samuelcolvin Sep 14, 2022
9c0b13d
fix tests
samuelcolvin Sep 14, 2022
3c50562
disable pyright, fix mypy
samuelcolvin Sep 14, 2022
4fd2027
moving to class-based model generation
samuelcolvin Sep 14, 2022
169db6e
working on validators
samuelcolvin Sep 15, 2022
9b4a41d
change how models are created
samuelcolvin Sep 16, 2022
59bdd49
start fixing test_main.py
samuelcolvin Sep 16, 2022
246fff2
fixing mypy
samuelcolvin Sep 20, 2022
5b19c5e
SelfType
samuelcolvin Sep 20, 2022
99a8a15
recursive models working, more tests fixed
samuelcolvin Sep 24, 2022
10000
8426d5c
fix tests on <3.10
samuelcolvin Sep 24, 2022
034132e
get docs build to pass
samuelcolvin Sep 24, 2022
a4cf9d7
starting to cleanup types.py
samuelcolvin Sep 24, 2022
b948a30
starting works on custom types
samuelcolvin Sep 24, 2022
98c3391
working on using annotated-types
samuelcolvin Sep 25, 2022
8638a00
using annoated types for constraints
samuelcolvin Sep 26, 2022
e6f424d
lots of cleanup, fixing network tests
samuelcolvin Sep 26, 2022
e40986d
network tests passing :tada:
samuelcolvin Sep 27, 2022
cd96301
working on types
samuelcolvin Sep 27, 2022
0ec15d8
working on types and cleanup
samuelcolvin Sep 29, 2022
b73d44a
fixing UUID type, restructing again
samuelcolvin Sep 30, 2022
8ddc5ba
more types and newer pydantic-core
samuelcolvin Sep 30, 2022
e1060fa
working on Iterable
samuelcolvin Sep 30, 2022
20eefe2
more test_types tests
samuelcolvin Oct 11, 2022
9e81a50
support newer pydantic-core, fixing more test_types.py
samuelcolvin Oct 13, 2022
eaf4aa6
working through more test_types.py
samuelcolvin Oct 14, 2022
3bf5ef7
test_types.py at last passing locally :tada:
samuelcolvin Oct 16, 2022
38ee60e
fixing more tests in test_types.py
samuelcolvin Oct 16, 2022
50b745c
fix datetime_parse tests and linting
samuelcolvin Oct 16, 2022
0c6ebd6
get tests running again, rename to test_datetime.py
samuelcolvin Oct 16, 2022
e0f0a69
renaming internal modules
samuelcolvin Oct 17, 2022
7b1f337
working through mypy errors
samuelcolvin Oct 17, 2022
167313a
fixing mypy
samuelcolvin Oct 18, 2022
018e2c9
refactoring _generate_schema.py
samuelcolvin Oct 18, 2022
43b160d
test_main.py passing
samuelcolvin Oct 18, 2022
508db66
uprev deps
samuelcolvin Oct 18, 2022
7dd6a1b
fix conftest and linting?
samuelcolvin Oct 18, 2022
d1ac9c9
importing Annotated
samuelcolvin Oct 18, 2022
6b7d15b
ltining
samuelcolvin Oct 18, 2022
6c5bd7a
import Annotated from typing_extensions
samuelcolvin Oct 18, 2022
f6b8281
fixing 3.7 compatibility
samuelcolvin Oct 18, 2022
996a922
fixing tests on 3.9
samuelcolvin Oct 18, 2022
41c0530
fix linting
samuelcolvin Oct 19, 2022
66ac24c
fixing SecretField and 3.9 tests
samuelcolvin Oct 19, 2022
e7d8be8
customising get_type_hints
samuelcolvin Oct 19, 2022
7768b6d
ignore warnings on 3.11
samuelcolvin Oct 19, 2022
f3a3101
spliting repr out of utils
samuelcolvin Oct 19, 2022
aa65758
removing unused bits of _repr, fix tests for 3.7
samuelcolvin Oct 19, 2022
8c9639c
more cleanup, removing many type aliases
samuelcolvin Oct 19, 2022
730ebbd
clean up repr
samuelcolvin Oct 19, 2022
c6bf124
support namedtuples and typeddicts
samuelcolvin Oct 20, 2022
fa0462c
test is_union
samuelcolvin Oct 20, 2022
50c193b
removing errors, uprev pydantic-core
samuelcolvin Oct 20, 2022
60f1e1b
fix tests on 3.8
samuelcolvin Oct 25, 2022
3d2603b
fixing private attributes and model_post_init
samuelcolvin Oct 25, 2022
10cf0b4
renaming and cleanup
samuelcolvin Oct 26, 2022
fe1c3b2
remove unnecessary PydanticMetadata inheritance
samuelcolvin Oct 26, 2022
eb610b4
fixing forward refs and mypy tests
samuelcolvin Oct 26, 2022
6c1ba91
fix signatures, change how xfail works
samuelcolvin Oct 26, 2022
10ac55f
revert mypy tests to 3.7 syntax
samuelcolvin Oct 26, 2022
bb3db68
correct model title
samuelcolvin Oct 26, 2022
5656056
try to fix tests
samuelcolvin Oct 26, 2022
40358de
fixing ClassVar forward refs
samuelcolvin Oct 27, 2022
fa83ac9
uprev pydantic-core, new error format
samuelcolvin Oct 27, 2022
eaa2ad1
add "force" argument to model_rebuild
samuelcolvin Oct 27, 2022
cc7aaab
Apply suggestions from code review
samuelcolvin Nov 2, 2022
a356144
more suggestions from @tiangolo
samuelcolvin Nov 2, 2022
708c77b
extra -> json_schema_extra on Field
samuelcolvin Nov 2, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
recursive models working, more tests fixed
  • Loading branch information
samuelcolvin committed Sep 24, 2022
commit 99a8a15f6b5698c3e99f807b91d8fe1be96378e5
149 changes: 83 additions & 66 deletions pydantic/_internal/babelfish.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,7 @@
import typing
from typing import TYPE_CHECKING, Any

from pydantic_core import Schema as PydanticCoreSchema
from pydantic_core._types import (
Config as PydanticCoreConfig,
DictSchema,
IsInstanceSchema,
TuplePositionalSchema,
TupleVariableSchema,
TypedDictField,
TypedDictSchema,
)
from pydantic_core import schema_types as core_schema
from typing_extensions import get_args, is_typeddict

from pydantic.fields import FieldInfo, Undefined
Expand All @@ -40,25 +31,28 @@
__all__ = 'generate_config', 'generate_schema', 'model_fields_schema'


def model_fields_schema(fields: dict[str, FieldInfo], validator_functions: ValidationFunctions) -> PydanticCoreSchema:
schema: PydanticCoreSchema = {
'type': 'typed-dict',
'return_fields_set': True,
'fields': {k: generate_field_schema(k, v, validator_functions) for k, v in fields.items()},
}
def model_fields_schema(
ref: str, fields: dict[str, FieldInfo], validator_functions: ValidationFunctions
) -> core_schema.Schema:
schema: core_schema.Schema = core_schema.TypedDictSchema(
type='typed-dict',
ref=ref,
return_fields_set=True,
fields={k: generate_field_schema(k, v, validator_functions) for k, v in fields.items()},
)
schema = apply_validators(schema, validator_functions.get_root_validators())
return schema


def generate_config(config: type[BaseConfig]) -> PydanticCoreConfig:
return {
'typed_dict_extra_behavior': config.extra.value,
# 'allow_inf_nan': config.allow_inf_nan,
'populate_by_name': config.allow_population_by_field_name,
}
def generate_config(config: type[BaseConfig]) -> core_schema.Config:
return core_schema.Config(
typed_dict_extra_behavior=config.extra.value,
# allow_inf_nan=config.allow_inf_nan,
populate_by_name=config.allow_population_by_field_name,
)


def generate_schema(obj: Any) -> PydanticCoreSchema: # noqa: C901 (ignore complexity)
def generate_schema(obj: Any) -> core_schema.Schema: # noqa: C901 (ignore complexity)
"""
Recursively generate a pydantic-core schema for any supported python type.
"""
Expand All @@ -72,7 +66,7 @@ def generate_schema(obj: Any) -> PydanticCoreSchema: # noqa: C901 (ignore compl
elif obj is None or obj is NoneType:
return 'none'
elif obj == type:
return {'type': 'is-instance', 'class_': type}
return core_schema.IsInstanceSchema(type='is-instance', class_=type)
elif is_callable_type(obj):
return 'callable'
elif is_literal_type(obj):
Expand All @@ -90,6 +84,10 @@ def generate_schema(obj: Any) -> PydanticCoreSchema: # noqa: C901 (ignore compl
if schema_property is not None:
return schema_property

get_schema = getattr(obj, '__get_pydantic_validation_schema__', None)
if get_schema is not None:
return get_schema()

origin = get_origin(obj)
if origin is None:
raise PydanticSchemaGenerationError(f'Unable to generate pydantic-core schema for {obj!r}.')
Expand All @@ -108,62 +106,81 @@ def generate_schema(obj: Any) -> PydanticCoreSchema: # noqa: C901 (ignore compl
raise PydanticSchemaGenerationError(f'Unable to generate pydantic-core schema for {obj!r} (origin={origin!r}).')


def generate_field_schema(name: str, field: FieldInfo, validator_functions: ValidationFunctions) -> TypedDictField:
def generate_field_schema(
name: str, field: FieldInfo, validator_functions: ValidationFunctions
) -> core_schema.TypedDictField:
"""
Prepare a TypedDictField to represent a model or typeddict field.
"""
assert field.annotation is not None, 'field.annotation is None'
schema: PydanticCoreSchema = generate_schema(field.annotation)
assert field.annotation is not None, 'field.annotation should not be None when generating a schema'
schema: core_schema.Schema = generate_schema(field.annotation)
schema = apply_validators(schema, validator_functions.get_field_validators(name))

field_schema: TypedDictField = {'schema': schema}
required = False
if field.default_factory:
field_schema['default_factory'] = field.default_factory
schema = core_schema.WithDefaultSchema(type='default', schema=schema, default_factory=field.default_factory)
elif field.default is Undefined:
field_schema['required'] = True
required = True
else:
field_schema['default'] = field.default
schema = core_schema.WithDefaultSchema(type='default', schema=schema, default=field.default)

field_schema = core_schema.TypedDictField(schema=schema, required=required)
if field.alias is not None:
field_schema['alias'] = field.alias
return field_schema


def apply_validators(schema: PydanticCoreSchema, validators: list[Validator]) -> PydanticCoreSchema:
def apply_validators(schema: core_schema.Schema, validators: list[Validator]) -> core_schema.Schema:
"""
Apply validators to a schema.
"""
for validator in validators:
assert validator.sub_path is None, 'validator.sub_path is not yet supported'
function = typing.cast(typing.Callable[..., Any], validator.function)
if validator.mode == 'plain':
schema = { # type: ignore[misc,assignment]
'type': 'function',
'mode': 'plain',
'function': function,
}
schema = core_schema.FunctionPlainSchema(
type='function',
mode='plain',
function=function,
)
else:
schema = {
'type': 'function',
'mode': validator.mode,
'function': function,
'schema': schema,
}
schema = core_schema.FunctionSchema(
type='function',
mode=validator.mode,
function=function,
schema=schema,
)
return schema


class PydanticSchemaGenerationError(TypeError):
pass


def union_schema(union_type: Any) -> PydanticCoreSchema:
def union_schema(union_type: Any) -> core_schema.Schema:
"""
Generate schema for a Union.
"""
return {'type': 'union', 'choices': [generate_schema(arg) for arg in get_args(union_type)]}
args = get_args(union_type)
choices = []
nullable = False
for arg in args:
if arg is None or arg is NoneType:
nullable = True
else:
choices.append(generate_schema(arg))

if len(choices) == 1:
s = choices[0]
else:
s = core_schema.UnionSchema(type='union', choices=choices)

if nullable:
s = core_schema.NullableSchema(type='nullable', schema=s)
return s

def literal_schema(literal_type: Any) -> PydanticCoreSchema:

def literal_schema(literal_type: Any) -> core_schema.Schema:
"""
Generate schema for a Literal.
"""
Expand All @@ -172,12 +189,12 @@ def literal_schema(literal_type: Any) -> PydanticCoreSchema:
return {'type': 'literal', 'expected': list(expected)}


def type_dict_schema(typed_dict: Any) -> TypedDictSchema:
def type_dict_schema(typed_dict: Any) -> core_schema.TypedDictSchema:
"""
Generate schema for a TypedDict.
"""
required_keys: typing.Set[str] = getattr(typed_dict, '__required_keys__', set())
fields: typing.Dict[str, TypedDictField] = {}
fields: typing.Dict[str, core_schema.TypedDictField] = {}

for field_name, field_type in typed_dict.__annotations__.items():
required = field_name in required_keys
Expand Down F438 Expand Up @@ -206,10 +223,10 @@ def type_dict_schema(typed_dict: Any) -> TypedDictSchema:

fields[field_name] = {'schema': schema, 'required': required}

return {'type': 'typed-dict', 'fields': fields, 'extra_behavior': 'forbid'}
return core_schema.TypedDictSchema(type='typed-dict', fields=fields, extra_behavior='forbid')


def generic_collection_schema(obj: Any) -> PydanticCoreSchema:
def generic_collection_schema(obj: Any) -> core_schema.Schema:
"""
Generate schema for List, Set etc. - where the schema includes `items_schema`

Expand All @@ -221,14 +238,14 @@ def generic_collection_schema(obj: Any) -> PydanticCoreSchema:
} # type: ignore[misc,return-value]


def tuple_schema(tuple_type: Any) -> typing.Union[TupleVariableSchema, TuplePositionalSchema]:
def tuple_schema(tuple_type: Any) -> core_schema.Schema:
"""
Generate schema for a Tuple, e.g. `tuple[int, str]` or `tuple[int, ...]`.
"""
params = get_args(tuple_type)
if params[-1] is Ellipsis:
if len(params) == 2:
sv: TupleVariableSchema = {'type': 'tuple', 'mode': 'variable', 'items_schema': generate_schema(params[0])}
sv = core_schema.TupleVariableSchema(type='tuple', mode='variable', items_schema=generate_schema(params[0]))
return sv

# not sure this case is valid in python, but may as well support it here since pydantic-core does
Expand All @@ -240,32 +257,32 @@ def tuple_schema(tuple_type: Any) -> typing.Union[TupleVariableSchema, TuplePosi
'extra_schema': generate_schema(extra_schema),
}
else:
sp: TuplePositionalSchema = {
'type': 'tuple',
'mode': 'positional',
'items_schema': [generate_schema(p) for p in params],
}
sp = core_schema.TuplePositionalSchema(
type='tuple',
mode='positional',
items_schema=[generate_schema(p) for p in params],
)
return sp


def dict_schema(dict_type: Any) -> DictSchema:
def dict_schema(dict_type: Any) -> core_schema.DictSchema:
"""
Generate schema for a Dict, e.g. `dict[str, int]`.
"""
arg0, arg1 = get_args(dict_type)
return {
'type': 'dict',
'keys_schema': generate_schema(arg0),
'values_schema': generate_schema(arg1),
}
return core_schema.DictSchema(
type='dict',
keys_schema=generate_schema(arg0),
values_schema=generate_schema(arg1),
)


def type_schema(type_: Any) -> IsInstanceSchema:
def type_schema(type_: Any) -> core_schema.IsInstanceSchema:
"""
Generate schema for a Type, e.g. `Type[int]`.
"""
type_param = get_args(type_)[0]
if type_param == Any:
return {'type': 'is-instance', 'class_': type}
return core_schema.IsInstanceSchema(type='is-instance', class_=type)
else:
return {'type': 'is-instance', 'class_': type_param}
return core_schema.IsInstanceSchema(type='is-instance', class_=type_param)
49 changes: 36 additions & 13 deletions pydantic/_internal/model_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import sys
import warnings
from types import FunctionType
from typing import TYPE_CHECKING, Any, Callable, Type, get_type_hints
from typing import TYPE_CHECKING, Any, Callable, Type, Union, get_type_hints

from pydantic_core import SchemaValidator
from pydantic_core.schema_types import NewClassSchema, RecursiveReferenceSchema, Schema as PydanticCoreSchema

from ..fields import FieldInfo, ModelPrivateAttr, PrivateAttr
from ..utils import ClassAttribute, is_valid_identifier
Expand Down Expand Up @@ -69,10 +70,26 @@ class SelfType:
This is used to identify a reference to the current model class, e.g. in recursive classes
"""

__slots__ = ('name',)
__slots__ = ('__pydantic_validation_schema__',)

def __init__(self, module_name: str, cls_name: str):
self.name = f'{module_name}.{cls_name}'
def __init__(self, schema: PydanticCoreSchema):
self.__pydantic_validation_schema__ = schema

def __call__(self) -> None:
"""
This is here just to mollify typing._type_check which expects typing types
but will also accept callables
"""
raise TypeError('SelfType cannot be called')

def __or__(self, right: Any) -> Any:
return Union[self, right]

def __ror__(self, left: Any) -> Any:
return Union[left, self]

def __repr__(self) -> str:
return f'SelfType[{self.__pydantic_validation_schema__}]'


def complete_model_class(
Expand All @@ -98,7 +115,14 @@ def complete_model_class(
base_globals = module.__dict__

fields: dict[str, FieldInfo] = {}
for ann_name, ann_type in get_type_hints(cls, base_globals, {name: SelfType(module_name, name)}).items():
core_config = generate_config(cls.__config__)
model_ref = f'{module_name}.{name}'
self_schema = NewClassSchema(
type='new-class',
class_type=cls,
schema=RecursiveReferenceSchema(type='recursive-ref', schema_ref=model_ref),
)
for ann_name, ann_type in get_type_hints(cls, base_globals, {name: SelfType(self_schema)}).items():
# TODO NotField
if ann_name.startswith('_') or is_classvar(ann_type):
continue
Expand All @@ -121,18 +145,17 @@ def complete_model_class(
# 2. To avoid false positives in the NameError check above
delattr(cls, ann_name)

core_config = generate_config(cls.__config__)
inner_schema = model_fields_schema(fields, validator_functions)
inner_schema = model_fields_schema(model_ref, fields, validator_functions)
validator_functions.check_for_unused()

cls.__fields__ = fields
cls.__pydantic_validator__ = SchemaValidator(inner_schema, core_config)
cls.__pydantic_validation_schema__ = {
'type': 'new-class',
'class_type': cls,
'schema': inner_schema,
'config': core_config,
}
cls.__pydantic_validation_schema__ = NewClassSchema(
type='new-class',
class_type=cls,
schema=inner_schema,
config=core_config,
)

# set __signature__ attr only for model class, but not for its instances
cls.__signature__ = ClassAttribute('__signature__', generate_model_signature(cls.__init__, fields, cls.__config__))
Expand Down
2 changes: 1 addition & 1 deletion pydantic/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ def update_forward_refs(cls, **localns: Any) -> None:
"""
Try to update ForwardRefs on fields based on this Model and localns.
"""
raise NotImplementedError('TODO, also rename to model_rebuild')
raise RuntimeError('TODO, also rename to model_rebuild')

def __iter__(self) -> 'TupleGenerator':
"""
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ classifiers = [
requires-python = '>=3.7'
dependencies = [
'typing-extensions>=4.1.0',
'pydantic-core>=0.1.0',
'pydantic-core>=0.2.1',
]
optional-dependencies = { email = ['email-validator>=1.0.3'] }
dynamic = ['version']
Expand Down
6 changes: 5 additions & 1 deletion requirements/pyproject-all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@ email-validator==1.2.1
# via pydantic (pyproject.toml)
idna==3.3
# via email-validator
typing-extensions==4.3.0
pydantic-core==0.2.1
# via pydantic (pyproject.toml)
typing-extensions==4.3.0
# via
# pydantic (pyproject.toml)
# pydantic-core
6 changes: 5 additions & 1 deletion requirements/pyproject-min.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
#
# pip-compile --output-file=requirements/pyproject-min.txt pyproject.toml
#
typing-extensions==4.3.0
pydantic-core==0.2.1
# via pydantic (pyproject.toml)
typing-extensions==4.3.0
# via
# pydantic (pyproject.toml)
# pydantic-core
Loading
0