Skip to content
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

wip precompiled schema #993

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 16 additions & 5 deletions generate_self_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ def get_schema(obj: Any, definitions: dict[str, core_schema.CoreSchema]) -> core
return type_dict_schema(obj, definitions)
elif obj == Any or obj == type:
return {'type': 'any'}
if isinstance(obj, type) and issubclass(obj, core_schema.Protocol):
elif isinstance(obj, type) and issubclass(obj, core_schema.Protocol):
return {'type': 'callable'}
# elif isinstance(obj, ForwardRef):

origin = get_origin(obj)
assert origin is not None, f'origin cannot be None, obj={obj}, you probably need to fix generate_self_schema.py'
Expand Down Expand Up @@ -151,6 +152,9 @@ def type_dict_schema( # noqa: C901
else:
field_type = eval_forward_ref(field_type)

if fr_arg == 'SchemaValidator' or fr_arg == 'SchemaSerializer':
schema = {'type': 'is-instance', 'cls': Ident(fr_arg), 'cls_repr': f'pydantic_core.{fr_arg}'}

if schema is None:
if get_origin(field_type) == core_schema.Required:
required = True
Expand Down Expand Up @@ -202,7 +206,7 @@ def main() -> None:
definitions: dict[str, core_schema.CoreSchema] = {}

choices = {}
for s in schema_union.__args__:
for s in get_args(schema_union):
type_ = s.__annotations__['type']
m = re.search(r"Literal\['(.+?)']", type_.__forward_arg__)
assert m, f'Unknown schema type: {type_}'
Expand All @@ -217,9 +221,9 @@ def main() -> None:
*definitions.values(),
],
)
python_code = (
f'# this file is auto-generated by generate_self_schema.py, DO NOT edit manually\nself_schema = {schema}\n'
)
python_code = f"""# this file is auto-generated by generate_self_schema.py, DO NOT edit manually
from pydantic_core import SchemaValidator, SchemaSerializer
self_schema = {schema}\n"""
try:
from black import Mode, TargetVersion, format_file_contents
except ImportError:
Expand All @@ -236,5 +240,12 @@ def main() -> None:
print(f'Self schema definition written to {SAVE_PATH}')


class Ident(str):
"""Format a literal as a Ident in the output"""

def __repr__(self) -> str:
return str(self)


if __name__ == '__main__':
main()
56 changes: 52 additions & 4 deletions python/pydantic_core/core_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@
from typing import Literal

if TYPE_CHECKING:
from pydantic_core import PydanticUndefined
from pydantic_core import PydanticUndefined, SchemaSerializer, SchemaValidator
else:
# The initial build of pydantic_core requires PydanticUndefined to generate
# The initial build of pydantic_core requires some Rust structures to generate
# the core schema; so we need to conditionally skip it. mypy doesn't like
# this at all, hence the TYPE_CHECKING branch above.
try:
from pydantic_core import PydanticUndefined
from pydantic_core import PydanticUndefined, SchemaSerializer, SchemaValidator
except ImportError:
PydanticUndefined = object()
PydanticUndefined = 'PydanticUndefined'
SchemaValidator = 'SchemaValidator'
SchemaSerializer = 'SchemaSerializer'


ExtraBehavior = Literal['allow', 'forbid', 'ignore']
Expand Down Expand Up @@ -3605,6 +3607,50 @@ def definition_reference_schema(
return _dict_not_none(type='definition-ref', schema_ref=schema_ref, metadata=metadata, serialization=serialization)


class PrecompiledSchema(TypedDict, total=False):
type: Required[Literal['precompiled']]
schema: CoreSchema
validator: SchemaValidator
serializer: SchemaSerializer
ref: str
metadata: Any


def precompiled_schema(
schema: CoreSchema,
validator: SchemaValidator,
serializer: SchemaSerializer,
ref: str | None = None,
metadata: Any = None,
) -> PrecompiledSchema:
"""
Returns a schema that points to a schema stored in "definitions", this is useful for nested recursive
models and also when you want to define validators separately from the main schema, e.g.:

```py
from pydantic_core import SchemaValidator, core_schema

schema_definition = core_schema.definition_reference_schema('list-schema')
schema = core_schema.definitions_schema(
schema=schema_definition,
definitions=[
core_schema.list_schema(items_schema=schema_definition, ref='list-schema'),
],
)
v = SchemaValidator(schema)
assert v.validate_python([()]) == [[]]
```

Args:
schema_ref: The schema ref to use for the definition reference schema
metadata: Any other information you want to include with the schema, not used by pydantic-core
serialization: Custom serialization schema
"""
return _dict_not_none(
type='precompiled', schema=schema, validator=validator, serializer=serializer, ref=ref, metadata=metadata
)


MYPY = False
# See https://github.com/python/mypy/issues/14034 for details, in summary mypy is extremely slow to process this
# union which kills performance not just for pydantic, but even for code using pydantic
Expand Down Expand Up @@ -3658,6 +3704,7 @@ def definition_reference_schema(
DefinitionsSchema,
DefinitionReferenceSchema,
UuidSchema,
PrecompiledSchema,
]
elif False:
CoreSchema: TypeAlias = Mapping[str, Any]
Expand Down Expand Up @@ -3713,6 +3760,7 @@ def definition_reference_schema(
'definitions',
'definition-ref',
'uuid',
'precompiled',
]

CoreSchemaFieldType = Literal['model-field', 'dataclass-field', 'typed-dict-field', 'computed-field']
Expand Down
Loading
Loading