Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pydantic/json_schema.py: 17%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""!!! abstract "Usage Documentation"
2 [JSON Schema](../concepts/json_schema.md)
4The `json_schema` module contains classes and functions to allow the way [JSON Schema](https://json-schema.org/)
5is generated to be customized.
7In general you shouldn't need to use this module directly; instead, you can use
8[`BaseModel.model_json_schema`][pydantic.BaseModel.model_json_schema] and
9[`TypeAdapter.json_schema`][pydantic.TypeAdapter.json_schema].
10"""
12from __future__ import annotations as _annotations
14import collections.abc
15import dataclasses
16import inspect
17import math
18import os
19import re
20import warnings
21from collections import Counter, defaultdict
22from collections.abc import Hashable, Iterable, Sequence
23from copy import deepcopy
24from enum import Enum
25from re import Pattern
26from typing import (
27 TYPE_CHECKING,
28 Annotated,
29 Any,
30 Callable,
31 Literal,
32 NewType,
33 TypeVar,
34 Union,
35 cast,
36 overload,
37)
39import pydantic_core
40from pydantic_core import MISSING, CoreSchema, PydanticOmit, core_schema, to_jsonable_python
41from pydantic_core.core_schema import ComputedField
42from typing_extensions import TypeAlias, assert_never, deprecated, final
43from typing_inspection.introspection import get_literal_values
45from pydantic.warnings import PydanticDeprecatedSince26, PydanticDeprecatedSince29
47from ._internal import (
48 _config,
49 _core_metadata,
50 _core_utils,
51 _decorators,
52 _internal_dataclass,
53 _mock_val_ser,
54 _schema_generation_shared,
55 _typing_extra,
56)
57from .annotated_handlers import GetJsonSchemaHandler
58from .config import JsonDict, JsonValue
59from .errors import PydanticInvalidForJsonSchema, PydanticSchemaGenerationError, PydanticUserError
61if TYPE_CHECKING:
62 from . import ConfigDict
63 from ._internal._core_utils import CoreSchemaField, CoreSchemaOrField
64 from ._internal._dataclasses import PydanticDataclass
65 from ._internal._schema_generation_shared import GetJsonSchemaFunction
66 from .main import BaseModel
69CoreSchemaOrFieldType = Literal[core_schema.CoreSchemaType, core_schema.CoreSchemaFieldType]
70"""
71A type alias for defined schema types that represents a union of
72`core_schema.CoreSchemaType` and
73`core_schema.CoreSchemaFieldType`.
74"""
76JsonSchemaValue = dict[str, Any]
77"""
78A type alias for a JSON schema value. This is a dictionary of string keys to arbitrary JSON values.
79"""
81JsonSchemaMode = Literal['validation', 'serialization']
82"""
83A type alias that represents the mode of a JSON schema; either 'validation' or 'serialization'.
85For some types, the inputs to validation differ from the outputs of serialization. For example,
86computed fields will only be present when serializing, and should not be provided when
87validating. This flag provides a way to indicate whether you want the JSON schema required
88for validation inputs, or that will be matched by serialization outputs.
89"""
91_MODE_TITLE_MAPPING: dict[JsonSchemaMode, str] = {'validation': 'Input', 'serialization': 'Output'}
94JsonSchemaWarningKind = Literal['skipped-choice', 'non-serializable-default', 'skipped-discriminator']
95"""
96A type alias representing the kinds of warnings that can be emitted during JSON schema generation.
98See [`GenerateJsonSchema.render_warning_message`][pydantic.json_schema.GenerateJsonSchema.render_warning_message]
99for more details.
100"""
103class PydanticJsonSchemaWarning(UserWarning):
104 """This class is used to emit warnings produced during JSON schema generation.
105 See the [`GenerateJsonSchema.emit_warning`][pydantic.json_schema.GenerateJsonSchema.emit_warning] and
106 [`GenerateJsonSchema.render_warning_message`][pydantic.json_schema.GenerateJsonSchema.render_warning_message]
107 methods for more details; these can be overridden to control warning behavior.
108 """
111NoDefault = object()
112"""A sentinel value used to indicate that no default value should be used when generating a JSON Schema
113for a core schema with a default value.
114"""
117# ##### JSON Schema Generation #####
118DEFAULT_REF_TEMPLATE = '#/$defs/{model}'
119"""The default format string used to generate reference names."""
121# There are three types of references relevant to building JSON schemas:
122# 1. core_schema "ref" values; these are not exposed as part of the JSON schema
123# * these might look like the fully qualified path of a model, its id, or something similar
124CoreRef = NewType('CoreRef', str)
125# 2. keys of the "definitions" object that will eventually go into the JSON schema
126# * by default, these look like "MyModel", though may change in the presence of collisions
127# * eventually, we may want to make it easier to modify the way these names are generated
128DefsRef = NewType('DefsRef', str)
129# 3. the values corresponding to the "$ref" key in the schema
130# * By default, these look like "#/$defs/MyModel", as in {"$ref": "#/$defs/MyModel"}
131JsonRef = NewType('JsonRef', str)
133CoreModeRef = tuple[CoreRef, JsonSchemaMode]
134JsonSchemaKeyT = TypeVar('JsonSchemaKeyT', bound=Hashable)
136_PRIMITIVE_JSON_SCHEMA_TYPES = ('string', 'boolean', 'null', 'integer', 'number')
139@dataclasses.dataclass(**_internal_dataclass.slots_true)
140class _DefinitionsRemapping:
141 defs_remapping: dict[DefsRef, DefsRef]
142 json_remapping: dict[JsonRef, JsonRef]
144 @staticmethod
145 def from_prioritized_choices(
146 prioritized_choices: dict[DefsRef, list[DefsRef]],
147 defs_to_json: dict[DefsRef, JsonRef],
148 definitions: dict[DefsRef, JsonSchemaValue],
149 ) -> _DefinitionsRemapping:
150 """
151 This function should produce a remapping that replaces complex DefsRef with the simpler ones from the
152 prioritized_choices such that applying the name remapping would result in an equivalent JSON schema.
153 """
154 # We need to iteratively simplify the definitions until we reach a fixed point.
155 # The reason for this is that outer definitions may reference inner definitions that get simplified
156 # into an equivalent reference, and the outer definitions won't be equivalent until we've simplified
157 # the inner definitions.
158 copied_definitions = deepcopy(definitions)
159 definitions_schema = {'$defs': copied_definitions}
160 for _iter in range(100): # prevent an infinite loop in the case of a bug, 100 iterations should be enough
161 # For every possible remapped DefsRef, collect all schemas that DefsRef might be used for:
162 schemas_for_alternatives: dict[DefsRef, list[JsonSchemaValue]] = defaultdict(list)
163 for defs_ref in copied_definitions:
164 alternatives = prioritized_choices[defs_ref]
165 for alternative in alternatives:
166 schemas_for_alternatives[alternative].append(copied_definitions[defs_ref])
168 # Deduplicate the schemas for each alternative; the idea is that we only want to remap to a new DefsRef
169 # if it introduces no ambiguity, i.e., there is only one distinct schema for that DefsRef.
170 for defs_ref in schemas_for_alternatives:
171 schemas_for_alternatives[defs_ref] = _deduplicate_schemas(schemas_for_alternatives[defs_ref])
173 # Build the remapping
174 defs_remapping: dict[DefsRef, DefsRef] = {}
175 json_remapping: dict[JsonRef, JsonRef] = {}
176 for original_defs_ref in definitions:
177 alternatives = prioritized_choices[original_defs_ref]
178 # Pick the first alternative that has only one schema, since that means there is no collision
179 remapped_defs_ref = next(x for x in alternatives if len(schemas_for_alternatives[x]) == 1)
180 defs_remapping[original_defs_ref] = remapped_defs_ref
182 # Map all alternatives after the remapped one to the remapped one
183 # This ensures that intermediate simplifications are also remapped
184 remapped_index = alternatives.index(remapped_defs_ref)
185 for alt in alternatives[remapped_index:]:
186 json_remapping[defs_to_json[alt]] = defs_to_json[remapped_defs_ref]
187 remapping = _DefinitionsRemapping(defs_remapping, json_remapping)
188 new_definitions_schema = remapping.remap_json_schema({'$defs': copied_definitions})
189 if definitions_schema == new_definitions_schema:
190 # We've reached the fixed point
191 return remapping
192 definitions_schema = new_definitions_schema
194 raise PydanticInvalidForJsonSchema('Failed to simplify the JSON schema definitions')
196 def remap_defs_ref(self, ref: DefsRef) -> DefsRef:
197 return self.defs_remapping.get(ref, ref)
199 def remap_json_ref(self, ref: JsonRef) -> JsonRef:
200 return self.json_remapping.get(ref, ref)
202 def remap_json_schema(self, schema: Any) -> Any:
203 """
204 Recursively update the JSON schema replacing all $refs
205 """
206 if isinstance(schema, str):
207 # Note: this may not really be a JsonRef; we rely on having no collisions between JsonRefs and other strings
208 return self.remap_json_ref(JsonRef(schema))
209 elif isinstance(schema, list):
210 return [self.remap_json_schema(item) for item in schema]
211 elif isinstance(schema, dict):
212 for key, value in schema.items():
213 if key == '$ref' and isinstance(value, str):
214 schema['$ref'] = self.remap_json_ref(JsonRef(value))
215 elif key == '$defs':
216 schema['$defs'] = {
217 self.remap_defs_ref(DefsRef(key)): self.remap_json_schema(value)
218 for key, value in schema['$defs'].items()
219 }
220 else:
221 schema[key] = self.remap_json_schema(value)
222 return schema
225class GenerateJsonSchema:
226 """!!! abstract "Usage Documentation"
227 [Customizing the JSON Schema Generation Process](../concepts/json_schema.md#customizing-the-json-schema-generation-process)
229 A class for generating JSON schemas.
231 This class generates JSON schemas based on configured parameters. The default schema dialect
232 is [https://json-schema.org/draft/2020-12/schema](https://json-schema.org/draft/2020-12/schema).
233 The class uses `by_alias` to configure how fields with
234 multiple names are handled and `ref_template` to format reference names.
236 Attributes:
237 schema_dialect: The JSON schema dialect used to generate the schema. See
238 [Declaring a Dialect](https://json-schema.org/understanding-json-schema/reference/schema.html#id4)
239 in the JSON Schema documentation for more information about dialects.
240 ignored_warning_kinds: Warnings to ignore when generating the schema. `self.render_warning_message` will
241 do nothing if its argument `kind` is in `ignored_warning_kinds`;
242 this value can be modified on subclasses to easily control which warnings are emitted.
243 by_alias: Whether to use field aliases when generating the schema.
244 ref_template: The format string used when generating reference names.
245 core_to_json_refs: A mapping of core refs to JSON refs.
246 core_to_defs_refs: A mapping of core refs to definition refs.
247 defs_to_core_refs: A mapping of definition refs to core refs.
248 json_to_defs_refs: A mapping of JSON refs to definition refs.
249 definitions: Definitions in the schema.
251 Args:
252 by_alias: Whether to use field aliases in the generated schemas.
253 ref_template: The format string to use when generating reference names.
254 union_format: The format to use when combining schemas from unions together. Can be one of:
256 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf)
257 keyword to combine schemas (the default).
258 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type)
259 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive
260 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to
261 `any_of`.
263 Raises:
264 JsonSchemaError: If the instance of the class is inadvertently reused after generating a schema.
265 """
267 schema_dialect = 'https://json-schema.org/draft/2020-12/schema'
269 # `self.render_warning_message` will do nothing if its argument `kind` is in `ignored_warning_kinds`;
270 # this value can be modified on subclasses to easily control which warnings are emitted
271 ignored_warning_kinds: set[JsonSchemaWarningKind] = {'skipped-choice'}
273 def __init__(
274 self,
275 by_alias: bool = True,
276 ref_template: str = DEFAULT_REF_TEMPLATE,
277 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of',
278 ) -> None:
279 self.by_alias = by_alias
280 self.ref_template = ref_template
281 self.union_format: Literal['any_of', 'primitive_type_array'] = union_format
283 self.core_to_json_refs: dict[CoreModeRef, JsonRef] = {}
284 self.core_to_defs_refs: dict[CoreModeRef, DefsRef] = {}
285 self.defs_to_core_refs: dict[DefsRef, CoreModeRef] = {}
286 self.json_to_defs_refs: dict[JsonRef, DefsRef] = {}
288 self.definitions: dict[DefsRef, JsonSchemaValue] = {}
289 self._config_wrapper_stack = _config.ConfigWrapperStack(_config.ConfigWrapper({}))
291 self._mode: JsonSchemaMode = 'validation'
293 # The following includes a mapping of a fully-unique defs ref choice to a list of preferred
294 # alternatives, which are generally simpler, such as only including the class name.
295 # At the end of schema generation, we use these to produce a JSON schema with more human-readable
296 # definitions, which would also work better in a generated OpenAPI client, etc.
297 self._prioritized_defsref_choices: dict[DefsRef, list[DefsRef]] = {}
298 self._collision_counter: dict[str, int] = defaultdict(int)
299 self._collision_index: dict[str, int] = {}
301 self._schema_type_to_method = self.build_schema_type_to_method()
303 # When we encounter definitions we need to try to build them immediately
304 # so that they are available schemas that reference them
305 # But it's possible that CoreSchema was never going to be used
306 # (e.g. because the CoreSchema that references short circuits is JSON schema generation without needing
307 # the reference) so instead of failing altogether if we can't build a definition we
308 # store the error raised and re-throw it if we end up needing that def
309 self._core_defs_invalid_for_json_schema: dict[DefsRef, PydanticInvalidForJsonSchema] = {}
311 # This changes to True after generating a schema, to prevent issues caused by accidental reuse
312 # of a single instance of a schema generator
313 self._used = False
315 @property
316 def _config(self) -> _config.ConfigWrapper:
317 return self._config_wrapper_stack.tail
319 @property
320 def mode(self) -> JsonSchemaMode:
321 if self._config.json_schema_mode_override is not None:
322 return self._config.json_schema_mode_override
323 else:
324 return self._mode
326 def build_schema_type_to_method(
327 self,
328 ) -> dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]]:
329 """Builds a dictionary mapping fields to methods for generating JSON schemas.
331 Returns:
332 A dictionary containing the mapping of `CoreSchemaOrFieldType` to a handler method.
334 Raises:
335 TypeError: If no method has been defined for generating a JSON schema for a given pydantic core schema type.
336 """
337 mapping: dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]] = {}
338 core_schema_types: list[CoreSchemaOrFieldType] = list(get_literal_values(CoreSchemaOrFieldType))
339 for key in core_schema_types:
340 method_name = f'{key.replace("-", "_")}_schema'
341 try:
342 mapping[key] = getattr(self, method_name)
343 except AttributeError as e: # pragma: no cover
344 if os.getenv('PYDANTIC_PRIVATE_ALLOW_UNHANDLED_SCHEMA_TYPES'):
345 continue
346 raise TypeError(
347 f'No method for generating JsonSchema for core_schema.type={key!r} '
348 f'(expected: {type(self).__name__}.{method_name})'
349 ) from e
350 return mapping
352 def generate_definitions(
353 self, inputs: Sequence[tuple[JsonSchemaKeyT, JsonSchemaMode, core_schema.CoreSchema]]
354 ) -> tuple[dict[tuple[JsonSchemaKeyT, JsonSchemaMode], JsonSchemaValue], dict[DefsRef, JsonSchemaValue]]:
355 """Generates JSON schema definitions from a list of core schemas, pairing the generated definitions with a
356 mapping that links the input keys to the definition references.
358 Args:
359 inputs: A sequence of tuples, where:
361 - The first element is a JSON schema key type.
362 - The second element is the JSON mode: either 'validation' or 'serialization'.
363 - The third element is a core schema.
365 Returns:
366 A tuple where:
368 - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and
369 whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have
370 JsonRef references to definitions that are defined in the second returned element.)
371 - The second element is a dictionary whose keys are definition references for the JSON schemas
372 from the first returned element, and whose values are the actual JSON schema definitions.
374 Raises:
375 PydanticUserError: Raised if the JSON schema generator has already been used to generate a JSON schema.
376 """
377 if self._used:
378 raise PydanticUserError(
379 'This JSON schema generator has already been used to generate a JSON schema. '
380 f'You must create a new instance of {type(self).__name__} to generate a new JSON schema.',
381 code='json-schema-already-used',
382 )
384 for _, mode, schema in inputs:
385 self._mode = mode
386 self.generate_inner(schema)
388 definitions_remapping = self._build_definitions_remapping()
390 json_schemas_map: dict[tuple[JsonSchemaKeyT, JsonSchemaMode], DefsRef] = {}
391 for key, mode, schema in inputs:
392 self._mode = mode
393 json_schema = self.generate_inner(schema)
394 json_schemas_map[(key, mode)] = definitions_remapping.remap_json_schema(json_schema)
396 json_schema = {'$defs': self.definitions}
397 json_schema = definitions_remapping.remap_json_schema(json_schema)
398 self._used = True
399 return json_schemas_map, self.sort(json_schema['$defs']) # type: ignore
401 def generate(self, schema: CoreSchema, mode: JsonSchemaMode = 'validation') -> JsonSchemaValue:
402 """Generates a JSON schema for a specified schema in a specified mode.
404 Args:
405 schema: A Pydantic model.
406 mode: The mode in which to generate the schema. Defaults to 'validation'.
408 Returns:
409 A JSON schema representing the specified schema.
411 Raises:
412 PydanticUserError: If the JSON schema generator has already been used to generate a JSON schema.
413 """
414 self._mode = mode
415 if self._used:
416 raise PydanticUserError(
417 'This JSON schema generator has already been used to generate a JSON schema. '
418 f'You must create a new instance of {type(self).__name__} to generate a new JSON schema.',
419 code='json-schema-already-used',
420 )
422 json_schema: JsonSchemaValue = self.generate_inner(schema)
423 json_ref_counts = self.get_json_ref_counts(json_schema)
425 ref = cast(JsonRef, json_schema.get('$ref'))
426 while ref is not None: # may need to unpack multiple levels
427 ref_json_schema = self.get_schema_from_definitions(ref)
428 if json_ref_counts[ref] == 1 and ref_json_schema is not None and len(json_schema) == 1:
429 # "Unpack" the ref since this is the only reference and there are no sibling keys
430 json_schema = ref_json_schema.copy() # copy to prevent recursive dict reference
431 json_ref_counts[ref] -= 1
432 ref = cast(JsonRef, json_schema.get('$ref'))
433 ref = None
435 self._garbage_collect_definitions(json_schema)
436 definitions_remapping = self._build_definitions_remapping()
438 if self.definitions:
439 json_schema['$defs'] = self.definitions
441 json_schema = definitions_remapping.remap_json_schema(json_schema)
443 # For now, we will not set the $schema key. However, if desired, this can be easily added by overriding
444 # this method and adding the following line after a call to super().generate(schema):
445 # json_schema['$schema'] = self.schema_dialect
447 self._used = True
448 return self.sort(json_schema)
450 def generate_inner(self, schema: CoreSchemaOrField) -> JsonSchemaValue: # noqa: C901
451 """Generates a JSON schema for a given core schema.
453 Args:
454 schema: The given core schema.
456 Returns:
457 The generated JSON schema.
459 TODO: the nested function definitions here seem like bad practice, I'd like to unpack these
460 in a future PR. It'd be great if we could shorten the call stack a bit for JSON schema generation,
461 and I think there's potential for that here.
462 """
463 # If a schema with the same CoreRef has been handled, just return a reference to it
464 # Note that this assumes that it will _never_ be the case that the same CoreRef is used
465 # on types that should have different JSON schemas
466 if 'ref' in schema:
467 core_ref = CoreRef(schema['ref']) # type: ignore[typeddict-item]
468 core_mode_ref = (core_ref, self.mode)
469 if core_mode_ref in self.core_to_defs_refs and self.core_to_defs_refs[core_mode_ref] in self.definitions:
470 return {'$ref': self.core_to_json_refs[core_mode_ref]}
472 def populate_defs(core_schema: CoreSchema, json_schema: JsonSchemaValue) -> JsonSchemaValue:
473 if 'ref' in core_schema:
474 core_ref = CoreRef(core_schema['ref']) # type: ignore[typeddict-item]
475 defs_ref, ref_json_schema = self.get_cache_defs_ref_schema(core_ref)
476 json_ref = JsonRef(ref_json_schema['$ref'])
477 # Replace the schema if it's not a reference to itself
478 # What we want to avoid is having the def be just a ref to itself
479 # which is what would happen if we blindly assigned any
480 if json_schema.get('$ref', None) != json_ref:
481 self.definitions[defs_ref] = json_schema
482 self._core_defs_invalid_for_json_schema.pop(defs_ref, None)
483 json_schema = ref_json_schema
484 return json_schema
486 def handler_func(schema_or_field: CoreSchemaOrField) -> JsonSchemaValue:
487 """Generate a JSON schema based on the input schema.
489 Args:
490 schema_or_field: The core schema to generate a JSON schema from.
492 Returns:
493 The generated JSON schema.
495 Raises:
496 TypeError: If an unexpected schema type is encountered.
497 """
498 # Generate the core-schema-type-specific bits of the schema generation:
499 json_schema: JsonSchemaValue | None = None
500 if self.mode == 'serialization' and 'serialization' in schema_or_field:
501 # In this case, we skip the JSON Schema generation of the schema
502 # and use the `'serialization'` schema instead (canonical example:
503 # `Annotated[int, PlainSerializer(str)]`).
504 ser_schema = schema_or_field['serialization'] # type: ignore
505 json_schema = self.ser_schema(ser_schema)
507 # It might be that the 'serialization'` is skipped depending on `when_used`.
508 # This is only relevant for `nullable` schemas though, so we special case here.
509 if (
510 json_schema is not None
511 and ser_schema.get('when_used') in ('unless-none', 'json-unless-none')
512 and schema_or_field['type'] == 'nullable'
513 ):
514 json_schema = self.get_union_of_schemas([{'type': 'null'}, json_schema])
515 if json_schema is None:
516 if _core_utils.is_core_schema(schema_or_field) or _core_utils.is_core_schema_field(schema_or_field):
517 generate_for_schema_type = self._schema_type_to_method[schema_or_field['type']]
518 json_schema = generate_for_schema_type(schema_or_field)
519 else:
520 raise TypeError(f'Unexpected schema type: schema={schema_or_field}')
521 return json_schema
523 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, handler_func)
525 metadata = cast(_core_metadata.CoreMetadata, schema.get('metadata', {}))
527 # TODO: I dislike that we have to wrap these basic dict updates in callables, is there any way around this?
529 if js_updates := metadata.get('pydantic_js_updates'):
531 def js_updates_handler_func(
532 schema_or_field: CoreSchemaOrField,
533 current_handler: GetJsonSchemaHandler = current_handler,
534 ) -> JsonSchemaValue:
535 json_schema = {**current_handler(schema_or_field), **js_updates}
536 return json_schema
538 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_updates_handler_func)
540 if js_extra := metadata.get('pydantic_js_extra'):
542 def js_extra_handler_func(
543 schema_or_field: CoreSchemaOrField,
544 current_handler: GetJsonSchemaHandler = current_handler,
545 ) -> JsonSchemaValue:
546 json_schema = current_handler(schema_or_field)
547 if isinstance(js_extra, dict):
548 json_schema.update(to_jsonable_python(js_extra))
549 elif callable(js_extra):
550 # similar to typing issue in _update_class_schema when we're working with callable js extra
551 js_extra(json_schema) # type: ignore
552 return json_schema
554 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_extra_handler_func)
556 for js_modify_function in metadata.get('pydantic_js_functions', ()):
558 def new_handler_func(
559 schema_or_field: CoreSchemaOrField,
560 current_handler: GetJsonSchemaHandler = current_handler,
561 js_modify_function: GetJsonSchemaFunction = js_modify_function,
562 ) -> JsonSchemaValue:
563 json_schema = js_modify_function(schema_or_field, current_handler)
564 if _core_utils.is_core_schema(schema_or_field):
565 json_schema = populate_defs(schema_or_field, json_schema)
566 original_schema = current_handler.resolve_ref_schema(json_schema)
567 ref = json_schema.pop('$ref', None)
568 if ref and json_schema:
569 original_schema.update(json_schema)
570 return original_schema
572 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func)
574 for js_modify_function in metadata.get('pydantic_js_annotation_functions', ()):
576 def new_handler_func(
577 schema_or_field: CoreSchemaOrField,
578 current_handler: GetJsonSchemaHandler = current_handler,
579 js_modify_function: GetJsonSchemaFunction = js_modify_function,
580 ) -> JsonSchemaValue:
581 return js_modify_function(schema_or_field, current_handler)
583 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func)
585 json_schema = current_handler(schema)
586 if _core_utils.is_core_schema(schema):
587 json_schema = populate_defs(schema, json_schema)
588 return json_schema
590 def sort(self, value: JsonSchemaValue, parent_key: str | None = None) -> JsonSchemaValue:
591 """Override this method to customize the sorting of the JSON schema (e.g., don't sort at all, sort all keys unconditionally, etc.)
593 By default, alphabetically sort the keys in the JSON schema, skipping the 'properties' and 'default' keys to preserve field definition order.
594 This sort is recursive, so it will sort all nested dictionaries as well.
595 """
596 sorted_dict: dict[str, JsonSchemaValue] = {}
597 keys = value.keys()
598 if parent_key not in ('properties', 'default'):
599 keys = sorted(keys)
600 for key in keys:
601 sorted_dict[key] = self._sort_recursive(value[key], parent_key=key)
602 return sorted_dict
604 def _sort_recursive(self, value: Any, parent_key: str | None = None) -> Any:
605 """Recursively sort a JSON schema value."""
606 if isinstance(value, dict):
607 sorted_dict: dict[str, JsonSchemaValue] = {}
608 keys = value.keys()
609 if parent_key not in ('properties', 'default'):
610 keys = sorted(keys)
611 for key in keys:
612 sorted_dict[key] = self._sort_recursive(value[key], parent_key=key)
613 return sorted_dict
614 elif isinstance(value, list):
615 sorted_list: list[JsonSchemaValue] = [self._sort_recursive(item, parent_key) for item in value]
616 return sorted_list
617 else:
618 return value
620 # ### Schema generation methods
622 def invalid_schema(self, schema: core_schema.InvalidSchema) -> JsonSchemaValue:
623 """Placeholder - should never be called."""
625 raise RuntimeError('Cannot generate schema for invalid_schema. This is a bug! Please report it.')
627 def any_schema(self, schema: core_schema.AnySchema) -> JsonSchemaValue:
628 """Generates a JSON schema that matches any value.
630 Args:
631 schema: The core schema.
633 Returns:
634 The generated JSON schema.
635 """
636 return {}
638 def none_schema(self, schema: core_schema.NoneSchema) -> JsonSchemaValue:
639 """Generates a JSON schema that matches `None`.
641 Args:
642 schema: The core schema.
644 Returns:
645 The generated JSON schema.
646 """
647 return {'type': 'null'}
649 def bool_schema(self, schema: core_schema.BoolSchema) -> JsonSchemaValue:
650 """Generates a JSON schema that matches a bool value.
652 Args:
653 schema: The core schema.
655 Returns:
656 The generated JSON schema.
657 """
658 return {'type': 'boolean'}
660 def int_schema(self, schema: core_schema.IntSchema) -> JsonSchemaValue:
661 """Generates a JSON schema that matches an int value.
663 Args:
664 schema: The core schema.
666 Returns:
667 The generated JSON schema.
668 """
669 json_schema: dict[str, Any] = {'type': 'integer'}
670 self.update_with_validations(json_schema, schema, self.ValidationsMapping.numeric)
671 json_schema = {k: v for k, v in json_schema.items() if v not in {math.inf, -math.inf}}
672 return json_schema
674 def float_schema(self, schema: core_schema.FloatSchema) -> JsonSchemaValue:
675 """Generates a JSON schema that matches a float value.
677 Args:
678 schema: The core schema.
680 Returns:
681 The generated JSON schema.
682 """
683 json_schema: dict[str, Any] = {'type': 'number'}
684 self.update_with_validations(json_schema, schema, self.ValidationsMapping.numeric)
685 json_schema = {k: v for k, v in json_schema.items() if v not in {math.inf, -math.inf}}
686 return json_schema
688 def decimal_schema(self, schema: core_schema.DecimalSchema) -> JsonSchemaValue:
689 """Generates a JSON schema that matches a decimal value.
691 Args:
692 schema: The core schema.
694 Returns:
695 The generated JSON schema.
696 """
698 def get_decimal_pattern(schema: core_schema.DecimalSchema) -> str:
699 max_digits = schema.get('max_digits')
700 decimal_places = schema.get('decimal_places')
702 pattern = (
703 r'^(?!^[-+.]*$)[+-]?0*' # check it is not empty string and not one or sequence of ".+-" characters.
704 )
706 # Case 1: Both max_digits and decimal_places are set
707 if max_digits is not None and decimal_places is not None:
708 integer_places = max(0, max_digits - decimal_places)
709 pattern += (
710 rf'(?:'
711 rf'\d{{0,{integer_places}}}'
712 rf'|'
713 rf'(?=[\d.]{{1,{max_digits + 1}}}0*$)'
714 rf'\d{{0,{integer_places}}}\.\d{{0,{decimal_places}}}0*$'
715 rf')'
716 )
718 # Case 2: Only max_digits is set
719 elif max_digits is not None and decimal_places is None:
720 pattern += (
721 rf'(?:'
722 rf'\d{{0,{max_digits}}}'
723 rf'|'
724 rf'(?=[\d.]{{1,{max_digits + 1}}}0*$)'
725 rf'\d*\.\d*0*$'
726 rf')'
727 )
729 # Case 3: Only decimal_places is set
730 elif max_digits is None and decimal_places is not None:
731 pattern += rf'\d*\.?\d{{0,{decimal_places}}}0*$'
733 # Case 4: Both are None (no restrictions)
734 else:
735 pattern += r'\d*\.?\d*$' # look for arbitrary integer or decimal
737 return pattern
739 json_schema = self.str_schema(core_schema.str_schema(pattern=get_decimal_pattern(schema)))
740 if self.mode == 'validation':
741 multiple_of = schema.get('multiple_of')
742 le = schema.get('le')
743 ge = schema.get('ge')
744 lt = schema.get('lt')
745 gt = schema.get('gt')
746 json_schema = {
747 'anyOf': [
748 self.float_schema(
749 core_schema.float_schema(
750 allow_inf_nan=schema.get('allow_inf_nan'),
751 multiple_of=None if multiple_of is None else float(multiple_of),
752 le=None if le is None else float(le),
753 ge=None if ge is None else float(ge),
754 lt=None if lt is None else float(lt),
755 gt=None if gt is None else float(gt),
756 )
757 ),
758 json_schema,
759 ],
760 }
761 return json_schema
763 def str_schema(self, schema: core_schema.StringSchema) -> JsonSchemaValue:
764 """Generates a JSON schema that matches a string value.
766 Args:
767 schema: The core schema.
769 Returns:
770 The generated JSON schema.
771 """
772 json_schema = {'type': 'string'}
773 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string)
774 if isinstance(json_schema.get('pattern'), Pattern):
775 # TODO: should we add regex flags to the pattern?
776 json_schema['pattern'] = json_schema.get('pattern').pattern # type: ignore
777 return json_schema
779 def bytes_schema(self, schema: core_schema.BytesSchema) -> JsonSchemaValue:
780 """Generates a JSON schema that matches a bytes value.
782 Args:
783 schema: The core schema.
785 Returns:
786 The generated JSON schema.
787 """
788 json_schema = {'type': 'string', 'format': 'base64url' if self._config.ser_json_bytes == 'base64' else 'binary'}
789 self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes)
790 return json_schema
792 def date_schema(self, schema: core_schema.DateSchema) -> JsonSchemaValue:
793 """Generates a JSON schema that matches a date value.
795 Args:
796 schema: The core schema.
798 Returns:
799 The generated JSON schema.
800 """
801 return {'type': 'string', 'format': 'date'}
803 def time_schema(self, schema: core_schema.TimeSchema) -> JsonSchemaValue:
804 """Generates a JSON schema that matches a time value.
806 Args:
807 schema: The core schema.
809 Returns:
810 The generated JSON schema.
811 """
812 return {'type': 'string', 'format': 'time'}
814 def datetime_schema(self, schema: core_schema.DatetimeSchema) -> JsonSchemaValue:
815 """Generates a JSON schema that matches a datetime value.
817 Args:
818 schema: The core schema.
820 Returns:
821 The generated JSON schema.
822 """
823 return {'type': 'string', 'format': 'date-time'}
825 def timedelta_schema(self, schema: core_schema.TimedeltaSchema) -> JsonSchemaValue:
826 """Generates a JSON schema that matches a timedelta value.
828 Args:
829 schema: The core schema.
831 Returns:
832 The generated JSON schema.
833 """
834 if self._config.ser_json_timedelta == 'float':
835 return {'type': 'number'}
836 return {'type': 'string', 'format': 'duration'}
838 def literal_schema(self, schema: core_schema.LiteralSchema) -> JsonSchemaValue:
839 """Generates a JSON schema that matches a literal value.
841 Args:
842 schema: The core schema.
844 Returns:
845 The generated JSON schema.
846 """
847 expected = [to_jsonable_python(v.value if isinstance(v, Enum) else v) for v in schema['expected']]
849 result: dict[str, Any] = {}
850 if len(expected) == 1:
851 result['const'] = expected[0]
852 else:
853 result['enum'] = expected
855 types = {type(e) for e in expected}
856 if types == {str}:
857 result['type'] = 'string'
858 elif types == {int}:
859 result['type'] = 'integer'
860 elif types == {float}:
861 result['type'] = 'number'
862 elif types == {bool}:
863 result['type'] = 'boolean'
864 elif types == {list}:
865 result['type'] = 'array'
866 elif types == {type(None)}:
867 result['type'] = 'null'
868 return result
870 def missing_sentinel_schema(self, schema: core_schema.MissingSentinelSchema) -> JsonSchemaValue:
871 """Generates a JSON schema that matches the `MISSING` sentinel value.
873 Args:
874 schema: The core schema.
876 Returns:
877 The generated JSON schema.
878 """
879 raise PydanticOmit
881 def enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue:
882 """Generates a JSON schema that matches an Enum value.
884 Args:
885 schema: The core schema.
887 Returns:
888 The generated JSON schema.
889 """
890 enum_type = schema['cls']
891 description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__)
892 if (
893 description == 'An enumeration.'
894 ): # This is the default value provided by enum.EnumMeta.__new__; don't use it
895 description = None
896 result: dict[str, Any] = {'title': enum_type.__name__, 'description': description}
897 result = {k: v for k, v in result.items() if v is not None}
899 expected = [to_jsonable_python(v.value) for v in schema['members']]
901 result['enum'] = expected
903 types = {type(e) for e in expected}
904 if isinstance(enum_type, str) or types == {str}:
905 result['type'] = 'string'
906 elif isinstance(enum_type, int) or types == {int}:
907 result['type'] = 'integer'
908 elif isinstance(enum_type, float) or types == {float}:
909 result['type'] = 'number'
910 elif types == {bool}:
911 result['type'] = 'boolean'
912 elif types == {list}:
913 result['type'] = 'array'
915 return result
917 def is_instance_schema(self, schema: core_schema.IsInstanceSchema) -> JsonSchemaValue:
918 """Handles JSON schema generation for a core schema that checks if a value is an instance of a class.
920 Unless overridden in a subclass, this raises an error.
922 Args:
923 schema: The core schema.
925 Returns:
926 The generated JSON schema.
927 """
928 return self.handle_invalid_for_json_schema(schema, f'core_schema.IsInstanceSchema ({schema["cls"]})')
930 def is_subclass_schema(self, schema: core_schema.IsSubclassSchema) -> JsonSchemaValue:
931 """Handles JSON schema generation for a core schema that checks if a value is a subclass of a class.
933 For backwards compatibility with v1, this does not raise an error, but can be overridden to change this.
935 Args:
936 schema: The core schema.
938 Returns:
939 The generated JSON schema.
940 """
941 # Note: This is for compatibility with V1; you can override if you want different behavior.
942 return {}
944 def callable_schema(self, schema: core_schema.CallableSchema) -> JsonSchemaValue:
945 """Generates a JSON schema that matches a callable value.
947 Unless overridden in a subclass, this raises an error.
949 Args:
950 schema: The core schema.
952 Returns:
953 The generated JSON schema.
954 """
955 return self.handle_invalid_for_json_schema(schema, 'core_schema.CallableSchema')
957 def list_schema(self, schema: core_schema.ListSchema) -> JsonSchemaValue:
958 """Returns a schema that matches a list schema.
960 Args:
961 schema: The core schema.
963 Returns:
964 The generated JSON schema.
965 """
966 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema'])
967 json_schema = {'type': 'array', 'items': items_schema}
968 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
969 return json_schema
971 @deprecated('`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.', category=None)
972 @final
973 def tuple_positional_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue:
974 """Replaced by `tuple_schema`."""
975 warnings.warn(
976 '`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.',
977 PydanticDeprecatedSince26,
978 stacklevel=2,
979 )
980 return self.tuple_schema(schema)
982 @deprecated('`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.', category=None)
983 @final
984 def tuple_variable_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue:
985 """Replaced by `tuple_schema`."""
986 warnings.warn(
987 '`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.',
988 PydanticDeprecatedSince26,
989 stacklevel=2,
990 )
991 return self.tuple_schema(schema)
993 def tuple_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue:
994 """Generates a JSON schema that matches a tuple schema e.g. `tuple[int,
995 str, bool]` or `tuple[int, ...]`.
997 Args:
998 schema: The core schema.
1000 Returns:
1001 The generated JSON schema.
1002 """
1003 json_schema: JsonSchemaValue = {'type': 'array'}
1004 if 'variadic_item_index' in schema:
1005 variadic_item_index = schema['variadic_item_index']
1006 if variadic_item_index > 0:
1007 json_schema['minItems'] = variadic_item_index
1008 json_schema['prefixItems'] = [
1009 self.generate_inner(item) for item in schema['items_schema'][:variadic_item_index]
1010 ]
1011 if variadic_item_index + 1 == len(schema['items_schema']):
1012 # if the variadic item is the last item, then represent it faithfully
1013 json_schema['items'] = self.generate_inner(schema['items_schema'][variadic_item_index])
1014 else:
1015 # otherwise, 'items' represents the schema for the variadic
1016 # item plus the suffix, so just allow anything for simplicity
1017 # for now
1018 json_schema['items'] = True
1019 else:
1020 prefixItems = [self.generate_inner(item) for item in schema['items_schema']]
1021 if prefixItems:
1022 json_schema['prefixItems'] = prefixItems
1023 json_schema['minItems'] = len(prefixItems)
1024 json_schema['maxItems'] = len(prefixItems)
1025 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
1026 return json_schema
1028 def set_schema(self, schema: core_schema.SetSchema) -> JsonSchemaValue:
1029 """Generates a JSON schema that matches a set schema.
1031 Args:
1032 schema: The core schema.
1034 Returns:
1035 The generated JSON schema.
1036 """
1037 return self._common_set_schema(schema)
1039 def frozenset_schema(self, schema: core_schema.FrozenSetSchema) -> JsonSchemaValue:
1040 """Generates a JSON schema that matches a frozenset schema.
1042 Args:
1043 schema: The core schema.
1045 Returns:
1046 The generated JSON schema.
1047 """
1048 return self._common_set_schema(schema)
1050 def _common_set_schema(self, schema: core_schema.SetSchema | core_schema.FrozenSetSchema) -> JsonSchemaValue:
1051 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema'])
1052 json_schema = {'type': 'array', 'uniqueItems': True, 'items': items_schema}
1053 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
1054 return json_schema
1056 def generator_schema(self, schema: core_schema.GeneratorSchema) -> JsonSchemaValue:
1057 """Returns a JSON schema that represents the provided GeneratorSchema.
1059 Args:
1060 schema: The schema.
1062 Returns:
1063 The generated JSON schema.
1064 """
1065 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema'])
1066 json_schema = {'type': 'array', 'items': items_schema}
1067 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
1068 return json_schema
1070 def dict_schema(self, schema: core_schema.DictSchema) -> JsonSchemaValue:
1071 """Generates a JSON schema that matches a dict schema.
1073 Args:
1074 schema: The core schema.
1076 Returns:
1077 The generated JSON schema.
1078 """
1079 json_schema: JsonSchemaValue = {'type': 'object'}
1081 keys_schema = self.generate_inner(schema['keys_schema']).copy() if 'keys_schema' in schema else {}
1082 if '$ref' not in keys_schema:
1083 keys_pattern = keys_schema.pop('pattern', None)
1084 # Don't give a title to patternProperties/propertyNames:
1085 keys_schema.pop('title', None)
1086 else:
1087 # Here, we assume that if the keys schema is a definition reference,
1088 # it can't be a simple string core schema (and thus no pattern can exist).
1089 # However, this is only in practice (in theory, a definition reference core
1090 # schema could be generated for a simple string schema).
1091 # Note that we avoid calling `self.resolve_ref_schema`, as it might not exist yet.
1092 keys_pattern = None
1094 values_schema = self.generate_inner(schema['values_schema']).copy() if 'values_schema' in schema else {}
1095 # don't give a title to additionalProperties:
1096 values_schema.pop('title', None)
1098 if values_schema or keys_pattern is not None:
1099 if keys_pattern is None:
1100 json_schema['additionalProperties'] = values_schema
1101 else:
1102 json_schema['patternProperties'] = {keys_pattern: values_schema}
1103 else: # for `dict[str, Any]`, we allow any key and any value, since `str` is the default key type
1104 json_schema['additionalProperties'] = True
1106 if (
1107 # The len check indicates that constraints are probably present:
1108 (keys_schema.get('type') == 'string' and len(keys_schema) > 1)
1109 # If this is a definition reference schema, it most likely has constraints:
1110 or '$ref' in keys_schema
1111 ):
1112 keys_schema.pop('type', None)
1113 json_schema['propertyNames'] = keys_schema
1115 self.update_with_validations(json_schema, schema, self.ValidationsMapping.object)
1116 return json_schema
1118 def function_before_schema(self, schema: core_schema.BeforeValidatorFunctionSchema) -> JsonSchemaValue:
1119 """Generates a JSON schema that matches a function-before schema.
1121 Args:
1122 schema: The core schema.
1124 Returns:
1125 The generated JSON schema.
1126 """
1127 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')):
1128 return self.generate_inner(input_schema)
1130 return self.generate_inner(schema['schema'])
1132 def function_after_schema(self, schema: core_schema.AfterValidatorFunctionSchema) -> JsonSchemaValue:
1133 """Generates a JSON schema that matches a function-after schema.
1135 Args:
1136 schema: The core schema.
1138 Returns:
1139 The generated JSON schema.
1140 """
1141 return self.generate_inner(schema['schema'])
1143 def function_plain_schema(self, schema: core_schema.PlainValidatorFunctionSchema) -> JsonSchemaValue:
1144 """Generates a JSON schema that matches a function-plain schema.
1146 Args:
1147 schema: The core schema.
1149 Returns:
1150 The generated JSON schema.
1151 """
1152 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')):
1153 return self.generate_inner(input_schema)
1155 return self.handle_invalid_for_json_schema(
1156 schema, f'core_schema.PlainValidatorFunctionSchema ({schema["function"]})'
1157 )
1159 def function_wrap_schema(self, schema: core_schema.WrapValidatorFunctionSchema) -> JsonSchemaValue:
1160 """Generates a JSON schema that matches a function-wrap schema.
1162 Args:
1163 schema: The core schema.
1165 Returns:
1166 The generated JSON schema.
1167 """
1168 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')):
1169 return self.generate_inner(input_schema)
1171 return self.generate_inner(schema['schema'])
1173 def default_schema(self, schema: core_schema.WithDefaultSchema) -> JsonSchemaValue:
1174 """Generates a JSON schema that matches a schema with a default value.
1176 Args:
1177 schema: The core schema.
1179 Returns:
1180 The generated JSON schema.
1181 """
1182 json_schema = self.generate_inner(schema['schema'])
1184 default = self.get_default_value(schema)
1185 if default is NoDefault or default is MISSING:
1186 return json_schema
1188 # we reflect the application of custom plain, no-info serializers to defaults for
1189 # JSON Schemas viewed in serialization mode:
1190 # TODO: improvements along with https://github.com/pydantic/pydantic/issues/8208
1191 if self.mode == 'serialization':
1192 # `_get_ser_schema_for_default_value()` is used to unpack potentially nested validator schemas:
1193 ser_schema = _get_ser_schema_for_default_value(schema['schema'])
1194 if (
1195 ser_schema is not None
1196 and (ser_func := ser_schema.get('function'))
1197 and not (default is None and ser_schema.get('when_used') in ('unless-none', 'json-unless-none'))
1198 ):
1199 try:
1200 default = ser_func(default) # type: ignore
1201 except Exception:
1202 # It might be that the provided default needs to be validated (read: parsed) first
1203 # (assuming `validate_default` is enabled). However, we can't perform
1204 # such validation during JSON Schema generation so we don't support
1205 # this pattern for now.
1206 # (One example is when using `foo: ByteSize = '1MB'`, which validates and
1207 # serializes as an int. In this case, `ser_func` is `int` and `int('1MB')` fails).
1208 self.emit_warning(
1209 'non-serializable-default',
1210 f'Unable to serialize value {default!r} with the plain serializer; excluding default from JSON schema',
1211 )
1212 return json_schema
1214 # Sort set/frozenset defaults to ensure deterministic JSON schema generation
1215 # We only sort if len > 1 because sets of size 0 or 1 are already deterministic
1216 if isinstance(default, collections.abc.Set) and len(default) > 1:
1217 try:
1218 default = sorted(default)
1219 except TypeError: # pragma: no cover
1220 # If items aren't comparable (e.g. mixed types), we can't sort them.
1221 pass
1223 try:
1224 encoded_default = self.encode_default(default)
1225 except pydantic_core.PydanticSerializationError:
1226 self.emit_warning(
1227 'non-serializable-default',
1228 f'Default value {default} is not JSON serializable; excluding default from JSON schema',
1229 )
1230 # Return the inner schema, as though there was no default
1231 return json_schema
1233 json_schema['default'] = encoded_default
1234 return json_schema
1236 def get_default_value(self, schema: core_schema.WithDefaultSchema) -> Any:
1237 """Get the default value to be used when generating a JSON Schema for a core schema with a default.
1239 The default implementation is to use the statically defined default value. This method can be overridden
1240 if you want to make use of the default factory.
1242 Args:
1243 schema: The `'with-default'` core schema.
1245 Returns:
1246 The default value to use, or [`NoDefault`][pydantic.json_schema.NoDefault] if no default
1247 value is available.
1248 """
1249 return schema.get('default', NoDefault)
1251 def nullable_schema(self, schema: core_schema.NullableSchema) -> JsonSchemaValue:
1252 """Generates a JSON schema that matches a schema that allows null values.
1254 Args:
1255 schema: The core schema.
1257 Returns:
1258 The generated JSON schema.
1259 """
1260 null_schema = {'type': 'null'}
1261 inner_json_schema = self.generate_inner(schema['schema'])
1263 if inner_json_schema == null_schema:
1264 return null_schema
1265 else:
1266 return self.get_union_of_schemas([inner_json_schema, null_schema])
1268 def union_schema(self, schema: core_schema.UnionSchema) -> JsonSchemaValue:
1269 """Generates a JSON schema that matches a schema that allows values matching any of the given schemas.
1271 Args:
1272 schema: The core schema.
1274 Returns:
1275 The generated JSON schema.
1276 """
1277 generated: list[JsonSchemaValue] = []
1279 for choice in core_schema.iter_union_choices(schema):
1280 try:
1281 generated.append(self.generate_inner(choice))
1282 except PydanticOmit: # noqa: PERF203
1283 continue
1284 except PydanticInvalidForJsonSchema as exc:
1285 self.emit_warning('skipped-choice', exc.message)
1286 if len(generated) == 1:
1287 return generated[0]
1288 return self.get_union_of_schemas(generated)
1290 def get_union_of_schemas(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue:
1291 """Returns the JSON Schema representation for the union of the provided JSON Schemas.
1293 The result depends on the configured `'union_format'`.
1295 Args:
1296 schemas: The list of JSON Schemas to be included in the union.
1298 Returns:
1299 The JSON Schema representing the union of schemas.
1300 """
1301 if self.union_format == 'primitive_type_array':
1302 types: list[str] = []
1303 for schema in schemas:
1304 schema_types: list[str] | str | None = schema.get('type')
1305 if schema_types is None:
1306 # No type, meaning it can be a ref or an empty schema.
1307 break
1308 if not isinstance(schema_types, list):
1309 schema_types = [schema_types]
1310 if not all(t in _PRIMITIVE_JSON_SCHEMA_TYPES for t in schema_types):
1311 break
1312 if len(schema) != 1:
1313 # We only want to include types that don't have any constraints. For instance,
1314 # if `schemas = [{'type': 'string', 'maxLength': 3}, {'type': 'string', 'minLength': 5}]`,
1315 # we don't want to produce `{'type': 'string', 'maxLength': 3, 'minLength': 5}`.
1316 # Same if we have some metadata (e.g. `title`) on a specific union member, we want to preserve it.
1317 break
1319 types.extend(schema_types)
1320 else:
1321 # If we got there, all the schemas where valid to be used with the `'primitive_type_array` format
1322 return {'type': list(dict.fromkeys(types))}
1324 return self.get_flattened_anyof(schemas)
1326 def tagged_union_schema(self, schema: core_schema.TaggedUnionSchema) -> JsonSchemaValue:
1327 """Generates a JSON schema that matches a schema that allows values matching any of the given schemas, where
1328 the schemas are tagged with a discriminator field that indicates which schema should be used to validate
1329 the value.
1331 Args:
1332 schema: The core schema.
1334 Returns:
1335 The generated JSON schema.
1336 """
1337 generated: dict[str, JsonSchemaValue] = {}
1338 for k, v in schema['choices'].items():
1339 if isinstance(k, Enum):
1340 k = k.value
1341 try:
1342 # Use str(k) since keys must be strings for json; while not technically correct,
1343 # it's the closest that can be represented in valid JSON
1344 generated[str(k)] = self.generate_inner(v).copy()
1345 except PydanticOmit:
1346 continue
1347 except PydanticInvalidForJsonSchema as exc:
1348 self.emit_warning('skipped-choice', exc.message)
1350 one_of_choices = _deduplicate_schemas(generated.values())
1351 json_schema: JsonSchemaValue = {'oneOf': one_of_choices}
1353 # This reflects the v1 behavior; TODO: we should make it possible to exclude OpenAPI stuff from the JSON schema
1354 openapi_discriminator = self._extract_discriminator(schema, one_of_choices)
1355 if openapi_discriminator is not None:
1356 json_schema['discriminator'] = {
1357 'propertyName': openapi_discriminator,
1358 'mapping': {k: v.get('$ref', v) for k, v in generated.items()},
1359 }
1361 return json_schema
1363 def _extract_discriminator(
1364 self, schema: core_schema.TaggedUnionSchema, one_of_choices: list[JsonDict]
1365 ) -> str | None:
1366 """Extract a compatible OpenAPI discriminator from the schema and one_of choices that end up in the final
1367 schema."""
1368 openapi_discriminator: str | None = None
1370 if isinstance(schema['discriminator'], str):
1371 return schema['discriminator']
1373 if isinstance(schema['discriminator'], list):
1374 # If the discriminator is a single item list containing a string, that is equivalent to the string case
1375 if len(schema['discriminator']) == 1 and isinstance(schema['discriminator'][0], str):
1376 return schema['discriminator'][0]
1377 # When an alias is used that is different from the field name, the discriminator will be a list of single
1378 # str lists, one for the attribute and one for the actual alias. The logic here will work even if there is
1379 # more than one possible attribute, and looks for whether a single alias choice is present as a documented
1380 # property on all choices. If so, that property will be used as the OpenAPI discriminator.
1381 for alias_path in schema['discriminator']:
1382 if not isinstance(alias_path, list):
1383 break # this means that the discriminator is not a list of alias paths
1384 if len(alias_path) != 1:
1385 continue # this means that the "alias" does not represent a single field
1386 alias = alias_path[0]
1387 if not isinstance(alias, str):
1388 continue # this means that the "alias" does not represent a field
1389 alias_is_present_on_all_choices = True
1390 for choice in one_of_choices:
1391 try:
1392 choice = self.resolve_ref_schema(choice)
1393 except RuntimeError as exc:
1394 # TODO: fixme - this is a workaround for the fact that we can't always resolve refs
1395 # for tagged union choices at this point in the schema gen process, we might need to do
1396 # another pass at the end like we do for core schemas
1397 self.emit_warning('skipped-discriminator', str(exc))
1398 choice = {}
1399 properties = choice.get('properties', {})
1400 if not isinstance(properties, dict) or alias not in properties:
1401 alias_is_present_on_all_choices = False
1402 break
1403 if alias_is_present_on_all_choices:
1404 openapi_discriminator = alias
1405 break
1406 return openapi_discriminator
1408 def chain_schema(self, schema: core_schema.ChainSchema) -> JsonSchemaValue:
1409 """Generates a JSON schema that matches a core_schema.ChainSchema.
1411 When generating a schema for validation, we return the validation JSON schema for the first step in the chain.
1412 For serialization, we return the serialization JSON schema for the last step in the chain.
1414 Args:
1415 schema: The core schema.
1417 Returns:
1418 The generated JSON schema.
1419 """
1420 step_index = 0 if self.mode == 'validation' else -1 # use first step for validation, last for serialization
1421 return self.generate_inner(schema['steps'][step_index])
1423 def lax_or_strict_schema(self, schema: core_schema.LaxOrStrictSchema) -> JsonSchemaValue:
1424 """Generates a JSON schema that matches a schema that allows values matching either the lax schema or the
1425 strict schema.
1427 Args:
1428 schema: The core schema.
1430 Returns:
1431 The generated JSON schema.
1432 """
1433 # TODO: Need to read the default value off of model config or whatever
1434 use_strict = schema.get('strict', False) # TODO: replace this default False
1435 # If your JSON schema fails to generate it is probably
1436 # because one of the following two branches failed.
1437 if use_strict:
1438 return self.generate_inner(schema['strict_schema'])
1439 else:
1440 return self.generate_inner(schema['lax_schema'])
1442 def json_or_python_schema(self, schema: core_schema.JsonOrPythonSchema) -> JsonSchemaValue:
1443 """Generates a JSON schema that matches a schema that allows values matching either the JSON schema or the
1444 Python schema.
1446 The JSON schema is used instead of the Python schema. If you want to use the Python schema, you should override
1447 this method.
1449 Args:
1450 schema: The core schema.
1452 Returns:
1453 The generated JSON schema.
1454 """
1455 return self.generate_inner(schema['json_schema'])
1457 def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue:
1458 """Generates a JSON schema that matches a schema that defines a typed dict.
1460 Args:
1461 schema: The core schema.
1463 Returns:
1464 The generated JSON schema.
1465 """
1466 total = schema.get('total', True)
1467 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [
1468 (name, self.field_is_required(field, total), field)
1469 for name, field in schema['fields'].items()
1470 if self.field_is_present(field)
1471 ]
1472 if self.mode == 'serialization':
1473 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', [])))
1474 cls = schema.get('cls')
1475 config = _get_typed_dict_config(cls)
1476 with self._config_wrapper_stack.push(config):
1477 json_schema = self._named_required_fields_schema(named_required_fields)
1479 # There's some duplication between `extra_behavior` and
1480 # the config's `extra`/core config's `extra_fields_behavior`.
1481 # However, it is common to manually create TypedDictSchemas,
1482 # where you don't necessarily have a class.
1483 # At runtime, `extra_behavior` takes priority over the config
1484 # for validation, so follow the same for the JSON Schema:
1485 if 'extras_schema' in schema and schema['extras_schema'] != core_schema.any_schema():
1486 allow_additional_props = self.generate_inner(schema['extras_schema'])
1487 else:
1488 allow_additional_props = True
1490 if schema.get('extra_behavior') == 'forbid':
1491 json_schema['additionalProperties'] = False
1492 elif schema.get('extra_behavior') == 'allow':
1493 json_schema['additionalProperties'] = allow_additional_props
1495 if cls is not None:
1496 # `_update_class_schema()` will not override
1497 # `additionalProperties` if already present:
1498 self._update_class_schema(json_schema, cls, config)
1499 elif 'additionalProperties' not in json_schema:
1500 extra = schema.get('config', {}).get('extra_fields_behavior')
1501 if extra == 'forbid':
1502 json_schema['additionalProperties'] = False
1503 elif extra == 'allow':
1504 json_schema['additionalProperties'] = allow_additional_props
1506 return json_schema
1508 @staticmethod
1509 def _name_required_computed_fields(
1510 computed_fields: list[ComputedField],
1511 ) -> list[tuple[str, bool, core_schema.ComputedField]]:
1512 return [(field['property_name'], True, field) for field in computed_fields]
1514 def _named_required_fields_schema(
1515 self, named_required_fields: Sequence[tuple[str, bool, CoreSchemaField]]
1516 ) -> JsonSchemaValue:
1517 properties: dict[str, JsonSchemaValue] = {}
1518 required_fields: list[str] = []
1519 for name, required, field in named_required_fields:
1520 if self.by_alias:
1521 name = self._get_alias_name(field, name)
1522 try:
1523 field_json_schema = self.generate_inner(field).copy()
1524 except PydanticOmit:
1525 continue
1526 if 'title' not in field_json_schema and self.field_title_should_be_set(field):
1527 title = self.get_title_from_name(name)
1528 field_json_schema['title'] = title
1529 field_json_schema = self.handle_ref_overrides(field_json_schema)
1530 properties[name] = field_json_schema
1531 if required:
1532 required_fields.append(name)
1534 json_schema = {'type': 'object', 'properties': properties}
1535 if required_fields:
1536 json_schema['required'] = required_fields
1537 return json_schema
1539 def _get_alias_name(self, field: CoreSchemaField, name: str) -> str:
1540 if field['type'] == 'computed-field':
1541 alias: Any = field.get('alias', name)
1542 elif self.mode == 'validation':
1543 alias = field.get('validation_alias', name)
1544 else:
1545 alias = field.get('serialization_alias', name)
1546 if isinstance(alias, str):
1547 name = alias
1548 elif isinstance(alias, list):
1549 alias = cast('list[str] | str', alias)
1550 for path in alias:
1551 if isinstance(path, list) and len(path) == 1 and isinstance(path[0], str):
1552 # Use the first valid single-item string path; the code that constructs the alias array
1553 # should ensure the first such item is what belongs in the JSON schema
1554 name = path[0]
1555 break
1556 else:
1557 assert_never(alias)
1558 return name
1560 def typed_dict_field_schema(self, schema: core_schema.TypedDictField) -> JsonSchemaValue:
1561 """Generates a JSON schema that matches a schema that defines a typed dict field.
1563 Args:
1564 schema: The core schema.
1566 Returns:
1567 The generated JSON schema.
1568 """
1569 return self.generate_inner(schema['schema'])
1571 def dataclass_field_schema(self, schema: core_schema.DataclassField) -> JsonSchemaValue:
1572 """Generates a JSON schema that matches a schema that defines a dataclass field.
1574 Args:
1575 schema: The core schema.
1577 Returns:
1578 The generated JSON schema.
1579 """
1580 return self.generate_inner(schema['schema'])
1582 def model_field_schema(self, schema: core_schema.ModelField) -> JsonSchemaValue:
1583 """Generates a JSON schema that matches a schema that defines a model field.
1585 Args:
1586 schema: The core schema.
1588 Returns:
1589 The generated JSON schema.
1590 """
1591 return self.generate_inner(schema['schema'])
1593 def computed_field_schema(self, schema: core_schema.ComputedField) -> JsonSchemaValue:
1594 """Generates a JSON schema that matches a schema that defines a computed field.
1596 Args:
1597 schema: The core schema.
1599 Returns:
1600 The generated JSON schema.
1601 """
1602 return self.generate_inner(schema['return_schema'])
1604 def model_schema(self, schema: core_schema.ModelSchema) -> JsonSchemaValue:
1605 """Generates a JSON schema that matches a schema that defines a model.
1607 Args:
1608 schema: The core schema.
1610 Returns:
1611 The generated JSON schema.
1612 """
1613 # We do not use schema['model'].model_json_schema() here
1614 # because it could lead to inconsistent refs handling, etc.
1615 cls = cast('type[BaseModel]', schema['cls'])
1616 config = cls.model_config
1618 with self._config_wrapper_stack.push(config):
1619 json_schema = self.generate_inner(schema['schema'])
1621 self._update_class_schema(json_schema, cls, config)
1623 return json_schema
1625 def _update_class_schema(self, json_schema: JsonSchemaValue, cls: type[Any], config: ConfigDict) -> None:
1626 """Update json_schema with the following, extracted from `config` and `cls`:
1628 * title
1629 * description
1630 * additional properties
1631 * json_schema_extra
1632 * deprecated
1634 Done in place, hence there's no return value as the original json_schema is mutated.
1635 No ref resolving is involved here, as that's not appropriate for simple updates.
1636 """
1637 from ._internal._dataclasses import is_stdlib_dataclass
1638 from .main import BaseModel
1640 if (config_title := config.get('title')) is not None:
1641 json_schema.setdefault('title', config_title)
1642 elif model_title_generator := config.get('model_title_generator'):
1643 title = model_title_generator(cls)
1644 if not isinstance(title, str):
1645 raise TypeError(f'model_title_generator {model_title_generator} must return str, not {title.__class__}')
1646 json_schema.setdefault('title', title)
1647 if 'title' not in json_schema:
1648 json_schema['title'] = cls.__name__
1650 # BaseModel and dataclasses; don't use cls.__doc__ as it will contain the verbose class signature by default
1651 if cls is BaseModel:
1652 docstring = None
1653 elif is_stdlib_dataclass(cls): # For Pydantic dataclasses, we already handle this at class creation
1654 # The `dataclass` module generates a `__doc__` based on the `inspect.signature()`
1655 # result, which we don't want to use as a description. Such `__doc__` startswith
1656 # `cls.__name__(`, which could lead to mistakenly discarding it if for some reason
1657 # an explicitly set class docstring follows the same pattern, but this is unlikely
1658 # to happen.
1659 doc = cls.__doc__
1660 docstring = None if doc is None or doc.startswith(f'{cls.__name__}(') else doc
1661 else:
1662 docstring = cls.__doc__
1664 if docstring:
1665 json_schema.setdefault('description', inspect.cleandoc(docstring))
1667 extra = config.get('extra')
1668 if 'additionalProperties' not in json_schema: # This check is particularly important for `typed_dict_schema()`
1669 if extra == 'allow':
1670 json_schema['additionalProperties'] = True
1671 elif extra == 'forbid':
1672 json_schema['additionalProperties'] = False
1674 json_schema_extra = config.get('json_schema_extra')
1675 if issubclass(cls, BaseModel) and cls.__pydantic_root_model__:
1676 root_json_schema_extra = cls.model_fields['root'].json_schema_extra
1677 if json_schema_extra and root_json_schema_extra:
1678 raise ValueError(
1679 '"model_config[\'json_schema_extra\']" and "Field.json_schema_extra" on "RootModel.root"'
1680 ' field must not be set simultaneously'
1681 )
1682 if root_json_schema_extra:
1683 json_schema_extra = root_json_schema_extra
1685 if isinstance(json_schema_extra, (staticmethod, classmethod)):
1686 # In older versions of python, this is necessary to ensure staticmethod/classmethods are callable
1687 json_schema_extra = json_schema_extra.__get__(cls)
1689 if isinstance(json_schema_extra, dict):
1690 json_schema.update(json_schema_extra)
1691 elif callable(json_schema_extra):
1692 if len(_typing_extra.signature_no_eval(json_schema_extra).parameters) > 1:
1693 json_schema_extra = cast(Callable[[JsonDict, type[Any]], None], json_schema_extra)
1694 json_schema_extra(json_schema, cls)
1695 else:
1696 json_schema_extra = cast(Callable[[JsonDict], None], json_schema_extra)
1697 json_schema_extra(json_schema)
1698 elif json_schema_extra is not None:
1699 raise ValueError(
1700 f"model_config['json_schema_extra']={json_schema_extra} should be a dict, callable, or None"
1701 )
1703 if hasattr(cls, '__deprecated__'):
1704 json_schema['deprecated'] = True
1706 def resolve_ref_schema(self, json_schema: JsonSchemaValue) -> JsonSchemaValue:
1707 """Resolve a JsonSchemaValue to the non-ref schema if it is a $ref schema.
1709 Args:
1710 json_schema: The schema to resolve.
1712 Returns:
1713 The resolved schema.
1715 Raises:
1716 RuntimeError: If the schema reference can't be found in definitions.
1717 """
1718 while '$ref' in json_schema:
1719 ref = json_schema['$ref']
1720 schema_to_update = self.get_schema_from_definitions(JsonRef(ref))
1721 if schema_to_update is None:
1722 raise RuntimeError(f'Cannot update undefined schema for $ref={ref}')
1723 json_schema = schema_to_update
1724 return json_schema
1726 def model_fields_schema(self, schema: core_schema.ModelFieldsSchema) -> JsonSchemaValue:
1727 """Generates a JSON schema that matches a schema that defines a model's fields.
1729 Args:
1730 schema: The core schema.
1732 Returns:
1733 The generated JSON schema.
1734 """
1735 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [
1736 (name, self.field_is_required(field, total=True), field)
1737 for name, field in schema['fields'].items()
1738 if self.field_is_present(field)
1739 ]
1740 if self.mode == 'serialization':
1741 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', [])))
1742 json_schema = self._named_required_fields_schema(named_required_fields)
1743 extras_schema = schema.get('extras_schema', None)
1744 if extras_schema is not None:
1745 schema_to_update = self.resolve_ref_schema(json_schema)
1746 schema_to_update['additionalProperties'] = self.generate_inner(extras_schema)
1747 return json_schema
1749 def field_is_present(self, field: CoreSchemaField) -> bool:
1750 """Whether the field should be included in the generated JSON schema.
1752 Args:
1753 field: The schema for the field itself.
1755 Returns:
1756 `True` if the field should be included in the generated JSON schema, `False` otherwise.
1757 """
1758 if self.mode == 'serialization':
1759 # If you still want to include the field in the generated JSON schema,
1760 # override this method and return True
1761 return not field.get('serialization_exclude')
1762 elif self.mode == 'validation':
1763 return True
1764 else:
1765 assert_never(self.mode)
1767 def field_is_required(
1768 self,
1769 field: core_schema.ModelField | core_schema.DataclassField | core_schema.TypedDictField,
1770 total: bool,
1771 ) -> bool:
1772 """Whether the field should be marked as required in the generated JSON schema.
1773 (Note that this is irrelevant if the field is not present in the JSON schema.).
1775 Args:
1776 field: The schema for the field itself.
1777 total: Only applies to `TypedDictField`s.
1778 Indicates if the `TypedDict` this field belongs to is total, in which case any fields that don't
1779 explicitly specify `required=False` are required.
1781 Returns:
1782 `True` if the field should be marked as required in the generated JSON schema, `False` otherwise.
1783 """
1784 if field['type'] == 'typed-dict-field':
1785 required = field.get('required', total)
1786 else:
1787 required = field['schema']['type'] != 'default'
1789 if self.mode == 'serialization':
1790 has_exclude_if = field.get('serialization_exclude_if') is not None
1791 if self._config.json_schema_serialization_defaults_required:
1792 return not has_exclude_if
1793 else:
1794 return required and not has_exclude_if
1795 else:
1796 return required
1798 def dataclass_args_schema(self, schema: core_schema.DataclassArgsSchema) -> JsonSchemaValue:
1799 """Generates a JSON schema that matches a schema that defines a dataclass's constructor arguments.
1801 Args:
1802 schema: The core schema.
1804 Returns:
1805 The generated JSON schema.
1806 """
1807 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [
1808 (field['name'], self.field_is_required(field, total=True), field)
1809 for field in schema['fields']
1810 if self.field_is_present(field)
1811 ]
1812 if self.mode == 'serialization':
1813 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', [])))
1814 return self._named_required_fields_schema(named_required_fields)
1816 def dataclass_schema(self, schema: core_schema.DataclassSchema) -> JsonSchemaValue:
1817 """Generates a JSON schema that matches a schema that defines a dataclass.
1819 Args:
1820 schema: The core schema.
1822 Returns:
1823 The generated JSON schema.
1824 """
1826 cls = schema['cls']
1827 config = cast('ConfigDict', getattr(cls, '__pydantic_config__', {}))
1829 with self._config_wrapper_stack.push(config):
1830 json_schema = self.generate_inner(schema['schema']).copy()
1832 self._update_class_schema(json_schema, cls, config)
1834 return json_schema
1836 def arguments_schema(self, schema: core_schema.ArgumentsSchema) -> JsonSchemaValue:
1837 """Generates a JSON schema that matches a schema that defines a function's arguments.
1839 Args:
1840 schema: The core schema.
1842 Returns:
1843 The generated JSON schema.
1844 """
1845 prefer_positional = schema.get('metadata', {}).get('pydantic_js_prefer_positional_arguments')
1847 arguments = schema['arguments_schema']
1848 kw_only_arguments = [a for a in arguments if a.get('mode') == 'keyword_only']
1849 kw_or_p_arguments = [a for a in arguments if a.get('mode') in {'positional_or_keyword', None}]
1850 p_only_arguments = [a for a in arguments if a.get('mode') == 'positional_only']
1851 var_args_schema = schema.get('var_args_schema')
1852 var_kwargs_schema = schema.get('var_kwargs_schema')
1854 if prefer_positional:
1855 positional_possible = not kw_only_arguments and not var_kwargs_schema
1856 if positional_possible:
1857 return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema)
1859 keyword_possible = not p_only_arguments and not var_args_schema
1860 if keyword_possible:
1861 return self.kw_arguments_schema(kw_or_p_arguments + kw_only_arguments, var_kwargs_schema)
1863 if not prefer_positional:
1864 positional_possible = not kw_only_arguments and not var_kwargs_schema
1865 if positional_possible:
1866 return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema)
1868 raise PydanticInvalidForJsonSchema(
1869 'Unable to generate JSON schema for arguments validator with positional-only and keyword-only arguments'
1870 )
1872 def kw_arguments_schema(
1873 self, arguments: list[core_schema.ArgumentsParameter], var_kwargs_schema: CoreSchema | None
1874 ) -> JsonSchemaValue:
1875 """Generates a JSON schema that matches a schema that defines a function's keyword arguments.
1877 Args:
1878 arguments: The core schema.
1880 Returns:
1881 The generated JSON schema.
1882 """
1883 properties: dict[str, JsonSchemaValue] = {}
1884 required: list[str] = []
1885 for argument in arguments:
1886 name = self.get_argument_name(argument)
1887 argument_schema = self.generate_inner(argument['schema']).copy()
1888 if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']):
1889 argument_schema['title'] = self.get_title_from_name(name)
1890 properties[name] = argument_schema
1892 if argument['schema']['type'] != 'default':
1893 # This assumes that if the argument has a default value,
1894 # the inner schema must be of type WithDefaultSchema.
1895 # I believe this is true, but I am not 100% sure
1896 required.append(name)
1898 json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties}
1899 if required:
1900 json_schema['required'] = required
1902 if var_kwargs_schema:
1903 additional_properties_schema = self.generate_inner(var_kwargs_schema)
1904 if additional_properties_schema:
1905 json_schema['additionalProperties'] = additional_properties_schema
1906 else:
1907 json_schema['additionalProperties'] = False
1908 return json_schema
1910 def p_arguments_schema(
1911 self, arguments: list[core_schema.ArgumentsParameter], var_args_schema: CoreSchema | None
1912 ) -> JsonSchemaValue:
1913 """Generates a JSON schema that matches a schema that defines a function's positional arguments.
1915 Args:
1916 arguments: The core schema.
1918 Returns:
1919 The generated JSON schema.
1920 """
1921 prefix_items: list[JsonSchemaValue] = []
1922 min_items = 0
1924 for argument in arguments:
1925 name = self.get_argument_name(argument)
1927 argument_schema = self.generate_inner(argument['schema']).copy()
1928 if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']):
1929 argument_schema['title'] = self.get_title_from_name(name)
1930 prefix_items.append(argument_schema)
1932 if argument['schema']['type'] != 'default':
1933 # This assumes that if the argument has a default value,
1934 # the inner schema must be of type WithDefaultSchema.
1935 # I believe this is true, but I am not 100% sure
1936 min_items += 1
1938 json_schema: JsonSchemaValue = {'type': 'array'}
1939 if prefix_items:
1940 json_schema['prefixItems'] = prefix_items
1941 if min_items:
1942 json_schema['minItems'] = min_items
1944 if var_args_schema:
1945 items_schema = self.generate_inner(var_args_schema)
1946 if items_schema:
1947 json_schema['items'] = items_schema
1948 else:
1949 json_schema['maxItems'] = len(prefix_items)
1951 return json_schema
1953 def get_argument_name(self, argument: core_schema.ArgumentsParameter | core_schema.ArgumentsV3Parameter) -> str:
1954 """Retrieves the name of an argument.
1956 Args:
1957 argument: The core schema.
1959 Returns:
1960 The name of the argument.
1961 """
1962 name = argument['name']
1963 if self.by_alias:
1964 alias = argument.get('alias')
1965 if isinstance(alias, str):
1966 name = alias
1967 else:
1968 pass # might want to do something else?
1969 return name
1971 def arguments_v3_schema(self, schema: core_schema.ArgumentsV3Schema) -> JsonSchemaValue:
1972 """Generates a JSON schema that matches a schema that defines a function's arguments.
1974 Args:
1975 schema: The core schema.
1977 Returns:
1978 The generated JSON schema.
1979 """
1980 arguments = schema['arguments_schema']
1981 properties: dict[str, JsonSchemaValue] = {}
1982 required: list[str] = []
1983 for argument in arguments:
1984 mode = argument.get('mode', 'positional_or_keyword')
1985 name = self.get_argument_name(argument)
1986 argument_schema = self.generate_inner(argument['schema']).copy()
1987 if mode == 'var_args':
1988 argument_schema = {'type': 'array', 'items': argument_schema}
1989 elif mode == 'var_kwargs_uniform':
1990 argument_schema = {'type': 'object', 'additionalProperties': argument_schema}
1992 argument_schema.setdefault('title', self.get_title_from_name(name))
1993 properties[name] = argument_schema
1995 if (
1996 (mode == 'var_kwargs_unpacked_typed_dict' and 'required' in argument_schema)
1997 or mode not in {'var_args', 'var_kwargs_uniform', 'var_kwargs_unpacked_typed_dict'}
1998 and argument['schema']['type'] != 'default'
1999 ):
2000 # This assumes that if the argument has a default value,
2001 # the inner schema must be of type WithDefaultSchema.
2002 # I believe this is true, but I am not 100% sure
2003 required.append(name)
2005 json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties}
2006 if required:
2007 json_schema['required'] = required
2008 return json_schema
2010 def call_schema(self, schema: core_schema.CallSchema) -> JsonSchemaValue:
2011 """Generates a JSON schema that matches a schema that defines a function call.
2013 Args:
2014 schema: The core schema.
2016 Returns:
2017 The generated JSON schema.
2018 """
2019 return self.generate_inner(schema['arguments_schema'])
2021 def custom_error_schema(self, schema: core_schema.CustomErrorSchema) -> JsonSchemaValue:
2022 """Generates a JSON schema that matches a schema that defines a custom error.
2024 Args:
2025 schema: The core schema.
2027 Returns:
2028 The generated JSON schema.
2029 """
2030 return self.generate_inner(schema['schema'])
2032 def json_schema(self, schema: core_schema.JsonSchema) -> JsonSchemaValue:
2033 """Generates a JSON schema that matches a schema that defines a JSON object.
2035 Args:
2036 schema: The core schema.
2038 Returns:
2039 The generated JSON schema.
2040 """
2041 content_core_schema = schema.get('schema') or core_schema.any_schema()
2042 content_json_schema = self.generate_inner(content_core_schema)
2043 if self.mode == 'validation':
2044 return {'type': 'string', 'contentMediaType': 'application/json', 'contentSchema': content_json_schema}
2045 else:
2046 # self.mode == 'serialization'
2047 return content_json_schema
2049 def url_schema(self, schema: core_schema.UrlSchema) -> JsonSchemaValue:
2050 """Generates a JSON schema that matches a schema that defines a URL.
2052 Args:
2053 schema: The core schema.
2055 Returns:
2056 The generated JSON schema.
2057 """
2058 json_schema = {'type': 'string', 'format': 'uri', 'minLength': 1}
2059 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string)
2060 return json_schema
2062 def multi_host_url_schema(self, schema: core_schema.MultiHostUrlSchema) -> JsonSchemaValue:
2063 """Generates a JSON schema that matches a schema that defines a URL that can be used with multiple hosts.
2065 Args:
2066 schema: The core schema.
2068 Returns:
2069 The generated JSON schema.
2070 """
2071 # Note: 'multi-host-uri' is a custom/pydantic-specific format, not part of the JSON Schema spec
2072 json_schema = {'type': 'string', 'format': 'multi-host-uri', 'minLength': 1}
2073 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string)
2074 return json_schema
2076 def uuid_schema(self, schema: core_schema.UuidSchema) -> JsonSchemaValue:
2077 """Generates a JSON schema that matches a UUID.
2079 Args:
2080 schema: The core schema.
2082 Returns:
2083 The generated JSON schema.
2084 """
2085 return {'type': 'string', 'format': 'uuid'}
2087 def definitions_schema(self, schema: core_schema.DefinitionsSchema) -> JsonSchemaValue:
2088 """Generates a JSON schema that matches a schema that defines a JSON object with definitions.
2090 Args:
2091 schema: The core schema.
2093 Returns:
2094 The generated JSON schema.
2095 """
2096 for definition in schema['definitions']:
2097 try:
2098 self.generate_inner(definition)
2099 except PydanticInvalidForJsonSchema as e: # noqa: PERF203
2100 core_ref: CoreRef = CoreRef(definition['ref']) # type: ignore
2101 self._core_defs_invalid_for_json_schema[self.get_defs_ref((core_ref, self.mode))] = e
2102 continue
2103 return self.generate_inner(schema['schema'])
2105 def definition_ref_schema(self, schema: core_schema.DefinitionReferenceSchema) -> JsonSchemaValue:
2106 """Generates a JSON schema that matches a schema that references a definition.
2108 Args:
2109 schema: The core schema.
2111 Returns:
2112 The generated JSON schema.
2113 """
2114 core_ref = CoreRef(schema['schema_ref'])
2115 _, ref_json_schema = self.get_cache_defs_ref_schema(core_ref)
2116 return ref_json_schema
2118 def ser_schema(
2119 self, schema: core_schema.SerSchema | core_schema.IncExSeqSerSchema | core_schema.IncExDictSerSchema
2120 ) -> JsonSchemaValue | None:
2121 """Generates a JSON schema that matches a schema that defines a serialized object.
2123 Args:
2124 schema: The core schema.
2126 Returns:
2127 The generated JSON schema.
2128 """
2129 schema_type = schema['type']
2130 if schema_type == 'function-plain' or schema_type == 'function-wrap':
2131 # PlainSerializerFunctionSerSchema or WrapSerializerFunctionSerSchema
2132 return_schema = schema.get('return_schema')
2133 if return_schema is not None:
2134 return self.generate_inner(return_schema)
2135 elif schema_type == 'format' or schema_type == 'to-string':
2136 # FormatSerSchema or ToStringSerSchema
2137 return self.str_schema(core_schema.str_schema())
2138 elif schema['type'] == 'model':
2139 # ModelSerSchema
2140 return self.generate_inner(schema['schema'])
2141 return None
2143 def complex_schema(self, schema: core_schema.ComplexSchema) -> JsonSchemaValue:
2144 """Generates a JSON schema that matches a complex number.
2146 JSON has no standard way to represent complex numbers. Complex number is not a numeric
2147 type. Here we represent complex number as strings following the rule defined by Python.
2148 For instance, '1+2j' is an accepted complex string. Details can be found in
2149 [Python's `complex` documentation][complex].
2151 Args:
2152 schema: The core schema.
2154 Returns:
2155 The generated JSON schema.
2156 """
2157 return {'type': 'string'}
2159 # ### Utility methods
2161 def get_title_from_name(self, name: str) -> str:
2162 """Retrieves a title from a name.
2164 Args:
2165 name: The name to retrieve a title from.
2167 Returns:
2168 The title.
2169 """
2170 return name.title().replace('_', ' ').strip()
2172 def field_title_should_be_set(self, schema: CoreSchemaOrField) -> bool:
2173 """Returns true if a field with the given schema should have a title set based on the field name.
2175 Intuitively, we want this to return true for schemas that wouldn't otherwise provide their own title
2176 (e.g., int, float, str), and false for those that would (e.g., BaseModel subclasses).
2178 Args:
2179 schema: The schema to check.
2181 Returns:
2182 `True` if the field should have a title set, `False` otherwise.
2183 """
2184 if _core_utils.is_core_schema_field(schema):
2185 if schema['type'] == 'computed-field':
2186 field_schema = schema['return_schema']
2187 else:
2188 field_schema = schema['schema']
2189 return self.field_title_should_be_set(field_schema)
2191 elif _core_utils.is_core_schema(schema):
2192 if schema.get('ref'): # things with refs, such as models and enums, should not have titles set
2193 return False
2194 if schema['type'] in {'default', 'nullable', 'definitions'}:
2195 return self.field_title_should_be_set(schema['schema']) # type: ignore[typeddict-item]
2196 if _core_utils.is_function_with_inner_schema(schema):
2197 return self.field_title_should_be_set(schema['schema'])
2198 if schema['type'] == 'definition-ref':
2199 # Referenced schemas should not have titles set for the same reason
2200 # schemas with refs should not
2201 return False
2202 return True # anything else should have title set
2204 else:
2205 raise PydanticInvalidForJsonSchema(f'Unexpected schema type: schema={schema}') # pragma: no cover
2207 def normalize_name(self, name: str) -> str:
2208 """Normalizes a name to be used as a key in a dictionary.
2210 Args:
2211 name: The name to normalize.
2213 Returns:
2214 The normalized name.
2215 """
2216 return re.sub(r'[^a-zA-Z0-9.\-_]', '_', name).replace('.', '__')
2218 def get_defs_ref(self, core_mode_ref: CoreModeRef) -> DefsRef:
2219 """Override this method to change the way that definitions keys are generated from a core reference.
2221 Args:
2222 core_mode_ref: The core reference.
2224 Returns:
2225 The definitions key.
2226 """
2227 # Split the core ref into "components"; generic origins and arguments are each separate components
2228 core_ref, mode = core_mode_ref
2229 components = re.split(r'([\][,])', core_ref)
2230 # Remove IDs from each component
2231 components = [x.rsplit(':', 1)[0] for x in components]
2232 core_ref_no_id = ''.join(components)
2233 # Remove everything before the last period from each "component"
2234 components = [re.sub(r'(?:[^.[\]]+\.)+((?:[^.[\]]+))', r'\1', x) for x in components]
2235 short_ref = ''.join(components)
2237 mode_title = _MODE_TITLE_MAPPING[mode]
2239 # It is important that the generated defs_ref values be such that at least one choice will not
2240 # be generated for any other core_ref. Currently, this should be the case because we include
2241 # the id of the source type in the core_ref
2242 name = DefsRef(self.normalize_name(short_ref))
2243 name_mode = DefsRef(self.normalize_name(short_ref) + f'-{mode_title}')
2244 module_qualname = DefsRef(self.normalize_name(core_ref_no_id))
2245 module_qualname_mode = DefsRef(f'{module_qualname}-{mode_title}')
2246 module_qualname_id = DefsRef(self.normalize_name(core_ref))
2247 occurrence_index = self._collision_index.get(module_qualname_id)
2248 if occurrence_index is None:
2249 self._collision_counter[module_qualname] += 1
2250 occurrence_index = self._collision_index[module_qualname_id] = self._collision_counter[module_qualname]
2252 module_qualname_occurrence = DefsRef(f'{module_qualname}__{occurrence_index}')
2253 module_qualname_occurrence_mode = DefsRef(f'{module_qualname_mode}__{occurrence_index}')
2255 self._prioritized_defsref_choices[module_qualname_occurrence_mode] = [
2256 name,
2257 name_mode,
2258 module_qualname,
2259 module_qualname_mode,
2260 module_qualname_occurrence,
2261 module_qualname_occurrence_mode,
2262 ]
2264 return module_qualname_occurrence_mode
2266 def get_cache_defs_ref_schema(self, core_ref: CoreRef) -> tuple[DefsRef, JsonSchemaValue]:
2267 """This method wraps the get_defs_ref method with some cache-lookup/population logic,
2268 and returns both the produced defs_ref and the JSON schema that will refer to the right definition.
2270 Args:
2271 core_ref: The core reference to get the definitions reference for.
2273 Returns:
2274 A tuple of the definitions reference and the JSON schema that will refer to it.
2275 """
2276 core_mode_ref = (core_ref, self.mode)
2277 maybe_defs_ref = self.core_to_defs_refs.get(core_mode_ref)
2278 if maybe_defs_ref is not None:
2279 json_ref = self.core_to_json_refs[core_mode_ref]
2280 return maybe_defs_ref, {'$ref': json_ref}
2282 defs_ref = self.get_defs_ref(core_mode_ref)
2284 # populate the ref translation mappings
2285 self.core_to_defs_refs[core_mode_ref] = defs_ref
2286 self.defs_to_core_refs[defs_ref] = core_mode_ref
2288 json_ref = JsonRef(self.ref_template.format(model=defs_ref))
2289 self.core_to_json_refs[core_mode_ref] = json_ref
2290 self.json_to_defs_refs[json_ref] = defs_ref
2291 ref_json_schema = {'$ref': json_ref}
2292 return defs_ref, ref_json_schema
2294 def handle_ref_overrides(self, json_schema: JsonSchemaValue) -> JsonSchemaValue:
2295 """Remove any sibling keys that are redundant with the referenced schema.
2297 Args:
2298 json_schema: The schema to remove redundant sibling keys from.
2300 Returns:
2301 The schema with redundant sibling keys removed.
2302 """
2303 if '$ref' in json_schema:
2304 # prevent modifications to the input; this copy may be safe to drop if there is significant overhead
2305 json_schema = json_schema.copy()
2307 referenced_json_schema = self.get_schema_from_definitions(JsonRef(json_schema['$ref']))
2308 if referenced_json_schema is None:
2309 # This can happen when building schemas for models with not-yet-defined references.
2310 # It may be a good idea to do a recursive pass at the end of the generation to remove
2311 # any redundant override keys.
2312 return json_schema
2313 for k, v in list(json_schema.items()):
2314 if k == '$ref':
2315 continue
2316 if k in referenced_json_schema and referenced_json_schema[k] == v:
2317 del json_schema[k] # redundant key
2319 return json_schema
2321 def get_schema_from_definitions(self, json_ref: JsonRef) -> JsonSchemaValue | None:
2322 try:
2323 def_ref = self.json_to_defs_refs[json_ref]
2324 if def_ref in self._core_defs_invalid_for_json_schema:
2325 raise self._core_defs_invalid_for_json_schema[def_ref]
2326 return self.definitions.get(def_ref, None)
2327 except KeyError:
2328 if json_ref.startswith(('http://', 'https://')):
2329 return None
2330 raise
2332 def encode_default(self, dft: Any) -> Any:
2333 """Encode a default value to a JSON-serializable value.
2335 This is used to encode default values for fields in the generated JSON schema.
2337 Args:
2338 dft: The default value to encode.
2340 Returns:
2341 The encoded default value.
2342 """
2343 from .type_adapter import TypeAdapter, _type_has_config
2345 config = self._config
2346 try:
2347 default = (
2348 dft
2349 if _type_has_config(type(dft))
2350 else TypeAdapter(type(dft), config=config.config_dict).dump_python(
2351 dft, by_alias=self.by_alias, mode='json'
2352 )
2353 )
2354 except PydanticSchemaGenerationError:
2355 raise pydantic_core.PydanticSerializationError(f'Unable to encode default value {dft}')
2357 return pydantic_core.to_jsonable_python(
2358 default, timedelta_mode=config.ser_json_timedelta, bytes_mode=config.ser_json_bytes, by_alias=self.by_alias
2359 )
2361 def update_with_validations(
2362 self, json_schema: JsonSchemaValue, core_schema: CoreSchema, mapping: dict[str, str]
2363 ) -> None:
2364 """Update the json_schema with the corresponding validations specified in the core_schema,
2365 using the provided mapping to translate keys in core_schema to the appropriate keys for a JSON schema.
2367 Args:
2368 json_schema: The JSON schema to update.
2369 core_schema: The core schema to get the validations from.
2370 mapping: A mapping from core_schema attribute names to the corresponding JSON schema attribute names.
2371 """
2372 for core_key, json_schema_key in mapping.items():
2373 if core_key in core_schema:
2374 json_schema[json_schema_key] = core_schema[core_key]
2376 class ValidationsMapping:
2377 """This class just contains mappings from core_schema attribute names to the corresponding
2378 JSON schema attribute names. While I suspect it is unlikely to be necessary, you can in
2379 principle override this class in a subclass of GenerateJsonSchema (by inheriting from
2380 GenerateJsonSchema.ValidationsMapping) to change these mappings.
2381 """
2383 numeric = {
2384 'multiple_of': 'multipleOf',
2385 'le': 'maximum',
2386 'ge': 'minimum',
2387 'lt': 'exclusiveMaximum',
2388 'gt': 'exclusiveMinimum',
2389 }
2390 bytes = {
2391 'min_length': 'minLength',
2392 'max_length': 'maxLength',
2393 }
2394 string = {
2395 'min_length': 'minLength',
2396 'max_length': 'maxLength',
2397 'pattern': 'pattern',
2398 }
2399 array = {
2400 'min_length': 'minItems',
2401 'max_length': 'maxItems',
2402 }
2403 object = {
2404 'min_length': 'minProperties',
2405 'max_length': 'maxProperties',
2406 }
2408 def get_flattened_anyof(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue:
2409 members = []
2410 for schema in schemas:
2411 if len(schema) == 1 and 'anyOf' in schema:
2412 members.extend(schema['anyOf'])
2413 else:
2414 members.append(schema)
2415 members = _deduplicate_schemas(members)
2416 if len(members) == 1:
2417 return members[0]
2418 return {'anyOf': members}
2420 def get_json_ref_counts(self, json_schema: JsonSchemaValue) -> dict[JsonRef, int]:
2421 """Get all values corresponding to the key '$ref' anywhere in the json_schema."""
2422 json_refs: dict[JsonRef, int] = Counter()
2424 def _add_json_refs(schema: Any) -> None:
2425 if isinstance(schema, dict):
2426 if '$ref' in schema:
2427 json_ref = JsonRef(schema['$ref'])
2428 if not isinstance(json_ref, str):
2429 return # in this case, '$ref' might have been the name of a property
2430 already_visited = json_ref in json_refs
2431 json_refs[json_ref] += 1
2432 if already_visited:
2433 return # prevent recursion on a definition that was already visited
2434 try:
2435 defs_ref = self.json_to_defs_refs[json_ref]
2436 if defs_ref in self._core_defs_invalid_for_json_schema:
2437 raise self._core_defs_invalid_for_json_schema[defs_ref]
2438 _add_json_refs(self.definitions[defs_ref])
2439 except KeyError:
2440 if not json_ref.startswith(('http://', 'https://')):
2441 raise
2443 for k, v in schema.items():
2444 if k == 'examples' and isinstance(v, list):
2445 # Skip examples that may contain arbitrary values and references
2446 # (see the comment in `_get_all_json_refs` for more details).
2447 continue
2448 _add_json_refs(v)
2449 elif isinstance(schema, list):
2450 for v in schema:
2451 _add_json_refs(v)
2453 _add_json_refs(json_schema)
2454 return json_refs
2456 def handle_invalid_for_json_schema(self, schema: CoreSchemaOrField, error_info: str) -> JsonSchemaValue:
2457 raise PydanticInvalidForJsonSchema(f'Cannot generate a JsonSchema for {error_info}')
2459 def emit_warning(self, kind: JsonSchemaWarningKind, detail: str) -> None:
2460 """This method simply emits PydanticJsonSchemaWarnings based on handling in the `warning_message` method."""
2461 message = self.render_warning_message(kind, detail)
2462 if message is not None:
2463 warnings.warn(message, PydanticJsonSchemaWarning)
2465 def render_warning_message(self, kind: JsonSchemaWarningKind, detail: str) -> str | None:
2466 """This method is responsible for ignoring warnings as desired, and for formatting the warning messages.
2468 You can override the value of `ignored_warning_kinds` in a subclass of GenerateJsonSchema
2469 to modify what warnings are generated. If you want more control, you can override this method;
2470 just return None in situations where you don't want warnings to be emitted.
2472 Args:
2473 kind: The kind of warning to render. It can be one of the following:
2475 - 'skipped-choice': A choice field was skipped because it had no valid choices.
2476 - 'non-serializable-default': A default value was skipped because it was not JSON-serializable.
2477 detail: A string with additional details about the warning.
2479 Returns:
2480 The formatted warning message, or `None` if no warning should be emitted.
2481 """
2482 if kind in self.ignored_warning_kinds:
2483 return None
2484 return f'{detail} [{kind}]'
2486 def _build_definitions_remapping(self) -> _DefinitionsRemapping:
2487 defs_to_json: dict[DefsRef, JsonRef] = {}
2488 for defs_refs in self._prioritized_defsref_choices.values():
2489 for defs_ref in defs_refs:
2490 json_ref = JsonRef(self.ref_template.format(model=defs_ref))
2491 defs_to_json[defs_ref] = json_ref
2493 return _DefinitionsRemapping.from_prioritized_choices(
2494 self._prioritized_defsref_choices, defs_to_json, self.definitions
2495 )
2497 def _garbage_collect_definitions(self, schema: JsonSchemaValue) -> None:
2498 visited_defs_refs: set[DefsRef] = set()
2499 unvisited_json_refs = _get_all_json_refs(schema)
2500 while unvisited_json_refs:
2501 next_json_ref = unvisited_json_refs.pop()
2502 try:
2503 next_defs_ref = self.json_to_defs_refs[next_json_ref]
2504 if next_defs_ref in visited_defs_refs:
2505 continue
2506 visited_defs_refs.add(next_defs_ref)
2507 unvisited_json_refs.update(_get_all_json_refs(self.definitions[next_defs_ref]))
2508 except KeyError:
2509 if not next_json_ref.startswith(('http://', 'https://')):
2510 raise
2512 self.definitions = {k: v for k, v in self.definitions.items() if k in visited_defs_refs}
2515# ##### Start JSON Schema Generation Functions #####
2518def model_json_schema(
2519 cls: type[BaseModel] | type[PydanticDataclass],
2520 by_alias: bool = True,
2521 ref_template: str = DEFAULT_REF_TEMPLATE,
2522 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of',
2523 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
2524 mode: JsonSchemaMode = 'validation',
2525) -> dict[str, Any]:
2526 """Utility function to generate a JSON Schema for a model.
2528 Args:
2529 cls: The model class to generate a JSON Schema for.
2530 by_alias: If `True` (the default), fields will be serialized according to their alias.
2531 If `False`, fields will be serialized according to their attribute name.
2532 ref_template: The template to use for generating JSON Schema references.
2533 union_format: The format to use when combining schemas from unions together. Can be one of:
2535 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf)
2536 keyword to combine schemas (the default).
2537 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type)
2538 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive
2539 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to
2540 `any_of`.
2541 schema_generator: The class to use for generating the JSON Schema.
2542 mode: The mode to use for generating the JSON Schema. It can be one of the following:
2544 - 'validation': Generate a JSON Schema for validating data.
2545 - 'serialization': Generate a JSON Schema for serializing data.
2547 Returns:
2548 The generated JSON Schema.
2549 """
2550 from .main import BaseModel
2552 schema_generator_instance = schema_generator(
2553 by_alias=by_alias, ref_template=ref_template, union_format=union_format
2554 )
2556 if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema):
2557 cls.__pydantic_core_schema__.rebuild()
2559 if cls is BaseModel:
2560 raise AttributeError('model_json_schema() must be called on a subclass of BaseModel, not BaseModel itself.')
2562 assert not isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema), 'this is a bug! please report it'
2563 return schema_generator_instance.generate(cls.__pydantic_core_schema__, mode=mode)
2566def models_json_schema(
2567 models: Sequence[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode]],
2568 *,
2569 by_alias: bool = True,
2570 title: str | None = None,
2571 description: str | None = None,
2572 ref_template: str = DEFAULT_REF_TEMPLATE,
2573 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of',
2574 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
2575) -> tuple[dict[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode], JsonSchemaValue], JsonSchemaValue]:
2576 """Utility function to generate a JSON Schema for multiple models.
2578 Args:
2579 models: A sequence of tuples of the form (model, mode).
2580 by_alias: Whether field aliases should be used as keys in the generated JSON Schema.
2581 title: The title of the generated JSON Schema.
2582 description: The description of the generated JSON Schema.
2583 ref_template: The reference template to use for generating JSON Schema references.
2584 union_format: The format to use when combining schemas from unions together. Can be one of:
2586 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf)
2587 keyword to combine schemas (the default).
2588 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type)
2589 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive
2590 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to
2591 `any_of`.
2592 schema_generator: The schema generator to use for generating the JSON Schema.
2594 Returns:
2595 A tuple where:
2596 - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and
2597 whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have
2598 JsonRef references to definitions that are defined in the second returned element.)
2599 - The second element is a JSON schema containing all definitions referenced in the first returned
2600 element, along with the optional title and description keys.
2601 """
2602 for cls, _ in models:
2603 if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema):
2604 cls.__pydantic_core_schema__.rebuild()
2606 instance = schema_generator(by_alias=by_alias, ref_template=ref_template, union_format=union_format)
2607 inputs: list[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode, CoreSchema]] = [
2608 (m, mode, m.__pydantic_core_schema__) for m, mode in models
2609 ]
2610 json_schemas_map, definitions = instance.generate_definitions(inputs)
2612 json_schema: dict[str, Any] = {}
2613 if definitions:
2614 json_schema['$defs'] = definitions
2615 if title:
2616 json_schema['title'] = title
2617 if description:
2618 json_schema['description'] = description
2620 return json_schemas_map, json_schema
2623# ##### End JSON Schema Generation Functions #####
2626_HashableJsonValue: TypeAlias = Union[
2627 int, float, str, bool, None, tuple['_HashableJsonValue', ...], tuple[tuple[str, '_HashableJsonValue'], ...]
2628]
2631def _deduplicate_schemas(schemas: Iterable[JsonDict]) -> list[JsonDict]:
2632 return list({_make_json_hashable(schema): schema for schema in schemas}.values())
2635def _make_json_hashable(value: JsonValue) -> _HashableJsonValue:
2636 if isinstance(value, dict):
2637 return tuple(sorted((k, _make_json_hashable(v)) for k, v in value.items()))
2638 elif isinstance(value, list):
2639 return tuple(_make_json_hashable(v) for v in value)
2640 else:
2641 return value
2644@dataclasses.dataclass(**_internal_dataclass.slots_true)
2645class WithJsonSchema:
2646 """!!! abstract "Usage Documentation"
2647 [`WithJsonSchema` Annotation](../concepts/json_schema.md#withjsonschema-annotation)
2649 An annotation used to override the JSON Schema for a type.
2651 This is useful when you want to set a JSON Schema for a type that don't produce any JSON Schemas by default
2652 (e.g. [`Callable`][collections.abc.Callable]).
2654 If `mode` is set this will only apply to that schema generation mode, allowing you to set different JSON Schemas for validation and serialization.
2656 !!! note
2657 If the `WithJsonSchema` annotation is coupled with the [`Field()`][pydantic.Field] function, the behavior overriding will vary depending on the location:
2659 * If the [`Annotated`][typing.Annotated] metadata is specified at the "top-level" field, `Field()` metadata arguments
2660 (excluding [constraints](../concepts/fields.md#field-constraints)) such as `title` and `description` will be applied on
2661 top of the `WithJsonSchema`, no matter the order:
2663 ```python
2664 from typing import Annotated
2666 from pydantic import BaseModel, Field, WithJsonSchema
2668 class Model(BaseModel):
2669 field: Annotated[
2670 int,
2671 Field(title='My Field'),
2672 WithJsonSchema({'type': 'integer', 'extra': 'data'}),
2673 ]
2675 Model.model_json_schema()['properties']['field']
2676 #> {'type': 'integer', 'extra': 'data', 'title': 'My Field'}
2677 ```
2679 * If the [`Annotated`][typing.Annotated] metadata is specified on a specific inner type, `WithJsonSchema` will unconditionally
2680 override the JSON Schema:
2682 ```python
2683 from typing import Annotated
2685 from pydantic import BaseModel, Field, WithJsonSchema
2687 class Model(BaseModel):
2688 field: list[
2689 Annotated[
2690 int,
2691 Field(title='My Field'),
2692 WithJsonSchema({'type': 'integer', 'extra': 'data'}),
2693 ]
2694 ]
2696 Model.model_json_schema()['properties']['field']
2697 #> {'items': {'extra': 'data', 'type': 'integer'}, 'title': 'Field', 'type': 'array'}
2698 ```
2700 See also the documentation about [the annotated pattern](../concepts/fields.md#the-annotated-pattern).
2701 """
2703 json_schema: JsonSchemaValue | None
2704 mode: Literal['validation', 'serialization'] | None = None
2706 def __get_pydantic_json_schema__(
2707 self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
2708 ) -> JsonSchemaValue:
2709 if self.mode is not None and self.mode != handler.mode:
2710 return handler(core_schema)
2711 if self.json_schema is None:
2712 # This exception is handled in pydantic.json_schema.GenerateJsonSchema._named_required_fields_schema
2713 raise PydanticOmit
2714 else:
2715 return self.json_schema.copy()
2717 def __hash__(self) -> int:
2718 return hash(type(self.mode))
2721class Examples:
2722 """Add examples to a JSON schema.
2724 If the JSON Schema already contains examples, the provided examples
2725 will be appended.
2727 If `mode` is set this will only apply to that schema generation mode,
2728 allowing you to add different examples for validation and serialization.
2729 """
2731 @overload
2732 @deprecated('Using a dict for `examples` is deprecated since v2.9 and will be removed in v3.0. Use a list instead.')
2733 def __init__(
2734 self, examples: dict[str, Any], mode: Literal['validation', 'serialization'] | None = None
2735 ) -> None: ...
2737 @overload
2738 def __init__(self, examples: list[Any], mode: Literal['validation', 'serialization'] | None = None) -> None: ...
2740 def __init__(
2741 self, examples: dict[str, Any] | list[Any], mode: Literal['validation', 'serialization'] | None = None
2742 ) -> None:
2743 if isinstance(examples, dict):
2744 warnings.warn(
2745 'Using a dict for `examples` is deprecated, use a list instead.',
2746 PydanticDeprecatedSince29,
2747 stacklevel=2,
2748 )
2749 self.examples = examples
2750 self.mode = mode
2752 def __get_pydantic_json_schema__(
2753 self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
2754 ) -> JsonSchemaValue:
2755 mode = self.mode or handler.mode
2756 json_schema = handler(core_schema)
2757 if mode != handler.mode:
2758 return json_schema
2759 examples = json_schema.get('examples')
2760 if examples is None:
2761 json_schema['examples'] = to_jsonable_python(self.examples)
2762 if isinstance(examples, dict):
2763 if isinstance(self.examples, list):
2764 warnings.warn(
2765 'Updating existing JSON Schema examples of type dict with examples of type list. '
2766 'Only the existing examples values will be retained. Note that dict support for '
2767 'examples is deprecated and will be removed in v3.0.',
2768 UserWarning,
2769 )
2770 json_schema['examples'] = to_jsonable_python(
2771 [ex for value in examples.values() for ex in value] + self.examples
2772 )
2773 else:
2774 json_schema['examples'] = to_jsonable_python({**examples, **self.examples})
2775 if isinstance(examples, list):
2776 if isinstance(self.examples, list):
2777 json_schema['examples'] = to_jsonable_python(examples + self.examples)
2778 elif isinstance(self.examples, dict):
2779 warnings.warn(
2780 'Updating existing JSON Schema examples of type list with examples of type dict. '
2781 'Only the examples values will be retained. Note that dict support for '
2782 'examples is deprecated and will be removed in v3.0.',
2783 UserWarning,
2784 )
2785 json_schema['examples'] = to_jsonable_python(
2786 examples + [ex for value in self.examples.values() for ex in value]
2787 )
2789 return json_schema
2791 def __hash__(self) -> int:
2792 return hash(type(self.mode))
2795def _get_all_json_refs(item: Any) -> set[JsonRef]:
2796 """Get all the definitions references from a JSON schema."""
2797 refs: set[JsonRef] = set()
2798 stack = [item]
2800 while stack:
2801 current = stack.pop()
2802 if isinstance(current, dict):
2803 for key, value in current.items():
2804 if key == 'examples' and isinstance(value, list):
2805 # Skip examples that may contain arbitrary values and references
2806 # (e.g. `{"examples": [{"$ref": "..."}]}`). Note: checking for value
2807 # of type list is necessary to avoid skipping valid portions of the schema,
2808 # for instance when "examples" is used as a property key. A more robust solution
2809 # could be found, but would require more advanced JSON Schema parsing logic.
2810 continue
2811 if key == '$ref' and isinstance(value, str):
2812 refs.add(JsonRef(value))
2813 elif isinstance(value, dict):
2814 stack.append(value)
2815 elif isinstance(value, list):
2816 stack.extend(value)
2817 elif isinstance(current, list):
2818 stack.extend(current)
2820 return refs
2823AnyType = TypeVar('AnyType')
2825if TYPE_CHECKING:
2826 SkipJsonSchema = Annotated[AnyType, ...]
2827else:
2829 @dataclasses.dataclass(**_internal_dataclass.slots_true)
2830 class SkipJsonSchema:
2831 """!!! abstract "Usage Documentation"
2832 [`SkipJsonSchema` Annotation](../concepts/json_schema.md#skipjsonschema-annotation)
2834 Add this as an annotation on a field to skip generating a JSON schema for that field.
2836 Example:
2837 ```python
2838 from pprint import pprint
2839 from typing import Union
2841 from pydantic import BaseModel
2842 from pydantic.json_schema import SkipJsonSchema
2844 class Model(BaseModel):
2845 a: Union[int, None] = None # (1)!
2846 b: Union[int, SkipJsonSchema[None]] = None # (2)!
2847 c: SkipJsonSchema[Union[int, None]] = None # (3)!
2849 pprint(Model.model_json_schema())
2850 '''
2851 {
2852 'properties': {
2853 'a': {
2854 'anyOf': [
2855 {'type': 'integer'},
2856 {'type': 'null'}
2857 ],
2858 'default': None,
2859 'title': 'A'
2860 },
2861 'b': {
2862 'default': None,
2863 'title': 'B',
2864 'type': 'integer'
2865 }
2866 },
2867 'title': 'Model',
2868 'type': 'object'
2869 }
2870 '''
2871 ```
2873 1. The integer and null types are both included in the schema for `a`.
2874 2. The integer type is the only type included in the schema for `b`.
2875 3. The entirety of the `c` field is omitted from the schema.
2876 """
2878 def __class_getitem__(cls, item: AnyType) -> AnyType:
2879 return Annotated[item, cls()]
2881 def __get_pydantic_json_schema__(
2882 self, core_schema: CoreSchema, handler: GetJsonSchemaHandler
2883 ) -> JsonSchemaValue:
2884 raise PydanticOmit
2886 def __hash__(self) -> int:
2887 return hash(type(self))
2890def _get_typed_dict_config(cls: type[Any] | None) -> ConfigDict:
2891 if cls is not None:
2892 try:
2893 return _decorators.get_attribute_from_bases(cls, '__pydantic_config__')
2894 except AttributeError:
2895 pass
2896 return {}
2899def _get_ser_schema_for_default_value(schema: CoreSchema) -> core_schema.PlainSerializerFunctionSerSchema | None:
2900 """Get a `'function-plain'` serialization schema that can be used to serialize a default value.
2902 This takes into account having the serialization schema nested under validation schema(s).
2903 """
2904 if (
2905 (ser_schema := schema.get('serialization'))
2906 and ser_schema['type'] == 'function-plain'
2907 and not ser_schema.get('info_arg')
2908 ):
2909 return ser_schema
2910 if _core_utils.is_function_with_inner_schema(schema):
2911 return _get_ser_schema_for_default_value(schema['schema'])