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

1149 statements  

1"""!!! abstract "Usage Documentation" 

2 [JSON Schema](../concepts/json_schema.md) 

3 

4The `json_schema` module contains classes and functions to allow the way [JSON Schema](https://json-schema.org/) 

5is generated to be customized. 

6 

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""" 

11 

12from __future__ import annotations as _annotations 

13 

14import dataclasses 

15import inspect 

16import math 

17import os 

18import re 

19import warnings 

20from collections import Counter, defaultdict 

21from collections.abc import Hashable, Iterable, Sequence 

22from copy import deepcopy 

23from enum import Enum 

24from re import Pattern 

25from typing import ( 

26 TYPE_CHECKING, 

27 Annotated, 

28 Any, 

29 Callable, 

30 Literal, 

31 NewType, 

32 TypeVar, 

33 Union, 

34 cast, 

35 overload, 

36) 

37 

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 TypeAlias, assert_never, deprecated, final 

42from typing_inspection.introspection import get_literal_values 

43 

44from pydantic.warnings import PydanticDeprecatedSince26, PydanticDeprecatedSince29 

45 

46from ._internal import ( 

47 _config, 

48 _core_metadata, 

49 _core_utils, 

50 _decorators, 

51 _internal_dataclass, 

52 _mock_val_ser, 

53 _schema_generation_shared, 

54) 

55from .annotated_handlers import GetJsonSchemaHandler 

56from .config import JsonDict, JsonValue 

57from .errors import PydanticInvalidForJsonSchema, PydanticSchemaGenerationError, PydanticUserError 

58 

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 

65 

66 

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""" 

73 

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""" 

78 

79JsonSchemaMode = Literal['validation', 'serialization'] 

80""" 

81A type alias that represents the mode of a JSON schema; either 'validation' or 'serialization'. 

82 

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""" 

88 

89_MODE_TITLE_MAPPING: dict[JsonSchemaMode, str] = {'validation': 'Input', 'serialization': 'Output'} 

90 

91 

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. 

95 

96See [`GenerateJsonSchema.render_warning_message`][pydantic.json_schema.GenerateJsonSchema.render_warning_message] 

97for more details. 

98""" 

99 

100 

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 """ 

107 

108 

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""" 

113 

114 

115# ##### JSON Schema Generation ##### 

116DEFAULT_REF_TEMPLATE = '#/$defs/{model}' 

117"""The default format string used to generate reference names.""" 

118 

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) 

130 

131CoreModeRef = tuple[CoreRef, JsonSchemaMode] 

132JsonSchemaKeyT = TypeVar('JsonSchemaKeyT', bound=Hashable) 

133 

134_PRIMITIVE_JSON_SCHEMA_TYPES = ('string', 'boolean', 'null', 'integer', 'number') 

135 

136 

137@dataclasses.dataclass(**_internal_dataclass.slots_true) 

138class _DefinitionsRemapping: 

139 defs_remapping: dict[DefsRef, DefsRef] 

140 json_remapping: dict[JsonRef, JsonRef] 

141 

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 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]) 

165 

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]) 

170 

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 

179 json_remapping[defs_to_json[original_defs_ref]] = defs_to_json[remapped_defs_ref] 

180 remapping = _DefinitionsRemapping(defs_remapping, json_remapping) 

181 new_definitions_schema = remapping.remap_json_schema({'$defs': copied_definitions}) 

182 if definitions_schema == new_definitions_schema: 

183 # We've reached the fixed point 

184 return remapping 

185 definitions_schema = new_definitions_schema 

186 

187 raise PydanticInvalidForJsonSchema('Failed to simplify the JSON schema definitions') 

188 

189 def remap_defs_ref(self, ref: DefsRef) -> DefsRef: 

190 return self.defs_remapping.get(ref, ref) 

191 

192 def remap_json_ref(self, ref: JsonRef) -> JsonRef: 

193 return self.json_remapping.get(ref, ref) 

194 

195 def remap_json_schema(self, schema: Any) -> Any: 

196 """ 

197 Recursively update the JSON schema replacing all $refs 

198 """ 

199 if isinstance(schema, str): 

200 # Note: this may not really be a JsonRef; we rely on having no collisions between JsonRefs and other strings 

201 return self.remap_json_ref(JsonRef(schema)) 

202 elif isinstance(schema, list): 

203 return [self.remap_json_schema(item) for item in schema] 

204 elif isinstance(schema, dict): 

205 for key, value in schema.items(): 

206 if key == '$ref' and isinstance(value, str): 

207 schema['$ref'] = self.remap_json_ref(JsonRef(value)) 

208 elif key == '$defs': 

209 schema['$defs'] = { 

210 self.remap_defs_ref(DefsRef(key)): self.remap_json_schema(value) 

211 for key, value in schema['$defs'].items() 

212 } 

213 else: 

214 schema[key] = self.remap_json_schema(value) 

215 return schema 

216 

217 

218class GenerateJsonSchema: 

219 """!!! abstract "Usage Documentation" 

220 [Customizing the JSON Schema Generation Process](../concepts/json_schema.md#customizing-the-json-schema-generation-process) 

221 

222 A class for generating JSON schemas. 

223 

224 This class generates JSON schemas based on configured parameters. The default schema dialect 

225 is [https://json-schema.org/draft/2020-12/schema](https://json-schema.org/draft/2020-12/schema). 

226 The class uses `by_alias` to configure how fields with 

227 multiple names are handled and `ref_template` to format reference names. 

228 

229 Attributes: 

230 schema_dialect: The JSON schema dialect used to generate the schema. See 

231 [Declaring a Dialect](https://json-schema.org/understanding-json-schema/reference/schema.html#id4) 

232 in the JSON Schema documentation for more information about dialects. 

233 ignored_warning_kinds: Warnings to ignore when generating the schema. `self.render_warning_message` will 

234 do nothing if its argument `kind` is in `ignored_warning_kinds`; 

235 this value can be modified on subclasses to easily control which warnings are emitted. 

236 by_alias: Whether to use field aliases when generating the schema. 

237 ref_template: The format string used when generating reference names. 

238 core_to_json_refs: A mapping of core refs to JSON refs. 

239 core_to_defs_refs: A mapping of core refs to definition refs. 

240 defs_to_core_refs: A mapping of definition refs to core refs. 

241 json_to_defs_refs: A mapping of JSON refs to definition refs. 

242 definitions: Definitions in the schema. 

243 

244 Args: 

245 by_alias: Whether to use field aliases in the generated schemas. 

246 ref_template: The format string to use when generating reference names. 

247 union_format: The format to use when combining schemas from unions together. Can be one of: 

248 

249 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) 

250 keyword to combine schemas (the default). 

251 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) 

252 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive 

253 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to 

254 `any_of`. 

255 

256 Raises: 

257 JsonSchemaError: If the instance of the class is inadvertently reused after generating a schema. 

258 """ 

259 

260 schema_dialect = 'https://json-schema.org/draft/2020-12/schema' 

261 

262 # `self.render_warning_message` will do nothing if its argument `kind` is in `ignored_warning_kinds`; 

263 # this value can be modified on subclasses to easily control which warnings are emitted 

264 ignored_warning_kinds: set[JsonSchemaWarningKind] = {'skipped-choice'} 

265 

266 def __init__( 

267 self, 

268 by_alias: bool = True, 

269 ref_template: str = DEFAULT_REF_TEMPLATE, 

270 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', 

271 ) -> None: 

272 self.by_alias = by_alias 

273 self.ref_template = ref_template 

274 self.union_format: Literal['any_of', 'primitive_type_array'] = union_format 

275 

276 self.core_to_json_refs: dict[CoreModeRef, JsonRef] = {} 

277 self.core_to_defs_refs: dict[CoreModeRef, DefsRef] = {} 

278 self.defs_to_core_refs: dict[DefsRef, CoreModeRef] = {} 

279 self.json_to_defs_refs: dict[JsonRef, DefsRef] = {} 

280 

281 self.definitions: dict[DefsRef, JsonSchemaValue] = {} 

282 self._config_wrapper_stack = _config.ConfigWrapperStack(_config.ConfigWrapper({})) 

283 

284 self._mode: JsonSchemaMode = 'validation' 

285 

286 # The following includes a mapping of a fully-unique defs ref choice to a list of preferred 

287 # alternatives, which are generally simpler, such as only including the class name. 

288 # At the end of schema generation, we use these to produce a JSON schema with more human-readable 

289 # definitions, which would also work better in a generated OpenAPI client, etc. 

290 self._prioritized_defsref_choices: dict[DefsRef, list[DefsRef]] = {} 

291 self._collision_counter: dict[str, int] = defaultdict(int) 

292 self._collision_index: dict[str, int] = {} 

293 

294 self._schema_type_to_method = self.build_schema_type_to_method() 

295 

296 # When we encounter definitions we need to try to build them immediately 

297 # so that they are available schemas that reference them 

298 # But it's possible that CoreSchema was never going to be used 

299 # (e.g. because the CoreSchema that references short circuits is JSON schema generation without needing 

300 # the reference) so instead of failing altogether if we can't build a definition we 

301 # store the error raised and re-throw it if we end up needing that def 

302 self._core_defs_invalid_for_json_schema: dict[DefsRef, PydanticInvalidForJsonSchema] = {} 

303 

304 # This changes to True after generating a schema, to prevent issues caused by accidental reuse 

305 # of a single instance of a schema generator 

306 self._used = False 

307 

308 @property 

309 def _config(self) -> _config.ConfigWrapper: 

310 return self._config_wrapper_stack.tail 

311 

312 @property 

313 def mode(self) -> JsonSchemaMode: 

314 if self._config.json_schema_mode_override is not None: 

315 return self._config.json_schema_mode_override 

316 else: 

317 return self._mode 

318 

319 def build_schema_type_to_method( 

320 self, 

321 ) -> dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]]: 

322 """Builds a dictionary mapping fields to methods for generating JSON schemas. 

323 

324 Returns: 

325 A dictionary containing the mapping of `CoreSchemaOrFieldType` to a handler method. 

326 

327 Raises: 

328 TypeError: If no method has been defined for generating a JSON schema for a given pydantic core schema type. 

329 """ 

330 mapping: dict[CoreSchemaOrFieldType, Callable[[CoreSchemaOrField], JsonSchemaValue]] = {} 

331 core_schema_types: list[CoreSchemaOrFieldType] = list(get_literal_values(CoreSchemaOrFieldType)) 

332 for key in core_schema_types: 

333 method_name = f'{key.replace("-", "_")}_schema' 

334 try: 

335 mapping[key] = getattr(self, method_name) 

336 except AttributeError as e: # pragma: no cover 

337 if os.getenv('PYDANTIC_PRIVATE_ALLOW_UNHANDLED_SCHEMA_TYPES'): 

338 continue 

339 raise TypeError( 

340 f'No method for generating JsonSchema for core_schema.type={key!r} ' 

341 f'(expected: {type(self).__name__}.{method_name})' 

342 ) from e 

343 return mapping 

344 

345 def generate_definitions( 

346 self, inputs: Sequence[tuple[JsonSchemaKeyT, JsonSchemaMode, core_schema.CoreSchema]] 

347 ) -> tuple[dict[tuple[JsonSchemaKeyT, JsonSchemaMode], JsonSchemaValue], dict[DefsRef, JsonSchemaValue]]: 

348 """Generates JSON schema definitions from a list of core schemas, pairing the generated definitions with a 

349 mapping that links the input keys to the definition references. 

350 

351 Args: 

352 inputs: A sequence of tuples, where: 

353 

354 - The first element is a JSON schema key type. 

355 - The second element is the JSON mode: either 'validation' or 'serialization'. 

356 - The third element is a core schema. 

357 

358 Returns: 

359 A tuple where: 

360 

361 - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and 

362 whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have 

363 JsonRef references to definitions that are defined in the second returned element.) 

364 - The second element is a dictionary whose keys are definition references for the JSON schemas 

365 from the first returned element, and whose values are the actual JSON schema definitions. 

366 

367 Raises: 

368 PydanticUserError: Raised if the JSON schema generator has already been used to generate a JSON schema. 

369 """ 

370 if self._used: 

371 raise PydanticUserError( 

372 'This JSON schema generator has already been used to generate a JSON schema. ' 

373 f'You must create a new instance of {type(self).__name__} to generate a new JSON schema.', 

374 code='json-schema-already-used', 

375 ) 

376 

377 for _, mode, schema in inputs: 

378 self._mode = mode 

379 self.generate_inner(schema) 

380 

381 definitions_remapping = self._build_definitions_remapping() 

382 

383 json_schemas_map: dict[tuple[JsonSchemaKeyT, JsonSchemaMode], DefsRef] = {} 

384 for key, mode, schema in inputs: 

385 self._mode = mode 

386 json_schema = self.generate_inner(schema) 

387 json_schemas_map[(key, mode)] = definitions_remapping.remap_json_schema(json_schema) 

388 

389 json_schema = {'$defs': self.definitions} 

390 json_schema = definitions_remapping.remap_json_schema(json_schema) 

391 self._used = True 

392 return json_schemas_map, self.sort(json_schema['$defs']) # type: ignore 

393 

394 def generate(self, schema: CoreSchema, mode: JsonSchemaMode = 'validation') -> JsonSchemaValue: 

395 """Generates a JSON schema for a specified schema in a specified mode. 

396 

397 Args: 

398 schema: A Pydantic model. 

399 mode: The mode in which to generate the schema. Defaults to 'validation'. 

400 

401 Returns: 

402 A JSON schema representing the specified schema. 

403 

404 Raises: 

405 PydanticUserError: If the JSON schema generator has already been used to generate a JSON schema. 

