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 Callable, 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 Literal,
31 NewType,
32 TypeAlias,
33 TypeVar,
34 cast,
35 overload,
36)
38import pydantic_core
39from pydantic_core import MISSING, CoreSchema, PydanticOmit, core_schema, to_jsonable_python
40from pydantic_core.core_schema import ComputedField
41from typing_extensions import assert_never, deprecated, final
42from typing_inspection.introspection import get_literal_values
44from pydantic.warnings import PydanticDeprecatedSince26, PydanticDeprecatedSince29
46from ._internal import (
47 _config,
48 _core_metadata,
49 _core_utils,
50 _decorators,
51 _mock_val_ser,
52 _schema_generation_shared,
53 _typing_extra,
54)
55from .annotated_handlers import GetJsonSchemaHandler
56from .config import JsonDict, JsonValue
57from .errors import PydanticInvalidForJsonSchema, PydanticUserError
59if TYPE_CHECKING:
60 from . import ConfigDict
61 from ._internal._core_utils import CoreSchemaField, CoreSchemaOrField
62 from ._internal._dataclasses import PydanticDataclass
63 from ._internal._schema_generation_shared import GetJsonSchemaFunction
64 from .main import BaseModel
67CoreSchemaOrFieldType = Literal[core_schema.CoreSchemaType, core_schema.CoreSchemaFieldType]
68"""
69A type alias for defined schema types that represents a union of
70`core_schema.CoreSchemaType` and
71`core_schema.CoreSchemaFieldType`.
72"""
74JsonSchemaValue = dict[str, Any]
75"""
76A type alias for a JSON schema value. This is a dictionary of string keys to arbitrary JSON values.
77"""
79JsonSchemaMode = Literal['validation', 'serialization']
80"""
81A type alias that represents the mode of a JSON schema; either 'validation' or 'serialization'.
83For some types, the inputs to validation differ from the outputs of serialization. For example,
84computed fields will only be present when serializing, and should not be provided when
85validating. This flag provides a way to indicate whether you want the JSON schema required
86for validation inputs, or that will be matched by serialization outputs.
87"""
89_MODE_TITLE_MAPPING: dict[JsonSchemaMode, str] = {'validation': 'Input', 'serialization': 'Output'}
92JsonSchemaWarningKind = Literal['skipped-choice', 'non-serializable-default', 'skipped-discriminator']
93"""
94A type alias representing the kinds of warnings that can be emitted during JSON schema generation.
96See [`GenerateJsonSchema.render_warning_message`][pydantic.json_schema.GenerateJsonSchema.render_warning_message]
97for more details.
98"""
101class PydanticJsonSchemaWarning(UserWarning):
102 """This class is used to emit warnings produced during JSON schema generation.
103 See the [`GenerateJsonSchema.emit_warning`][pydantic.json_schema.GenerateJsonSchema.emit_warning] and
104 [`GenerateJsonSchema.render_warning_message`][pydantic.json_schema.GenerateJsonSchema.render_warning_message]
105 methods for more details; these can be overridden to control warning behavior.
106 """
109NoDefault = object()
110"""A sentinel value used to indicate that no default value should be used when generating a JSON Schema
111for a core schema with a default value.
112"""
115# ##### JSON Schema Generation #####
116DEFAULT_REF_TEMPLATE = '#/$defs/{model}'
117"""The default format string used to generate reference names."""
119# There are three types of references relevant to building JSON schemas:
120# 1. core_schema "ref" values; these are not exposed as part of the JSON schema
121# * these might look like the fully qualified path of a model, its id, or something similar
122CoreRef = NewType('CoreRef', str)
123# 2. keys of the "definitions" object that will eventually go into the JSON schema
124# * by default, these look like "MyModel", though may change in the presence of collisions
125# * eventually, we may want to make it easier to modify the way these names are generated
126DefsRef = NewType('DefsRef', str)
127# 3. the values corresponding to the "$ref" key in the schema
128# * By default, these look like "#/$defs/MyModel", as in {"$ref": "#/$defs/MyModel"}
129JsonRef = NewType('JsonRef', str)
131CoreModeRef = tuple[CoreRef, JsonSchemaMode]
132JsonSchemaKeyT = TypeVar('JsonSchemaKeyT', bound=Hashable)
134_PRIMITIVE_JSON_SCHEMA_TYPES = ('string', 'boolean', 'null', 'integer', 'number')
137@dataclasses.dataclass(slots=True)
138class _DefinitionsRemapping:
139 defs_remapping: dict[DefsRef, DefsRef]
140 json_remapping: dict[JsonRef, JsonRef]
142 @staticmethod
143 def from_prioritized_choices(
144 prioritized_choices: dict[DefsRef, list[DefsRef]],
145 defs_to_json: dict[DefsRef, JsonRef],
146 definitions: dict[DefsRef, JsonSchemaValue],
147 ) -> _DefinitionsRemapping:
148 """
149 This function should produce a remapping that replaces complex DefsRef with the simpler ones from the
150 prioritized_choices such that applying the name remapping would result in an equivalent JSON schema.
151 """
152 # We need to iteratively simplify the definitions until we reach a fixed point.
153 # The reason for this is that outer definitions may reference inner definitions that get simplified
154 # into an equivalent reference, and the outer definitions won't be equivalent until we've simplified
155 # the inner definitions.
156 copied_definitions = deepcopy(definitions)
157 definitions_schema = {'$defs': copied_definitions}
158 for _iter in range(100): # prevent an infinite loop in the case of a bug, 100 iterations should be enough
159 # For every possible remapped DefsRef, collect all schemas that DefsRef might be used for:
160 schemas_for_alternatives: dict[DefsRef, list[JsonSchemaValue]] = defaultdict(list)
161 for defs_ref in copied_definitions:
162 alternatives = prioritized_choices[defs_ref]
163 for alternative in alternatives:
164 schemas_for_alternatives[alternative].append(copied_definitions[defs_ref])
166 # Deduplicate the schemas for each alternative; the idea is that we only want to remap to a new DefsRef
167 # if it introduces no ambiguity, i.e., there is only one distinct schema for that DefsRef.
168 for defs_ref in schemas_for_alternatives:
169 schemas_for_alternatives[defs_ref] = _deduplicate_schemas(schemas_for_alternatives[defs_ref])
171 # Build the remapping
172 defs_remapping: dict[DefsRef, DefsRef] = {}
173 json_remapping: dict[JsonRef, JsonRef] = {}
174 for original_defs_ref in definitions:
175 alternatives = prioritized_choices[original_defs_ref]
176 # Pick the first alternative that has only one schema, since that means there is no collision
177 remapped_defs_ref = next(x for x in alternatives if len(schemas_for_alternatives[x]) == 1)
178 defs_remapping[original_defs_ref] = remapped_defs_ref
180 # Map all alternatives after the remapped one to the remapped one
181 # This ensures that intermediate simplifications are also remapped
182 remapped_index = alternatives.index(remapped_defs_ref)
183 for alt in alternatives[remapped_index:]:
184 json_remapping[defs_to_json[alt]] = defs_to_json[remapped_defs_ref]
185 remapping = _DefinitionsRemapping(defs_remapping, json_remapping)
186 new_definitions_schema = remapping.remap_json_schema({'$defs': copied_definitions})
187 if definitions_schema == new_definitions_schema:
188 # We've reached the fixed point
189 return remapping
190 definitions_schema = new_definitions_schema
192 raise PydanticInvalidForJsonSchema('Failed to simplify the JSON schema definitions')
194 def remap_defs_ref(self, ref: DefsRef) -> DefsRef:
195 return self.defs_remapping.get(ref, ref)
197 def remap_json_ref(self, ref: JsonRef) -> JsonRef:
198 return self.json_remapping.get(ref, ref)
200 def remap_json_schema(self, schema: Any) -> Any:
201 """
202 Recursively update the JSON schema replacing all $refs
203 """
204 if isinstance(schema, str):
205 # Note: this may not really be a JsonRef; we rely on having no collisions between JsonRefs and other strings
206 return self.remap_json_ref(JsonRef(schema))
207 elif isinstance(schema, list):
208 return [self.remap_json_schema(item) for item in schema]
209 elif isinstance(schema, dict):
210 for key, value in schema.items():
211 if key == '$ref' and isinstance(value, str):
212 schema['$ref'] = self.remap_json_ref(JsonRef(value))
213 elif key == '$defs':
214 schema['$defs'] = {
215 self.remap_defs_ref(DefsRef(key)): self.remap_json_schema(value)
216 for key, value in schema['$defs'].items()
217 }
218 else:
219 schema[key] = self.remap_json_schema(value)
220 return schema
223class GenerateJsonSchema:
224 """!!! abstract "Usage Documentation"
225 [Customizing the JSON Schema Generation Process](../concepts/json_schema.md#customizing-the-json-schema-generation-process)
227 A class for generating JSON schemas.
229 This class generates JSON schemas based on configured parameters. The default schema dialect
230 is [https://json-schema.org/draft/2020-12/schema](https://json-schema.org/draft/2020-12/schema).
231 The class uses `by_alias` to configure how fields with
232 multiple names are handled and `ref_template` to format reference names.
234 Attributes:
235 schema_dialect: The JSON schema dialect used to generate the schema. See
236 [Declaring a Dialect](https://json-schema.org/understanding-json-schema/reference/schema.html#id4)
237 in the JSON Schema documentation for more information about dialects.
238 ignored_warning_kinds: Warnings to ignore when generating the schema. `self.render_warning_message` will
239 do nothing if its argument `kind` is in `ignored_warning_kinds`;
240 this value can be modified on subclasses to easily control which warnings are emitted.
241 by_alias: Whether to use field aliases when generating the schema.
242 ref_template: The format string used when generating reference names.
243 core_to_json_refs: A mapping of core refs to JSON refs.
244 core_to_defs_refs: A mapping of core refs to definition refs.
245 defs_to_core_refs: A mapping of definition refs to core refs.
246 json_to_defs_refs: A mapping of JSON refs to definition refs.
247 definitions: Definitions in the schema.
249 Args:
250 by_alias: Whether to use field aliases in the generated schemas.
251 ref_template: The format string to use when generating reference names.
252 union_format: The format to use when combining schemas from unions together. Can be one of:
254 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf)
255 keyword to combine schemas (the default).
256 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type)
257 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive
258 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to
259 `any_of`.
261 Raises:
262 JsonSchemaError: If the instance of the class is inadvertently reused after generating a schema.
263 """
265 schema_dialect = 'https://json-schema.org/draft/2020-12/schema'
267 # `self.render_warning_message` will do nothing if its argument `kind` is in `ignored_warning_kinds`;
268 # this value can be modified on subclasses to easily control which warnings are emitted
269 ignored_warning_kinds: set[JsonSchemaWarningKind] = {'skipped-choice'}
271 def __init__(
272 self,
273 by_alias: bool = True,
274 ref_template: str = DEFAULT_REF_TEMPLATE,
275 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of',
276 ) -> None:
277 self.by_alias = by_alias
278 self.ref_template = ref_template
279 self.union_format: Literal['any_of', 'primitive_type_array'] = union_format
281 self.core_to_json_refs: dict[CoreModeRef, JsonRef] = {}
282 self.core_to_defs_refs: dict[CoreModeRef, DefsRef] = {}
283 self.defs_to_core_refs: dict[DefsRef, CoreModeRef] = {}
284 self.json_to_defs_refs: dict[JsonRef, DefsRef] = {}
286 self.definitions: dict[DefsRef, JsonSchemaValue] = {}
287 self._config_wrapper_stack = _config.ConfigWrapperStack(_config.ConfigWrapper({}))
289 self._mode: JsonSchemaMode = 'validation'
291 # The following includes a mapping of a fully-unique defs ref choice to a list of preferred
292 # alternatives, which are generally simpler, such as only including the class name.
293 # At the end of schema generation, we use these to produce a JSON schema with more human-readable
294 # definitions, which would also work better in a generated OpenAPI client, etc.
295 self._prioritized_defsref_choices: dict[DefsRef, list[DefsRef]] = {}
296 self._collision_counter: dict[str, int] = defaultdict(int)
297 self._collision_index: dict[str, int] = {}
299 self._schema_type_to_method = self.build_schema_type_to_method()
301 # When we encounter definitions we need to try to build them immediately
302 # so that they are available schemas that reference them
303 # But it's possible that CoreSchema was never going to be used
304 # (e.g. because the CoreSchema that references short circuits is JSON schema generation without needing
305 # the reference) so instead of failing altogether if we can't build a definition we
306 # store the error raised and re-throw it if we end up needing that def
307 self._core_defs_invalid_for_json_schema: dict[DefsRef, PydanticInvalidForJsonSchema] = {}
309 # This changes to True after generating a schema, to prevent issues caused by accidental reuse
310 # of a single instance of a schema generator
311 self._used = False
313 @property
314 def _config(self) -> _config.ConfigWrapper:
315 return self._config_wrapper_stack.tail
317 @property
318 def mode(self) -> JsonSchemaMode:
319 if self._config.json_schema_mode_override is not None:
320 return self._config.json_schema_mode_override
321 else:
322 return self._mode
324 def build_schema_type_to_method(
325 self,
326 ) -> dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]]:
327 """Builds a dictionary mapping fields to methods for generating JSON schemas.
329 Returns:
330 A dictionary containing the mapping of `CoreSchemaOrFieldType` to a handler method.
332 Raises:
333 TypeError: If no method has been defined for generating a JSON schema for a given pydantic core schema type.
334 """
335 mapping: dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]] = {}
336 core_schema_types: list[CoreSchemaOrFieldType] = list(get_literal_values(CoreSchemaOrFieldType))
337 for key in core_schema_types:
338 method_name = f'{key.replace("-", "_")}_schema'
339 try:
340 mapping[key] = getattr(self, method_name)
341 except AttributeError as e: # pragma: no cover
342 if os.getenv('PYDANTIC_PRIVATE_ALLOW_UNHANDLED_SCHEMA_TYPES'):
343 continue
344 raise TypeError(
345 f'No method for generating JsonSchema for core_schema.type={key!r} '
346 f'(expected: {type(self).__name__}.{method_name})'
347 ) from e
348 return mapping
350 def generate_definitions(
351 self, inputs: Sequence[tuple[JsonSchemaKeyT, JsonSchemaMode, core_schema.CoreSchema]]
352 ) -> tuple[dict[tuple[JsonSchemaKeyT, JsonSchemaMode], JsonSchemaValue], dict[DefsRef, JsonSchemaValue]]:
353 """Generates JSON schema definitions from a list of core schemas, pairing the generated definitions with a
354 mapping that links the input keys to the definition references.
356 Args:
357 inputs: A sequence of tuples, where:
359 - The first element is a JSON schema key type.
360 - The second element is the JSON mode: either 'validation' or 'serialization'.
361 - The third element is a core schema.
363 Returns:
364 A tuple where:
366 - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and
367 whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have
368 JsonRef references to definitions that are defined in the second returned element.)
369 - The second element is a dictionary whose keys are definition references for the JSON schemas
370 from the first returned element, and whose values are the actual JSON schema definitions.
372 Raises:
373 PydanticUserError: Raised if the JSON schema generator has already been used to generate a JSON schema.
374 """
375 if self._used:
376 raise PydanticUserError(
377 'This JSON schema generator has already been used to generate a JSON schema. '
378 f'You must create a new instance of {type(self).__name__} to generate a new JSON schema.',
379 code='json-schema-already-used',
380 )
382 for _, mode, schema in inputs:
383 self._mode = mode
384 self.generate_inner(schema)
386 definitions_remapping = self._build_definitions_remapping()
388 json_schemas_map: dict[tuple[JsonSchemaKeyT, JsonSchemaMode], DefsRef] = {}
389 for key, mode, schema in inputs:
390 self._mode = mode
391 json_schema = self.generate_inner(schema)
392 json_schemas_map[(key, mode)] = definitions_remapping.remap_json_schema(json_schema)
394 json_schema = {'$defs': self.definitions}
395 json_schema = definitions_remapping.remap_json_schema(json_schema)
396 self._used = True
397 return json_schemas_map, self.sort(json_schema['$defs']) # type: ignore
399 def generate(self, schema: CoreSchema, mode: JsonSchemaMode = 'validation') -> JsonSchemaValue:
400 """Generates a JSON schema for a specified schema in a specified mode.
402 Args:
403 schema: A Pydantic model.
404 mode: The mode in which to generate the schema. Defaults to 'validation'.
406 Returns:
407 A JSON schema representing the specified schema.
409 Raises:
410 PydanticUserError: If the JSON schema generator has already been used to generate a JSON schema.
411 """
412 self._mode = mode
413 if self._used:
414 raise PydanticUserError(
415 'This JSON schema generator has already been used to generate a JSON schema. '
416 f'You must create a new instance of {type(self).__name__} to generate a new JSON schema.',
417 code='json-schema-already-used',
418 )
420 json_schema: JsonSchemaValue = self.generate_inner(schema)
421 json_ref_counts = self.get_json_ref_counts(json_schema)
423 ref = cast(JsonRef, json_schema.get('$ref'))
424 while ref is not None: # may need to unpack multiple levels
425 ref_json_schema = self.get_schema_from_definitions(ref)
426 if json_ref_counts[ref] == 1 and ref_json_schema is not None and len(json_schema) == 1:
427 # "Unpack" the ref since this is the only reference and there are no sibling keys
428 json_schema = ref_json_schema.copy() # copy to prevent recursive dict reference
429 json_ref_counts[ref] -= 1
430 ref = cast(JsonRef, json_schema.get('$ref'))
431 ref = None
433 self._garbage_collect_definitions(json_schema)
434 definitions_remapping = self._build_definitions_remapping()
436 if self.definitions:
437 json_schema['$defs'] = self.definitions
439 json_schema = definitions_remapping.remap_json_schema(json_schema)
441 # For now, we will not set the $schema key. However, if desired, this can be easily added by overriding
442 # this method and adding the following line after a call to super().generate(schema):
443 # json_schema['$schema'] = self.schema_dialect
445 self._used = True
446 return self.sort(json_schema)
448 def generate_inner(self, schema: CoreSchemaOrField) -> JsonSchemaValue: # noqa: C901
449 """Generates a JSON schema for a given core schema.
451 Args:
452 schema: The given core schema.
454 Returns:
455 The generated JSON schema.
457 TODO: the nested function definitions here seem like bad practice, I'd like to unpack these
458 in a future PR. It'd be great if we could shorten the call stack a bit for JSON schema generation,
459 and I think there's potential for that here.
460 """
461 # If a schema with the same CoreRef has been handled, just return a reference to it
462 # Note that this assumes that it will _never_ be the case that the same CoreRef is used
463 # on types that should have different JSON schemas
464 if 'ref' in schema:
465 core_ref = CoreRef(schema['ref']) # type: ignore[typeddict-item]
466 core_mode_ref = (core_ref, self.mode)
467 if core_mode_ref in self.core_to_defs_refs and self.core_to_defs_refs[core_mode_ref] in self.definitions:
468 return {'$ref': self.core_to_json_refs[core_mode_ref]}
470 def populate_defs(core_schema: CoreSchema, json_schema: JsonSchemaValue) -> JsonSchemaValue:
471 if 'ref' in core_schema:
472 core_ref = CoreRef(core_schema['ref']) # type: ignore[typeddict-item]
473 defs_ref, ref_json_schema = self.get_cache_defs_ref_schema(core_ref)
474 json_ref = JsonRef(ref_json_schema['$ref'])
475 # Replace the schema if it's not a reference to itself
476 # What we want to avoid is having the def be just a ref to itself
477 # which is what would happen if we blindly assigned any
478 if json_schema.get('$ref', None) != json_ref:
479 self.definitions[defs_ref] = json_schema
480 self._core_defs_invalid_for_json_schema.pop(defs_ref, None)
481 json_schema = ref_json_schema
482 return json_schema
484 def handler_func(schema_or_field: CoreSchemaOrField) -> JsonSchemaValue:
485 """Generate a JSON schema based on the input schema.
487 Args:
488 schema_or_field: The core schema to generate a JSON schema from.
490 Returns:
491 The generated JSON schema.
493 Raises:
494 TypeError: If an unexpected schema type is encountered.
495 """
496 # Generate the core-schema-type-specific bits of the schema generation:
497 json_schema: JsonSchemaValue | None = None
498 if self.mode == 'serialization' and 'serialization' in schema_or_field:
499 # In this case, we skip the JSON Schema generation of the schema
500 # and use the `'serialization'` schema instead (canonical example:
501 # `Annotated[int, PlainSerializer(str)]`).
502 ser_schema = schema_or_field['serialization'] # type: ignore
503 json_schema = self.ser_schema(ser_schema)
505 # It might be that the 'serialization'` is skipped depending on `when_used`.
506 # This is only relevant for `nullable` schemas though, so we special case here.
507 if (
508 json_schema is not None
509 and ser_schema.get('when_used') in ('unless-none', 'json-unless-none')
510 and schema_or_field['type'] == 'nullable'
511 ):
512 json_schema = self.get_union_of_schemas([{'type': 'null'}, json_schema])
513 if json_schema is None:
514 if _core_utils.is_core_schema(schema_or_field) or _core_utils.is_core_schema_field(schema_or_field):
515 generate_for_schema_type = self._schema_type_to_method[schema_or_field['type']]
516 json_schema = generate_for_schema_type(schema_or_field)
517 else:
518 raise TypeError(f'Unexpected schema type: schema={schema_or_field}')
519 return json_schema
521 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, handler_func)
523 metadata = cast(_core_metadata.CoreMetadata, schema.get('metadata', {}))
525 # TODO: I dislike that we have to wrap these basic dict updates in callables, is there any way around this?
527 if js_updates := metadata.get('pydantic_js_updates'):
529 def js_updates_handler_func(
530 schema_or_field: CoreSchemaOrField,
531 current_handler: GetJsonSchemaHandler = current_handler,
532 ) -> JsonSchemaValue:
533 json_schema = {**current_handler(schema_or_field), **js_updates}
534 return json_schema
536 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_updates_handler_func)
538 if js_extra := metadata.get('pydantic_js_extra'):
540 def js_extra_handler_func(
541 schema_or_field: CoreSchemaOrField,
542 current_handler: GetJsonSchemaHandler = current_handler,
543 ) -> JsonSchemaValue:
544 json_schema = current_handler(schema_or_field)
545 if isinstance(js_extra, dict):
546 json_schema.update(to_jsonable_python(js_extra))
547 elif callable(js_extra):
548 # similar to typing issue in _update_class_schema when we're working with callable js extra
549 js_extra(json_schema) # type: ignore
550 return json_schema
552 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_extra_handler_func)
554 for js_modify_function in metadata.get('pydantic_js_functions', ()):
556 def new_handler_func(
557 schema_or_field: CoreSchemaOrField,
558 current_handler: GetJsonSchemaHandler = current_handler,
559 js_modify_function: GetJsonSchemaFunction = js_modify_function,
560 ) -> JsonSchemaValue:
561 json_schema = js_modify_function(schema_or_field, current_handler)
562 if _core_utils.is_core_schema(schema_or_field):
563 json_schema = populate_defs(schema_or_field, json_schema)
564 original_schema = current_handler.resolve_ref_schema(json_schema)
565 ref = json_schema.pop('$ref', None)
566 if ref and json_schema:
567 original_schema.update(json_schema)
568 return original_schema
570 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func)
572 for js_modify_function in metadata.get('pydantic_js_annotation_functions', ()):
574 def new_handler_func(
575 schema_or_field: CoreSchemaOrField,
576 current_handler: GetJsonSchemaHandler = current_handler,
577 js_modify_function: GetJsonSchemaFunction = js_modify_function,
578 ) -> JsonSchemaValue:
579 return js_modify_function(schema_or_field, current_handler)
581 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func)
583 json_schema = current_handler(schema)
584 if _core_utils.is_core_schema(schema):
585 json_schema = populate_defs(schema, json_schema)
586 return json_schema
588 def sort(self, value: JsonSchemaValue, parent_key: str | None = None) -> JsonSchemaValue:
589 """Override this method to customize the sorting of the JSON schema (e.g., don't sort at all, sort all keys unconditionally, etc.)
591 By default, alphabetically sort the keys in the JSON schema, skipping the 'properties' and 'default' keys to preserve field definition order.
592 This sort is recursive, so it will sort all nested dictionaries as well.
593 """
594 sorted_dict: dict[str, JsonSchemaValue] = {}
595 keys = value.keys()
596 if parent_key not in ('properties', 'default'):
597 keys = sorted(keys)
598 for key in keys:
599 sorted_dict[key] = self._sort_recursive(value[key], parent_key=key)
600 return sorted_dict
602 def _sort_recursive(self, value: Any, parent_key: str | None = None) -> Any:
603 """Recursively sort a JSON schema value."""
604 if isinstance(value, dict):
605 sorted_dict: dict[str, JsonSchemaValue] = {}
606 keys = value.keys()
607 if parent_key not in ('properties', 'default'):
608 keys = sorted(keys)
609 for key in keys:
610 sorted_dict[key] = self._sort_recursive(value[key], parent_key=key)
611 return sorted_dict
612 elif isinstance(value, list):
613 sorted_list: list[JsonSchemaValue] = [self._sort_recursive(item, parent_key) for item in value]
614 return sorted_list
615 else:
616 return value
618 # ### Schema generation methods
620 def invalid_schema(self, schema: core_schema.InvalidSchema) -> JsonSchemaValue:
621 """Placeholder - should never be called."""
623 raise RuntimeError('Cannot generate schema for invalid_schema. This is a bug! Please report it.')
625 def any_schema(self, schema: core_schema.AnySchema) -> JsonSchemaValue:
626 """Generates a JSON schema that matches any value.
628 Args:
629 schema: The core schema.
631 Returns:
632 The generated JSON schema.
633 """
634 return {}
636 def none_schema(self, schema: core_schema.NoneSchema) -> JsonSchemaValue:
637 """Generates a JSON schema that matches `None`.
639 Args:
640 schema: The core schema.
642 Returns:
643 The generated JSON schema.
644 """
645 return {'type': 'null'}
647 def bool_schema(self, schema: core_schema.BoolSchema) -> JsonSchemaValue:
648 """Generates a JSON schema that matches a bool value.
650 Args:
651 schema: The core schema.
653 Returns:
654 The generated JSON schema.
655 """
656 return {'type': 'boolean'}
658 def int_schema(self, schema: core_schema.IntSchema) -> JsonSchemaValue:
659 """Generates a JSON schema that matches an int value.
661 Args:
662 schema: The core schema.
664 Returns:
665 The generated JSON schema.
666 """
667 json_schema: dict[str, Any] = {'type': 'integer'}
668 self.update_with_validations(json_schema, schema, self.ValidationsMapping.numeric)
669 json_schema = {k: v for k, v in json_schema.items() if v not in {math.inf, -math.inf}}
670 return json_schema
672 def float_schema(self, schema: core_schema.FloatSchema) -> JsonSchemaValue:
673 """Generates a JSON schema that matches a float value.
675 Args:
676 schema: The core schema.
678 Returns:
679 The generated JSON schema.
680 """
681 json_schema: dict[str, Any] = {'type': 'number'}
682 self.update_with_validations(json_schema, schema, self.ValidationsMapping.numeric)
683 json_schema = {k: v for k, v in json_schema.items() if v not in {math.inf, -math.inf}}
684 return json_schema
686 def fraction_schema(self, schema: core_schema.FractionSchema) -> JsonSchemaValue:
687 """Generates a JSON schema that matches a fraction value.
689 Args:
690 schema: The core schema.
692 Returns:
693 The generated JSON schema.
695 """
696 json_schema: JsonSchemaValue = {'type': 'string', 'format': 'fraction'}
697 if self.mode == 'validation':
698 le = schema.get('le')
699 ge = schema.get('ge')
700 lt = schema.get('lt')
701 gt = schema.get('gt')
702 json_schema = {
703 'anyOf': [
704 self.float_schema(
705 core_schema.float_schema(
706 le=None if le is None else float(le),
707 ge=None if ge is None else float(ge),
708 lt=None if lt is None else float(lt),
709 gt=None if gt is None else float(gt),
710 )
711 ),
712 json_schema,
713 ],
714 }
715 return json_schema
717 def decimal_schema(self, schema: core_schema.DecimalSchema) -> JsonSchemaValue:
718 """Generates a JSON schema that matches a decimal value.
720 Args:
721 schema: The core schema.
723 Returns:
724 The generated JSON schema.
725 """
726 str_schema = core_schema.str_schema()
727 pattern = self.get_decimal_pattern(schema)
728 if pattern is not None:
729 str_schema['pattern'] = pattern
730 json_schema = self.str_schema(str_schema)
731 if self.mode == 'validation':
732 multiple_of = schema.get('multiple_of')
733 le = schema.get('le')
734 ge = schema.get('ge')
735 lt = schema.get('lt')
736 gt = schema.get('gt')
737 json_schema = {
738 'anyOf': [
739 self.float_schema(
740 core_schema.float_schema(
741 allow_inf_nan=schema.get('allow_inf_nan'),
742 multiple_of=None if multiple_of is None else float(multiple_of),
743 le=None if le is None else float(le),
744 ge=None if ge is None else float(ge),
745 lt=None if lt is None else float(lt),
746 gt=None if gt is None else float(gt),
747 )
748 ),
749 json_schema,
750 ],
751 }
752 return json_schema
754 def get_decimal_pattern(self, schema: core_schema.DecimalSchema) -> str | None:
755 """Get the regular expression pattern to apply to the string representation of a decimal.
757 By default, no pattern is applied (`None` is returned). Subclasses can override this method
758 to return a custom pattern, or [`build_decimal_pattern()`][pydantic.json_schema.GenerateJsonSchema.build_decimal_pattern]
759 can be used to get a pattern reflecting the `max_digits` and `decimal_places` constraints:
761 ```python
762 from pydantic.json_schema import GenerateJsonSchema
764 class MyGenerateJsonSchema(GenerateJsonSchema):
765 def get_decimal_pattern(self, schema):
766 return self.build_decimal_pattern(schema)
767 ```
769 Args:
770 schema: The core schema.
772 Returns:
773 The regular expression pattern, or `None` if no pattern should be applied.
774 """
775 return None
777 def build_decimal_pattern(self, schema: core_schema.DecimalSchema) -> str:
778 """Build a regular expression pattern for the string representation of a decimal.
780 The pattern takes into account the `max_digits` and `decimal_places` constraints, and the current
781 mode:
783 - In `'validation'` mode, the pattern accepts the string syntax understood by the [`Decimal`][decimal.Decimal]
784 constructor. The `max_digits` and `decimal_places` constraints are only reflected for strings
785 *without* an exponent: strings using an exponent are accepted as long as they are syntactically valid,
786 as the constraints can't be expressed with a regular expression in this case.
787 - In `'serialization'` mode, the pattern exactly describes the strings produced by pydantic
788 (i.e. by `str(decimal)`) for decimals satisfying the constraints,
789 including the ones using scientific notation (e.g. `'1E-7'`, `'1.5E+3'`).
791 In both modes, trailing zeros in the decimal part are not counted (e.g. `'1.10'` is accepted with
792 `decimal_places=1`), consistent with the validation behavior. If `allow_inf_nan` is enabled (on the
793 core schema or in the configuration), the string representations of infinity and NaN are also accepted.
795 Args:
796 schema: The core schema.
798 Returns:
799 The regular expression pattern.
800 """
801 max_digits = schema.get('max_digits')
802 decimal_places = schema.get('decimal_places')
803 allow_inf_nan = schema.get('allow_inf_nan')
804 if allow_inf_nan is None:
805 allow_inf_nan = self._config.config_dict.get('allow_inf_nan', False)
806 if self.mode == 'validation':
807 return _decimal_validation_pattern(max_digits, decimal_places, allow_inf_nan)
808 else:
809 return _decimal_serialization_pattern(max_digits, decimal_places, allow_inf_nan)
811 def str_schema(self, schema: core_schema.StringSchema) -> JsonSchemaValue:
812 """Generates a JSON schema that matches a string value.
814 Args:
815 schema: The core schema.
817 Returns:
818 The generated JSON schema.
819 """
820 json_schema: JsonSchemaValue = {'type': 'string'}
821 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string)
822 if 'minLength' not in json_schema and self._config.str_min_length:
823 json_schema['minLength'] = self._config.str_min_length
824 if 'maxLength' not in json_schema and self._config.str_max_length is not None:
825 json_schema['maxLength'] = self._config.str_max_length
826 if isinstance(json_schema.get('pattern'), Pattern):
827 # TODO: should we add regex flags to the pattern?
828 json_schema['pattern'] = json_schema.get('pattern').pattern # type: ignore
829 return json_schema
831 def bytes_schema(self, schema: core_schema.BytesSchema) -> JsonSchemaValue:
832 """Generates a JSON schema that matches a bytes value.
834 Args:
835 schema: The core schema.
837 Returns:
838 The generated JSON schema.
839 """
840 json_schema = {'type': 'string', 'format': 'base64url' if self._config.ser_json_bytes == 'base64' else 'binary'}
841 self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes)
842 return json_schema
844 def date_schema(self, schema: core_schema.DateSchema) -> JsonSchemaValue:
845 """Generates a JSON schema that matches a date value.
847 Args:
848 schema: The core schema.
850 Returns:
851 The generated JSON schema.
852 """
853 return self._common_temporal_schema('date', self._config.ser_json_temporal)
855 def time_schema(self, schema: core_schema.TimeSchema) -> JsonSchemaValue:
856 """Generates a JSON schema that matches a time value.
858 Args:
859 schema: The core schema.
861 Returns:
862 The generated JSON schema.
863 """
864 return self._common_temporal_schema('time', self._config.ser_json_temporal)
866 def datetime_schema(self, schema: core_schema.DatetimeSchema) -> JsonSchemaValue:
867 """Generates a JSON schema that matches a datetime value.
869 Args:
870 schema: The core schema.
872 Returns:
873 The generated JSON schema.
874 """
875 return self._common_temporal_schema('date-time', self._config.ser_json_temporal)
877 def timedelta_schema(self, schema: core_schema.TimedeltaSchema) -> JsonSchemaValue:
878 """Generates a JSON schema that matches a timedelta value.
880 Args:
881 schema: The core schema.
883 Returns:
884 The generated JSON schema.
885 """
886 if 'ser_json_temporal' in self._config.config_dict:
887 temporal_format = self._config.ser_json_temporal
888 else:
889 # `ser_json_temporal` supersedes `ser_json_timedelta`, which only applies when the former isn't
890 # explicitly set.
891 temporal_format = 'seconds' if self._config.ser_json_timedelta == 'float' else 'iso8601'
892 return self._common_temporal_schema('duration', temporal_format)
894 def _common_temporal_schema(
895 self, format: str, temporal_format: Literal['iso8601', 'seconds', 'milliseconds']
896 ) -> JsonSchemaValue:
897 if self.mode == 'serialization' and temporal_format != 'iso8601':
898 # Both `'seconds'` and `'milliseconds'` serialize to a number:
899 return {'type': 'number'}
900 return {'type': 'string', 'format': format}
902 def literal_schema(self, schema: core_schema.LiteralSchema) -> JsonSchemaValue:
903 """Generates a JSON schema that matches a literal value.
905 Args:
906 schema: The core schema.
908 Returns:
909 The generated JSON schema.
910 """
911 expected = [to_jsonable_python(v.value if isinstance(v, Enum) else v) for v in schema['expected']]
913 result: dict[str, Any] = {}
914 if len(expected) == 1:
915 result['const'] = expected[0]
916 else:
917 result['enum'] = expected
919 types = {type(e) for e in expected}
920 if types == {str}:
921 result['type'] = 'string'
922 elif types == {int}:
923 result['type'] = 'integer'
924 elif types == {float}:
925 result['type'] = 'number'
926 elif types == {bool}:
927 result['type'] = 'boolean'
928 elif types == {list}:
929 result['type'] = 'array'
930 elif types == {type(None)}:
931 result['type'] = 'null'
932 return result
934 def missing_sentinel_schema(self, schema: core_schema.MissingSentinelSchema) -> JsonSchemaValue:
935 """Generates a JSON schema that matches a schema that allows the `MISSING` sentinel value.
937 Args:
938 schema: The core schema.
940 Returns:
941 The generated JSON schema.
942 """
943 inner_schema = schema.get('schema')
944 if inner_schema is None:
945 raise PydanticOmit
946 return self.generate_inner(inner_schema)
948 def ellipsis_schema(self, schema: core_schema.EllipsisSchema) -> JsonSchemaValue:
949 """Handles JSON schema generation for a core schema that checks if a value is the [`Ellipsis`][] literal.
951 Unless overridden in a subclass, this raises an error.
953 Args:
954 schema: The core schema.
956 Returns:
957 The generated JSON schema.
958 """
959 return self.handle_invalid_for_json_schema(schema, 'core_schema.EllipsisSchema')
961 def enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue:
962 """Generates a JSON schema that matches an Enum value.
964 Args:
965 schema: The core schema.
967 Returns:
968 The generated JSON schema.
969 """
970 enum_type = schema['cls']
971 description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__)
972 if (
973 description == 'An enumeration.'
974 ): # This is the default value provided by enum.EnumMeta.__new__; don't use it
975 description = None
976 result: dict[str, Any] = {'title': enum_type.__name__, 'description': description}
977 result = {k: v for k, v in result.items() if v is not None}
979 expected = [to_jsonable_python(v.value) for v in schema['members']]
981 result['enum'] = expected
983 types = {type(e) for e in expected}
984 if isinstance(enum_type, str) or types == {str}:
985 result['type'] = 'string'
986 elif isinstance(enum_type, int) or types == {int}:
987 result['type'] = 'integer'
988 elif isinstance(enum_type, float) or types == {float}:
989 result['type'] = 'number'
990 elif types == {bool}:
991 result['type'] = 'boolean'
992 elif types == {list}:
993 result['type'] = 'array'
995 return result
997 def is_instance_schema(self, schema: core_schema.IsInstanceSchema) -> JsonSchemaValue:
998 """Handles JSON schema generation for a core schema that checks if a value is an instance of a class.
1000 Unless overridden in a subclass, this raises an error.
1002 Args:
1003 schema: The core schema.
1005 Returns:
1006 The generated JSON schema.
1007 """
1008 return self.handle_invalid_for_json_schema(schema, f'core_schema.IsInstanceSchema ({schema["cls"]})')
1010 def is_subclass_schema(self, schema: core_schema.IsSubclassSchema) -> JsonSchemaValue:
1011 """Handles JSON schema generation for a core schema that checks if a value is a subclass of a class.
1013 For backwards compatibility with v1, this does not raise an error, but can be overridden to change this.
1015 Args:
1016 schema: The core schema.
1018 Returns:
1019 The generated JSON schema.
1020 """
1021 # Note: This is for compatibility with V1; you can override if you want different behavior.
1022 return {}
1024 def callable_schema(self, schema: core_schema.CallableSchema) -> JsonSchemaValue:
1025 """Generates a JSON schema that matches a callable value.
1027 Unless overridden in a subclass, this raises an error.
1029 Args:
1030 schema: The core schema.
1032 Returns:
1033 The generated JSON schema.
1034 """
1035 return self.handle_invalid_for_json_schema(schema, 'core_schema.CallableSchema')
1037 def list_schema(self, schema: core_schema.ListSchema) -> JsonSchemaValue:
1038 """Returns a schema that matches a list schema.
1040 Args:
1041 schema: The core schema.
1043 Returns:
1044 The generated JSON schema.
1045 """
1046 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema'])
1047 json_schema = {'type': 'array', 'items': items_schema}
1048 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
1049 return json_schema
1051 def deque_schema(self, schema: core_schema.DequeSchema) -> JsonSchemaValue:
1052 """Returns a schema that matches a deque schema.
1054 Args:
1055 schema: The core schema.
1057 Returns:
1058 The generated JSON schema.
1059 """
1060 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema'])
1061 json_schema = {'type': 'array', 'items': items_schema}
1062 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
1063 return json_schema
1065 @deprecated('`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.', category=None)
1066 @final
1067 def tuple_positional_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue:
1068 """Replaced by `tuple_schema`."""
1069 warnings.warn(
1070 '`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.',
1071 PydanticDeprecatedSince26,
1072 stacklevel=2,
1073 )
1074 return self.tuple_schema(schema)
1076 @deprecated('`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.', category=None)
1077 @final
1078 def tuple_variable_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue:
1079 """Replaced by `tuple_schema`."""
1080 warnings.warn(
1081 '`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.',
1082 PydanticDeprecatedSince26,
1083 stacklevel=2,
1084 )
1085 return self.tuple_schema(schema)
1087 def tuple_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue:
1088 """Generates a JSON schema that matches a tuple schema e.g. `tuple[int,
1089 str, bool]` or `tuple[int, ...]`.
1091 Args:
1092 schema: The core schema.
1094 Returns:
1095 The generated JSON schema.
1096 """
1097 json_schema: JsonSchemaValue = {'type': 'array'}
1098 if 'variadic_item_index' in schema:
1099 variadic_item_index = schema['variadic_item_index']
1100 if variadic_item_index > 0:
1101 json_schema['minItems'] = variadic_item_index
1102 json_schema['prefixItems'] = [
1103 self.generate_inner(item) for item in schema['items_schema'][:variadic_item_index]
1104 ]
1105 if variadic_item_index + 1 == len(schema['items_schema']):
1106 # if the variadic item is the last item, then represent it faithfully
1107 json_schema['items'] = self.generate_inner(schema['items_schema'][variadic_item_index])
1108 else:
1109 # otherwise, 'items' represents the schema for the variadic
1110 # item plus the suffix, so just allow anything for simplicity
1111 # for now
1112 json_schema['items'] = True
1113 else:
1114 prefixItems = [self.generate_inner(item) for item in schema['items_schema']]
1115 if prefixItems:
1116 json_schema['prefixItems'] = prefixItems
1117 json_schema['minItems'] = len(prefixItems)
1118 json_schema['maxItems'] = len(prefixItems)
1119 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
1120 return json_schema
1122 def set_schema(self, schema: core_schema.SetSchema) -> JsonSchemaValue:
1123 """Generates a JSON schema that matches a set schema.
1125 Args:
1126 schema: The core schema.
1128 Returns:
1129 The generated JSON schema.
1130 """
1131 return self._common_set_schema(schema)
1133 def frozenset_schema(self, schema: core_schema.FrozenSetSchema) -> JsonSchemaValue:
1134 """Generates a JSON schema that matches a frozenset schema.
1136 Args:
1137 schema: The core schema.
1139 Returns:
1140 The generated JSON schema.
1141 """
1142 return self._common_set_schema(schema)
1144 def _common_set_schema(self, schema: core_schema.SetSchema | core_schema.FrozenSetSchema) -> JsonSchemaValue:
1145 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema'])
1146 json_schema = {'type': 'array', 'uniqueItems': True, 'items': items_schema}
1147 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
1148 return json_schema
1150 def generator_schema(self, schema: core_schema.GeneratorSchema) -> JsonSchemaValue:
1151 """Returns a JSON schema that represents the provided GeneratorSchema.
1153 Args:
1154 schema: The schema.
1156 Returns:
1157 The generated JSON schema.
1158 """
1159 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema'])
1160 json_schema = {'type': 'array', 'items': items_schema}
1161 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array)
1162 return json_schema
1164 def dict_schema(self, schema: core_schema.DictSchema) -> JsonSchemaValue:
1165 """Generates a JSON schema that matches a dict schema.
1167 Args:
1168 schema: The core schema.
1170 Returns:
1171 The generated JSON schema.
1172 """
1173 return self._common_dict_schema(schema)
1175 def frozendict_schema(self, schema: core_schema.FrozenDictSchema) -> JsonSchemaValue:
1176 """Generates a JSON schema that matches a frozendict schema.
1178 Args:
1179 schema: The core schema.
1181 Returns:
1182 The generated JSON schema.
1183 """
1184 return self._common_dict_schema(schema)
1186 def ordered_dict_schema(self, schema: core_schema.OrderedDictSchema) -> JsonSchemaValue:
1187 """Generates a JSON schema that matches an `OrderedDict` schema.
1189 Args:
1190 schema: The core schema.
1192 Returns:
1193 The generated JSON schema.
1194 """
1195 return self._common_dict_schema(schema)
1197 def counter_schema(self, schema: core_schema.CounterSchema) -> JsonSchemaValue:
1198 """Generates a JSON schema that matches a `Counter` schema.
1200 Args:
1201 schema: The core schema.
1203 Returns:
1204 The generated JSON schema.
1205 """
1206 return self._common_dict_schema(schema)
1208 def _common_dict_schema(
1209 self,
1210 schema: core_schema.DictSchema
1211 | core_schema.FrozenDictSchema
1212 | core_schema.OrderedDictSchema
1213 | core_schema.CounterSchema,
1214 ) -> JsonSchemaValue:
1215 json_schema: JsonSchemaValue = {'type': 'object'}
1217 keys_schema = self.generate_inner(schema['keys_schema']).copy() if 'keys_schema' in schema else {}
1218 if '$ref' not in keys_schema:
1219 keys_pattern = keys_schema.pop('pattern', None)
1220 # Don't give a title to patternProperties/propertyNames:
1221 keys_schema.pop('title', None)
1222 else:
1223 # Here, we assume that if the keys schema is a definition reference,
1224 # it can't be a simple string core schema (and thus no pattern can exist).
1225 # However, this is only in practice (in theory, a definition reference core
1226 # schema could be generated for a simple string schema).
1227 # Note that we avoid calling `self.resolve_ref_schema`, as it might not exist yet.
1228 keys_pattern = None
1230 values_schema = self.generate_inner(schema['values_schema']).copy() if 'values_schema' in schema else {}
1231 # don't give a title to additionalProperties:
1232 values_schema.pop('title', None)
1234 if values_schema or keys_pattern is not None:
1235 if keys_pattern is None:
1236 json_schema['additionalProperties'] = values_schema
1237 else:
1238 json_schema['patternProperties'] = {keys_pattern: values_schema}
1239 else: # for `dict[str, Any]`, we allow any key and any value, since `str` is the default key type
1240 json_schema['additionalProperties'] = True
1242 if (
1243 # The len check indicates that constraints are probably present:
1244 (keys_schema.get('type') == 'string' and len(keys_schema) > 1)
1245 # If this is a definition reference schema, it most likely has constraints:
1246 or '$ref' in keys_schema
1247 ):
1248 keys_schema.pop('type', None)
1249 json_schema['propertyNames'] = keys_schema
1251 self.update_with_validations(json_schema, schema, self.ValidationsMapping.object)
1252 return json_schema
1254 def function_before_schema(self, schema: core_schema.BeforeValidatorFunctionSchema) -> JsonSchemaValue:
1255 """Generates a JSON schema that matches a function-before schema.
1257 Args:
1258 schema: The core schema.
1260 Returns:
1261 The generated JSON schema.
1262 """
1263 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')):
1264 return self.generate_inner(input_schema)
1266 return self.generate_inner(schema['schema'])
1268 def function_after_schema(self, schema: core_schema.AfterValidatorFunctionSchema) -> JsonSchemaValue:
1269 """Generates a JSON schema that matches a function-after schema.
1271 Args:
1272 schema: The core schema.
1274 Returns:
1275 The generated JSON schema.
1276 """
1277 return self.generate_inner(schema['schema'])
1279 def function_plain_schema(self, schema: core_schema.PlainValidatorFunctionSchema) -> JsonSchemaValue:
1280 """Generates a JSON schema that matches a function-plain schema.
1282 Args:
1283 schema: The core schema.
1285 Returns:
1286 The generated JSON schema.
1287 """
1288 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')):
1289 return self.generate_inner(input_schema)
1291 return self.handle_invalid_for_json_schema(
1292 schema, f'core_schema.PlainValidatorFunctionSchema ({schema["function"]})'
1293 )
1295 def function_wrap_schema(self, schema: core_schema.WrapValidatorFunctionSchema) -> JsonSchemaValue:
1296 """Generates a JSON schema that matches a function-wrap schema.
1298 Args:
1299 schema: The core schema.
1301 Returns:
1302 The generated JSON schema.
1303 """
1304 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')):
1305 return self.generate_inner(input_schema)
1307 return self.generate_inner(schema['schema'])
1309 def default_schema(self, schema: core_schema.WithDefaultSchema) -> JsonSchemaValue:
1310 """Generates a JSON schema that matches a schema with a default value.
1312 Args:
1313 schema: The core schema.
1315 Returns:
1316 The generated JSON schema.
1317 """
1318 json_schema = self.generate_inner(schema['schema'])
1320 default = self.get_default_value(schema)
1321 if default is NoDefault or default is MISSING:
1322 return json_schema
1324 # we reflect the application of custom plain, no-info serializers to defaults for
1325 # JSON Schemas viewed in serialization mode:
1326 # TODO: improvements along with https://github.com/pydantic/pydantic/issues/8208
1327 if self.mode == 'serialization':
1328 # `_get_ser_schema_for_default_value()` is used to unpack potentially nested validator schemas:
1329 ser_schema = _get_ser_schema_for_default_value(schema['schema'])
1330 if (
1331 ser_schema is not None
1332 and (ser_func := ser_schema.get('function'))
1333 and not (default is None and ser_schema.get('when_used') in ('unless-none', 'json-unless-none'))
1334 ):
1335 try:
1336 default = ser_func(default) # type: ignore
1337 except Exception:
1338 # It might be that the provided default needs to be validated (read: parsed) first
1339 # (assuming `validate_default` is enabled). However, we can't perform
1340 # such validation during JSON Schema generation so we don't support
1341 # this pattern for now.
1342 # (One example is when using `foo: ByteSize = '1MB'`, which validates and
1343 # serializes as an int. In this case, `ser_func` is `int` and `int('1MB')` fails).
1344 self.emit_warning(
1345 'non-serializable-default',
1346 f'Unable to serialize value {default!r} with the plain serializer; excluding default from JSON schema',
1347 )
1348 return json_schema
1350 # Sort set/frozenset defaults to ensure deterministic JSON schema generation
1351 # We only sort if len > 1 because sets of size 0 or 1 are already deterministic
1352 if isinstance(default, collections.abc.Set) and len(default) > 1:
1353 try:
1354 default = sorted(default)
1355 except TypeError: # pragma: no cover
1356 # If items aren't comparable (e.g. mixed types), we can't sort them.
1357 pass
1359 try:
1360 encoded_default = self.encode_default(default)
1361 except pydantic_core.PydanticSerializationError:
1362 self.emit_warning(
1363 'non-serializable-default',
1364 f'Default value {default} is not JSON serializable; excluding default from JSON schema',
1365 )
1366 # Return the inner schema, as though there was no default
1367 return json_schema
1369 json_schema['default'] = encoded_default
1370 return json_schema
1372 def get_default_value(self, schema: core_schema.WithDefaultSchema) -> Any:
1373 """Get the default value to be used when generating a JSON Schema for a core schema with a default.
1375 The default implementation is to use the statically defined default value. This method can be overridden
1376 if you want to make use of the default factory.
1378 Args:
1379 schema: The `'with-default'` core schema.
1381 Returns:
1382 The default value to use, or [`NoDefault`][pydantic.json_schema.NoDefault] if no default
1383 value is available.
1384 """
1385 return schema.get('default', NoDefault)
1387 def nullable_schema(self, schema: core_schema.NullableSchema) -> JsonSchemaValue:
1388 """Generates a JSON schema that matches a schema that allows null values.
1390 Args:
1391 schema: The core schema.
1393 Returns:
1394 The generated JSON schema.
1395 """
1396 null_schema = {'type': 'null'}
1397 inner_json_schema = self.generate_inner(schema['schema'])
1399 if inner_json_schema == null_schema:
1400 return null_schema
1401 else:
1402 return self.get_union_of_schemas([inner_json_schema, null_schema])
1404 def union_schema(self, schema: core_schema.UnionSchema) -> JsonSchemaValue:
1405 """Generates a JSON schema that matches a schema that allows values matching any of the given schemas.
1407 Args:
1408 schema: The core schema.
1410 Returns:
1411 The generated JSON schema.
1412 """
1413 generated: list[JsonSchemaValue] = []
1415 for choice in core_schema.iter_union_choices(schema):
1416 try:
1417 generated.append(self.generate_inner(choice))
1418 except PydanticOmit: # noqa: PERF203
1419 continue
1420 except PydanticInvalidForJsonSchema as exc:
1421 self.emit_warning('skipped-choice', exc.message)
1422 if len(generated) == 1:
1423 return generated[0]
1424 return self.get_union_of_schemas(generated)
1426 def get_union_of_schemas(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue:
1427 """Returns the JSON Schema representation for the union of the provided JSON Schemas.
1429 The result depends on the configured `'union_format'`.
1431 Args:
1432 schemas: The list of JSON Schemas to be included in the union.
1434 Returns:
1435 The JSON Schema representing the union of schemas.
1436 """
1437 if self.union_format == 'primitive_type_array':
1438 types: list[str] = []
1439 for schema in schemas:
1440 schema_types: list[str] | str | None = schema.get('type')
1441 if schema_types is None:
1442 # No type, meaning it can be a ref or an empty schema.
1443 break
1444 if not isinstance(schema_types, list):
1445 schema_types = [schema_types]
1446 if not all(t in _PRIMITIVE_JSON_SCHEMA_TYPES for t in schema_types):
1447 break
1448 if len(schema) != 1:
1449 # We only want to include types that don't have any constraints. For instance,
1450 # if `schemas = [{'type': 'string', 'maxLength': 3}, {'type': 'string', 'minLength': 5}]`,
1451 # we don't want to produce `{'type': 'string', 'maxLength': 3, 'minLength': 5}`.
1452 # Same if we have some metadata (e.g. `title`) on a specific union member, we want to preserve it.
1453 break
1455 types.extend(schema_types)
1456 else:
1457 # If we got there, all the schemas where valid to be used with the `'primitive_type_array` format
1458 return {'type': list(dict.fromkeys(types))}
1460 return self.get_flattened_anyof(schemas)
1462 def tagged_union_schema(self, schema: core_schema.TaggedUnionSchema) -> JsonSchemaValue:
1463 """Generates a JSON schema that matches a schema that allows values matching any of the given schemas, where
1464 the schemas are tagged with a discriminator field that indicates which schema should be used to validate
1465 the value.
1467 Args:
1468 schema: The core schema.
1470 Returns:
1471 The generated JSON schema.
1472 """
1473 generated: dict[str, JsonSchemaValue] = {}
1474 for k, v in schema['choices'].items():
1475 if isinstance(k, Enum):
1476 k = k.value
1477 elif isinstance(k, bool):
1478 # Use the JSON representation so that the discriminator mapping
1479 # can be matched against the serialized payload value
1480 k = 'true' if k else 'false'
1481 elif k is None:
1482 k = 'null'
1483 try:
1484 # Use str(k) since keys must be strings for json; while not technically correct,
1485 # it's the closest that can be represented in valid JSON
1486 generated[str(k)] = self.generate_inner(v).copy()
1487 except PydanticOmit:
1488 continue
1489 except PydanticInvalidForJsonSchema as exc:
1490 self.emit_warning('skipped-choice', exc.message)
1492 one_of_choices = _deduplicate_schemas(generated.values())
1493 json_schema: JsonSchemaValue = {'oneOf': one_of_choices}
1495 # This reflects the v1 behavior; TODO: we should make it possible to exclude OpenAPI stuff from the JSON schema
1496 openapi_discriminator = self._extract_discriminator(schema, one_of_choices)
1497 if openapi_discriminator is not None:
1498 json_schema['discriminator'] = {
1499 'propertyName': openapi_discriminator,
1500 'mapping': {k: v.get('$ref', v) for k, v in generated.items()},
1501 }
1503 return json_schema
1505 def _extract_discriminator(
1506 self, schema: core_schema.TaggedUnionSchema, one_of_choices: list[JsonDict]
1507 ) -> str | None:
1508 """Extract a compatible OpenAPI discriminator from the schema and one_of choices that end up in the final
1509 schema."""
1510 openapi_discriminator: str | None = None
1512 if isinstance(schema['discriminator'], str):
1513 return schema['discriminator']
1515 if isinstance(schema['discriminator'], list):
1516 # If the discriminator is a single item list containing a string, that is equivalent to the string case
1517 if len(schema['discriminator']) == 1 and isinstance(schema['discriminator'][0], str):
1518 return schema['discriminator'][0]
1519 # When an alias is used that is different from the field name, the discriminator will be a list of single
1520 # str lists, one for the attribute and one for the actual alias. The logic here will work even if there is
1521 # more than one possible attribute, and looks for whether a single alias choice is present as a documented
1522 # property on all choices. If so, that property will be used as the OpenAPI discriminator.
1523 for alias_path in schema['discriminator']:
1524 if not isinstance(alias_path, list):
1525 break # this means that the discriminator is not a list of alias paths
1526 if len(alias_path) != 1:
1527 continue # this means that the "alias" does not represent a single field
1528 alias = alias_path[0]
1529 if not isinstance(alias, str):
1530 continue # this means that the "alias" does not represent a field
1531 alias_is_present_on_all_choices = True
1532 for choice in one_of_choices:
1533 try:
1534 choice = self.resolve_ref_schema(choice)
1535 except RuntimeError as exc:
1536 # TODO: fixme - this is a workaround for the fact that we can't always resolve refs
1537 # for tagged union choices at this point in the schema gen process, we might need to do
1538 # another pass at the end like we do for core schemas
1539 self.emit_warning('skipped-discriminator', str(exc))
1540 choice = {}
1541 properties = choice.get('properties', {})
1542 if not isinstance(properties, dict) or alias not in properties:
1543 alias_is_present_on_all_choices = False
1544 break
1545 if alias_is_present_on_all_choices:
1546 openapi_discriminator = alias
1547 break
1548 return openapi_discriminator
1550 def chain_schema(self, schema: core_schema.ChainSchema) -> JsonSchemaValue:
1551 """Generates a JSON schema that matches a core_schema.ChainSchema.
1553 When generating a schema for validation, we return the validation JSON schema for the first step in the chain.
1554 For serialization, we return the serialization JSON schema for the last step in the chain.
1556 Args:
1557 schema: The core schema.
1559 Returns:
1560 The generated JSON schema.
1561 """
1562 step_index = 0 if self.mode == 'validation' else -1 # use first step for validation, last for serialization
1563 return self.generate_inner(schema['steps'][step_index])
1565 def lax_or_strict_schema(self, schema: core_schema.LaxOrStrictSchema) -> JsonSchemaValue:
1566 """Generates a JSON schema that matches a schema that allows values matching either the lax schema or the
1567 strict schema.
1569 Args:
1570 schema: The core schema.
1572 Returns:
1573 The generated JSON schema.
1574 """
1575 # TODO: Need to read the default value off of model config or whatever
1576 use_strict = schema.get('strict', False) # TODO: replace this default False
1577 # If your JSON schema fails to generate it is probably
1578 # because one of the following two branches failed.
1579 if use_strict:
1580 return self.generate_inner(schema['strict_schema'])
1581 else:
1582 return self.generate_inner(schema['lax_schema'])
1584 def json_or_python_schema(self, schema: core_schema.JsonOrPythonSchema) -> JsonSchemaValue:
1585 """Generates a JSON schema that matches a schema that allows values matching either the JSON schema or the
1586 Python schema.
1588 The JSON schema is used instead of the Python schema. If you want to use the Python schema, you should override
1589 this method.
1591 Args:
1592 schema: The core schema.
1594 Returns:
1595 The generated JSON schema.
1596 """
1597 return self.generate_inner(schema['json_schema'])
1599 def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue:
1600 """Generates a JSON schema that matches a schema that defines a typed dict.
1602 Args:
1603 schema: The core schema.
1605 Returns:
1606 The generated JSON schema.
1607 """
1608 total = schema.get('total', True)
1609 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [
1610 (name, self.field_is_required(field, total), field)
1611 for name, field in schema['fields'].items()
1612 if self.field_is_present(field)
1613 ]
1614 if self.mode == 'serialization':
1615 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', [])))
1616 cls = schema.get('cls')
1617 config = _get_typed_dict_config(cls)
1618 with self._config_wrapper_stack.push(config):
1619 json_schema = self._named_required_fields_schema(named_required_fields)
1621 # There's some duplication between `extra_behavior` and
1622 # the config's `extra`/core config's `extra_fields_behavior`.
1623 # However, it is common to manually create TypedDictSchemas,
1624 # where you don't necessarily have a class.
1625 # At runtime, `extra_behavior` takes priority over the config
1626 # for validation, so follow the same for the JSON Schema:
1627 if 'extras_schema' in schema and schema['extras_schema'] != core_schema.any_schema():
1628 allow_additional_props = self.generate_inner(schema['extras_schema'])
1629 else:
1630 allow_additional_props = True
1632 if schema.get('extra_behavior') == 'forbid':
1633 json_schema['additionalProperties'] = False
1634 elif schema.get('extra_behavior') == 'allow':
1635 json_schema['additionalProperties'] = allow_additional_props
1637 if cls is not None:
1638 # `_update_class_schema()` will not override
1639 # `additionalProperties` if already present:
1640 self._update_class_schema(json_schema, cls, config or {})
1641 elif 'additionalProperties' not in json_schema:
1642 extra = schema.get('config', {}).get('extra_fields_behavior')
1643 if extra == 'forbid':
1644 json_schema['additionalProperties'] = False
1645 elif extra == 'allow':
1646 json_schema['additionalProperties'] = allow_additional_props
1648 return json_schema
1650 @staticmethod
1651 def _name_required_computed_fields(
1652 computed_fields: list[ComputedField],
1653 ) -> list[tuple[str, bool, core_schema.ComputedField]]:
1654 return [
1655 (field['property_name'], field.get('serialization_exclude_if') is None, field) for field in computed_fields
1656 ]
1658 def _named_required_fields_schema(
1659 self, named_required_fields: Sequence[tuple[str, bool, CoreSchemaField]]
1660 ) -> JsonSchemaValue:
1661 properties: dict[str, JsonSchemaValue] = {}
1662 required_fields: list[str] = []
1663 for name, required, field in named_required_fields:
1664 if self.by_alias:
1665 name = self._get_alias_name(field, name)
1666 try:
1667 field_json_schema = self.generate_inner(field).copy()
1668 except PydanticOmit:
1669 continue
1670 if 'title' not in field_json_schema and self.field_title_should_be_set(field):
1671 title = self.get_title_from_name(name)
1672 field_json_schema['title'] = title
1673 field_json_schema = self.handle_ref_overrides(field_json_schema)
1674 properties[name] = field_json_schema
1675 if required:
1676 required_fields.append(name)
1678 json_schema = {'type': 'object', 'properties': properties}
1679 if required_fields:
1680 json_schema['required'] = required_fields
1681 return json_schema
1683 def _get_alias_name(self, field: CoreSchemaField, name: str) -> str:
1684 if field['type'] == 'computed-field':
1685 alias: Any = field.get('alias', name)
1686 elif self.mode == 'validation':
1687 alias = name if not self._config.validate_by_alias else field.get('validation_alias', name)
1688 else:
1689 alias = field.get('serialization_alias', name)
1690 if isinstance(alias, str):
1691 name = alias
1692 elif isinstance(alias, list):
1693 alias = cast('list[str] | str', alias)
1694 for path in alias:
1695 if isinstance(path, list) and len(path) == 1 and isinstance(path[0], str):
1696 # Use the first valid single-item string path; the code that constructs the alias array
1697 # should ensure the first such item is what belongs in the JSON schema
1698 name = path[0]
1699 break
1700 else:
1701 assert_never(alias)
1702 return name
1704 def typed_dict_field_schema(self, schema: core_schema.TypedDictField) -> JsonSchemaValue:
1705 """Generates a JSON schema that matches a schema that defines a typed dict field.
1707 Args:
1708 schema: The core schema.
1710 Returns:
1711 The generated JSON schema.
1712 """
1713 return self.generate_inner(schema['schema'])
1715 def dataclass_field_schema(self, schema: core_schema.DataclassField) -> JsonSchemaValue:
1716 """Generates a JSON schema that matches a schema that defines a dataclass field.
1718 Args:
1719 schema: The core schema.
1721 Returns:
1722 The generated JSON schema.
1723 """
1724 return self.generate_inner(schema['schema'])
1726 def model_field_schema(self, schema: core_schema.ModelField) -> JsonSchemaValue:
1727 """Generates a JSON schema that matches a schema that defines a model field.
1729 Args:
1730 schema: The core schema.
1732 Returns:
1733 The generated JSON schema.
1734 """
1735 return self.generate_inner(schema['schema'])
1737 def computed_field_schema(self, schema: core_schema.ComputedField) -> JsonSchemaValue:
1738 """Generates a JSON schema that matches a schema that defines a computed field.
1740 Args:
1741 schema: The core schema.
1743 Returns:
1744 The generated JSON schema.
1745 """
1746 return self.generate_inner(schema['return_schema'])
1748 def model_schema(self, schema: core_schema.ModelSchema) -> JsonSchemaValue:
1749 """Generates a JSON schema that matches a schema that defines a model.
1751 Args:
1752 schema: The core schema.
1754 Returns:
1755 The generated JSON schema.
1756 """
1757 # We do not use schema['model'].model_json_schema() here
1758 # because it could lead to inconsistent refs handling, etc.
1759 cls = cast('type[BaseModel]', schema['cls'])
1760 config = cls.model_config
1762 with self._config_wrapper_stack.push(config):
1763 json_schema = self.generate_inner(schema['schema'])
1765 self._update_class_schema(json_schema, cls, config)
1767 return json_schema
1769 def _update_class_schema(self, json_schema: JsonSchemaValue, cls: type[Any], config: ConfigDict) -> None:
1770 """Update json_schema with the following, extracted from `config` and `cls`:
1772 * title
1773 * description
1774 * additional properties
1775 * json_schema_extra
1776 * deprecated
1778 Done in place, hence there's no return value as the original json_schema is mutated.
1779 No ref resolving is involved here, as that's not appropriate for simple updates.
1780 """
1781 from ._internal._dataclasses import is_stdlib_dataclass
1782 from .main import BaseModel
1784 if (config_title := config.get('title')) is not None:
1785 json_schema.setdefault('title', config_title)
1786 elif model_title_generator := config.get('model_title_generator'):
1787 title = model_title_generator(cls)
1788 if not isinstance(title, str):
1789 raise TypeError(f'model_title_generator {model_title_generator} must return str, not {title.__class__}')
1790 json_schema.setdefault('title', title)
1791 if 'title' not in json_schema:
1792 json_schema['title'] = cls.__name__
1794 # BaseModel and dataclasses; don't use cls.__doc__ as it will contain the verbose class signature by default
1795 if cls is BaseModel:
1796 docstring = None
1797 elif is_stdlib_dataclass(cls): # For Pydantic dataclasses, we already handle this at class creation
1798 # The `dataclass` module generates a `__doc__` based on the `inspect.signature()`
1799 # result, which we don't want to use as a description. Such `__doc__` startswith
1800 # `cls.__name__(`, which could lead to mistakenly discarding it if for some reason
1801 # an explicitly set class docstring follows the same pattern, but this is unlikely
1802 # to happen.
1803 doc = cls.__doc__
1804 docstring = None if doc is None or doc.startswith(f'{cls.__name__}(') else doc
1805 else:
1806 docstring = cls.__doc__
1808 if docstring:
1809 json_schema.setdefault('description', inspect.cleandoc(docstring))
1811 extra = config.get('extra')
1812 if 'additionalProperties' not in json_schema: # This check is particularly important for `typed_dict_schema()`
1813 if extra == 'allow':
1814 json_schema['additionalProperties'] = True
1815 elif extra == 'forbid':
1816 json_schema['additionalProperties'] = False
1818 json_schema_extra = config.get('json_schema_extra')
1819 if issubclass(cls, BaseModel) and cls.__pydantic_root_model__:
1820 root_json_schema_extra = cls.model_fields['root'].json_schema_extra
1821 if json_schema_extra and root_json_schema_extra:
1822 raise ValueError(
1823 '"model_config[\'json_schema_extra\']" and "Field.json_schema_extra" on "RootModel.root"'
1824 ' field must not be set simultaneously'
1825 )
1826 if root_json_schema_extra:
1827 json_schema_extra = root_json_schema_extra
1829 if isinstance(json_schema_extra, (staticmethod, classmethod)):
1830 # In older versions of python, this is necessary to ensure staticmethod/classmethods are callable
1831 json_schema_extra = json_schema_extra.__get__(cls)
1833 if isinstance(json_schema_extra, dict):
1834 json_schema.update(json_schema_extra)
1835 elif callable(json_schema_extra):
1836 if len(_typing_extra.signature_no_eval(json_schema_extra).parameters) > 1:
1837 json_schema_extra = cast(Callable[[JsonDict, type[Any]], None], json_schema_extra)
1838 json_schema_extra(json_schema, cls)
1839 else:
1840 json_schema_extra = cast(Callable[[JsonDict], None], json_schema_extra)
1841 json_schema_extra(json_schema)
1842 elif json_schema_extra is not None:
1843 raise ValueError(
1844 f"model_config['json_schema_extra']={json_schema_extra} should be a dict, callable, or None"
1845 )
1847 if hasattr(cls, '__deprecated__'):
1848 json_schema['deprecated'] = True
1850 def resolve_ref_schema(self, json_schema: JsonSchemaValue) -> JsonSchemaValue:
1851 """Resolve a JsonSchemaValue to the non-ref schema if it is a $ref schema.
1853 Args:
1854 json_schema: The schema to resolve.
1856 Returns:
1857 The resolved schema.
1859 Raises:
1860 RuntimeError: If the schema reference can't be found in definitions.
1861 """
1862 while '$ref' in json_schema:
1863 ref = json_schema['$ref']
1864 schema_to_update = self.get_schema_from_definitions(JsonRef(ref))
1865 if schema_to_update is None:
1866 raise RuntimeError(f'Cannot update undefined schema for $ref={ref}')
1867 json_schema = schema_to_update
1868 return json_schema
1870 def model_fields_schema(self, schema: core_schema.ModelFieldsSchema) -> JsonSchemaValue:
1871 """Generates a JSON schema that matches a schema that defines a model's fields.
1873 Args:
1874 schema: The core schema.
1876 Returns:
1877 The generated JSON schema.
1878 """
1879 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [
1880 (name, self.field_is_required(field, total=True), field)
1881 for name, field in schema['fields'].items()
1882 if self.field_is_present(field)
1883 ]
1884 if self.mode == 'serialization':
1885 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', [])))
1886 json_schema = self._named_required_fields_schema(named_required_fields)
1887 extras_schema = schema.get('extras_schema', None)
1888 if extras_schema is not None:
1889 schema_to_update = self.resolve_ref_schema(json_schema)
1890 schema_to_update['additionalProperties'] = self.generate_inner(extras_schema)
1891 return json_schema
1893 def field_is_present(self, field: CoreSchemaField) -> bool:
1894 """Whether the field should be included in the generated JSON schema.
1896 Args:
1897 field: The schema for the field itself.
1899 Returns:
1900 `True` if the field should be included in the generated JSON schema, `False` otherwise.
1901 """
1902 if self.mode == 'serialization':
1903 # If you still want to include the field in the generated JSON schema,
1904 # override this method and return True
1905 return not field.get('serialization_exclude')
1906 elif self.mode == 'validation':
1907 return True
1908 else:
1909 assert_never(self.mode)
1911 def field_is_required(
1912 self,
1913 field: core_schema.ModelField | core_schema.DataclassField | core_schema.TypedDictField,
1914 total: bool,
1915 ) -> bool:
1916 """Whether the field should be marked as required in the generated JSON schema.
1917 (Note that this is irrelevant if the field is not present in the JSON schema.).
1919 Args:
1920 field: The schema for the field itself.
1921 total: Only applies to `TypedDictField`s.
1922 Indicates if the `TypedDict` this field belongs to is total, in which case any fields that don't
1923 explicitly specify `required=False` are required.
1925 Returns:
1926 `True` if the field should be marked as required in the generated JSON schema, `False` otherwise.
1927 """
1928 if field['type'] == 'typed-dict-field':
1929 required = field.get('required', total)
1930 else:
1931 required = field['schema']['type'] != 'default'
1933 if self.mode == 'serialization':
1934 has_exclude_if = field.get('serialization_exclude_if') is not None
1935 if self._config.json_schema_serialization_defaults_required:
1936 return not has_exclude_if
1937 else:
1938 return required and not has_exclude_if
1939 else:
1940 return required
1942 def dataclass_args_schema(self, schema: core_schema.DataclassArgsSchema) -> JsonSchemaValue:
1943 """Generates a JSON schema that matches a schema that defines a dataclass's constructor arguments.
1945 Args:
1946 schema: The core schema.
1948 Returns:
1949 The generated JSON schema.
1950 """
1951 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [
1952 (field['name'], self.field_is_required(field, total=True), field)
1953 for field in schema['fields']
1954 if self.field_is_present(field)
1955 ]
1956 if self.mode == 'serialization':
1957 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', [])))
1958 return self._named_required_fields_schema(named_required_fields)
1960 def dataclass_schema(self, schema: core_schema.DataclassSchema) -> JsonSchemaValue:
1961 """Generates a JSON schema that matches a schema that defines a dataclass.
1963 Args:
1964 schema: The core schema.
1966 Returns:
1967 The generated JSON schema.
1968 """
1970 cls = schema['cls']
1971 # If (stdlib) dataclass doesn't have config, the parent's config is used (as during core schema generation):
1972 config = cast('ConfigDict | None', getattr(cls, '__pydantic_config__', None))
1974 with self._config_wrapper_stack.push(config):
1975 json_schema = self.generate_inner(schema['schema']).copy()
1977 # Only the parent's `extra` config value is relevant to the class schema. It is propagated
1978 # to the core config (and used for validation), so we reuse it:
1979 class_config = config or {}
1980 if 'extra' not in class_config and (extra := schema.get('config', {}).get('extra_fields_behavior')) is not None:
1981 class_config = cast('ConfigDict', {**class_config, 'extra': extra})
1983 self._update_class_schema(json_schema, cls, class_config)
1985 return json_schema
1987 def named_tuple_field_schema(self, schema: core_schema.NamedTupleField) -> JsonSchemaValue:
1988 """Generates a JSON schema that matches a schema that defines a named tuple field.
1990 Args:
1991 schema: The core schema.
1993 Returns:
1994 The generated JSON schema.
1995 """
1996 return self.generate_inner(schema['schema'])
1998 def named_tuple_schema(self, schema: core_schema.NamedTupleSchema) -> JsonSchemaValue:
1999 """Generates a JSON schema that matches a schema that defines a named tuple.
2001 Args:
2002 schema: The core schema.
2004 Returns:
2005 The generated JSON schema.
2006 """
2007 prefix_items: list[JsonSchemaValue] = []
2008 min_items = 0
2010 for field in schema['fields']:
2011 name = field['name']
2012 if self.by_alias:
2013 alias = field.get('validation_alias')
2014 if isinstance(alias, str):
2015 name = alias
2017 field_schema = self.generate_inner(field['schema']).copy()
2018 if 'title' not in field_schema and self.field_title_should_be_set(field['schema']):
2019 field_schema['title'] = self.get_title_from_name(name)
2020 prefix_items.append(field_schema)
2022 if field['schema']['type'] != 'default':
2023 # This assumes that if the field has a default value,
2024 # the inner schema must be of type WithDefaultSchema.
2025 min_items += 1
2027 json_schema: JsonSchemaValue = {'type': 'array'}
2028 if prefix_items:
2029 json_schema['prefixItems'] = prefix_items
2030 if min_items:
2031 json_schema['minItems'] = min_items
2032 json_schema['maxItems'] = len(prefix_items)
2034 return json_schema
2036 def arguments_schema(self, schema: core_schema.ArgumentsSchema) -> JsonSchemaValue:
2037 """Generates a JSON schema that matches a schema that defines a function's arguments.
2039 Args:
2040 schema: The core schema.
2042 Returns:
2043 The generated JSON schema.
2044 """
2045 prefer_positional = schema.get('metadata', {}).get('pydantic_js_prefer_positional_arguments')
2047 arguments = schema['arguments_schema']
2048 kw_only_arguments = [a for a in arguments if a.get('mode') == 'keyword_only']
2049 kw_or_p_arguments = [a for a in arguments if a.get('mode') in {'positional_or_keyword', None}]
2050 p_only_arguments = [a for a in arguments if a.get('mode') == 'positional_only']
2051 var_args_schema = schema.get('var_args_schema')
2052 var_kwargs_schema = schema.get('var_kwargs_schema')
2054 if prefer_positional:
2055 positional_possible = not kw_only_arguments and not var_kwargs_schema
2056 if positional_possible:
2057 return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema)
2059 keyword_possible = not p_only_arguments and not var_args_schema
2060 if keyword_possible:
2061 return self.kw_arguments_schema(kw_or_p_arguments + kw_only_arguments, var_kwargs_schema)
2063 if not prefer_positional:
2064 positional_possible = not kw_only_arguments and not var_kwargs_schema
2065 if positional_possible:
2066 return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema)
2068 raise PydanticInvalidForJsonSchema(
2069 'Unable to generate JSON schema for arguments validator with positional-only and keyword-only arguments'
2070 )
2072 def kw_arguments_schema(
2073 self, arguments: list[core_schema.ArgumentsParameter], var_kwargs_schema: CoreSchema | None
2074 ) -> JsonSchemaValue:
2075 """Generates a JSON schema that matches a schema that defines a function's keyword arguments.
2077 Args:
2078 arguments: The core schema.
2080 Returns:
2081 The generated JSON schema.
2082 """
2083 properties: dict[str, JsonSchemaValue] = {}
2084 required: list[str] = []
2085 for argument in arguments:
2086 name = self.get_argument_name(argument)
2087 argument_schema = self.generate_inner(argument['schema']).copy()
2088 if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']):
2089 argument_schema['title'] = self.get_title_from_name(name)
2090 properties[name] = argument_schema
2092 if argument['schema']['type'] != 'default':
2093 # This assumes that if the argument has a default value,
2094 # the inner schema must be of type WithDefaultSchema.
2095 # I believe this is true, but I am not 100% sure
2096 required.append(name)
2098 json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties}
2099 if required:
2100 json_schema['required'] = required
2102 if var_kwargs_schema:
2103 additional_properties_schema = self.generate_inner(var_kwargs_schema)
2104 if additional_properties_schema:
2105 json_schema['additionalProperties'] = additional_properties_schema
2106 else:
2107 json_schema['additionalProperties'] = False
2108 return json_schema
2110 def p_arguments_schema(
2111 self, arguments: list[core_schema.ArgumentsParameter], var_args_schema: CoreSchema | None
2112 ) -> JsonSchemaValue:
2113 """Generates a JSON schema that matches a schema that defines a function's positional arguments.
2115 Args:
2116 arguments: The core schema.
2118 Returns:
2119 The generated JSON schema.
2120 """
2121 prefix_items: list[JsonSchemaValue] = []
2122 min_items = 0
2124 for argument in arguments:
2125 name = self.get_argument_name(argument)
2127 argument_schema = self.generate_inner(argument['schema']).copy()
2128 if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']):
2129 argument_schema['title'] = self.get_title_from_name(name)
2130 prefix_items.append(argument_schema)
2132 if argument['schema']['type'] != 'default':
2133 # This assumes that if the argument has a default value,
2134 # the inner schema must be of type WithDefaultSchema.
2135 # I believe this is true, but I am not 100% sure
2136 min_items += 1
2138 json_schema: JsonSchemaValue = {'type': 'array'}
2139 if prefix_items:
2140 json_schema['prefixItems'] = prefix_items
2141 if min_items:
2142 json_schema['minItems'] = min_items
2144 if var_args_schema:
2145 items_schema = self.generate_inner(var_args_schema)
2146 if items_schema:
2147 json_schema['items'] = items_schema
2148 else:
2149 json_schema['maxItems'] = len(prefix_items)
2151 return json_schema
2153 def get_argument_name(self, argument: core_schema.ArgumentsParameter | core_schema.ArgumentsV3Parameter) -> str:
2154 """Retrieves the name of an argument.
2156 Args:
2157 argument: The core schema.
2159 Returns:
2160 The name of the argument.
2161 """
2162 name = argument['name']
2163 if self.by_alias:
2164 alias = argument.get('alias')
2165 if isinstance(alias, str):
2166 name = alias
2167 else:
2168 pass # might want to do something else?
2169 return name
2171 def arguments_v3_schema(self, schema: core_schema.ArgumentsV3Schema) -> JsonSchemaValue:
2172 """Generates a JSON schema that matches a schema that defines a function's arguments.
2174 Args:
2175 schema: The core schema.
2177 Returns:
2178 The generated JSON schema.
2179 """
2180 arguments = schema['arguments_schema']
2181 properties: dict[str, JsonSchemaValue] = {}
2182 required: list[str] = []
2183 for argument in arguments:
2184 mode = argument.get('mode', 'positional_or_keyword')
2185 name = self.get_argument_name(argument)
2186 argument_schema = self.generate_inner(argument['schema']).copy()
2187 if mode == 'var_args':
2188 argument_schema = {'type': 'array', 'items': argument_schema}
2189 elif mode == 'var_kwargs_uniform':
2190 argument_schema = {'type': 'object', 'additionalProperties': argument_schema}
2192 argument_schema.setdefault('title', self.get_title_from_name(name))
2193 properties[name] = argument_schema
2195 if (
2196 (mode == 'var_kwargs_unpacked_typed_dict' and 'required' in argument_schema)
2197 or mode not in {'var_args', 'var_kwargs_uniform', 'var_kwargs_unpacked_typed_dict'}
2198 and argument['schema']['type'] != 'default'
2199 ):
2200 # This assumes that if the argument has a default value,
2201 # the inner schema must be of type WithDefaultSchema.
2202 # I believe this is true, but I am not 100% sure
2203 required.append(name)
2205 json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties}
2206 if required:
2207 json_schema['required'] = required
2208 return json_schema
2210 def call_schema(self, schema: core_schema.CallSchema) -> JsonSchemaValue:
2211 """Generates a JSON schema that matches a schema that defines a function call.
2213 Args:
2214 schema: The core schema.
2216 Returns:
2217 The generated JSON schema.
2218 """
2219 return self.generate_inner(schema['arguments_schema'])
2221 def custom_error_schema(self, schema: core_schema.CustomErrorSchema) -> JsonSchemaValue:
2222 """Generates a JSON schema that matches a schema that defines a custom error.
2224 Args:
2225 schema: The core schema.
2227 Returns:
2228 The generated JSON schema.
2229 """
2230 return self.generate_inner(schema['schema'])
2232 def json_schema(self, schema: core_schema.JsonSchema) -> JsonSchemaValue:
2233 """Generates a JSON schema that matches a schema that defines a JSON object.
2235 Args:
2236 schema: The core schema.
2238 Returns:
2239 The generated JSON schema.
2240 """
2241 content_core_schema = schema.get('schema') or core_schema.any_schema()
2242 content_json_schema = self.generate_inner(content_core_schema)
2243 if self.mode == 'validation':
2244 return {'type': 'string', 'contentMediaType': 'application/json', 'contentSchema': content_json_schema}
2245 else:
2246 # self.mode == 'serialization'
2247 return content_json_schema
2249 def url_schema(self, schema: core_schema.UrlSchema) -> JsonSchemaValue:
2250 """Generates a JSON schema that matches a schema that defines a URL.
2252 Args:
2253 schema: The core schema.
2255 Returns:
2256 The generated JSON schema.
2257 """
2258 json_schema = {'type': 'string', 'format': 'uri', 'minLength': 1}
2259 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string)
2260 return json_schema
2262 def multi_host_url_schema(self, schema: core_schema.MultiHostUrlSchema) -> JsonSchemaValue:
2263 """Generates a JSON schema that matches a schema that defines a URL that can be used with multiple hosts.
2265 Args:
2266 schema: The core schema.
2268 Returns:
2269 The generated JSON schema.
2270 """
2271 # Note: 'multi-host-uri' is a custom/pydantic-specific format, not part of the JSON Schema spec
2272 json_schema = {'type': 'string', 'format': 'multi-host-uri', 'minLength': 1}
2273 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string)
2274 return json_schema
2276 def uuid_schema(self, schema: core_schema.UuidSchema) -> JsonSchemaValue:
2277 """Generates a JSON schema that matches a UUID.
2279 Args:
2280 schema: The core schema.
2282 Returns:
2283 The generated JSON schema.
2284 """
2285 return {'type': 'string', 'format': 'uuid'}
2287 def definitions_schema(self, schema: core_schema.DefinitionsSchema) -> JsonSchemaValue:
2288 """Generates a JSON schema that matches a schema that defines a JSON object with definitions.
2290 Args:
2291 schema: The core schema.
2293 Returns:
2294 The generated JSON schema.
2295 """
2296 for definition in schema['definitions']:
2297 try:
2298 self.generate_inner(definition)
2299 except PydanticInvalidForJsonSchema as e: # noqa: PERF203
2300 core_ref: CoreRef = CoreRef(definition['ref']) # type: ignore
2301 self._core_defs_invalid_for_json_schema[self.get_defs_ref((core_ref, self.mode))] = e
2302 continue
2303 return self.generate_inner(schema['schema'])
2305 def definition_ref_schema(self, schema: core_schema.DefinitionReferenceSchema) -> JsonSchemaValue:
2306 """Generates a JSON schema that matches a schema that references a definition.
2308 Args:
2309 schema: The core schema.
2311 Returns:
2312 The generated JSON schema.
2313 """
2314 core_ref = CoreRef(schema['schema_ref'])
2315 _, ref_json_schema = self.get_cache_defs_ref_schema(core_ref)
2316 return ref_json_schema
2318 def ser_schema(
2319 self, schema: core_schema.SerSchema | core_schema.IncExSeqSerSchema | core_schema.IncExDictSerSchema
2320 ) -> JsonSchemaValue | None:
2321 """Generates a JSON schema that matches a schema that defines a serialized object.
2323 Args:
2324 schema: The core schema.
2326 Returns:
2327 The generated JSON schema.
2328 """
2329 match schema:
2330 case {'type': 'function-plain' | 'function-wrap'}:
2331 # PlainSerializerFunctionSerSchema or WrapSerializerFunctionSerSchema
2332 return_schema = schema.get('return_schema')
2333 if return_schema is not None:
2334 return self.generate_inner(return_schema)
2335 case {'type': 'format' | 'to-string'}:
2336 # FormatSerSchema or ToStringSerSchema
2337 return self.str_schema(core_schema.str_schema())
2338 case {'type': 'model'}:
2339 # ModelSerSchema
2340 return self.generate_inner(schema['schema'])
2341 return None
2343 def complex_schema(self, schema: core_schema.ComplexSchema) -> JsonSchemaValue:
2344 """Generates a JSON schema that matches a complex number.
2346 JSON has no standard way to represent complex numbers. Complex number is not a numeric
2347 type. Here we represent complex number as strings following the rule defined by Python.
2348 For instance, '1+2j' is an accepted complex string. Details can be found in
2349 [Python's `complex` documentation][complex].
2351 Args:
2352 schema: The core schema.
2354 Returns:
2355 The generated JSON schema.
2356 """
2357 return {'type': 'string'}
2359 # ### Utility methods
2361 def get_title_from_name(self, name: str) -> str:
2362 """Retrieves a title from a name.
2364 Args:
2365 name: The name to retrieve a title from.
2367 Returns:
2368 The title.
2369 """
2370 return name.title().replace('_', ' ').strip()
2372 def field_title_should_be_set(self, schema: CoreSchemaOrField) -> bool:
2373 """Returns true if a field with the given schema should have a title set based on the field name.
2375 Intuitively, we want this to return true for schemas that wouldn't otherwise provide their own title
2376 (e.g., int, float, str), and false for those that would (e.g., BaseModel subclasses).
2378 Args:
2379 schema: The schema to check.
2381 Returns:
2382 `True` if the field should have a title set, `False` otherwise.
2383 """
2384 if _core_utils.is_core_schema_field(schema):
2385 if schema['type'] == 'computed-field':
2386 field_schema = schema['return_schema']
2387 else:
2388 field_schema = schema['schema']
2389 return self.field_title_should_be_set(field_schema)
2391 elif _core_utils.is_core_schema(schema):
2392 if schema.get('ref'): # things with refs, such as models and enums, should not have titles set
2393 return False
2394 if schema['type'] in {'default', 'nullable', 'definitions'}:
2395 return self.field_title_should_be_set(schema['schema']) # type: ignore[typeddict-item]
2396 if schema['type'] == 'missing-sentinel' and (inner_schema := schema.get('schema')) is not None:
2397 return self.field_title_should_be_set(inner_schema)
2398 if _core_utils.is_function_with_inner_schema(schema):
2399 return self.field_title_should_be_set(schema['schema'])
2400 if schema['type'] == 'definition-ref':
2401 # Referenced schemas should not have titles set for the same reason
2402 # schemas with refs should not
2403 return False
2404 return True # anything else should have title set
2406 else:
2407 raise PydanticInvalidForJsonSchema(f'Unexpected schema type: schema={schema}') # pragma: no cover
2409 def normalize_name(self, name: str) -> str:
2410 """Normalizes a name to be used as a key in a dictionary.
2412 Args:
2413 name: The name to normalize.
2415 Returns:
2416 The normalized name.
2417 """
2418 return re.sub(r'[^a-zA-Z0-9.\-_]', '_', name).replace('.', '__')
2420 def get_defs_ref(self, core_mode_ref: CoreModeRef) -> DefsRef:
2421 """Override this method to change the way that definitions keys are generated from a core reference.
2423 Args:
2424 core_mode_ref: The core reference.
2426 Returns:
2427 The definitions key.
2428 """
2429 # Split the core ref into "components"; generic origins and arguments are each separate components
2430 core_ref, mode = core_mode_ref
2431 components = re.split(r'([\][,])', core_ref)
2432 # Remove IDs from each component
2433 components = [x.rsplit(':', 1)[0] for x in components]
2434 core_ref_no_id = ''.join(components)
2435 # Remove everything before the last period from each "component"
2436 components = [re.sub(r'(?:[^.[\]]+\.)+((?:[^.[\]]+))', r'\1', x) for x in components]
2437 short_ref = ''.join(components)
2439 mode_title = _MODE_TITLE_MAPPING[mode]
2441 # It is important that the generated defs_ref values be such that at least one choice will not
2442 # be generated for any other core_ref. Currently, this should be the case because we include
2443 # the id of the source type in the core_ref
2444 name = DefsRef(self.normalize_name(short_ref))
2445 name_mode = DefsRef(self.normalize_name(short_ref) + f'-{mode_title}')
2446 module_qualname = DefsRef(self.normalize_name(core_ref_no_id))
2447 module_qualname_mode = DefsRef(f'{module_qualname}-{mode_title}')
2448 module_qualname_id = DefsRef(self.normalize_name(core_ref))
2449 occurrence_index = self._collision_index.get(module_qualname_id)
2450 if occurrence_index is None:
2451 self._collision_counter[module_qualname] += 1
2452 occurrence_index = self._collision_index[module_qualname_id] = self._collision_counter[module_qualname]
2454 module_qualname_occurrence = DefsRef(f'{module_qualname}__{occurrence_index}')
2455 module_qualname_occurrence_mode = DefsRef(f'{module_qualname_mode}__{occurrence_index}')
2457 self._prioritized_defsref_choices[module_qualname_occurrence_mode] = [
2458 name,
2459 name_mode,
2460 module_qualname,
2461 module_qualname_mode,
2462 module_qualname_occurrence,
2463 module_qualname_occurrence_mode,
2464 ]
2466 return module_qualname_occurrence_mode
2468 def get_cache_defs_ref_schema(self, core_ref: CoreRef) -> tuple[DefsRef, JsonSchemaValue]:
2469 """This method wraps the get_defs_ref method with some cache-lookup/population logic,
2470 and returns both the produced defs_ref and the JSON schema that will refer to the right definition.
2472 Args:
2473 core_ref: The core reference to get the definitions reference for.
2475 Returns:
2476 A tuple of the definitions reference and the JSON schema that will refer to it.
2477 """
2478 core_mode_ref = (core_ref, self.mode)
2479 maybe_defs_ref = self.core_to_defs_refs.get(core_mode_ref)
2480 if maybe_defs_ref is not None:
2481 json_ref = self.core_to_json_refs[core_mode_ref]
2482 return maybe_defs_ref, {'$ref': json_ref}
2484 defs_ref = self.get_defs_ref(core_mode_ref)
2486 # populate the ref translation mappings
2487 self.core_to_defs_refs[core_mode_ref] = defs_ref
2488 self.defs_to_core_refs[defs_ref] = core_mode_ref
2490 json_ref = JsonRef(self.ref_template.format(model=defs_ref))
2491 self.core_to_json_refs[core_mode_ref] = json_ref
2492 self.json_to_defs_refs[json_ref] = defs_ref
2493 ref_json_schema = {'$ref': json_ref}
2494 return defs_ref, ref_json_schema
2496 def handle_ref_overrides(self, json_schema: JsonSchemaValue) -> JsonSchemaValue:
2497 """Remove any sibling keys that are redundant with the referenced schema.
2499 Args:
2500 json_schema: The schema to remove redundant sibling keys from.
2502 Returns:
2503 The schema with redundant sibling keys removed.
2504 """
2505 if '$ref' in json_schema:
2506 # prevent modifications to the input; this copy may be safe to drop if there is significant overhead
2507 json_schema = json_schema.copy()
2509 referenced_json_schema = self.get_schema_from_definitions(JsonRef(json_schema['$ref']))
2510 if referenced_json_schema is None:
2511 # This can happen when building schemas for models with not-yet-defined references.
2512 # It may be a good idea to do a recursive pass at the end of the generation to remove
2513 # any redundant override keys.
2514 return json_schema
2515 for k, v in list(json_schema.items()):
2516 if k == '$ref':
2517 continue
2518 if k in referenced_json_schema and referenced_json_schema[k] == v:
2519 del json_schema[k] # redundant key
2521 return json_schema
2523 def get_schema_from_definitions(self, json_ref: JsonRef) -> JsonSchemaValue | None:
2524 try:
2525 def_ref = self.json_to_defs_refs[json_ref]
2526 if def_ref in self._core_defs_invalid_for_json_schema:
2527 raise self._core_defs_invalid_for_json_schema[def_ref]
2528 return self.definitions.get(def_ref, None)
2529 except KeyError:
2530 if json_ref.startswith(('http://', 'https://')):
2531 return None
2532 raise
2534 def encode_default(self, dft: Any) -> Any:
2535 """Encode a default value to a JSON-serializable value.
2537 This is used to encode default values for fields in the generated JSON schema.
2539 Args:
2540 dft: The default value to encode.
2542 Returns:
2543 The encoded default value.
2544 """
2545 from .type_adapter import TypeAdapter, _type_has_config
2547 config = self._config.config_dict
2548 if self.mode == 'validation' and ('ser_json_temporal' in config or 'ser_json_timedelta' in config):
2549 # Temporal serialization formats aren't applied to validation JSON Schemas (see `_common_temporal_schema()`),
2550 # so they shouldn't be applied to the default either:
2551 config = cast(
2552 'ConfigDict',
2553 {k: v for k, v in config.items() if k not in ('ser_json_temporal', 'ser_json_timedelta')},
2554 )
2555 try:
2556 # Types with their own config (e.g. models) can't be used with a config, so we serialize "as Any",
2557 # which will serialize using their own serializer.
2558 return TypeAdapter(Any if _type_has_config(type(dft)) else type(dft), config=config).dump_python(
2559 dft, by_alias=self.by_alias, mode='json'
2560 )
2561 except Exception as e:
2562 raise pydantic_core.PydanticSerializationError(f'Unable to encode default value {dft}') from e
2564 def update_with_validations(
2565 self, json_schema: JsonSchemaValue, core_schema: CoreSchema, mapping: dict[str, str]
2566 ) -> None:
2567 """Update the json_schema with the corresponding validations specified in the core_schema,
2568 using the provided mapping to translate keys in core_schema to the appropriate keys for a JSON schema.
2570 Args:
2571 json_schema: The JSON schema to update.
2572 core_schema: The core schema to get the validations from.
2573 mapping: A mapping from core_schema attribute names to the corresponding JSON schema attribute names.
2574 """
2575 for core_key, json_schema_key in mapping.items():
2576 if core_key in core_schema:
2577 json_schema[json_schema_key] = core_schema[core_key]
2579 class ValidationsMapping:
2580 """This class just contains mappings from core_schema attribute names to the corresponding
2581 JSON schema attribute names. While I suspect it is unlikely to be necessary, you can in
2582 principle override this class in a subclass of GenerateJsonSchema (by inheriting from
2583 GenerateJsonSchema.ValidationsMapping) to change these mappings.
2584 """
2586 numeric = {
2587 'multiple_of': 'multipleOf',
2588 'le': 'maximum',
2589 'ge': 'minimum',
2590 'lt': 'exclusiveMaximum',
2591 'gt': 'exclusiveMinimum',
2592 }
2593 bytes = {
2594 'min_length': 'minLength',
2595 'max_length': 'maxLength',
2596 }
2597 string = {
2598 'min_length': 'minLength',
2599 'max_length': 'maxLength',
2600 'pattern': 'pattern',
2601 }
2602 array = {
2603 'min_length': 'minItems',
2604 'max_length': 'maxItems',
2605 }
2606 object = {
2607 'min_length': 'minProperties',
2608 'max_length': 'maxProperties',
2609 }
2611 def get_flattened_anyof(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue:
2612 members = []
2613 for schema in schemas:
2614 if len(schema) == 1 and 'anyOf' in schema:
2615 members.extend(schema['anyOf'])
2616 else:
2617 members.append(schema)
2618 members = _deduplicate_schemas(members)
2619 if len(members) == 1:
2620 return members[0]
2621 return {'anyOf': members}
2623 def get_json_ref_counts(self, json_schema: JsonSchemaValue) -> dict[JsonRef, int]:
2624 """Get all values corresponding to the key '$ref' anywhere in the json_schema."""
2625 json_refs: dict[JsonRef, int] = Counter()
2627 def _add_json_refs(schema: Any) -> None:
2628 if isinstance(schema, dict):
2629 if '$ref' in schema:
2630 json_ref = JsonRef(schema['$ref'])
2631 if not isinstance(json_ref, str):
2632 return # in this case, '$ref' might have been the name of a property
2633 already_visited = json_ref in json_refs
2634 json_refs[json_ref] += 1
2635 if already_visited:
2636 return # prevent recursion on a definition that was already visited
2637 try:
2638 defs_ref = self.json_to_defs_refs[json_ref]
2639 if defs_ref in self._core_defs_invalid_for_json_schema:
2640 raise self._core_defs_invalid_for_json_schema[defs_ref]
2641 _add_json_refs(self.definitions[defs_ref])
2642 except KeyError:
2643 if not json_ref.startswith(('http://', 'https://')):
2644 raise
2646 for k, v in schema.items():
2647 if k == 'examples' and isinstance(v, list):
2648 # Skip examples that may contain arbitrary values and references
2649 # (see the comment in `_get_all_json_refs` for more details).
2650 continue
2651 _add_json_refs(v)
2652 elif isinstance(schema, list):
2653 for v in schema:
2654 _add_json_refs(v)
2656 _add_json_refs(json_schema)
2657 return json_refs
2659 def handle_invalid_for_json_schema(self, schema: CoreSchemaOrField, error_info: str) -> JsonSchemaValue:
2660 raise PydanticInvalidForJsonSchema(f'Cannot generate a JsonSchema for {error_info}')
2662 def emit_warning(self, kind: JsonSchemaWarningKind, detail: str) -> None:
2663 """This method simply emits PydanticJsonSchemaWarnings based on handling in the `warning_message` method."""
2664 message = self.render_warning_message(kind, detail)
2665 if message is not None:
2666 warnings.warn(message, PydanticJsonSchemaWarning)
2668 def render_warning_message(self, kind: JsonSchemaWarningKind, detail: str) -> str | None:
2669 """This method is responsible for ignoring warnings as desired, and for formatting the warning messages.
2671 You can override the value of `ignored_warning_kinds` in a subclass of GenerateJsonSchema
2672 to modify what warnings are generated. If you want more control, you can override this method;
2673 just return None in situations where you don't want warnings to be emitted.
2675 Args:
2676 kind: The kind of warning to render. It can be one of the following:
2678 - 'skipped-choice': A choice field was skipped because it had no valid choices.
2679 - 'non-serializable-default': A default value was skipped because it was not JSON-serializable.
2680 detail: A string with additional details about the warning.
2682 Returns:
2683 The formatted warning message, or `None` if no warning should be emitted.
2684 """
2685 if kind in self.ignored_warning_kinds:
2686 return None
2687 return f'{detail} [{kind}]'
2689 def _build_definitions_remapping(self) -> _DefinitionsRemapping:
2690 defs_to_json: dict[DefsRef, JsonRef] = {}
2691 for defs_refs in self._prioritized_defsref_choices.values():
2692 for defs_ref in defs_refs:
2693 json_ref = JsonRef(self.ref_template.format(model=defs_ref))
2694 defs_to_json[defs_ref] = json_ref
2696 return _DefinitionsRemapping.from_prioritized_choices(
2697 self._prioritized_defsref_choices, defs_to_json, self.definitions
2698 )
2700 def _garbage_collect_definitions(self, schema: JsonSchemaValue) -> None:
2701 visited_defs_refs: set[DefsRef] = set()
2702 unvisited_json_refs = _get_all_json_refs(schema)
2703 while unvisited_json_refs:
2704 next_json_ref = unvisited_json_refs.pop()
2705 try:
2706 next_defs_ref = self.json_to_defs_refs[next_json_ref]
2707 if next_defs_ref in visited_defs_refs:
2708 continue
2709 visited_defs_refs.add(next_defs_ref)
2710 unvisited_json_refs.update(_get_all_json_refs(self.definitions[next_defs_ref]))
2711 except KeyError:
2712 if not next_json_ref.startswith(('http://', 'https://')):
2713 raise
2715 self.definitions = {k: v for k, v in self.definitions.items() if k in visited_defs_refs}
2718# ##### Start JSON Schema Generation Functions #####
2721def model_json_schema(
2722 cls: type[BaseModel] | type[PydanticDataclass],
2723 by_alias: bool = True,
2724 ref_template: str = DEFAULT_REF_TEMPLATE,
2725 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of',
2726 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
2727 mode: JsonSchemaMode = 'validation',
2728) -> dict[str, Any]:
2729 """Utility function to generate a JSON Schema for a model.
2731 Args:
2732 cls: The model class to generate a JSON Schema for.
2733 by_alias: If `True` (the default), fields will be serialized according to their alias.
2734 If `False`, fields will be serialized according to their attribute name.
2735 ref_template: The template to use for generating JSON Schema references.
2736 union_format: The format to use when combining schemas from unions together. Can be one of:
2738 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf)
2739 keyword to combine schemas (the default).
2740 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type)
2741 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive
2742 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to
2743 `any_of`.
2744 schema_generator: The class to use for generating the JSON Schema.
2745 mode: The mode to use for generating the JSON Schema. It can be one of the following:
2747 - 'validation': Generate a JSON Schema for validating data.
2748 - 'serialization': Generate a JSON Schema for serializing data.
2750 Returns:
2751 The generated JSON Schema.
2752 """
2753 from .main import BaseModel
2755 schema_generator_instance = schema_generator(
2756 by_alias=by_alias, ref_template=ref_template, union_format=union_format
2757 )
2759 if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema):
2760 cls.__pydantic_core_schema__.rebuild()
2762 if cls is BaseModel:
2763 raise AttributeError('model_json_schema() must be called on a subclass of BaseModel, not BaseModel itself.')
2765 assert not isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema), 'this is a bug! please report it'
2766 return schema_generator_instance.generate(cls.__pydantic_core_schema__, mode=mode)
2769def models_json_schema(
2770 models: Sequence[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode]],
2771 *,
2772 by_alias: bool = True,
2773 title: str | None = None,
2774 description: str | None = None,
2775 ref_template: str = DEFAULT_REF_TEMPLATE,
2776 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of',
2777 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,
2778) -> tuple[dict[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode], JsonSchemaValue], JsonSchemaValue]:
2779 """Utility function to generate a JSON Schema for multiple models.
2781 Args:
2782 models: A sequence of tuples of the form (model, mode).
2783 by_alias: Whether field aliases should be used as keys in the generated JSON Schema.
2784 title: The title of the generated JSON Schema.
2785 description: The description of the generated JSON Schema.
2786 ref_template: The reference template to use for generating JSON Schema references.
2787 union_format: The format to use when combining schemas from unions together. Can be one of:
2789 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf)
2790 keyword to combine schemas (the default).
2791 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type)
2792 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive
2793 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to
2794 `any_of`.
2795 schema_generator: The schema generator to use for generating the JSON Schema.
2797 Returns:
2798 A tuple where:
2799 - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and
2800 whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have
2801 JsonRef references to definitions that are defined in the second returned element.)
2802 - The second element is a JSON schema containing all definitions referenced in the first returned
2803 element, along with the optional title and description keys.
2804 """
2805 for cls, _ in models:
2806 if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema):
2807 cls.__pydantic_core_schema__.rebuild()
2809 instance = schema_generator(by_alias=by_alias, ref_template=ref_template, union_format=union_format)
2810 inputs: list[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode, CoreSchema]] = [
2811 (m, mode, m.__pydantic_core_schema__) for m, mode in models
2812 ]
2813 json_schemas_map, definitions = instance.generate_definitions(inputs)
2815 json_schema: dict[str, Any] = {}
2816 if definitions:
2817 json_schema['$defs'] = definitions
2818 if title:
2819 json_schema['title'] = title
2820 if description:
2821 json_schema['description'] = description
2823 return json_schemas_map, json_schema
2826# ##### End JSON Schema Generation Functions #####
2829# Decimal JSON Schema pattern utilities.
2830# Note: while we could use `\d` to represent digits (`Decimal` objects allow Unicode decimal digits), we use [0-9] instead:
2831# - to reduce complexity (`\d` expands to a large set, and with `max_digits`/`decimal_places` constraints set, it can blow
2832# the compile limit on some engines).
2833# - [0-9] is a portable pattern (e.g. `\d` is equivalent to `[0-9]` in ECMA (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Character_class_escape#d)
2834# while it allows any Unicode decimal digit in Python.
2837def _repeat(atom: str, min_count: int, max_count: int) -> str:
2838 """Build a regex repetition of `atom` between `min_count` and `max_count` times (inclusive), using the shortest quantifier."""
2839 if min_count > max_count or max_count == 0:
2840 return ''
2841 if min_count == max_count:
2842 return atom if min_count == 1 else f'{atom}{{{min_count}}}'
2843 if (min_count, max_count) == (0, 1):
2844 return f'{atom}?'
2845 return f'{atom}{{{min_count},{max_count}}}'
2848def _int_range_pattern(max_value: int) -> str:
2849 """Build a regex matching the integers between 1 and `max_value` (inclusive), without leading zeros."""
2850 digits = str(max_value)
2851 length = len(digits)
2852 alts: list[str] = [f'[1-9]{_repeat("[0-9]", k - 1, k - 1)}' for k in range(1, length)]
2853 for i, char in enumerate(digits):
2854 d = int(char)
2855 low = 1 if i == 0 else 0
2856 high = d if i == length - 1 else d - 1
2857 if high >= low:
2858 char_class = str(low) if low == high else f'[{low}-{high}]'
2859 alts.append(f'{digits[:i]}{char_class}{_repeat("[0-9]", length - i - 1, length - i - 1)}')
2860 return alts[0] if len(alts) == 1 else f'(?:{"|".join(alts)})'
2863def _decimal_validation_pattern(max_digits: int | None, decimal_places: int | None, allow_inf_nan: bool) -> str:
2864 """Build the decimal pattern for the `'validation'` mode."""
2866 number = r'(?:[0-9]+\.?[0-9]*|\.[0-9]+)' # e.g. 1, 1., .5
2867 exponent = r'[eE][+-]?[0-9]+' # e.g. e5, E5, e-7
2868 # e.g. Infinity, inf, NaN, sNaN, NaN123 (case-insensitive). Spelled out explicitly, as inline flags
2869 # aren't supported by all regular expression engines:
2870 non_finite = r'|[iI][nN][fF](?:[iI][nN][iI][tT][yY])?|[sS]?[nN][aA][nN][0-9]*' if allow_inf_nan else ''
2871 if max_digits is None and decimal_places is None:
2872 return rf'^[+-]?(?:{number}(?:{exponent})?{non_finite})$'
2874 alts: list[str] = []
2875 if max_digits is not None and decimal_places is not None:
2876 # If `decimal_places > max_digits`, no whole digits are allowed and `max_digits` bounds the decimal places:
2877 whole = max(0, max_digits - decimal_places)
2878 places = min(max_digits, decimal_places)
2879 int_part = f'0*{_repeat("[0-9]", 1, whole)}' if whole else '0+'
2880 opt_int_part = f'0*{_repeat("[0-9]", 0, whole)}'
2881 opt_frac_part = rf'\.{_repeat("[0-9]", 0, places)}0*'
2882 frac_part = rf'\.{_repeat("[0-9]", 1, places)}0*' if places else r'\.0+'
2883 alts += [f'{int_part}(?:{opt_frac_part})?', f'{opt_int_part}{frac_part}']
2884 elif max_digits is not None:
2885 # The whole part and decimal part must have at most `max_digits` digits in total,
2886 # so we enumerate the possible widths of the whole part:
2887 int_part = f'0*{_repeat("[0-9]", 1, max_digits)}' if max_digits else '0+'
2888 alts.append(rf'{int_part}(?:\.0*)?')
2889 alts += [
2890 rf'0*{_repeat("[0-9]", whole, whole)}\.{_repeat("[0-9]", 1, max_digits - whole)}0*'
2891 for whole in range(max_digits)
2892 ] or [r'0*\.0+']
2893 else:
2894 assert decimal_places is not None
2895 opt_frac_part = rf'\.{_repeat("[0-9]", 0, decimal_places)}0*'
2896 frac_part = rf'\.{_repeat("[0-9]", 1, decimal_places)}0*' if decimal_places else r'\.0+'
2897 alts += [f'[0-9]+(?:{opt_frac_part})?', f'[0-9]*{frac_part}']
2899 alts.append(f'{number}{exponent}')
2900 return rf'^[+-]?(?:{"|".join(alts)}{non_finite})$'
2903def _decimal_serialization_pattern(max_digits: int | None, decimal_places: int | None, allow_inf_nan: bool) -> str:
2904 """Build the decimal pattern for the `'serialization'` mode.
2906 Decimals are serialized using `str()`, producing either:
2908 - The plain notation (`'123.45'`), when the exponent is lower than or equal to 0 and the adjusted
2909 exponent is greater than or equal to -6.
2910 - The scientific notation otherwise (`'1E+3'`, `'1.5E-7'`, `'0E-10'`), with a single digit before
2911 the optional decimal point and an always signed exponent.
2913 Trailing zeros of the decimal part are preserved by `str()`, but aren't counted by the validation logic.
2914 """
2916 def frac_part(places: int) -> str:
2917 return rf'\.{_repeat("[0-9]", 1, places)}0*' if places else r'\.0+'
2919 alts: list[str] = []
2921 # Plain notation:
2922 if max_digits is not None and decimal_places is not None:
2923 whole = max(0, max_digits - decimal_places)
2924 places = min(max_digits, decimal_places)
2925 int_part = f'(?:0|[1-9]{_repeat("[0-9]", 0, whole - 1)})' if whole else '0'
2926 alts.append(f'{int_part}(?:{frac_part(places)})?')
2927 elif max_digits is not None:
2928 # The whole part and decimal part must have at most `max_digits` digits in total,
2929 # so we enumerate the possible widths of the whole part:
2930 alts.append(rf'0(?:\.{_repeat("[0-9]", 1, max_digits)}0*)?')
2931 alts += [
2932 f'[1-9]{_repeat("[0-9]", whole - 1, whole - 1)}(?:{frac_part(max_digits - whole)})?'
2933 for whole in range(1, max_digits + 1)
2934 ]
2935 elif decimal_places is not None:
2936 alts.append(f'(?:0|[1-9][0-9]*)(?:{frac_part(decimal_places)})?')
2937 else:
2938 alts.append(r'(?:0|[1-9][0-9]*)(?:\.[0-9]+)?')
2940 # Zero with an exponent (e.g. `'0E-10'`), which normalizes to zero and thus satisfies any constraint:
2941 alts.append(r'0E[+-][1-9][0-9]*')
2943 # Scientific notation with a positive exponent `k` (e.g. `'1.5E+3'`): the value has `k + 1` whole digits
2944 # and no decimal places:
2945 if max_digits is None:
2946 alts.append(r'[1-9](?:\.[0-9]+)?E\+[1-9][0-9]*')
2947 else:
2948 max_exponent = max_digits - (decimal_places or 0) - 1
2949 if max_exponent >= 1:
2950 alts.append(rf'[1-9](?:\.[0-9]+)?E\+{_int_range_pattern(max_exponent)}')
2952 # Scientific notation with a negative exponent `k` (`k >= 7`, e.g. `'1.5E-7'`): the value has no whole digits,
2953 # and `k` + (the number of significant decimal digits of the coefficient) decimal places:
2954 if max_digits is None and decimal_places is None:
2955 alts.append(r'[1-9](?:\.[0-9]+)?E-[1-9][0-9]*')
2956 else:
2957 max_places = min(c for c in (max_digits, decimal_places) if c is not None)
2958 alts += [f'[1-9](?:{frac_part(max_places - k)})?E-{k}' for k in range(7, max_places + 1)]
2960 if allow_inf_nan:
2961 # `str()` produces `'Infinity'`, `'NaN'` and `'sNaN'` (with an optional payload):
2962 alts += ['Infinity', 's?NaN[0-9]*']
2964 return rf'^-?(?:{"|".join(alts)})$'
2967def _deduplicate_schemas(schemas: Iterable[JsonDict]) -> list[JsonDict]:
2968 return list({_make_json_hashable(schema): schema for schema in schemas}.values())
2971_HashableJsonValue: TypeAlias = (
2972 int | float | str | bool | None | tuple['_HashableJsonValue', ...] | tuple[tuple[str, '_HashableJsonValue'], ...]
2973)
2976def _make_json_hashable(value: JsonValue) -> _HashableJsonValue:
2977 if isinstance(value, dict):
2978 return tuple(sorted((k, _make_json_hashable(v)) for k, v in value.items()))
2979 elif isinstance(value, list):
2980 return tuple(_make_json_hashable(v) for v in value)
2981 else:
2982 return value
2985@dataclasses.dataclass(slots=True)
2986class WithJsonSchema:
2987 """!!! abstract "Usage Documentation"
2988 [`WithJsonSchema` Annotation](../concepts/json_schema.md#withjsonschema-annotation)
2990 An annotation used to override the JSON Schema for a type.
2992 This is useful when you want to set a JSON Schema for a type that don't produce any JSON Schemas by default
2993 (e.g. [`Callable`][collections.abc.Callable]).
2995 If `mode` is set this will only apply to that schema generation mode, allowing you to set different JSON Schemas for validation and serialization.
2997 !!! note
2998 If the `WithJsonSchema` annotation is coupled with the [`Field()`][pydantic.Field] function, the behavior overriding will vary depending on the location:
3000 * If the [`Annotated`][typing.Annotated] metadata is specified at the "top-level" field, `Field()` metadata arguments
3001 (excluding [constraints](../concepts/fields.md#field-constraints)) such as `title` and `description` will be applied on
3002 top of the `WithJsonSchema`, no matter the order:
3004 ```python
3005 from typing import Annotated
3007 from pydantic import BaseModel, Field, WithJsonSchema
3009 class Model(BaseModel):
3010 field: Annotated[
3011 int,
3012 Field(title='My Field'),
3013 WithJsonSchema({'type': 'integer', 'extra': 'data'}),
3014 ]
3016 Model.model_json_schema()['properties']['field']
3017 #> {'type': 'integer', 'extra': 'data', 'title': 'My Field'}
3018 ```
3020 * If the [`Annotated`][typing.Annotated] metadata is specified on a specific inner type, `WithJsonSchema` will unconditionally
3021 override the JSON Schema:
3023 ```python
3024 from typing import Annotated
3026 from pydantic import BaseModel, Field, WithJsonSchema
3028 class Model(BaseModel):
3029 field: list[
3030 Annotated[
3031 int,
3032 Field(title='My Field'),
3033 WithJsonSchema({'type': 'integer', 'extra': 'data'}),
3034 ]
3035 ]
3037 Model.model_json_schema()['properties']['field']
3038 #> {'items': {'extra': 'data', 'type': 'integer'}, 'title': 'Field', 'type': 'array'}
3039 ```
3041 See also the documentation about [the annotated pattern](../concepts/fields.md#the-annotated-pattern).
3042 """
3044 json_schema: JsonSchemaValue | None
3045 mode: Literal['validation', 'serialization'] | None = None
3047 def __get_pydantic_json_schema__(
3048 self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
3049 ) -> JsonSchemaValue:
3050 if self.mode is not None and self.mode != handler.mode:
3051 return handler(core_schema)
3052 if self.json_schema is None:
3053 # This exception is handled in pydantic.json_schema.GenerateJsonSchema._named_required_fields_schema
3054 raise PydanticOmit
3055 else:
3056 return self.json_schema.copy()
3058 def __hash__(self) -> int:
3059 return hash(self.mode)
3062class Examples:
3063 """Add examples to a JSON schema.
3065 If the JSON Schema already contains examples, the provided examples
3066 will be appended.
3068 If `mode` is set this will only apply to that schema generation mode,
3069 allowing you to add different examples for validation and serialization.
3070 """
3072 @overload
3073 @deprecated('Using a dict for `examples` is deprecated since v2.9 and will be removed in v3.0. Use a list instead.')
3074 def __init__(
3075 self, examples: dict[str, Any], mode: Literal['validation', 'serialization'] | None = None
3076 ) -> None: ...
3078 @overload
3079 def __init__(self, examples: list[Any], mode: Literal['validation', 'serialization'] | None = None) -> None: ...
3081 def __init__(
3082 self, examples: dict[str, Any] | list[Any], mode: Literal['validation', 'serialization'] | None = None
3083 ) -> None:
3084 if isinstance(examples, dict):
3085 warnings.warn(
3086 'Using a dict for `examples` is deprecated, use a list instead.',
3087 PydanticDeprecatedSince29,
3088 stacklevel=2,
3089 )
3090 self.examples = examples
3091 self.mode = mode
3093 def __get_pydantic_json_schema__(
3094 self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler
3095 ) -> JsonSchemaValue:
3096 mode = self.mode or handler.mode
3097 json_schema = handler(core_schema)
3098 if mode != handler.mode:
3099 return json_schema
3100 examples = json_schema.get('examples')
3101 if examples is None:
3102 json_schema['examples'] = to_jsonable_python(self.examples)
3103 if isinstance(examples, dict):
3104 if isinstance(self.examples, list):
3105 warnings.warn(
3106 'Updating existing JSON Schema examples of type dict with examples of type list. '
3107 'Only the existing examples values will be retained. Note that dict support for '
3108 'examples is deprecated and will be removed in v3.0.',
3109 UserWarning,
3110 )
3111 json_schema['examples'] = to_jsonable_python(
3112 [ex for value in examples.values() for ex in value] + self.examples
3113 )
3114 else:
3115 json_schema['examples'] = to_jsonable_python({**examples, **self.examples})
3116 if isinstance(examples, list):
3117 if isinstance(self.examples, list):
3118 json_schema['examples'] = to_jsonable_python(examples + self.examples)
3119 elif isinstance(self.examples, dict):
3120 warnings.warn(
3121 'Updating existing JSON Schema examples of type list with examples of type dict. '
3122 'Only the examples values will be retained. Note that dict support for '
3123 'examples is deprecated and will be removed in v3.0.',
3124 UserWarning,
3125 )
3126 json_schema['examples'] = to_jsonable_python(
3127 examples + [ex for value in self.examples.values() for ex in value]
3128 )
3130 return json_schema
3132 def __hash__(self) -> int:
3133 return hash(self.mode)
3136def _get_all_json_refs(item: Any) -> set[JsonRef]:
3137 """Get all the definitions references from a JSON schema."""
3138 refs: set[JsonRef] = set()
3139 stack = [item]
3141 while stack:
3142 current = stack.pop()
3143 if isinstance(current, dict):
3144 for key, value in current.items():
3145 if key == 'examples' and isinstance(value, list):
3146 # Skip examples that may contain arbitrary values and references
3147 # (e.g. `{"examples": [{"$ref": "..."}]}`). Note: checking for value
3148 # of type list is necessary to avoid skipping valid portions of the schema,
3149 # for instance when "examples" is used as a property key. A more robust solution
3150 # could be found, but would require more advanced JSON Schema parsing logic.
3151 continue
3152 if key == '$ref' and isinstance(value, str):
3153 refs.add(JsonRef(value))
3154 elif isinstance(value, dict):
3155 stack.append(value)
3156 elif isinstance(value, list):
3157 stack.extend(value)
3158 elif isinstance(current, list):
3159 stack.extend(current)
3161 return refs
3164AnyType = TypeVar('AnyType')
3166if TYPE_CHECKING:
3167 SkipJsonSchema = Annotated[AnyType, ...]
3168else:
3170 @dataclasses.dataclass(slots=True)
3171 class SkipJsonSchema:
3172 """!!! abstract "Usage Documentation"
3173 [`SkipJsonSchema` Annotation](../concepts/json_schema.md#skipjsonschema-annotation)
3175 Add this as an annotation on a field to skip generating a JSON schema for that field.
3177 Example:
3178 ```python
3179 from pprint import pprint
3181 from pydantic import BaseModel
3182 from pydantic.json_schema import SkipJsonSchema
3184 class Model(BaseModel):
3185 a: int | None = None # (1)!
3186 b: int | SkipJsonSchema[None] = None # (2)!
3187 c: SkipJsonSchema[int | None] = None # (3)!
3189 pprint(Model.model_json_schema())
3190 '''
3191 {
3192 'properties': {
3193 'a': {
3194 'anyOf': [
3195 {'type': 'integer'},
3196 {'type': 'null'}
3197 ],
3198 'default': None,
3199 'title': 'A'
3200 },
3201 'b': {
3202 'default': None,
3203 'title': 'B',
3204 'type': 'integer'
3205 }
3206 },
3207 'title': 'Model',
3208 'type': 'object'
3209 }
3210 '''
3211 ```
3213 1. The integer and null types are both included in the schema for `a`.
3214 2. The integer type is the only type included in the schema for `b`.
3215 3. The entirety of the `c` field is omitted from the schema.
3216 """
3218 def __class_getitem__(cls, item: AnyType) -> AnyType:
3219 return Annotated[item, cls()]
3221 def __get_pydantic_json_schema__(
3222 self, core_schema: CoreSchema, handler: GetJsonSchemaHandler
3223 ) -> JsonSchemaValue:
3224 raise PydanticOmit
3226 def __hash__(self) -> int:
3227 return hash(type(self))
3230def _get_typed_dict_config(cls: type[Any] | None) -> ConfigDict | None:
3231 # If typed dictionary doesn't have config, the parent's config is used (as during core schema generation),
3232 # hence a default of `None`:
3233 if cls is not None:
3234 try:
3235 return _decorators.get_attribute_from_bases(cls, '__pydantic_config__')
3236 except AttributeError:
3237 pass
3238 return None
3241def _get_ser_schema_for_default_value(schema: CoreSchema) -> core_schema.PlainSerializerFunctionSerSchema | None:
3242 """Get a `'function-plain'` serialization schema that can be used to serialize a default value.
3244 This takes into account having the serialization schema nested under validation schema(s).
3245 """
3246 if (
3247 (ser_schema := schema.get('serialization'))
3248 and ser_schema['type'] == 'function-plain'
3249 and not ser_schema.get('info_arg')
3250 ):
3251 return cast('core_schema.PlainSerializerFunctionSerSchema', ser_schema)
3252 if _core_utils.is_function_with_inner_schema(schema):
3253 return _get_ser_schema_for_default_value(schema['schema'])