-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
CoreMetadata refactor with an emphasis on documentation and reducing complexity
#10675
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
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
fbc5c9c
first step, removing CoreMetadataHandler construct - type checking va…
sydney-runkle bd181f1
remove reliance on build_metadata_dict
sydney-runkle f9c346d
standardizing dict updates and json schema extra
sydney-runkle a1442ff
removing as much json schema logic as possible from _generate_schema.…
sydney-runkle f93552c
minor docs fixes + revert enum change to fix tests
sydney-runkle 9d8f4a4
Add some casting + standardize update methods so that CoreMetadata of…
sydney-runkle e3b7426
remove deprecated internal function
sydney-runkle a1ebd03
adding warnings for lack of mixed composure support for json_schema_e…
sydney-runkle 41bc424
typing fix for 3.13
sydney-runkle 7e479fe
fix circular reference issue
sydney-runkle aea1c00
add list of failing tests
sydney-runkle d8429f1
maybe this is the way you skip that test?
sydney-runkle 88f9247
PR feedback
sydney-runkle b572456
root description fix
sydney-runkle 7a17254
Merge branch 'main' into metadata-stuck-on-a-flight
sydney-runkle 49e75eb
pr feedback re typing
sydney-runkle 8d09bc7
Merge branch 'metadata-stuck-on-a-flight' of https://github.com/pydan…
sydney-runkle d66ae3b
import fix
sydney-runkle 5f8b5ba
deselect multiple
sydney-runkle 2a08ddf
fix test skip formatting
sydney-runkle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,84 +1,95 @@ | ||
| from __future__ import annotations as _annotations | ||
|
|
||
| import typing | ||
| from typing import Any, cast | ||
| from typing import TYPE_CHECKING, Any, TypedDict, cast | ||
| from warnings import warn | ||
|
|
||
| import typing_extensions | ||
|
|
||
| if typing.TYPE_CHECKING: | ||
| if TYPE_CHECKING: | ||
| from pydantic_core import CoreSchema | ||
|
|
||
| from ..config import JsonDict, JsonSchemaExtraCallable | ||
| from ._schema_generation_shared import ( | ||
| CoreSchemaOrField, | ||
| GetJsonSchemaFunction, | ||
| ) | ||
|
|
||
|
|
||
| class CoreMetadata(typing_extensions.TypedDict, total=False): | ||
| class CoreMetadata(TypedDict, total=False): | ||
| """A `TypedDict` for holding the metadata dict of the schema. | ||
|
|
||
| Attributes: | ||
| pydantic_js_functions: List of JSON schema functions. | ||
| pydantic_js_functions: List of JSON schema functions that resolve refs during application. | ||
| pydantic_js_annotation_functions: List of JSON schema functions that don't resolve refs during application. | ||
| pydantic_js_prefer_positional_arguments: Whether JSON schema generator will | ||
| prefer positional over keyword arguments for an 'arguments' schema. | ||
| """ | ||
|
|
||
| pydantic_js_functions: list[GetJsonSchemaFunction] | ||
| pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | ||
|
|
||
| # If `pydantic_js_prefer_positional_arguments` is True, the JSON schema generator will | ||
| # prefer positional over keyword arguments for an 'arguments' schema. | ||
| pydantic_js_prefer_positional_arguments: bool | None | ||
| pydantic_js_input_core_schema: CoreSchema | None | ||
| pydantic_js_input_core_schema: Schema associated with the input value for the associated | ||
| custom validation function. Only applies to before, plain, and wrap validators. | ||
| pydantic_js_udpates: key / value pair updates to apply to the JSON schema for a type. | ||
| pydantic_js_extra: WIP, either key/value pair updates to apply to the JSON schema, or a custom callable. | ||
|
|
||
| TODO: Perhaps we should move this structure to pydantic-core. At the moment, though, | ||
| it's easier to iterate on if we leave it in pydantic until we feel there is a semi-stable API. | ||
|
|
||
| class CoreMetadataHandler: | ||
| """Because the metadata field in pydantic_core is of type `Dict[str, Any]`, we can't assume much about its contents. | ||
|
|
||
| This class is used to interact with the metadata field on a CoreSchema object in a consistent way throughout pydantic. | ||
|
|
||
| TODO: We'd like to refactor the storage of json related metadata to be more explicit, and less functionally oriented. | ||
| This should make its way into our v2.10 release. It's inevitable that we need to store some json schema related information | ||
| TODO: It's unfortunate how functionally oriented JSON schema generation is, especially that which occurs during | ||
| the core schema generation process. It's inevitable that we need to store some json schema related information | ||
| on core schemas, given that we generate JSON schemas directly from core schemas. That being said, debugging related | ||
| issues is quite difficult when JSON schema information is disguised via dynamically defined functions. | ||
| """ | ||
|
|
||
| __slots__ = ('_schema',) | ||
|
|
||
| def __init__(self, schema: CoreSchemaOrField): | ||
| self._schema = schema | ||
|
|
||
| metadata = schema.get('metadata') | ||
| if metadata is None: | ||
| schema['metadata'] = CoreMetadata() # type: ignore | ||
| elif not isinstance(metadata, dict): | ||
| raise TypeError(f'CoreSchema metadata should be a dict; got {metadata!r}.') | ||
|
|
||
| @property | ||
| def metadata(self) -> CoreMetadata: | ||
| """Retrieves the metadata dict from the schema, initializing it to a dict if it is None | ||
| and raises an error if it is not a dict. | ||
| """ | ||
| metadata = self._schema.get('metadata') | ||
| if metadata is None: | ||
| self._schema['metadata'] = metadata = CoreMetadata() # type: ignore | ||
| if not isinstance(metadata, dict): | ||
| raise TypeError(f'CoreSchema metadata should be a dict; got {metadata!r}.') | ||
| return cast(CoreMetadata, metadata) | ||
|
|
||
|
|
||
| def build_metadata_dict( | ||
| *, # force keyword arguments to make it easier to modify this signature in a backwards-compatible way | ||
| js_functions: list[GetJsonSchemaFunction] | None = None, | ||
| js_annotation_functions: list[GetJsonSchemaFunction] | None = None, | ||
| js_prefer_positional_arguments: bool | None = None, | ||
| js_input_core_schema: CoreSchema | None = None, | ||
| ) -> dict[str, Any]: | ||
| """Builds a dict to use as the metadata field of a CoreSchema object in a manner that is consistent with the `CoreMetadataHandler` class.""" | ||
| metadata = CoreMetadata( | ||
| pydantic_js_functions=js_functions or [], | ||
| pydantic_js_annotation_functions=js_annotation_functions or [], | ||
| pydantic_js_prefer_positional_arguments=js_prefer_positional_arguments, | ||
| pydantic_js_input_core_schema=js_input_core_schema, | ||
| ) | ||
| return {k: v for k, v in metadata.items() if v is not None} | ||
| pydantic_js_functions: list[GetJsonSchemaFunction] | ||
| pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | ||
| pydantic_js_prefer_positional_arguments: bool | ||
| pydantic_js_input_core_schema: CoreSchema | ||
| pydantic_js_updates: JsonDict | ||
| pydantic_js_extra: JsonDict | JsonSchemaExtraCallable | ||
|
|
||
|
|
||
| def update_core_metadata( | ||
| core_metadata: Any, | ||
| /, | ||
|
sydney-runkle marked this conversation as resolved.
|
||
| *, | ||
| pydantic_js_functions: list[GetJsonSchemaFunction] | None = None, | ||
| pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | None = None, | ||
| pydantic_js_updates: JsonDict | None = None, | ||
| pydantic_js_extra: JsonDict | JsonSchemaExtraCallable | None = None, | ||
| ) -> None: | ||
| from ..json_schema import PydanticJsonSchemaWarning | ||
|
|
||
| """Update CoreMetadata instance in place. When we make modifications in this function, they | ||
| take effect on the `core_metadata` reference passed in as the first (and only) positional argument. | ||
|
|
||
| First, cast to `CoreMetadata`, then finish with a cast to `dict[str, Any]` for core schema compatibility. | ||
| We do this here, instead of before / after each call to this function so that this typing hack | ||
| can be easily removed if/when we move `CoreMetadata` to `pydantic-core`. | ||
|
|
||
| For parameter descriptions, see `CoreMetadata` above. | ||
| """ | ||
| core_metadata = cast(CoreMetadata, core_metadata) | ||
|
|
||
| if pydantic_js_functions: | ||
| core_metadata.setdefault('pydantic_js_functions', []).extend(pydantic_js_functions) | ||
|
|
||
| if pydantic_js_annotation_functions: | ||
| core_metadata.setdefault('pydantic_js_annotation_functions', []).extend(pydantic_js_annotation_functions) | ||
|
|
||
| if pydantic_js_updates: | ||
| if (existing_updates := core_metadata.get('pydantic_js_updates')) is not None: | ||
| core_metadata['pydantic_js_updates'] = {**existing_updates, **pydantic_js_updates} | ||
| else: | ||
| core_metadata['pydantic_js_updates'] = pydantic_js_updates | ||
|
|
||
| if pydantic_js_extra is not None: | ||
| existing_pydantic_js_extra = core_metadata.get('pydantic_js_extra') | ||
| if existing_pydantic_js_extra is None: | ||
| core_metadata['pydantic_js_extra'] = pydantic_js_extra | ||
| if isinstance(existing_pydantic_js_extra, dict): | ||
| if isinstance(pydantic_js_extra, dict): | ||
| core_metadata['pydantic_js_extra'] = {**existing_pydantic_js_extra, **pydantic_js_extra} | ||
| if callable(pydantic_js_extra): | ||
| warn( | ||
| 'Composing `dict` and `callable` type `json_schema_extra` is not supported.' | ||
| 'The `callable` type is being ignored.' | ||
| "If you'd like support for this behavior, please open an issue on pydantic.", | ||
| PydanticJsonSchemaWarning, | ||
| ) | ||
| if callable(existing_pydantic_js_extra): | ||
| # if ever there's a case of a callable, we'll just keep the last json schema extra spec | ||
| core_metadata['pydantic_js_extra'] = pydantic_js_extra | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.