406 """ 

407 self._mode = mode 

408 if self._used: 

409 raise PydanticUserError( 

410 'This JSON schema generator has already been used to generate a JSON schema. ' 

411 f'You must create a new instance of {type(self).__name__} to generate a new JSON schema.', 

412 code='json-schema-already-used', 

413 ) 

414 

415 json_schema: JsonSchemaValue = self.generate_inner(schema) 

416 json_ref_counts = self.get_json_ref_counts(json_schema) 

417 

418 ref = cast(JsonRef, json_schema.get('$ref')) 

419 while ref is not None: # may need to unpack multiple levels 

420 ref_json_schema = self.get_schema_from_definitions(ref) 

421 if json_ref_counts[ref] == 1 and ref_json_schema is not None and len(json_schema) == 1: 

422 # "Unpack" the ref since this is the only reference and there are no sibling keys 

423 json_schema = ref_json_schema.copy() # copy to prevent recursive dict reference 

424 json_ref_counts[ref] -= 1 

425 ref = cast(JsonRef, json_schema.get('$ref')) 

426 ref = None 

427 

428 self._garbage_collect_definitions(json_schema) 

429 definitions_remapping = self._build_definitions_remapping() 

430 

431 if self.definitions: 

432 json_schema['$defs'] = self.definitions 

433 

434 json_schema = definitions_remapping.remap_json_schema(json_schema) 

435 

436 # For now, we will not set the $schema key. However, if desired, this can be easily added by overriding 

437 # this method and adding the following line after a call to super().generate(schema): 

438 # json_schema['$schema'] = self.schema_dialect 

439 

440 self._used = True 

441 return self.sort(json_schema) 

442 

443 def generate_inner(self, schema: CoreSchemaOrField) -> JsonSchemaValue: # noqa: C901 

444 """Generates a JSON schema for a given core schema. 

445 

446 Args: 

447 schema: The given core schema. 

448 

449 Returns: 

450 The generated JSON schema. 

451 

452 TODO: the nested function definitions here seem like bad practice, I'd like to unpack these 

453 in a future PR. It'd be great if we could shorten the call stack a bit for JSON schema generation, 

454 and I think there's potential for that here. 

455 """ 

456 # If a schema with the same CoreRef has been handled, just return a reference to it 

457 # Note that this assumes that it will _never_ be the case that the same CoreRef is used 

458 # on types that should have different JSON schemas 

459 if 'ref' in schema: 

460 core_ref = CoreRef(schema['ref']) # type: ignore[typeddict-item] 

461 core_mode_ref = (core_ref, self.mode) 

462 if core_mode_ref in self.core_to_defs_refs and self.core_to_defs_refs[core_mode_ref] in self.definitions: 

463 return {'$ref': self.core_to_json_refs[core_mode_ref]} 

464 

465 def populate_defs(core_schema: CoreSchema, json_schema: JsonSchemaValue) -> JsonSchemaValue: 

466 if 'ref' in core_schema: 

467 core_ref = CoreRef(core_schema['ref']) # type: ignore[typeddict-item] 

468 defs_ref, ref_json_schema = self.get_cache_defs_ref_schema(core_ref) 

469 json_ref = JsonRef(ref_json_schema['$ref']) 

470 # Replace the schema if it's not a reference to itself 

471 # What we want to avoid is having the def be just a ref to itself 

472 # which is what would happen if we blindly assigned any 

473 if json_schema.get('$ref', None) != json_ref: 

474 self.definitions[defs_ref] = json_schema 

475 self._core_defs_invalid_for_json_schema.pop(defs_ref, None) 

476 json_schema = ref_json_schema 

477 return json_schema 

478 

479 def handler_func(schema_or_field: CoreSchemaOrField) -> JsonSchemaValue: 

480 """Generate a JSON schema based on the input schema. 

481 

482 Args: 

483 schema_or_field: The core schema to generate a JSON schema from. 

484 

485 Returns: 

486 The generated JSON schema. 

487 

488 Raises: 

489 TypeError: If an unexpected schema type is encountered. 

