1from __future__ import annotations as _annotations
2
3from typing import TYPE_CHECKING, Any, TypedDict, cast
4from warnings import warn
5
6if TYPE_CHECKING:
7 from ..config import JsonDict, JsonSchemaExtraCallable
8 from ._schema_generation_shared import (
9 GetJsonSchemaFunction,
10 )
11
12
13class CoreMetadata(TypedDict, total=False):
14 """A `TypedDict` for holding the metadata dict of the schema.
15
16 Attributes:
17 pydantic_js_functions: List of JSON schema functions that resolve refs during application.
18 pydantic_js_annotation_functions: List of JSON schema functions that don't resolve refs during application.
19 pydantic_js_prefer_positional_arguments: Whether JSON schema generator will
20 prefer positional over keyword arguments for an 'arguments' schema.
21 custom validation function. Only applies to before, plain, and wrap validators.
22 pydantic_js_updates: key / value pair updates to apply to the JSON schema for a type.
23 pydantic_js_extra: WIP, either key/value pair updates to apply to the JSON schema, or a custom callable.
24 pydantic_internal_union_tag_key: Used internally by the `Tag` metadata to specify the tag used for a discriminated union.
25 pydantic_internal_union_discriminator: Used internally to specify the discriminator value for a discriminated union
26 when the discriminator was applied to a `'definition-ref'` schema, and that reference was missing at the time
27 of the annotation application.
28
29 TODO: Perhaps we should move this structure to pydantic-core. At the moment, though,
30 it's easier to iterate on if we leave it in pydantic until we feel there is a semi-stable API.
31
32 TODO: It's unfortunate how functionally oriented JSON schema generation is, especially that which occurs during
33 the core schema generation process. It's inevitable that we need to store some json schema related information
34 on core schemas, given that we generate JSON schemas directly from core schemas. That being said, debugging related
35 issues is quite difficult when JSON schema information is disguised via dynamically defined functions.
36 """
37
38 pydantic_js_functions: list[GetJsonSchemaFunction]
39 pydantic_js_annotation_functions: list[GetJsonSchemaFunction]
40 pydantic_js_prefer_positional_arguments: bool
41 pydantic_js_updates: JsonDict
42 pydantic_js_extra: JsonDict | JsonSchemaExtraCallable
43 pydantic_internal_union_tag_key: str
44 pydantic_internal_union_discriminator: str
45
46
47def update_core_metadata(
48 core_metadata: Any,
49 /,
50 *,
51 pydantic_js_functions: list[GetJsonSchemaFunction] | None = None,
52 pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | None = None,
53 pydantic_js_updates: JsonDict | None = None,
54 pydantic_js_extra: JsonDict | JsonSchemaExtraCallable | None = None,
55) -> None:
56 from ..json_schema import PydanticJsonSchemaWarning
57
58 """Update CoreMetadata instance in place. When we make modifications in this function, they
59 take effect on the `core_metadata` reference passed in as the first (and only) positional argument.
60
61 First, cast to `CoreMetadata`, then finish with a cast to `dict[str, Any]` for core schema compatibility.
62 We do this here, instead of before / after each call to this function so that this typing hack
63 can be easily removed if/when we move `CoreMetadata` to `pydantic-core`.
64
65 For parameter descriptions, see `CoreMetadata` above.
66 """
67 core_metadata = cast(CoreMetadata, core_metadata)
68
69 if pydantic_js_functions:
70 core_metadata.setdefault('pydantic_js_functions', []).extend(pydantic_js_functions)
71
72 if pydantic_js_annotation_functions:
73 core_metadata.setdefault('pydantic_js_annotation_functions', []).extend(pydantic_js_annotation_functions)
74
75 if pydantic_js_updates:
76 if (existing_updates := core_metadata.get('pydantic_js_updates')) is not None:
77 core_metadata['pydantic_js_updates'] = {**existing_updates, **pydantic_js_updates}
78 else:
79 core_metadata['pydantic_js_updates'] = pydantic_js_updates
80
81 if pydantic_js_extra is not None:
82 existing_pydantic_js_extra = core_metadata.get('pydantic_js_extra')
83 if existing_pydantic_js_extra is None:
84 core_metadata['pydantic_js_extra'] = pydantic_js_extra
85 if isinstance(existing_pydantic_js_extra, dict):
86 if isinstance(pydantic_js_extra, dict):
87 core_metadata['pydantic_js_extra'] = {**existing_pydantic_js_extra, **pydantic_js_extra}
88 if callable(pydantic_js_extra):
89 warn(
90 'Composing `dict` and `callable` type `json_schema_extra` is not supported.'
91 'The `callable` type is being ignored.'
92 "If you'd like support for this behavior, please open an issue on pydantic.",
93 PydanticJsonSchemaWarning,
94 )
95 if callable(existing_pydantic_js_extra):
96 # if ever there's a case of a callable, we'll just keep the last json schema extra spec
97 core_metadata['pydantic_js_extra'] = pydantic_js_extra