490 """ 

491 # Generate the core-schema-type-specific bits of the schema generation: 

492 json_schema: JsonSchemaValue | None = None 

493 if self.mode == 'serialization' and 'serialization' in schema_or_field: 

494 # In this case, we skip the JSON Schema generation of the schema 

495 # and use the `'serialization'` schema instead (canonical example: 

496 # `Annotated[int, PlainSerializer(str)]`). 

497 ser_schema = schema_or_field['serialization'] # type: ignore 

498 json_schema = self.ser_schema(ser_schema) 

499 

500 # It might be that the 'serialization'` is skipped depending on `when_used`. 

501 # This is only relevant for `nullable` schemas though, so we special case here. 

502 if ( 

503 json_schema is not None 

504 and ser_schema.get('when_used') in ('unless-none', 'json-unless-none') 

505 and schema_or_field['type'] == 'nullable' 

506 ): 

507 json_schema = self.get_union_of_schemas([{'type': 'null'}, json_schema]) 

508 if json_schema is None: 

509 if _core_utils.is_core_schema(schema_or_field) or _core_utils.is_core_schema_field(schema_or_field): 

510 generate_for_schema_type = self._schema_type_to_method[schema_or_field['type']] 

511 json_schema = generate_for_schema_type(schema_or_field) 

512 else: 

513 raise TypeError(f'Unexpected schema type: schema={schema_or_field}') 

514 return json_schema 

515 

516 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, handler_func) 

517 

518 metadata = cast(_core_metadata.CoreMetadata, schema.get('metadata', {})) 

519 

520 # TODO: I dislike that we have to wrap these basic dict updates in callables, is there any way around this? 

521 

522 if js_updates := metadata.get('pydantic_js_updates'): 

523 

524 def js_updates_handler_func( 

525 schema_or_field: CoreSchemaOrField, 

526 current_handler: GetJsonSchemaHandler = current_handler, 

527 ) -> JsonSchemaValue: 

528 json_schema = {**current_handler(schema_or_field), **js_updates} 

529 return json_schema 

530 

531 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_updates_handler_func) 

532 

533 if js_extra := metadata.get('pydantic_js_extra'): 

534 

535 def js_extra_handler_func( 

536 schema_or_field: CoreSchemaOrField, 

537 current_handler: GetJsonSchemaHandler = current_handler, 

538 ) -> JsonSchemaValue: 

539 json_schema = current_handler(schema_or_field) 

540 if isinstance(js_extra, dict): 

541 json_schema.update(to_jsonable_python(js_extra)) 

542 elif callable(js_extra): 

543 # similar to typing issue in _update_class_schema when we're working with callable js extra 

544 js_extra(json_schema) # type: ignore 

545 return json_schema 

546 

547 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_extra_handler_func) 

548 

549 for js_modify_function in metadata.get('pydantic_js_functions', ()): 

550 

551 def new_handler_func( 

552 schema_or_field: CoreSchemaOrField, 

553 current_handler: GetJsonSchemaHandler = current_handler, 

554 js_modify_function: GetJsonSchemaFunction = js_modify_function, 

555 ) -> JsonSchemaValue: 

556 json_schema = js_modify_function(schema_or_field, current_handler) 

557 if _core_utils.is_core_schema(schema_or_field): 

558 json_schema = populate_defs(schema_or_field, json_schema) 

559 original_schema = current_handler.resolve_ref_schema(json_schema) 

560 ref = json_schema.pop('$ref', None) 

561 if ref and json_schema: 

562 original_schema.update(json_schema) 

563 return original_schema 

564 

565 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func) 

566 

567 for js_modify_function in metadata.get('pydantic_js_annotation_functions', ()): 

568 

569 def new_handler_func( 

570 schema_or_field: CoreSchemaOrField, 

571 current_handler: GetJsonSchemaHandler = current_handler, 

572 js_modify_function: GetJsonSchemaFunction = js_modify_function, 

573 ) -> JsonSchemaValue: 

574 return js_modify_function(schema_or_field, current_handler) 

575 

576 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func) 

577 

578 json_schema = current_handler(schema) 

579 if _core_utils.is_core_schema(schema): 

580 json_schema = populate_defs(schema, json_schema) 

581 return json_schema 

582 

583 def sort(self, value: JsonSchemaValue, parent_key: str | None = None) -> JsonSchemaValue: 

584 """Override this method to customize the sorting of the JSON schema (e.g., don't sort at all, sort all keys unconditionally, etc.) 

585 

586 By default, alphabetically sort the keys in the JSON schema, skipping the 'properties' and 'default' keys to preserve field definition order. 

587 This sort is recursive, so it will sort all nested dictionaries as well. 

588 """ 

589 sorted_dict: dict[str, JsonSchemaValue] = {} 

590 keys = value.keys() 

591 if parent_key not in ('properties', 'default'): 

592 keys = sorted(keys) 

593 for key in keys: 

594 sorted_dict[key] = self._sort_recursive(value[key], parent_key=key) 

595 return sorted_dict 

596 

597 def _sort_recursive(self, value: Any, parent_key: str | None = None) -> Any: 

598 """Recursively sort a JSON schema value.""" 

599 if isinstance(value, dict): 

600 sorted_dict: dict[str, JsonSchemaValue] = {} 

601 keys = value.keys() 

602 if parent_key not in ('properties', 'default'): 

603 keys = sorted(keys) 

604 for key in keys: 

605 sorted_dict[key] = self._sort_recursive(value[key], parent_key=key) 

606 return sorted_dict 

607 elif isinstance(value, list): 

608 sorted_list: list[JsonSchemaValue] = [self._sort_recursive(item, parent_key) for item in value] 

609 return sorted_list 

610 else: 

611 return value 

612 

613 # ### Schema generation methods 

614 

615 def invalid_schema(self, schema: core_schema.InvalidSchema) -> JsonSchemaValue: 

616 """Placeholder - should never be called.""" 

617 

618 raise RuntimeError('Cannot generate schema for invalid_schema. This is a bug! Please report it.') 

619 

620 def any_schema(self, schema: core_schema.AnySchema) -> JsonSchemaValue: 

621 """Generates a JSON schema that matches any value. 

622 

623 Args: 

624 schema: The core schema. 

625 

626 Returns: 

627 The generated JSON schema. 

628 """ 

629 return {} 

630 

631 def none_schema(self, schema: core_schema.NoneSchema) -> JsonSchemaValue: 

632 """Generates a JSON schema that matches `None`. 

633 

634 Args: 

635 schema: The core schema. 

636 

637 Returns: 

638 The generated JSON schema. 

639 """ 

640 return {'type': 'null'} 

641 

642 def bool_schema(self, schema: core_schema.BoolSchema) -> JsonSchemaValue: 

643 """Generates a JSON schema that matches a bool value. 

644 

645 Args: 

646 schema: The core schema. 

647 

648 Returns: 

649 The generated JSON schema. 

650 """ 

651 return {'type': 'boolean'} 

652 

653 def int_schema(self, schema: core_schema.IntSchema) -> JsonSchemaValue: 

654 """Generates a JSON schema that matches an int value. 

655 

656 Args: 

657 schema: The core schema. 

658 

659 Returns: 

660 The generated JSON schema. 

661 """ 

662 json_schema: dict[str, Any] = {'type': 'integer'} 

663 self.update_with_validations(json_schema, schema, self.ValidationsMapping.numeric) 

664 json_schema = {k: v for k, v in json_schema.items() if v not in {math.inf, -math.inf}} 

665 return json_schema 

666 

667 def float_schema(self, schema: core_schema.FloatSchema) -> JsonSchemaValue: 

668 """Generates a JSON schema that matches a float value. 

669 

670 Args: 

671 schema: The core schema. 

672 

673 Returns: 

674 The generated JSON schema. 

675 """ 

676 json_schema: dict[str, Any] = {'type': 'number'} 

677 self.update_with_validations(json_schema, schema, self.ValidationsMapping.numeric) 

678 json_schema = {k: v for k, v in json_schema.items() if v not in {math.inf, -math.inf}} 

679 return json_schema 

680 

681 def decimal_schema(self, schema: core_schema.DecimalSchema) -> JsonSchemaValue: 

682 """Generates a JSON schema that matches a decimal value. 

683 

684 Args: 

685 schema: The core schema. 

686 

687 Returns: 

688 The generated JSON schema. 

689 """ 

690 

691 def get_decimal_pattern(schema: core_schema.DecimalSchema) -> str: 

692 max_digits = schema.get('max_digits') 

693 decimal_places = schema.get('decimal_places') 

694 

695 pattern = ( 

696 r'^(?!^[-+.]*$)[+-]?0*' # check it is not empty string and not one or sequence of ".+-" characters. 

697 ) 

698 

699 # Case 1: Both max_digits and decimal_places are set 

700 if max_digits is not None and decimal_places is not None: 

701 integer_places = max(0, max_digits - decimal_places) 

702 pattern += ( 

703 rf'(?:' 

704 rf'\d{{0,{integer_places}}}' 

705 rf'|' 

706 rf'(?=[\d.]{{1,{max_digits + 1}}}0*$)' 

707 rf'\d{{0,{integer_places}}}\.\d{{0,{decimal_places}}}0*$' 

708 rf')' 

709 ) 

710 

711 # Case 2: Only max_digits is set 

712 elif max_digits is not None and decimal_places is None: 

713 pattern += ( 

714 rf'(?:' 

715 rf'\d{{0,{max_digits}}}' 

716 rf'|' 

717 rf'(?=[\d.]{{1,{max_digits + 1}}}0*$)' 

718 rf'\d*\.\d*0*$' 

719 rf')' 

720 ) 

721 

722 # Case 3: Only decimal_places is set 

723 elif max_digits is None and decimal_places is not None: 

724 pattern += rf'\d*\.?\d{{0,{decimal_places}}}0*$' 

725 

726 # Case 4: Both are None (no restrictions) 

727 else: 

728 pattern += r'\d*\.?\d*$' # look for arbitrary integer or decimal 

729 

730 return pattern 

731 

732 json_schema = self.str_schema(core_schema.str_schema(pattern=get_decimal_pattern(schema))) 

733 if self.mode == 'validation': 

734 multiple_of = schema.get('multiple_of') 

735 le = schema.get('le') 

736 ge = schema.get('ge') 

737 lt = schema.get('lt') 

738 gt = schema.get('gt') 

739 json_schema = { 

740 'anyOf': [ 

741 self.float_schema( 

742 core_schema.float_schema( 

743 allow_inf_nan=schema.get('allow_inf_nan'), 

744 multiple_of=None if multiple_of is None else float(multiple_of), 

745 le=None if le is None else float(le), 

746 ge=None if ge is None else float(ge), 

747 lt=None if lt is None else float(lt), 

748 gt=None if gt is None else float(gt), 

749 ) 

750 ), 

751 json_schema, 

752 ], 

753 } 

754 return json_schema 

755 

756 def str_schema(self, schema: core_schema.StringSchema) -> JsonSchemaValue: 

757 """Generates a JSON schema that matches a string value. 

758 

759 Args: 

760 schema: The core schema. 

761 

762 Returns: 

763 The generated JSON schema. 

764 """ 

765 json_schema = {'type': 'string'} 

766 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string) 

767 if isinstance(json_schema.get('pattern'), Pattern): 

768 # TODO: should we add regex flags to the pattern? 

769 json_schema['pattern'] = json_schema.get('pattern').pattern # type: ignore 

770 return json_schema 

771 

772 def bytes_schema(self, schema: core_schema.BytesSchema) -> JsonSchemaValue: 

773 """Generates a JSON schema that matches a bytes value. 

774 

775 Args: 

776 schema: The core schema. 

777 

778 Returns: 

779 The generated JSON schema. 

780 """ 

781 json_schema = {'type': 'string', 'format': 'base64url' if self._config.ser_json_bytes == 'base64' else 'binary'} 

782 self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes) 

783 return json_schema 

784 

785 def date_schema(self, schema: core_schema.DateSchema) -> JsonSchemaValue: 

786 """Generates a JSON schema that matches a date value. 

787 

788 Args: 

789 schema: The core schema. 

790 

791 Returns: 

792 The generated JSON schema. 

793 """ 

794 return {'type': 'string', 'format': 'date'} 

795 

796 def time_schema(self, schema: core_schema.TimeSchema) -> JsonSchemaValue: 

797 """Generates a JSON schema that matches a time value. 

798 

799 Args: 

800 schema: The core schema. 

801 

802 Returns: 

803 The generated JSON schema. 

804 """ 

805 return {'type': 'string', 'format': 'time'} 

806 

807 def datetime_schema(self, schema: core_schema.DatetimeSchema) -> JsonSchemaValue: 

808 """Generates a JSON schema that matches a datetime value. 

809 

810 Args: 

811 schema: The core schema. 

812 

813 Returns: 

814 The generated JSON schema. 

815 """ 

816 return {'type': 'string', 'format': 'date-time'} 

817 

818 def timedelta_schema(self, schema: core_schema.TimedeltaSchema) -> JsonSchemaValue: 

819 """Generates a JSON schema that matches a timedelta value. 

820 

821 Args: 

822 schema: The core schema. 

823 

824 Returns: 

825 The generated JSON schema. 

826 """ 

827 if self._config.ser_json_timedelta == 'float': 

828 return {'type': 'number'} 

829 return {'type': 'string', 'format': 'duration'} 

830 

831 def literal_schema(self, schema: core_schema.LiteralSchema) -> JsonSchemaValue: 

832 """Generates a JSON schema that matches a literal value. 

833 

834 Args: 

835 schema: The core schema. 

836 

837 Returns: 

838 The generated JSON schema. 

839 """ 

840 expected = [to_jsonable_python(v.value if isinstance(v, Enum) else v) for v in schema['expected']] 

841 

842 result: dict[str, Any] = {} 

843 if len(expected) == 1: 

844 result['const'] = expected[0] 

845 else: 

846 result['enum'] = expected 

847 

848 types = {type(e) for e in expected} 

849 if types == {str}: 

850 result['type'] = 'string' 

851 elif types == {int}: 

852 result['type'] = 'integer' 

853 elif types == {float}: 

854 result['type'] = 'number' 

855 elif types == {bool}: 

856 result['type'] = 'boolean' 

857 elif types == {list}: 

858 result['type'] = 'array' 

859 elif types == {type(None)}: 

860 result['type'] = 'null' 

861 return result 

862 

863 def missing_sentinel_schema(self, schema: core_schema.MissingSentinelSchema) -> JsonSchemaValue: 

864 """Generates a JSON schema that matches the `MISSING` sentinel value. 

865 

866 Args: 

867 schema: The core schema. 

868 

869 Returns: 

870 The generated JSON schema. 

871 """ 

872 raise PydanticOmit 

873 

874 def enum_schema(self, schema: core_schema.EnumSchema) -> JsonSchemaValue: 

875 """Generates a JSON schema that matches an Enum value. 

876 

877 Args: 

878 schema: The core schema. 

879 

880 Returns: 

881 The generated JSON schema. 

882 """ 

883 enum_type = schema['cls'] 

884 description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__) 

885 if ( 

886 description == 'An enumeration.' 

887 ): # This is the default value provided by enum.EnumMeta.__new__; don't use it 

888 description = None 

889 result: dict[str, Any] = {'title': enum_type.__name__, 'description': description} 

890 result = {k: v for k, v in result.items() if v is not None} 

891 

892 expected = [to_jsonable_python(v.value) for v in schema['members']] 

893 

894 result['enum'] = expected 

895 

896 types = {type(e) for e in expected} 

897 if isinstance(enum_type, str) or types == {str}: 

898 result['type'] = 'string' 

899 elif isinstance(enum_type, int) or types == {int}: 

900 result['type'] = 'integer' 

901 elif isinstance(enum_type, float) or types == {float}: 

902 result['type'] = 'number' 

903 elif types == {bool}: 

904 result['type'] = 'boolean' 

905 elif types == {list}: 

906 result['type'] = 'array' 

907 

908 return result 

909 

910 def is_instance_schema(self, schema: core_schema.IsInstanceSchema) -> JsonSchemaValue: 

911 """Handles JSON schema generation for a core schema that checks if a value is an instance of a class. 

912 

913 Unless overridden in a subclass, this raises an error. 

914 

915 Args: 

916 schema: The core schema. 

917 

918 Returns: 

919 The generated JSON schema. 

920 """ 

921 return self.handle_invalid_for_json_schema(schema, f'core_schema.IsInstanceSchema ({schema["cls"]})') 

922 

923 def is_subclass_schema(self, schema: core_schema.IsSubclassSchema) -> JsonSchemaValue: 

924 """Handles JSON schema generation for a core schema that checks if a value is a subclass of a class. 

925 

926 For backwards compatibility with v1, this does not raise an error, but can be overridden to change this. 

927 

928 Args: 

929 schema: The core schema. 

930 

931 Returns: 

932 The generated JSON schema. 

933 """ 

934 # Note: This is for compatibility with V1; you can override if you want different behavior. 

935 return {} 

936 

937 def callable_schema(self, schema: core_schema.CallableSchema) -> JsonSchemaValue: 

938 """Generates a JSON schema that matches a callable value. 

939 

940 Unless overridden in a subclass, this raises an error. 

941 

942 Args: 

943 schema: The core schema. 

944 

945 Returns: 

946 The generated JSON schema. 

947 """ 

948 return self.handle_invalid_for_json_schema(schema, 'core_schema.CallableSchema') 

949 

950 def list_schema(self, schema: core_schema.ListSchema) -> JsonSchemaValue: 

951 """Returns a schema that matches a list schema. 

952 

953 Args: 

954 schema: The core schema. 

955 

956 Returns: 

957 The generated JSON schema. 

958 """ 

959 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema']) 

960 json_schema = {'type': 'array', 'items': items_schema} 

961 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) 

962 return json_schema 

963 

964 @deprecated('`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.', category=None) 

965 @final 

966 def tuple_positional_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: 

967 """Replaced by `tuple_schema`.""" 

968 warnings.warn( 

969 '`tuple_positional_schema` is deprecated. Use `tuple_schema` instead.', 

970 PydanticDeprecatedSince26, 

971 stacklevel=2, 

972 ) 

973 return self.tuple_schema(schema) 

974 

975 @deprecated('`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.', category=None) 

976 @final 

977 def tuple_variable_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: 

978 """Replaced by `tuple_schema`.""" 

979 warnings.warn( 

980 '`tuple_variable_schema` is deprecated. Use `tuple_schema` instead.', 

981 PydanticDeprecatedSince26, 

982 stacklevel=2, 

983 ) 

984 return self.tuple_schema(schema) 

985 

986 def tuple_schema(self, schema: core_schema.TupleSchema) -> JsonSchemaValue: 

987 """Generates a JSON schema that matches a tuple schema e.g. `tuple[int, 

988 str, bool]` or `tuple[int, ...]`. 

989 

990 Args: 

991 schema: The core schema. 

992 

993 Returns: 

994 The generated JSON schema. 

995 """ 

996 json_schema: JsonSchemaValue = {'type': 'array'} 

997 if 'variadic_item_index' in schema: 

998 variadic_item_index = schema['variadic_item_index'] 

999 if variadic_item_index > 0: 

1000 json_schema['minItems'] = variadic_item_index 

1001 json_schema['prefixItems'] = [ 

1002 self.generate_inner(item) for item in schema['items_schema'][:variadic_item_index] 

1003 ] 

1004 if variadic_item_index + 1 == len(schema['items_schema']): 

1005 # if the variadic item is the last item, then represent it faithfully 

1006 json_schema['items'] = self.generate_inner(schema['items_schema'][variadic_item_index]) 

1007 else: 

1008 # otherwise, 'items' represents the schema for the variadic 

1009 # item plus the suffix, so just allow anything for simplicity 

1010 # for now 

1011 json_schema['items'] = True 

1012 else: 

1013 prefixItems = [self.generate_inner(item) for item in schema['items_schema']] 

1014 if prefixItems: 

1015 json_schema['prefixItems'] = prefixItems 

1016 json_schema['minItems'] = len(prefixItems) 

1017 json_schema['maxItems'] = len(prefixItems) 

1018 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) 

1019 return json_schema 

1020 

1021 def set_schema(self, schema: core_schema.SetSchema) -> JsonSchemaValue: 

1022 """Generates a JSON schema that matches a set schema. 

1023 

1024 Args: 

1025 schema: The core schema. 

1026 

1027 Returns: 

1028 The generated JSON schema. 

1029 """ 

1030 return self._common_set_schema(schema) 

1031 

1032 def frozenset_schema(self, schema: core_schema.FrozenSetSchema) -> JsonSchemaValue: 

1033 """Generates a JSON schema that matches a frozenset schema. 

1034 

1035 Args: 

1036 schema: The core schema. 

1037 

1038 Returns: 

1039 The generated JSON schema. 

1040 """ 

1041 return self._common_set_schema(schema) 

1042 

1043 def _common_set_schema(self, schema: core_schema.SetSchema | core_schema.FrozenSetSchema) -> JsonSchemaValue: 

1044 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema']) 

1045 json_schema = {'type': 'array', 'uniqueItems': True, 'items': items_schema} 

1046 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) 

1047 return json_schema 

1048 

1049 def generator_schema(self, schema: core_schema.GeneratorSchema) -> JsonSchemaValue: 

1050 """Returns a JSON schema that represents the provided GeneratorSchema. 

1051 

1052 Args: 

1053 schema: The schema. 

1054 

1055 Returns: 

1056 The generated JSON schema. 

1057 """ 

1058 items_schema = {} if 'items_schema' not in schema else self.generate_inner(schema['items_schema']) 

1059 json_schema = {'type': 'array', 'items': items_schema} 

1060 self.update_with_validations(json_schema, schema, self.ValidationsMapping.array) 

1061 return json_schema 

1062 

1063 def dict_schema(self, schema: core_schema.DictSchema) -> JsonSchemaValue: 

1064 """Generates a JSON schema that matches a dict schema. 

1065 

1066 Args: 

1067 schema: The core schema. 

1068 

1069 Returns: 

1070 The generated JSON schema. 

1071 """ 

1072 json_schema: JsonSchemaValue = {'type': 'object'} 

1073 

1074 keys_schema = self.generate_inner(schema['keys_schema']).copy() if 'keys_schema' in schema else {} 

1075 if '$ref' not in keys_schema: 

1076 keys_pattern = keys_schema.pop('pattern', None) 

1077 # Don't give a title to patternProperties/propertyNames: 

1078 keys_schema.pop('title', None) 

1079 else: 

1080 # Here, we assume that if the keys schema is a definition reference, 

1081 # it can't be a simple string core schema (and thus no pattern can exist). 

1082 # However, this is only in practice (in theory, a definition reference core 

1083 # schema could be generated for a simple string schema). 

1084 # Note that we avoid calling `self.resolve_ref_schema`, as it might not exist yet. 

1085 keys_pattern = None 

1086 

1087 values_schema = self.generate_inner(schema['values_schema']).copy() if 'values_schema' in schema else {} 

1088 # don't give a title to additionalProperties: 

1089 values_schema.pop('title', None) 

1090 

1091 if values_schema or keys_pattern is not None: 

1092 if keys_pattern is None: 

1093 json_schema['additionalProperties'] = values_schema 

1094 else: 

1095 json_schema['patternProperties'] = {keys_pattern: values_schema} 

1096 else: # for `dict[str, Any]`, we allow any key and any value, since `str` is the default key type 

1097 json_schema['additionalProperties'] = True 

1098 

1099 if ( 

1100 # The len check indicates that constraints are probably present: 

1101 (keys_schema.get('type') == 'string' and len(keys_schema) > 1) 

1102 # If this is a definition reference schema, it most likely has constraints: 

1103 or '$ref' in keys_schema 

1104 ): 

1105 keys_schema.pop('type', None) 

1106 json_schema['propertyNames'] = keys_schema 

1107 

1108 self.update_with_validations(json_schema, schema, self.ValidationsMapping.object) 

1109 return json_schema 

1110 

1111 def function_before_schema(self, schema: core_schema.BeforeValidatorFunctionSchema) -> JsonSchemaValue: 

1112 """Generates a JSON schema that matches a function-before schema. 

1113 

1114 Args: 

1115 schema: The core schema. 

1116 

1117 Returns: 

1118 The generated JSON schema. 

1119 """ 

1120 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): 

1121 return self.generate_inner(input_schema) 

1122 

1123 return self.generate_inner(schema['schema']) 

1124 

1125 def function_after_schema(self, schema: core_schema.AfterValidatorFunctionSchema) -> JsonSchemaValue: 

1126 """Generates a JSON schema that matches a function-after schema. 

1127 

1128 Args: 

1129 schema: The core schema. 

1130 

1131 Returns: 

1132 The generated JSON schema. 

1133 """ 

1134 return self.generate_inner(schema['schema']) 

1135 

1136 def function_plain_schema(self, schema: core_schema.PlainValidatorFunctionSchema) -> JsonSchemaValue: 

1137 """Generates a JSON schema that matches a function-plain schema. 

1138 

1139 Args: 

1140 schema: The core schema. 

1141 

1142 Returns: 

1143 The generated JSON schema. 

1144 """ 

1145 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): 

1146 return self.generate_inner(input_schema) 

1147 

1148 return self.handle_invalid_for_json_schema( 

1149 schema, f'core_schema.PlainValidatorFunctionSchema ({schema["function"]})' 

1150 ) 

1151 

1152 def function_wrap_schema(self, schema: core_schema.WrapValidatorFunctionSchema) -> JsonSchemaValue: 

1153 """Generates a JSON schema that matches a function-wrap schema. 

1154 

1155 Args: 

1156 schema: The core schema. 

1157 

1158 Returns: 

1159 The generated JSON schema. 

1160 """ 

1161 if self.mode == 'validation' and (input_schema := schema.get('json_schema_input_schema')): 

1162 return self.generate_inner(input_schema) 

1163 

1164 return self.generate_inner(schema['schema']) 

1165 

1166 def default_schema(self, schema: core_schema.WithDefaultSchema) -> JsonSchemaValue: 

1167 """Generates a JSON schema that matches a schema with a default value. 

1168 

1169 Args: 

1170 schema: The core schema. 

1171 

1172 Returns: 

1173 The generated JSON schema. 

1174 """ 

1175 json_schema = self.generate_inner(schema['schema']) 

1176 

1177 default = self.get_default_value(schema) 

1178 if default is NoDefault or default is MISSING: 

1179 return json_schema 

1180 

1181 # we reflect the application of custom plain, no-info serializers to defaults for 

1182 # JSON Schemas viewed in serialization mode: 

1183 # TODO: improvements along with https://github.com/pydantic/pydantic/issues/8208 

1184 if self.mode == 'serialization': 

1185 # `_get_ser_schema_for_default_value()` is used to unpack potentially nested validator schemas: 

1186 ser_schema = _get_ser_schema_for_default_value(schema['schema']) 

1187 if ( 

1188 ser_schema is not None 

1189 and (ser_func := ser_schema.get('function')) 

1190 and not (default is None and ser_schema.get('when_used') in ('unless-none', 'json-unless-none')) 

1191 ): 

1192 try: 

1193 default = ser_func(default) # type: ignore 

1194 except Exception: 

1195 # It might be that the provided default needs to be validated (read: parsed) first 

1196 # (assuming `validate_default` is enabled). However, we can't perform 

1197 # such validation during JSON Schema generation so we don't support 

1198 # this pattern for now. 

1199 # (One example is when using `foo: ByteSize = '1MB'`, which validates and 

1200 # serializes as an int. In this case, `ser_func` is `int` and `int('1MB')` fails). 

1201 self.emit_warning( 

1202 'non-serializable-default', 

1203 f'Unable to serialize value {default!r} with the plain serializer; excluding default from JSON schema', 

1204 ) 

1205 return json_schema 

1206 

1207 try: 

1208 encoded_default = self.encode_default(default) 

1209 except pydantic_core.PydanticSerializationError: 

1210 self.emit_warning( 

1211 'non-serializable-default', 

1212 f'Default value {default} is not JSON serializable; excluding default from JSON schema', 

1213 ) 

1214 # Return the inner schema, as though there was no default 

1215 return json_schema 

1216 

1217 json_schema['default'] = encoded_default 

1218 return json_schema 

1219 

1220 def get_default_value(self, schema: core_schema.WithDefaultSchema) -> Any: 

1221 """Get the default value to be used when generating a JSON Schema for a core schema with a default. 

1222 

1223 The default implementation is to use the statically defined default value. This method can be overridden 

1224 if you want to make use of the default factory. 

1225 

1226 Args: 

1227 schema: The `'with-default'` core schema. 

1228 

1229 Returns: 

1230 The default value to use, or [`NoDefault`][pydantic.json_schema.NoDefault] if no default 

1231 value is available. 

1232 """ 

1233 return schema.get('default', NoDefault) 

1234 

1235 def nullable_schema(self, schema: core_schema.NullableSchema) -> JsonSchemaValue: 

1236 """Generates a JSON schema that matches a schema that allows null values. 

1237 

1238 Args: 

1239 schema: The core schema. 

1240 

1241 Returns: 

1242 The generated JSON schema. 

1243 """ 

1244 null_schema = {'type': 'null'} 

1245 inner_json_schema = self.generate_inner(schema['schema']) 

1246 

1247 if inner_json_schema == null_schema: 

1248 return null_schema 

1249 else: 

1250 return self.get_union_of_schemas([inner_json_schema, null_schema]) 

1251 

1252 def union_schema(self, schema: core_schema.UnionSchema) -> JsonSchemaValue: 

1253 """Generates a JSON schema that matches a schema that allows values matching any of the given schemas. 

1254 

1255 Args: 

1256 schema: The core schema. 

1257 

1258 Returns: 

1259 The generated JSON schema. 

1260 """ 

1261 generated: list[JsonSchemaValue] = [] 

1262 

1263 choices = schema['choices'] 

1264 for choice in choices: 

1265 # choice will be a tuple if an explicit label was provided 

1266 choice_schema = choice[0] if isinstance(choice, tuple) else choice 

1267 try: 

1268 generated.append(self.generate_inner(choice_schema)) 

1269 except PydanticOmit: 

1270 continue 

1271 except PydanticInvalidForJsonSchema as exc: 

1272 self.emit_warning('skipped-choice', exc.message) 

1273 if len(generated) == 1: 

1274 return generated[0] 

1275 return self.get_union_of_schemas(generated) 

1276 

1277 def get_union_of_schemas(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue: 

1278 """Returns the JSON Schema representation for the union of the provided JSON Schemas. 

1279 

1280 The result depends on the configured `'union_format'`. 

1281 

1282 Args: 

1283 schemas: The list of JSON Schemas to be included in the union. 

1284 

1285 Returns: 

1286 The JSON Schema representing the union of schemas. 

1287 """ 

1288 if self.union_format == 'primitive_type_array': 

1289 types: list[str] = [] 

1290 for schema in schemas: 

1291 schema_types: list[str] | str | None = schema.get('type') 

1292 if schema_types is None: 

1293 # No type, meaning it can be a ref or an empty schema. 

1294 break 

1295 if not isinstance(schema_types, list): 

1296 schema_types = [schema_types] 

1297 if not all(t in _PRIMITIVE_JSON_SCHEMA_TYPES for t in schema_types): 

1298 break 

1299 if len(schema) != 1: 

1300 # We only want to include types that don't have any constraints. For instance, 

1301 # if `schemas = [{'type': 'string', 'maxLength': 3}, {'type': 'string', 'minLength': 5}]`, 

1302 # we don't want to produce `{'type': 'string', 'maxLength': 3, 'minLength': 5}`. 

1303 # Same if we have some metadata (e.g. `title`) on a specific union member, we want to preserve it. 

1304 break 

1305 

1306 types.extend(schema_types) 

1307 else: 

1308 # If we got there, all the schemas where valid to be used with the `'primitive_type_array` format 

1309 return {'type': list(dict.fromkeys(types))} 

1310 

1311 return self.get_flattened_anyof(schemas) 

1312 

1313 def tagged_union_schema(self, schema: core_schema.TaggedUnionSchema) -> JsonSchemaValue: 

1314 """Generates a JSON schema that matches a schema that allows values matching any of the given schemas, where 

1315 the schemas are tagged with a discriminator field that indicates which schema should be used to validate 

1316 the value. 

1317 

1318 Args: 

1319 schema: The core schema. 

1320 

1321 Returns: 

1322 The generated JSON schema. 

1323 """ 

1324 generated: dict[str, JsonSchemaValue] = {} 

1325 for k, v in schema['choices'].items(): 

1326 if isinstance(k, Enum): 

1327 k = k.value 

1328 try: 

1329 # Use str(k) since keys must be strings for json; while not technically correct, 

1330 # it's the closest that can be represented in valid JSON 

1331 generated[str(k)] = self.generate_inner(v).copy() 

1332 except PydanticOmit: 

1333 continue 

1334 except PydanticInvalidForJsonSchema as exc: 

1335 self.emit_warning('skipped-choice', exc.message) 

1336 

1337 one_of_choices = _deduplicate_schemas(generated.values()) 

1338 json_schema: JsonSchemaValue = {'oneOf': one_of_choices} 

1339 

1340 # This reflects the v1 behavior; TODO: we should make it possible to exclude OpenAPI stuff from the JSON schema 

1341 openapi_discriminator = self._extract_discriminator(schema, one_of_choices) 

1342 if openapi_discriminator is not None: 

1343 json_schema['discriminator'] = { 

1344 'propertyName': openapi_discriminator, 

1345 'mapping': {k: v.get('$ref', v) for k, v in generated.items()}, 

1346 } 

1347 

1348 return json_schema 

1349 

1350 def _extract_discriminator( 

1351 self, schema: core_schema.TaggedUnionSchema, one_of_choices: list[JsonDict] 

1352 ) -> str | None: 

1353 """Extract a compatible OpenAPI discriminator from the schema and one_of choices that end up in the final 

1354 schema.""" 

1355 openapi_discriminator: str | None = None 

1356 

1357 if isinstance(schema['discriminator'], str): 

1358 return schema['discriminator'] 

1359 

1360 if isinstance(schema['discriminator'], list): 

1361 # If the discriminator is a single item list containing a string, that is equivalent to the string case 

1362 if len(schema['discriminator']) == 1 and isinstance(schema['discriminator'][0], str): 

1363 return schema['discriminator'][0] 

1364 # When an alias is used that is different from the field name, the discriminator will be a list of single 

1365 # str lists, one for the attribute and one for the actual alias. The logic here will work even if there is 

1366 # more than one possible attribute, and looks for whether a single alias choice is present as a documented 

1367 # property on all choices. If so, that property will be used as the OpenAPI discriminator. 

1368 for alias_path in schema['discriminator']: 

1369 if not isinstance(alias_path, list): 

1370 break # this means that the discriminator is not a list of alias paths 

1371 if len(alias_path) != 1: 

1372 continue # this means that the "alias" does not represent a single field 

1373 alias = alias_path[0] 

1374 if not isinstance(alias, str): 

1375 continue # this means that the "alias" does not represent a field 

1376 alias_is_present_on_all_choices = True 

1377 for choice in one_of_choices: 

1378 try: 

1379 choice = self.resolve_ref_schema(choice) 

1380 except RuntimeError as exc: 

1381 # TODO: fixme - this is a workaround for the fact that we can't always resolve refs 

1382 # for tagged union choices at this point in the schema gen process, we might need to do 

1383 # another pass at the end like we do for core schemas 

1384 self.emit_warning('skipped-discriminator', str(exc)) 

1385 choice = {} 

1386 properties = choice.get('properties', {}) 

1387 if not isinstance(properties, dict) or alias not in properties: 

1388 alias_is_present_on_all_choices = False 

1389 break 

1390 if alias_is_present_on_all_choices: 

1391 openapi_discriminator = alias 

1392 break 

1393 return openapi_discriminator 

1394 

1395 def chain_schema(self, schema: core_schema.ChainSchema) -> JsonSchemaValue: 

1396 """Generates a JSON schema that matches a core_schema.ChainSchema. 

1397 

1398 When generating a schema for validation, we return the validation JSON schema for the first step in the chain. 

1399 For serialization, we return the serialization JSON schema for the last step in the chain. 

1400 

1401 Args: 

1402 schema: The core schema. 

1403 

1404 Returns: 

1405 The generated JSON schema. 

1406 """ 

1407 step_index = 0 if self.mode == 'validation' else -1 # use first step for validation, last for serialization 

1408 return self.generate_inner(schema['steps'][step_index]) 

1409 

1410 def lax_or_strict_schema(self, schema: core_schema.LaxOrStrictSchema) -> JsonSchemaValue: 

1411 """Generates a JSON schema that matches a schema that allows values matching either the lax schema or the 

1412 strict schema. 

1413 

1414 Args: 

1415 schema: The core schema. 

1416 

1417 Returns: 

1418 The generated JSON schema. 

1419 """ 

1420 # TODO: Need to read the default value off of model config or whatever 

1421 use_strict = schema.get('strict', False) # TODO: replace this default False 

1422 # If your JSON schema fails to generate it is probably 

1423 # because one of the following two branches failed. 

1424 if use_strict: 

1425 return self.generate_inner(schema['strict_schema']) 

1426 else: 

1427 return self.generate_inner(schema['lax_schema']) 

1428 

1429 def json_or_python_schema(self, schema: core_schema.JsonOrPythonSchema) -> JsonSchemaValue: 

1430 """Generates a JSON schema that matches a schema that allows values matching either the JSON schema or the 

1431 Python schema. 

1432 

1433 The JSON schema is used instead of the Python schema. If you want to use the Python schema, you should override 

1434 this method. 

1435 

1436 Args: 

1437 schema: The core schema. 

1438 

1439 Returns: 

1440 The generated JSON schema. 

1441 """ 

1442 return self.generate_inner(schema['json_schema']) 

1443 

1444 def typed_dict_schema(self, schema: core_schema.TypedDictSchema) -> JsonSchemaValue: 

1445 """Generates a JSON schema that matches a schema that defines a typed dict. 

1446 

1447 Args: 

1448 schema: The core schema. 

1449 

1450 Returns: 

1451 The generated JSON schema. 

1452 """ 

1453 total = schema.get('total', True) 

1454 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [ 

1455 (name, self.field_is_required(field, total), field) 

1456 for name, field in schema['fields'].items() 

1457 if self.field_is_present(field) 

1458 ] 

1459 if self.mode == 'serialization': 

1460 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', []))) 

1461 cls = schema.get('cls') 

1462 config = _get_typed_dict_config(cls) 

1463 with self._config_wrapper_stack.push(config): 

1464 json_schema = self._named_required_fields_schema(named_required_fields) 

1465 

1466 # There's some duplication between `extra_behavior` and 

1467 # the config's `extra`/core config's `extra_fields_behavior`. 

1468 # However, it is common to manually create TypedDictSchemas, 

1469 # where you don't necessarily have a class. 

1470 # At runtime, `extra_behavior` takes priority over the config 

1471 # for validation, so follow the same for the JSON Schema: 

1472 if schema.get('extra_behavior') == 'forbid': 

1473 json_schema['additionalProperties'] = False 

1474 elif schema.get('extra_behavior') == 'allow': 

1475 if 'extras_schema' in schema and schema['extras_schema'] != {'type': 'any'}: 

1476 json_schema['additionalProperties'] = self.generate_inner(schema['extras_schema']) 

1477 else: 

1478 json_schema['additionalProperties'] = True 

1479 

1480 if cls is not None: 

1481 # `_update_class_schema()` will not override 

1482 # `additionalProperties` if already present: 

1483 self._update_class_schema(json_schema, cls, config) 

1484 elif 'additionalProperties' not in json_schema: 

1485 extra = schema.get('config', {}).get('extra_fields_behavior') 

1486 if extra == 'forbid': 

1487 json_schema['additionalProperties'] = False 

1488 elif extra == 'allow': 

1489 json_schema['additionalProperties'] = True 

1490 

1491 return json_schema 

1492 

1493 @staticmethod 

1494 def _name_required_computed_fields( 

1495 computed_fields: list[ComputedField], 

1496 ) -> list[tuple[str, bool, core_schema.ComputedField]]: 

1497 return [(field['property_name'], True, field) for field in computed_fields] 

1498 

1499 def _named_required_fields_schema( 

1500 self, named_required_fields: Sequence[tuple[str, bool, CoreSchemaField]] 

1501 ) -> JsonSchemaValue: 

1502 properties: dict[str, JsonSchemaValue] = {} 

1503 required_fields: list[str] = [] 

1504 for name, required, field in named_required_fields: 

1505 if self.by_alias: 

1506 name = self._get_alias_name(field, name) 

1507 try: 

1508 field_json_schema = self.generate_inner(field).copy() 

1509 except PydanticOmit: 

1510 continue 

1511 if 'title' not in field_json_schema and self.field_title_should_be_set(field): 

1512 title = self.get_title_from_name(name) 

1513 field_json_schema['title'] = title 

1514 field_json_schema = self.handle_ref_overrides(field_json_schema) 

1515 properties[name] = field_json_schema 

1516 if required: 

1517 required_fields.append(name) 

1518 

1519 json_schema = {'type': 'object', 'properties': properties} 

1520 if required_fields: 

1521 json_schema['required'] = required_fields 

1522 return json_schema 

1523 

1524 def _get_alias_name(self, field: CoreSchemaField, name: str) -> str: 

1525 if field['type'] == 'computed-field': 

1526 alias: Any = field.get('alias', name) 

1527 elif self.mode == 'validation': 

1528 alias = field.get('validation_alias', name) 

1529 else: 

1530 alias = field.get('serialization_alias', name) 

1531 if isinstance(alias, str): 

1532 name = alias 

1533 elif isinstance(alias, list): 

1534 alias = cast('list[str] | str', alias) 

1535 for path in alias: 

1536 if isinstance(path, list) and len(path) == 1 and isinstance(path[0], str): 

1537 # Use the first valid single-item string path; the code that constructs the alias array 

1538 # should ensure the first such item is what belongs in the JSON schema 

1539 name = path[0] 

1540 break 

1541 else: 

1542 assert_never(alias) 

1543 return name 

1544 

1545 def typed_dict_field_schema(self, schema: core_schema.TypedDictField) -> JsonSchemaValue: 

1546 """Generates a JSON schema that matches a schema that defines a typed dict field. 

1547 

1548 Args: 

1549 schema: The core schema. 

1550 

1551 Returns: 

1552 The generated JSON schema. 

1553 """ 

1554 return self.generate_inner(schema['schema']) 

1555 

1556 def dataclass_field_schema(self, schema: core_schema.DataclassField) -> JsonSchemaValue: 

1557 """Generates a JSON schema that matches a schema that defines a dataclass field. 

1558 

1559 Args: 

1560 schema: The core schema. 

1561 

1562 Returns: 

1563 The generated JSON schema. 

1564 """ 

1565 return self.generate_inner(schema['schema']) 

1566 

1567 def model_field_schema(self, schema: core_schema.ModelField) -> JsonSchemaValue: 

1568 """Generates a JSON schema that matches a schema that defines a model field. 

1569 

1570 Args: 

1571 schema: The core schema. 

1572 

1573 Returns: 

1574 The generated JSON schema. 

1575 """ 

1576 return self.generate_inner(schema['schema']) 

1577 

1578 def computed_field_schema(self, schema: core_schema.ComputedField) -> JsonSchemaValue: 

1579 """Generates a JSON schema that matches a schema that defines a computed field. 

1580 

1581 Args: 

1582 schema: The core schema. 

1583 

1584 Returns: 

1585 The generated JSON schema. 

1586 """ 

1587 return self.generate_inner(schema['return_schema']) 

1588 

1589 def model_schema(self, schema: core_schema.ModelSchema) -> JsonSchemaValue: 

1590 """Generates a JSON schema that matches a schema that defines a model. 

1591 

1592 Args: 

1593 schema: The core schema. 

1594 

1595 Returns: 

1596 The generated JSON schema. 

1597 """ 

1598 # We do not use schema['model'].model_json_schema() here 

1599 # because it could lead to inconsistent refs handling, etc. 

1600 cls = cast('type[BaseModel]', schema['cls']) 

1601 config = cls.model_config 

1602 

1603 with self._config_wrapper_stack.push(config): 

1604 json_schema = self.generate_inner(schema['schema']) 

1605 

1606 self._update_class_schema(json_schema, cls, config) 

1607 

1608 return json_schema 

1609 

1610 def _update_class_schema(self, json_schema: JsonSchemaValue, cls: type[Any], config: ConfigDict) -> None: 

1611 """Update json_schema with the following, extracted from `config` and `cls`: 

1612 

1613 * title 

1614 * description 

1615 * additional properties 

1616 * json_schema_extra 

1617 * deprecated 

1618 

1619 Done in place, hence there's no return value as the original json_schema is mutated. 

1620 No ref resolving is involved here, as that's not appropriate for simple updates. 

1621 """ 

1622 from .main import BaseModel 

1623 from .root_model import RootModel 

1624 

1625 if (config_title := config.get('title')) is not None: 

1626 json_schema.setdefault('title', config_title) 

1627 elif model_title_generator := config.get('model_title_generator'): 

1628 title = model_title_generator(cls) 

1629 if not isinstance(title, str): 

1630 raise TypeError(f'model_title_generator {model_title_generator} must return str, not {title.__class__}') 

1631 json_schema.setdefault('title', title) 

1632 if 'title' not in json_schema: 

1633 json_schema['title'] = cls.__name__ 

1634 

1635 # BaseModel and dataclasses; don't use cls.__doc__ as it will contain the verbose class signature by default 

1636 docstring = None if cls is BaseModel or dataclasses.is_dataclass(cls) else cls.__doc__ 

1637 

1638 if docstring: 

1639 json_schema.setdefault('description', inspect.cleandoc(docstring)) 

1640 elif issubclass(cls, RootModel) and (root_description := cls.__pydantic_fields__['root'].description): 

1641 json_schema.setdefault('description', root_description) 

1642 

1643 extra = config.get('extra') 

1644 if 'additionalProperties' not in json_schema: # This check is particularly important for `typed_dict_schema()` 

1645 if extra == 'allow': 

1646 json_schema['additionalProperties'] = True 

1647 elif extra == 'forbid': 

1648 json_schema['additionalProperties'] = False 

1649 

1650 json_schema_extra = config.get('json_schema_extra') 

1651 if issubclass(cls, BaseModel) and cls.__pydantic_root_model__: 

1652 root_json_schema_extra = cls.model_fields['root'].json_schema_extra 

1653 if json_schema_extra and root_json_schema_extra: 

1654 raise ValueError( 

1655 '"model_config[\'json_schema_extra\']" and "Field.json_schema_extra" on "RootModel.root"' 

1656 ' field must not be set simultaneously' 

1657 ) 

1658 if root_json_schema_extra: 

1659 json_schema_extra = root_json_schema_extra 

1660 

1661 if isinstance(json_schema_extra, (staticmethod, classmethod)): 

1662 # In older versions of python, this is necessary to ensure staticmethod/classmethods are callable 

1663 json_schema_extra = json_schema_extra.__get__(cls) 

1664 

1665 if isinstance(json_schema_extra, dict): 

1666 json_schema.update(json_schema_extra) 

1667 elif callable(json_schema_extra): 

1668 # FIXME: why are there type ignores here? We support two signatures for json_schema_extra callables... 

1669 if len(inspect.signature(json_schema_extra).parameters) > 1: 

1670 json_schema_extra(json_schema, cls) # type: ignore 

1671 else: 

1672 json_schema_extra(json_schema) # type: ignore 

1673 elif json_schema_extra is not None: 

1674 raise ValueError( 

1675 f"model_config['json_schema_extra']={json_schema_extra} should be a dict, callable, or None" 

1676 ) 

1677 

1678 if hasattr(cls, '__deprecated__'): 

1679 json_schema['deprecated'] = True 

1680 

1681 def resolve_ref_schema(self, json_schema: JsonSchemaValue) -> JsonSchemaValue: 

1682 """Resolve a JsonSchemaValue to the non-ref schema if it is a $ref schema. 

1683 

1684 Args: 

1685 json_schema: The schema to resolve. 

1686 

1687 Returns: 

1688 The resolved schema. 

1689 

1690 Raises: 

1691 RuntimeError: If the schema reference can't be found in definitions. 

1692 """ 

1693 while '$ref' in json_schema: 

1694 ref = json_schema['$ref'] 

1695 schema_to_update = self.get_schema_from_definitions(JsonRef(ref)) 

1696 if schema_to_update is None: 

1697 raise RuntimeError(f'Cannot update undefined schema for $ref={ref}') 

1698 json_schema = schema_to_update 

1699 return json_schema 

1700 

1701 def model_fields_schema(self, schema: core_schema.ModelFieldsSchema) -> JsonSchemaValue: 

1702 """Generates a JSON schema that matches a schema that defines a model's fields. 

1703 

1704 Args: 

1705 schema: The core schema. 

1706 

1707 Returns: 

1708 The generated JSON schema. 

1709 """ 

1710 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [ 

1711 (name, self.field_is_required(field, total=True), field) 

1712 for name, field in schema['fields'].items() 

1713 if self.field_is_present(field) 

1714 ] 

1715 if self.mode == 'serialization': 

1716 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', []))) 

1717 json_schema = self._named_required_fields_schema(named_required_fields) 

1718 extras_schema = schema.get('extras_schema', None) 

1719 if extras_schema is not None: 

1720 schema_to_update = self.resolve_ref_schema(json_schema) 

1721 schema_to_update['additionalProperties'] = self.generate_inner(extras_schema) 

1722 return json_schema 

1723 

1724 def field_is_present(self, field: CoreSchemaField) -> bool: 

1725 """Whether the field should be included in the generated JSON schema. 

1726 

1727 Args: 

1728 field: The schema for the field itself. 

1729 

1730 Returns: 

1731 `True` if the field should be included in the generated JSON schema, `False` otherwise. 

1732 """ 

1733 if self.mode == 'serialization': 

1734 # If you still want to include the field in the generated JSON schema, 

1735 # override this method and return True 

1736 return not field.get('serialization_exclude') 

1737 elif self.mode == 'validation': 

1738 return True 

1739 else: 

1740 assert_never(self.mode) 

1741 

1742 def field_is_required( 

1743 self, 

1744 field: core_schema.ModelField | core_schema.DataclassField | core_schema.TypedDictField, 

1745 total: bool, 

1746 ) -> bool: 

1747 """Whether the field should be marked as required in the generated JSON schema. 

1748 (Note that this is irrelevant if the field is not present in the JSON schema.). 

1749 

1750 Args: 

1751 field: The schema for the field itself. 

1752 total: Only applies to `TypedDictField`s. 

1753 Indicates if the `TypedDict` this field belongs to is total, in which case any fields that don't 

1754 explicitly specify `required=False` are required. 

1755 

1756 Returns: 

1757 `True` if the field should be marked as required in the generated JSON schema, `False` otherwise. 

1758 """ 

1759 if self.mode == 'serialization' and self._config.json_schema_serialization_defaults_required: 

1760 return not field.get('serialization_exclude') 

1761 else: 

1762 if field['type'] == 'typed-dict-field': 

1763 return field.get('required', total) 

1764 else: 

1765 return field['schema']['type'] != 'default' 

1766 

1767 def dataclass_args_schema(self, schema: core_schema.DataclassArgsSchema) -> JsonSchemaValue: 

1768 """Generates a JSON schema that matches a schema that defines a dataclass's constructor arguments. 

1769 

1770 Args: 

1771 schema: The core schema. 

1772 

1773 Returns: 

1774 The generated JSON schema. 

1775 """ 

1776 named_required_fields: list[tuple[str, bool, CoreSchemaField]] = [ 

1777 (field['name'], self.field_is_required(field, total=True), field) 

1778 for field in schema['fields'] 

1779 if self.field_is_present(field) 

1780 ] 

1781 if self.mode == 'serialization': 

1782 named_required_fields.extend(self._name_required_computed_fields(schema.get('computed_fields', []))) 

1783 return self._named_required_fields_schema(named_required_fields) 

1784 

1785 def dataclass_schema(self, schema: core_schema.DataclassSchema) -> JsonSchemaValue: 

1786 """Generates a JSON schema that matches a schema that defines a dataclass. 

1787 

1788 Args: 

1789 schema: The core schema. 

1790 

1791 Returns: 

1792 The generated JSON schema. 

1793 """ 

1794 from ._internal._dataclasses import is_stdlib_dataclass 

1795 

1796 cls = schema['cls'] 

1797 config: ConfigDict = getattr(cls, '__pydantic_config__', cast('ConfigDict', {})) 

1798 

1799 with self._config_wrapper_stack.push(config): 

1800 json_schema = self.generate_inner(schema['schema']).copy() 

1801 

1802 self._update_class_schema(json_schema, cls, config) 

1803 

1804 # Dataclass-specific handling of description 

1805 if is_stdlib_dataclass(cls): 

1806 # vanilla dataclass; don't use cls.__doc__ as it will contain the class signature by default 

1807 description = None 

1808 else: 

1809 description = None if cls.__doc__ is None else inspect.cleandoc(cls.__doc__) 

1810 if description: 

1811 json_schema['description'] = description 

1812 

1813 return json_schema 

1814 

1815 def arguments_schema(self, schema: core_schema.ArgumentsSchema) -> JsonSchemaValue: 

1816 """Generates a JSON schema that matches a schema that defines a function's arguments. 

1817 

1818 Args: 

1819 schema: The core schema. 

1820 

1821 Returns: 

1822 The generated JSON schema. 

1823 """ 

1824 prefer_positional = schema.get('metadata', {}).get('pydantic_js_prefer_positional_arguments') 

1825 

1826 arguments = schema['arguments_schema'] 

1827 kw_only_arguments = [a for a in arguments if a.get('mode') == 'keyword_only'] 

1828 kw_or_p_arguments = [a for a in arguments if a.get('mode') in {'positional_or_keyword', None}] 

1829 p_only_arguments = [a for a in arguments if a.get('mode') == 'positional_only'] 

1830 var_args_schema = schema.get('var_args_schema') 

1831 var_kwargs_schema = schema.get('var_kwargs_schema') 

1832 

1833 if prefer_positional: 

1834 positional_possible = not kw_only_arguments and not var_kwargs_schema 

1835 if positional_possible: 

1836 return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema) 

1837 

1838 keyword_possible = not p_only_arguments and not var_args_schema 

1839 if keyword_possible: 

1840 return self.kw_arguments_schema(kw_or_p_arguments + kw_only_arguments, var_kwargs_schema) 

1841 

1842 if not prefer_positional: 

1843 positional_possible = not kw_only_arguments and not var_kwargs_schema 

1844 if positional_possible: 

1845 return self.p_arguments_schema(p_only_arguments + kw_or_p_arguments, var_args_schema) 

1846 

1847 raise PydanticInvalidForJsonSchema( 

1848 'Unable to generate JSON schema for arguments validator with positional-only and keyword-only arguments' 

1849 ) 

1850 

1851 def kw_arguments_schema( 

1852 self, arguments: list[core_schema.ArgumentsParameter], var_kwargs_schema: CoreSchema | None 

1853 ) -> JsonSchemaValue: 

1854 """Generates a JSON schema that matches a schema that defines a function's keyword arguments. 

1855 

1856 Args: 

1857 arguments: The core schema. 

1858 

1859 Returns: 

1860 The generated JSON schema. 

1861 """ 

1862 properties: dict[str, JsonSchemaValue] = {} 

1863 required: list[str] = [] 

1864 for argument in arguments: 

1865 name = self.get_argument_name(argument) 

1866 argument_schema = self.generate_inner(argument['schema']).copy() 

1867 if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']): 

1868 argument_schema['title'] = self.get_title_from_name(name) 

1869 properties[name] = argument_schema 

1870 

1871 if argument['schema']['type'] != 'default': 

1872 # This assumes that if the argument has a default value, 

1873 # the inner schema must be of type WithDefaultSchema. 

1874 # I believe this is true, but I am not 100% sure 

1875 required.append(name) 

1876 

1877 json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties} 

1878 if required: 

1879 json_schema['required'] = required 

1880 

1881 if var_kwargs_schema: 

1882 additional_properties_schema = self.generate_inner(var_kwargs_schema) 

1883 if additional_properties_schema: 

1884 json_schema['additionalProperties'] = additional_properties_schema 

1885 else: 

1886 json_schema['additionalProperties'] = False 

1887 return json_schema 

1888 

1889 def p_arguments_schema( 

1890 self, arguments: list[core_schema.ArgumentsParameter], var_args_schema: CoreSchema | None 

1891 ) -> JsonSchemaValue: 

1892 """Generates a JSON schema that matches a schema that defines a function's positional arguments. 

1893 

1894 Args: 

1895 arguments: The core schema. 

1896 

1897 Returns: 

1898 The generated JSON schema. 

1899 """ 

1900 prefix_items: list[JsonSchemaValue] = [] 

1901 min_items = 0 

1902 

1903 for argument in arguments: 

1904 name = self.get_argument_name(argument) 

1905 

1906 argument_schema = self.generate_inner(argument['schema']).copy() 

1907 if 'title' not in argument_schema and self.field_title_should_be_set(argument['schema']): 

1908 argument_schema['title'] = self.get_title_from_name(name) 

1909 prefix_items.append(argument_schema) 

1910 

1911 if argument['schema']['type'] != 'default': 

1912 # This assumes that if the argument has a default value, 

1913 # the inner schema must be of type WithDefaultSchema. 

1914 # I believe this is true, but I am not 100% sure 

1915 min_items += 1 

1916 

1917 json_schema: JsonSchemaValue = {'type': 'array'} 

1918 if prefix_items: 

1919 json_schema['prefixItems'] = prefix_items 

1920 if min_items: 

1921 json_schema['minItems'] = min_items 

1922 

1923 if var_args_schema: 

1924 items_schema = self.generate_inner(var_args_schema) 

1925 if items_schema: 

1926 json_schema['items'] = items_schema 

1927 else: 

1928 json_schema['maxItems'] = len(prefix_items) 

1929 

1930 return json_schema 

1931 

1932 def get_argument_name(self, argument: core_schema.ArgumentsParameter | core_schema.ArgumentsV3Parameter) -> str: 

1933 """Retrieves the name of an argument. 

1934 

1935 Args: 

1936 argument: The core schema. 

1937 

1938 Returns: 

1939 The name of the argument. 

1940 """ 

1941 name = argument['name'] 

1942 if self.by_alias: 

1943 alias = argument.get('alias') 

1944 if isinstance(alias, str): 

1945 name = alias 

1946 else: 

1947 pass # might want to do something else? 

1948 return name 

1949 

1950 def arguments_v3_schema(self, schema: core_schema.ArgumentsV3Schema) -> JsonSchemaValue: 

1951 """Generates a JSON schema that matches a schema that defines a function's arguments. 

1952 

1953 Args: 

1954 schema: The core schema. 

1955 

1956 Returns: 

1957 The generated JSON schema. 

1958 """ 

1959 arguments = schema['arguments_schema'] 

1960 properties: dict[str, JsonSchemaValue] = {} 

1961 required: list[str] = [] 

1962 for argument in arguments: 

1963 mode = argument.get('mode', 'positional_or_keyword') 

1964 name = self.get_argument_name(argument) 

1965 argument_schema = self.generate_inner(argument['schema']).copy() 

1966 if mode == 'var_args': 

1967 argument_schema = {'type': 'array', 'items': argument_schema} 

1968 elif mode == 'var_kwargs_uniform': 

1969 argument_schema = {'type': 'object', 'additionalProperties': argument_schema} 

1970 

1971 argument_schema.setdefault('title', self.get_title_from_name(name)) 

1972 properties[name] = argument_schema 

1973 

1974 if ( 

1975 (mode == 'var_kwargs_unpacked_typed_dict' and 'required' in argument_schema) 

1976 or mode not in {'var_args', 'var_kwargs_uniform', 'var_kwargs_unpacked_typed_dict'} 

1977 and argument['schema']['type'] != 'default' 

1978 ): 

1979 # This assumes that if the argument has a default value, 

1980 # the inner schema must be of type WithDefaultSchema. 

1981 # I believe this is true, but I am not 100% sure 

1982 required.append(name) 

1983 

1984 json_schema: JsonSchemaValue = {'type': 'object', 'properties': properties} 

1985 if required: 

1986 json_schema['required'] = required 

1987 return json_schema 

1988 

1989 def call_schema(self, schema: core_schema.CallSchema) -> JsonSchemaValue: 

1990 """Generates a JSON schema that matches a schema that defines a function call. 

1991 

1992 Args: 

1993 schema: The core schema. 

1994 

1995 Returns: 

1996 The generated JSON schema. 

1997 """ 

1998 return self.generate_inner(schema['arguments_schema']) 

1999 

2000 def custom_error_schema(self, schema: core_schema.CustomErrorSchema) -> JsonSchemaValue: 

2001 """Generates a JSON schema that matches a schema that defines a custom error. 

2002 

2003 Args: 

2004 schema: The core schema. 

2005 

2006 Returns: 

2007 The generated JSON schema. 

2008 """ 

2009 return self.generate_inner(schema['schema']) 

2010 

2011 def json_schema(self, schema: core_schema.JsonSchema) -> JsonSchemaValue: 

2012 """Generates a JSON schema that matches a schema that defines a JSON object. 

2013 

2014 Args: 

2015 schema: The core schema. 

2016 

2017 Returns: 

2018 The generated JSON schema. 

2019 """ 

2020 content_core_schema = schema.get('schema') or core_schema.any_schema() 

2021 content_json_schema = self.generate_inner(content_core_schema) 

2022 if self.mode == 'validation': 

2023 return {'type': 'string', 'contentMediaType': 'application/json', 'contentSchema': content_json_schema} 

2024 else: 

2025 # self.mode == 'serialization' 

2026 return content_json_schema 

2027 

2028 def url_schema(self, schema: core_schema.UrlSchema) -> JsonSchemaValue: 

2029 """Generates a JSON schema that matches a schema that defines a URL. 

2030 

2031 Args: 

2032 schema: The core schema. 

2033 

2034 Returns: 

2035 The generated JSON schema. 

2036 """ 

2037 json_schema = {'type': 'string', 'format': 'uri', 'minLength': 1} 

2038 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string) 

2039 return json_schema 

2040 

2041 def multi_host_url_schema(self, schema: core_schema.MultiHostUrlSchema) -> JsonSchemaValue: 

2042 """Generates a JSON schema that matches a schema that defines a URL that can be used with multiple hosts. 

2043 

2044 Args: 

2045 schema: The core schema. 

2046 

2047 Returns: 

2048 The generated JSON schema. 

2049 """ 

2050 # Note: 'multi-host-uri' is a custom/pydantic-specific format, not part of the JSON Schema spec 

2051 json_schema = {'type': 'string', 'format': 'multi-host-uri', 'minLength': 1} 

2052 self.update_with_validations(json_schema, schema, self.ValidationsMapping.string) 

2053 return json_schema 

2054 

2055 def uuid_schema(self, schema: core_schema.UuidSchema) -> JsonSchemaValue: 

2056 """Generates a JSON schema that matches a UUID. 

2057 

2058 Args: 

2059 schema: The core schema. 

2060 

2061 Returns: 

2062 The generated JSON schema. 

2063 """ 

2064 return {'type': 'string', 'format': 'uuid'} 

2065 

2066 def definitions_schema(self, schema: core_schema.DefinitionsSchema) -> JsonSchemaValue: 

2067 """Generates a JSON schema that matches a schema that defines a JSON object with definitions. 

2068 

2069 Args: 

2070 schema: The core schema. 

2071 

2072 Returns: 

2073 The generated JSON schema. 

2074 """ 

2075 for definition in schema['definitions']: 

2076 try: 

2077 self.generate_inner(definition) 

2078 except PydanticInvalidForJsonSchema as e: # noqa: PERF203 

2079 core_ref: CoreRef = CoreRef(definition['ref']) # type: ignore 

2080 self._core_defs_invalid_for_json_schema[self.get_defs_ref((core_ref, self.mode))] = e 

2081 continue 

2082 return self.generate_inner(schema['schema']) 

2083 

2084 def definition_ref_schema(self, schema: core_schema.DefinitionReferenceSchema) -> JsonSchemaValue: 

2085 """Generates a JSON schema that matches a schema that references a definition. 

2086 

2087 Args: 

2088 schema: The core schema. 

2089 

2090 Returns: 

2091 The generated JSON schema. 

2092 """ 

2093 core_ref = CoreRef(schema['schema_ref']) 

2094 _, ref_json_schema = self.get_cache_defs_ref_schema(core_ref) 

2095 return ref_json_schema 

2096 

2097 def ser_schema( 

2098 self, schema: core_schema.SerSchema | core_schema.IncExSeqSerSchema | core_schema.IncExDictSerSchema 

2099 ) -> JsonSchemaValue | None: 

2100 """Generates a JSON schema that matches a schema that defines a serialized object. 

2101 

2102 Args: 

2103 schema: The core schema. 

2104 

2105 Returns: 

2106 The generated JSON schema. 

2107 """ 

2108 schema_type = schema['type'] 

2109 if schema_type == 'function-plain' or schema_type == 'function-wrap': 

2110 # PlainSerializerFunctionSerSchema or WrapSerializerFunctionSerSchema 

2111 return_schema = schema.get('return_schema') 

2112 if return_schema is not None: 

2113 return self.generate_inner(return_schema) 

2114 elif schema_type == 'format' or schema_type == 'to-string': 

2115 # FormatSerSchema or ToStringSerSchema 

2116 return self.str_schema(core_schema.str_schema()) 

2117 elif schema['type'] == 'model': 

2118 # ModelSerSchema 

2119 return self.generate_inner(schema['schema']) 

2120 return None 

2121 

2122 def complex_schema(self, schema: core_schema.ComplexSchema) -> JsonSchemaValue: 

2123 """Generates a JSON schema that matches a complex number. 

2124 

2125 JSON has no standard way to represent complex numbers. Complex number is not a numeric 

2126 type. Here we represent complex number as strings following the rule defined by Python. 

2127 For instance, '1+2j' is an accepted complex string. Details can be found in 

2128 [Python's `complex` documentation][complex]. 

2129 

2130 Args: 

2131 schema: The core schema. 

2132 

2133 Returns: 

2134 The generated JSON schema. 

2135 """ 

2136 return {'type': 'string'} 

2137 

2138 # ### Utility methods 

2139 

2140 def get_title_from_name(self, name: str) -> str: 

2141 """Retrieves a title from a name. 

2142 

2143 Args: 

2144 name: The name to retrieve a title from. 

2145 

2146 Returns: 

2147 The title. 

2148 """ 

2149 return name.title().replace('_', ' ').strip() 

2150 

2151 def field_title_should_be_set(self, schema: CoreSchemaOrField) -> bool: 

2152 """Returns true if a field with the given schema should have a title set based on the field name. 

2153 

2154 Intuitively, we want this to return true for schemas that wouldn't otherwise provide their own title 

2155 (e.g., int, float, str), and false for those that would (e.g., BaseModel subclasses). 

2156 

2157 Args: 

2158 schema: The schema to check. 

2159 

2160 Returns: 

2161 `True` if the field should have a title set, `False` otherwise. 

2162 """ 

2163 if _core_utils.is_core_schema_field(schema): 

2164 if schema['type'] == 'computed-field': 

2165 field_schema = schema['return_schema'] 

2166 else: 

2167 field_schema = schema['schema'] 

2168 return self.field_title_should_be_set(field_schema) 

2169 

2170 elif _core_utils.is_core_schema(schema): 

2171 if schema.get('ref'): # things with refs, such as models and enums, should not have titles set 

2172 return False 

2173 if schema['type'] in {'default', 'nullable', 'definitions'}: 

2174 return self.field_title_should_be_set(schema['schema']) # type: ignore[typeddict-item] 

2175 if _core_utils.is_function_with_inner_schema(schema): 

2176 return self.field_title_should_be_set(schema['schema']) 

2177 if schema['type'] == 'definition-ref': 

2178 # Referenced schemas should not have titles set for the same reason 

2179 # schemas with refs should not 

2180 return False 

2181 return True # anything else should have title set 

2182 

2183 else: 

2184 raise PydanticInvalidForJsonSchema(f'Unexpected schema type: schema={schema}') # pragma: no cover 

2185 

2186 def normalize_name(self, name: str) -> str: 

2187 """Normalizes a name to be used as a key in a dictionary. 

2188 

2189 Args: 

2190 name: The name to normalize. 

2191 

2192 Returns: 

2193 The normalized name. 

2194 """ 

2195 return re.sub(r'[^a-zA-Z0-9.\-_]', '_', name).replace('.', '__') 

2196 

2197 def get_defs_ref(self, core_mode_ref: CoreModeRef) -> DefsRef: 

2198 """Override this method to change the way that definitions keys are generated from a core reference. 

2199 

2200 Args: 

2201 core_mode_ref: The core reference. 

2202 

2203 Returns: 

2204 The definitions key. 

2205 """ 

2206 # Split the core ref into "components"; generic origins and arguments are each separate components 

2207 core_ref, mode = core_mode_ref 

2208 components = re.split(r'([\][,])', core_ref) 

2209 # Remove IDs from each component 

2210 components = [x.rsplit(':', 1)[0] for x in components] 

2211 core_ref_no_id = ''.join(components) 

2212 # Remove everything before the last period from each "component" 

2213 components = [re.sub(r'(?:[^.[\]]+\.)+((?:[^.[\]]+))', r'\1', x) for x in components] 

2214 short_ref = ''.join(components) 

2215 

2216 mode_title = _MODE_TITLE_MAPPING[mode] 

2217 

2218 # It is important that the generated defs_ref values be such that at least one choice will not 

2219 # be generated for any other core_ref. Currently, this should be the case because we include 

2220 # the id of the source type in the core_ref 

2221 name = DefsRef(self.normalize_name(short_ref)) 

2222 name_mode = DefsRef(self.normalize_name(short_ref) + f'-{mode_title}') 

2223 module_qualname = DefsRef(self.normalize_name(core_ref_no_id)) 

2224 module_qualname_mode = DefsRef(f'{module_qualname}-{mode_title}') 

2225 module_qualname_id = DefsRef(self.normalize_name(core_ref)) 

2226 occurrence_index = self._collision_index.get(module_qualname_id) 

2227 if occurrence_index is None: 

2228 self._collision_counter[module_qualname] += 1 

2229 occurrence_index = self._collision_index[module_qualname_id] = self._collision_counter[module_qualname] 

2230 

2231 module_qualname_occurrence = DefsRef(f'{module_qualname}__{occurrence_index}') 

2232 module_qualname_occurrence_mode = DefsRef(f'{module_qualname_mode}__{occurrence_index}') 

2233 

2234 self._prioritized_defsref_choices[module_qualname_occurrence_mode] = [ 

2235 name, 

2236 name_mode, 

2237 module_qualname, 

2238 module_qualname_mode, 

2239 module_qualname_occurrence, 

2240 module_qualname_occurrence_mode, 

2241 ] 

2242 

2243 return module_qualname_occurrence_mode 

2244 

2245 def get_cache_defs_ref_schema(self, core_ref: CoreRef) -> tuple[DefsRef, JsonSchemaValue]: 

2246 """This method wraps the get_defs_ref method with some cache-lookup/population logic, 

2247 and returns both the produced defs_ref and the JSON schema that will refer to the right definition. 

2248 

2249 Args: 

2250 core_ref: The core reference to get the definitions reference for. 

2251 

2252 Returns: 

2253 A tuple of the definitions reference and the JSON schema that will refer to it. 

2254 """ 

2255 core_mode_ref = (core_ref, self.mode) 

2256 maybe_defs_ref = self.core_to_defs_refs.get(core_mode_ref) 

2257 if maybe_defs_ref is not None: 

2258 json_ref = self.core_to_json_refs[core_mode_ref] 

2259 return maybe_defs_ref, {'$ref': json_ref} 

2260 

2261 defs_ref = self.get_defs_ref(core_mode_ref) 

2262 

2263 # populate the ref translation mappings 

2264 self.core_to_defs_refs[core_mode_ref] = defs_ref 

2265 self.defs_to_core_refs[defs_ref] = core_mode_ref 

2266 

2267 json_ref = JsonRef(self.ref_template.format(model=defs_ref)) 

2268 self.core_to_json_refs[core_mode_ref] = json_ref 

2269 self.json_to_defs_refs[json_ref] = defs_ref 

2270 ref_json_schema = {'$ref': json_ref} 

2271 return defs_ref, ref_json_schema 

2272 

2273 def handle_ref_overrides(self, json_schema: JsonSchemaValue) -> JsonSchemaValue: 

2274 """Remove any sibling keys that are redundant with the referenced schema. 

2275 

2276 Args: 

2277 json_schema: The schema to remove redundant sibling keys from. 

2278 

2279 Returns: 

2280 The schema with redundant sibling keys removed. 

2281 """ 

2282 if '$ref' in json_schema: 

2283 # prevent modifications to the input; this copy may be safe to drop if there is significant overhead 

2284 json_schema = json_schema.copy() 

2285 

2286 referenced_json_schema = self.get_schema_from_definitions(JsonRef(json_schema['$ref'])) 

2287 if referenced_json_schema is None: 

2288 # This can happen when building schemas for models with not-yet-defined references. 

2289 # It may be a good idea to do a recursive pass at the end of the generation to remove 

2290 # any redundant override keys. 

2291 return json_schema 

2292 for k, v in list(json_schema.items()): 

2293 if k == '$ref': 

2294 continue 

2295 if k in referenced_json_schema and referenced_json_schema[k] == v: 

2296 del json_schema[k] # redundant key 

2297 

2298 return json_schema 

2299 

2300 def get_schema_from_definitions(self, json_ref: JsonRef) -> JsonSchemaValue | None: 

2301 try: 

2302 def_ref = self.json_to_defs_refs[json_ref] 

2303 if def_ref in self._core_defs_invalid_for_json_schema: 

2304 raise self._core_defs_invalid_for_json_schema[def_ref] 

2305 return self.definitions.get(def_ref, None) 

2306 except KeyError: 

2307 if json_ref.startswith(('http://', 'https://')): 

2308 return None 

2309 raise 

2310 

2311 def encode_default(self, dft: Any) -> Any: 

2312 """Encode a default value to a JSON-serializable value. 

2313 

2314 This is used to encode default values for fields in the generated JSON schema. 

2315 

2316 Args: 

2317 dft: The default value to encode. 

2318 

2319 Returns: 

2320 The encoded default value. 

2321 """ 

2322 from .type_adapter import TypeAdapter, _type_has_config 

2323 

2324 config = self._config 

2325 try: 

2326 default = ( 

2327 dft 

2328 if _type_has_config(type(dft)) 

2329 else TypeAdapter(type(dft), config=config.config_dict).dump_python( 

2330 dft, by_alias=self.by_alias, mode='json' 

2331 ) 

2332 ) 

2333 except PydanticSchemaGenerationError: 

2334 raise pydantic_core.PydanticSerializationError(f'Unable to encode default value {dft}') 

2335 

2336 return pydantic_core.to_jsonable_python( 

2337 default, timedelta_mode=config.ser_json_timedelta, bytes_mode=config.ser_json_bytes, by_alias=self.by_alias 

2338 ) 

2339 

2340 def update_with_validations( 

2341 self, json_schema: JsonSchemaValue, core_schema: CoreSchema, mapping: dict[str, str] 

2342 ) -> None: 

2343 """Update the json_schema with the corresponding validations specified in the core_schema, 

2344 using the provided mapping to translate keys in core_schema to the appropriate keys for a JSON schema. 

2345 

2346 Args: 

2347 json_schema: The JSON schema to update. 

2348 core_schema: The core schema to get the validations from. 

2349 mapping: A mapping from core_schema attribute names to the corresponding JSON schema attribute names. 

2350 """ 

2351 for core_key, json_schema_key in mapping.items(): 

2352 if core_key in core_schema: 

2353 json_schema[json_schema_key] = core_schema[core_key] 

2354 

2355 class ValidationsMapping: 

2356 """This class just contains mappings from core_schema attribute names to the corresponding 

2357 JSON schema attribute names. While I suspect it is unlikely to be necessary, you can in 

2358 principle override this class in a subclass of GenerateJsonSchema (by inheriting from 

2359 GenerateJsonSchema.ValidationsMapping) to change these mappings. 

2360 """ 

2361 

2362 numeric = { 

2363 'multiple_of': 'multipleOf', 

2364 'le': 'maximum', 

2365 'ge': 'minimum', 

2366 'lt': 'exclusiveMaximum', 

2367 'gt': 'exclusiveMinimum', 

2368 } 

2369 bytes = { 

2370 'min_length': 'minLength', 

2371 'max_length': 'maxLength', 

2372 } 

2373 string = { 

2374 'min_length': 'minLength', 

2375 'max_length': 'maxLength', 

2376 'pattern': 'pattern', 

2377 } 

2378 array = { 

2379 'min_length': 'minItems', 

2380 'max_length': 'maxItems', 

2381 } 

2382 object = { 

2383 'min_length': 'minProperties', 

2384 'max_length': 'maxProperties', 

2385 } 

2386 

2387 def get_flattened_anyof(self, schemas: list[JsonSchemaValue]) -> JsonSchemaValue: 

2388 members = [] 

2389 for schema in schemas: 

2390 if len(schema) == 1 and 'anyOf' in schema: 

2391 members.extend(schema['anyOf']) 

2392 else: 

2393 members.append(schema) 

2394 members = _deduplicate_schemas(members) 

2395 if len(members) == 1: 

2396 return members[0] 

2397 return {'anyOf': members} 

2398 

2399 def get_json_ref_counts(self, json_schema: JsonSchemaValue) -> dict[JsonRef, int]: 

2400 """Get all values corresponding to the key '$ref' anywhere in the json_schema.""" 

2401 json_refs: dict[JsonRef, int] = Counter() 

2402 

2403 def _add_json_refs(schema: Any) -> None: 

2404 if isinstance(schema, dict): 

2405 if '$ref' in schema: 

2406 json_ref = JsonRef(schema['$ref']) 

2407 if not isinstance(json_ref, str): 

2408 return # in this case, '$ref' might have been the name of a property 

2409 already_visited = json_ref in json_refs 

2410 json_refs[json_ref] += 1 

2411 if already_visited: 

2412 return # prevent recursion on a definition that was already visited 

2413 try: 

2414 defs_ref = self.json_to_defs_refs[json_ref] 

2415 if defs_ref in self._core_defs_invalid_for_json_schema: 

2416 raise self._core_defs_invalid_for_json_schema[defs_ref] 

2417 _add_json_refs(self.definitions[defs_ref]) 

2418 except KeyError: 

2419 if not json_ref.startswith(('http://', 'https://')): 

2420 raise 

2421 

2422 for k, v in schema.items(): 

2423 if k == 'examples' and isinstance(v, list): 

2424 # Skip examples that may contain arbitrary values and references 

2425 # (see the comment in `_get_all_json_refs` for more details). 

2426 continue 

2427 _add_json_refs(v) 

2428 elif isinstance(schema, list): 

2429 for v in schema: 

2430 _add_json_refs(v) 

2431 

2432 _add_json_refs(json_schema) 

2433 return json_refs 

2434 

2435 def handle_invalid_for_json_schema(self, schema: CoreSchemaOrField, error_info: str) -> JsonSchemaValue: 

2436 raise PydanticInvalidForJsonSchema(f'Cannot generate a JsonSchema for {error_info}') 

2437 

2438 def emit_warning(self, kind: JsonSchemaWarningKind, detail: str) -> None: 

2439 """This method simply emits PydanticJsonSchemaWarnings based on handling in the `warning_message` method.""" 

2440 message = self.render_warning_message(kind, detail) 

2441 if message is not None: 

2442 warnings.warn(message, PydanticJsonSchemaWarning) 

2443 

2444 def render_warning_message(self, kind: JsonSchemaWarningKind, detail: str) -> str | None: 

2445 """This method is responsible for ignoring warnings as desired, and for formatting the warning messages. 

2446 

2447 You can override the value of `ignored_warning_kinds` in a subclass of GenerateJsonSchema 

2448 to modify what warnings are generated. If you want more control, you can override this method; 

2449 just return None in situations where you don't want warnings to be emitted. 

2450 

2451 Args: 

2452 kind: The kind of warning to render. It can be one of the following: 

2453 

2454 - 'skipped-choice': A choice field was skipped because it had no valid choices. 

2455 - 'non-serializable-default': A default value was skipped because it was not JSON-serializable. 

2456 detail: A string with additional details about the warning. 

2457 

2458 Returns: 

2459 The formatted warning message, or `None` if no warning should be emitted. 

2460 """ 

2461 if kind in self.ignored_warning_kinds: 

2462 return None 

2463 return f'{detail} [{kind}]' 

2464 

2465 def _build_definitions_remapping(self) -> _DefinitionsRemapping: 

2466 defs_to_json: dict[DefsRef, JsonRef] = {} 

2467 for defs_refs in self._prioritized_defsref_choices.values(): 

2468 for defs_ref in defs_refs: 

2469 json_ref = JsonRef(self.ref_template.format(model=defs_ref)) 

2470 defs_to_json[defs_ref] = json_ref 

2471 

2472 return _DefinitionsRemapping.from_prioritized_choices( 

2473 self._prioritized_defsref_choices, defs_to_json, self.definitions 

2474 ) 

2475 

2476 def _garbage_collect_definitions(self, schema: JsonSchemaValue) -> None: 

2477 visited_defs_refs: set[DefsRef] = set() 

2478 unvisited_json_refs = _get_all_json_refs(schema) 

2479 while unvisited_json_refs: 

2480 next_json_ref = unvisited_json_refs.pop() 

2481 try: 

2482 next_defs_ref = self.json_to_defs_refs[next_json_ref] 

2483 if next_defs_ref in visited_defs_refs: 

2484 continue 

2485 visited_defs_refs.add(next_defs_ref) 

2486 unvisited_json_refs.update(_get_all_json_refs(self.definitions[next_defs_ref])) 

2487 except KeyError: 

2488 if not next_json_ref.startswith(('http://', 'https://')): 

2489 raise 

2490 

2491 self.definitions = {k: v for k, v in self.definitions.items() if k in visited_defs_refs} 

2492 

2493 

2494# ##### Start JSON Schema Generation Functions ##### 

2495 

2496 

2497def model_json_schema( 

2498 cls: type[BaseModel] | type[PydanticDataclass], 

2499 by_alias: bool = True, 

2500 ref_template: str = DEFAULT_REF_TEMPLATE, 

2501 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', 

2502 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, 

2503 mode: JsonSchemaMode = 'validation', 

2504) -> dict[str, Any]: 

2505 """Utility function to generate a JSON Schema for a model. 

2506 

2507 Args: 

2508 cls: The model class to generate a JSON Schema for. 

2509 by_alias: If `True` (the default), fields will be serialized according to their alias. 

2510 If `False`, fields will be serialized according to their attribute name. 

2511 ref_template: The template to use for generating JSON Schema references. 

2512 union_format: The format to use when combining schemas from unions together. Can be one of: 

2513 

2514 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) 

2515 keyword to combine schemas (the default). 

2516 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) 

2517 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive 

2518 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to 

2519 `any_of`. 

2520 schema_generator: The class to use for generating the JSON Schema. 

2521 mode: The mode to use for generating the JSON Schema. It can be one of the following: 

2522 

2523 - 'validation': Generate a JSON Schema for validating data. 

2524 - 'serialization': Generate a JSON Schema for serializing data. 

2525 

2526 Returns: 

2527 The generated JSON Schema. 

2528 """ 

2529 from .main import BaseModel 

2530 

2531 schema_generator_instance = schema_generator( 

2532 by_alias=by_alias, ref_template=ref_template, union_format=union_format 

2533 ) 

2534 

2535 if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema): 

2536 cls.__pydantic_core_schema__.rebuild() 

2537 

2538 if cls is BaseModel: 

2539 raise AttributeError('model_json_schema() must be called on a subclass of BaseModel, not BaseModel itself.') 

2540 

2541 assert not isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema), 'this is a bug! please report it' 

2542 return schema_generator_instance.generate(cls.__pydantic_core_schema__, mode=mode) 

2543 

2544 

2545def models_json_schema( 

2546 models: Sequence[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode]], 

2547 *, 

2548 by_alias: bool = True, 

2549 title: str | None = None, 

2550 description: str | None = None, 

2551 ref_template: str = DEFAULT_REF_TEMPLATE, 

2552 union_format: Literal['any_of', 'primitive_type_array'] = 'any_of', 

2553 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, 

2554) -> tuple[dict[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode], JsonSchemaValue], JsonSchemaValue]: 

2555 """Utility function to generate a JSON Schema for multiple models. 

2556 

2557 Args: 

2558 models: A sequence of tuples of the form (model, mode). 

2559 by_alias: Whether field aliases should be used as keys in the generated JSON Schema. 

2560 title: The title of the generated JSON Schema. 

2561 description: The description of the generated JSON Schema. 

2562 ref_template: The reference template to use for generating JSON Schema references. 

2563 union_format: The format to use when combining schemas from unions together. Can be one of: 

2564 

2565 - `'any_of'`: Use the [`anyOf`](https://json-schema.org/understanding-json-schema/reference/combining#anyOf) 

2566 keyword to combine schemas (the default). 

2567 - `'primitive_type_array'`: Use the [`type`](https://json-schema.org/understanding-json-schema/reference/type) 

2568 keyword as an array of strings, containing each type of the combination. If any of the schemas is not a primitive 

2569 type (`string`, `boolean`, `null`, `integer` or `number`) or contains constraints/metadata, falls back to 

2570 `any_of`. 

2571 schema_generator: The schema generator to use for generating the JSON Schema. 

2572 

2573 Returns: 

2574 A tuple where: 

2575 - The first element is a dictionary whose keys are tuples of JSON schema key type and JSON mode, and 

2576 whose values are the JSON schema corresponding to that pair of inputs. (These schemas may have 

2577 JsonRef references to definitions that are defined in the second returned element.) 

2578 - The second element is a JSON schema containing all definitions referenced in the first returned 

2579 element, along with the optional title and description keys. 

2580 """ 

2581 for cls, _ in models: 

2582 if isinstance(cls.__pydantic_core_schema__, _mock_val_ser.MockCoreSchema): 

2583 cls.__pydantic_core_schema__.rebuild() 

2584 

2585 instance = schema_generator(by_alias=by_alias, ref_template=ref_template, union_format=union_format) 

2586 inputs: list[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode, CoreSchema]] = [ 

2587 (m, mode, m.__pydantic_core_schema__) for m, mode in models 

2588 ] 

2589 json_schemas_map, definitions = instance.generate_definitions(inputs) 

2590 

2591 json_schema: dict[str, Any] = {} 

2592 if definitions: 

2593 json_schema['$defs'] = definitions 

2594 if title: 

2595 json_schema['title'] = title 

2596 if description: 

2597 json_schema['description'] = description 

2598 

2599 return json_schemas_map, json_schema 

2600 

2601 

2602# ##### End JSON Schema Generation Functions ##### 

2603 

2604 

2605_HashableJsonValue: TypeAlias = Union[ 

2606 int, float, str, bool, None, tuple['_HashableJsonValue', ...], tuple[tuple[str, '_HashableJsonValue'], ...] 

2607] 

2608 

2609 

2610def _deduplicate_schemas(schemas: Iterable[JsonDict]) -> list[JsonDict]: 

2611 return list({_make_json_hashable(schema): schema for schema in schemas}.values()) 

2612 

2613 

2614def _make_json_hashable(value: JsonValue) -> _HashableJsonValue: 

2615 if isinstance(value, dict): 

2616 return tuple(sorted((k, _make_json_hashable(v)) for k, v in value.items())) 

2617 elif isinstance(value, list): 

2618 return tuple(_make_json_hashable(v) for v in value) 

2619 else: 

2620 return value 

2621 

2622 

2623@dataclasses.dataclass(**_internal_dataclass.slots_true) 

2624class WithJsonSchema: 

2625 """!!! abstract "Usage Documentation" 

2626 [`WithJsonSchema` Annotation](../concepts/json_schema.md#withjsonschema-annotation) 

2627 

2628 Add this as an annotation on a field to override the (base) JSON schema that would be generated for that field. 

2629 This provides a way to set a JSON schema for types that would otherwise raise errors when producing a JSON schema, 

2630 such as Callable, or types that have an is-instance core schema, without needing to go so far as creating a 

2631 custom subclass of pydantic.json_schema.GenerateJsonSchema. 

2632 Note that any _modifications_ to the schema that would normally be made (such as setting the title for model fields) 

2633 will still be performed. 

2634 

2635 If `mode` is set this will only apply to that schema generation mode, allowing you 

2636 to set different json schemas for validation and serialization. 

2637 """ 

2638 

2639 json_schema: JsonSchemaValue | None 

2640 mode: Literal['validation', 'serialization'] | None = None 

2641 

2642 def __get_pydantic_json_schema__( 

2643 self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler 

2644 ) -> JsonSchemaValue: 

2645 mode = self.mode or handler.mode 

2646 if mode != handler.mode: 

2647 return handler(core_schema) 

2648 if self.json_schema is None: 

2649 # This exception is handled in pydantic.json_schema.GenerateJsonSchema._named_required_fields_schema 

2650 raise PydanticOmit 

2651 else: 

2652 return self.json_schema.copy() 

2653 

2654 def __hash__(self) -> int: 

2655 return hash(type(self.mode)) 

2656 

2657 

2658class Examples: 

2659 """Add examples to a JSON schema. 

2660 

2661 If the JSON Schema already contains examples, the provided examples 

2662 will be appended. 

2663 

2664 If `mode` is set this will only apply to that schema generation mode, 

2665 allowing you to add different examples for validation and serialization. 

2666 """ 

2667 

2668 @overload 

2669 @deprecated('Using a dict for `examples` is deprecated since v2.9 and will be removed in v3.0. Use a list instead.') 

2670 def __init__( 

2671 self, examples: dict[str, Any], mode: Literal['validation', 'serialization'] | None = None 

2672 ) -> None: ... 

2673 

2674 @overload 

2675 def __init__(self, examples: list[Any], mode: Literal['validation', 'serialization'] | None = None) -> None: ... 

2676 

2677 def __init__( 

2678 self, examples: dict[str, Any] | list[Any], mode: Literal['validation', 'serialization'] | None = None 

2679 ) -> None: 

2680 if isinstance(examples, dict): 

2681 warnings.warn( 

2682 'Using a dict for `examples` is deprecated, use a list instead.', 

2683 PydanticDeprecatedSince29, 

2684 stacklevel=2, 

2685 ) 

2686 self.examples = examples 

2687 self.mode = mode 

2688 

2689 def __get_pydantic_json_schema__( 

2690 self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler 

2691 ) -> JsonSchemaValue: 

2692 mode = self.mode or handler.mode 

2693 json_schema = handler(core_schema) 

2694 if mode != handler.mode: 

2695 return json_schema 

2696 examples = json_schema.get('examples') 

2697 if examples is None: 

2698 json_schema['examples'] = to_jsonable_python(self.examples) 

2699 if isinstance(examples, dict): 

2700 if isinstance(self.examples, list): 

2701 warnings.warn( 

2702 'Updating existing JSON Schema examples of type dict with examples of type list. ' 

2703 'Only the existing examples values will be retained. Note that dict support for ' 

2704 'examples is deprecated and will be removed in v3.0.', 

2705 UserWarning, 

2706 ) 

2707 json_schema['examples'] = to_jsonable_python( 

2708 [ex for value in examples.values() for ex in value] + self.examples 

2709 ) 

2710 else: 

2711 json_schema['examples'] = to_jsonable_python({**examples, **self.examples}) 

2712 if isinstance(examples, list): 

2713 if isinstance(self.examples, list): 

2714 json_schema['examples'] = to_jsonable_python(examples + self.examples) 

2715 elif isinstance(self.examples, dict): 

2716 warnings.warn( 

2717 'Updating existing JSON Schema examples of type list with examples of type dict. ' 

2718 'Only the examples values will be retained. Note that dict support for ' 

2719 'examples is deprecated and will be removed in v3.0.', 

2720 UserWarning, 

2721 ) 

2722 json_schema['examples'] = to_jsonable_python( 

2723 examples + [ex for value in self.examples.values() for ex in value] 

2724 ) 

2725 

2726 return json_schema 

2727 

2728 def __hash__(self) -> int: 

2729 return hash(type(self.mode)) 

2730 

2731 

2732def _get_all_json_refs(item: Any) -> set[JsonRef]: 

2733 """Get all the definitions references from a JSON schema.""" 

2734 refs: set[JsonRef] = set() 

2735 stack = [item] 

2736 

2737 while stack: 

2738 current = stack.pop() 

2739 if isinstance(current, dict): 

2740 for key, value in current.items(): 

2741 if key == 'examples' and isinstance(value, list): 

2742 # Skip examples that may contain arbitrary values and references 

2743 # (e.g. `{"examples": [{"$ref": "..."}]}`). Note: checking for value 

2744 # of type list is necessary to avoid skipping valid portions of the schema, 

2745 # for instance when "examples" is used as a property key. A more robust solution 

2746 # could be found, but would require more advanced JSON Schema parsing logic. 

2747 continue 

2748 if key == '$ref' and isinstance(value, str): 

2749 refs.add(JsonRef(value)) 

2750 elif isinstance(value, dict): 

2751 stack.append(value) 

2752 elif isinstance(value, list): 

2753 stack.extend(value) 

2754 elif isinstance(current, list): 

2755 stack.extend(current) 

2756 

2757 return refs 

2758 

2759 

2760AnyType = TypeVar('AnyType') 

2761 

2762if TYPE_CHECKING: 

2763 SkipJsonSchema = Annotated[AnyType, ...] 

2764else: 

2765 

2766 @dataclasses.dataclass(**_internal_dataclass.slots_true) 

2767 class SkipJsonSchema: 

2768 """!!! abstract "Usage Documentation" 

2769 [`SkipJsonSchema` Annotation](../concepts/json_schema.md#skipjsonschema-annotation) 

2770 

2771 Add this as an annotation on a field to skip generating a JSON schema for that field. 

2772 

2773 Example: 

2774 ```python 

2775 from pprint import pprint 

2776 from typing import Union 

2777 

2778 from pydantic import BaseModel 

2779 from pydantic.json_schema import SkipJsonSchema 

2780 

2781 class Model(BaseModel): 

2782 a: Union[int, None] = None # (1)! 

2783 b: Union[int, SkipJsonSchema[None]] = None # (2)! 

2784 c: SkipJsonSchema[Union[int, None]] = None # (3)! 

2785 

2786 pprint(Model.model_json_schema()) 

2787 ''' 

2788 { 

2789 'properties': { 

2790 'a': { 

2791 'anyOf': [ 

2792 {'type': 'integer'}, 

2793 {'type': 'null'} 

2794 ], 

2795 'default': None, 

2796 'title': 'A' 

2797 }, 

2798 'b': { 

2799 'default': None, 

2800 'title': 'B', 

2801 'type': 'integer' 

2802 } 

2803 }, 

2804 'title': 'Model', 

2805 'type': 'object' 

2806 } 

2807 ''' 

2808 ``` 

2809 

2810 1. The integer and null types are both included in the schema for `a`. 

2811 2. The integer type is the only type included in the schema for `b`. 

2812 3. The entirety of the `c` field is omitted from the schema. 

2813 """ 

2814 

2815 def __class_getitem__(cls, item: AnyType) -> AnyType: 

2816 return Annotated[item, cls()] 

2817 

2818 def __get_pydantic_json_schema__( 

2819 self, core_schema: CoreSchema, handler: GetJsonSchemaHandler 

2820 ) -> JsonSchemaValue: 

2821 raise PydanticOmit 

2822 

2823 def __hash__(self) -> int: 

2824 return hash(type(self)) 

2825 

2826 

2827def _get_typed_dict_config(cls: type[Any] | None) -> ConfigDict: 

2828 if cls is not None: 

2829 try: 

2830 return _decorators.get_attribute_from_bases(cls, '__pydantic_config__') 

2831 except AttributeError: 

2832 pass 

2833 return {} 

2834 

2835 

2836def _get_ser_schema_for_default_value(schema: CoreSchema) -> core_schema.PlainSerializerFunctionSerSchema | None: 

2837 """Get a `'function-plain'` serialization schema that can be used to serialize a default value. 

2838 

2839 This takes into account having the serialization schema nested under validation schema(s). 

2840 """ 

2841 if ( 

2842 (ser_schema := schema.get('serialization')) 

2843 and ser_schema['type'] == 'function-plain' 

2844 and not ser_schema.get('info_arg') 

2845 ): 

2846 return ser_schema 

2847 if _core_utils.is_function_with_inner_schema(schema): 

2848 return _get_ser_schema_for_default_value(schema['schema'])