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

1153 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 field['type'] == 'typed-dict-field': 

1760 required = field.get('required', total) 

1761 else: 

1762 required = field['schema']['type'] != 'default' 

1763 

1764 if self.mode == 'serialization': 

1765 has_exclude_if = field.get('serialization_exclude_if') is not None 

1766 if self._config.json_schema_serialization_defaults_required: 

1767 return not has_exclude_if 

1768 else: 

1769 return required and not has_exclude_if 

1770 else: 

1771 return required 

1772 

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

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

1775 

1776 Args: 

1777 schema: The core schema. 

1778 

1779 Returns: 

1780 The generated JSON schema. 

1781 """ 

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

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

1784 for field in schema['fields'] 

1785 if self.field_is_present(field) 

1786 ] 

1787 if self.mode == 'serialization': 

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

1789 return self._named_required_fields_schema(named_required_fields) 

1790 

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

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

1793 

1794 Args: 

1795 schema: The core schema. 

1796 

1797 Returns: 

1798 The generated JSON schema. 

1799 """ 

1800 from ._internal._dataclasses import is_stdlib_dataclass 

1801 

1802 cls = schema['cls'] 

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

1804 

1805 with self._config_wrapper_stack.push(config): 

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

1807 

1808 self._update_class_schema(json_schema, cls, config) 

1809 

1810 # Dataclass-specific handling of description 

1811 if is_stdlib_dataclass(cls): 

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

1813 description = None 

1814 else: 

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

1816 if description: 

1817 json_schema['description'] = description 

1818 

1819 return json_schema 

1820 

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

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

1823 

1824 Args: 

1825 schema: The core schema. 

1826 

1827 Returns: 

1828 The generated JSON schema. 

1829 """ 

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

1831 

1832 arguments = schema['arguments_schema'] 

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

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

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

1836 var_args_schema = schema.get('var_args_schema') 

1837 var_kwargs_schema = schema.get('var_kwargs_schema') 

1838 

1839 if prefer_positional: 

1840 positional_possible = not kw_only_arguments and not var_kwargs_schema 

1841 if positional_possible: 

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

1843 

1844 keyword_possible = not p_only_arguments and not var_args_schema 

1845 if keyword_possible: 

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

1847 

1848 if not prefer_positional: 

1849 positional_possible = not kw_only_arguments and not var_kwargs_schema 

1850 if positional_possible: 

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

1852 

1853 raise PydanticInvalidForJsonSchema( 

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

1855 ) 

1856 

1857 def kw_arguments_schema( 

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

1859 ) -> JsonSchemaValue: 

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

1861 

1862 Args: 

1863 arguments: The core schema. 

1864 

1865 Returns: 

1866 The generated JSON schema. 

1867 """ 

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

1869 required: list[str] = [] 

1870 for argument in arguments: 

1871 name = self.get_argument_name(argument) 

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

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

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

1875 properties[name] = argument_schema 

1876 

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

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

1879 # the inner schema must be of type WithDefaultSchema. 

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

1881 required.append(name) 

1882 

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

1884 if required: 

1885 json_schema['required'] = required 

1886 

1887 if var_kwargs_schema: 

1888 additional_properties_schema = self.generate_inner(var_kwargs_schema) 

1889 if additional_properties_schema: 

1890 json_schema['additionalProperties'] = additional_properties_schema 

1891 else: 

1892 json_schema['additionalProperties'] = False 

1893 return json_schema 

1894 

1895 def p_arguments_schema( 

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

1897 ) -> JsonSchemaValue: 

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

1899 

1900 Args: 

1901 arguments: The core schema. 

1902 

1903 Returns: 

1904 The generated JSON schema. 

1905 """ 

1906 prefix_items: list[JsonSchemaValue] = [] 

1907 min_items = 0 

1908 

1909 for argument in arguments: 

1910 name = self.get_argument_name(argument) 

1911 

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

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

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

1915 prefix_items.append(argument_schema) 

1916 

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

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

1919 # the inner schema must be of type WithDefaultSchema. 

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

1921 min_items += 1 

1922 

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

1924 if prefix_items: 

1925 json_schema['prefixItems'] = prefix_items 

1926 if min_items: 

1927 json_schema['minItems'] = min_items 

1928 

1929 if var_args_schema: 

1930 items_schema = self.generate_inner(var_args_schema) 

1931 if items_schema: 

1932 json_schema['items'] = items_schema 

1933 else: 

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

1935 

1936 return json_schema 

1937 

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

1939 """Retrieves the name of an argument. 

1940 

1941 Args: 

1942 argument: The core schema. 

1943 

1944 Returns: 

1945 The name of the argument. 

1946 """ 

1947 name = argument['name'] 

1948 if self.by_alias: 

1949 alias = argument.get('alias') 

1950 if isinstance(alias, str): 

1951 name = alias 

1952 else: 

1953 pass # might want to do something else? 

1954 return name 

1955 

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

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

1958 

1959 Args: 

1960 schema: The core schema. 

1961 

1962 Returns: 

1963 The generated JSON schema. 

1964 """ 

1965 arguments = schema['arguments_schema'] 

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

1967 required: list[str] = [] 

1968 for argument in arguments: 

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

1970 name = self.get_argument_name(argument) 

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

1972 if mode == 'var_args': 

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

1974 elif mode == 'var_kwargs_uniform': 

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

1976 

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

1978 properties[name] = argument_schema 

1979 

1980 if ( 

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

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

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

1984 ): 

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

1986 # the inner schema must be of type WithDefaultSchema. 

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

1988 required.append(name) 

1989 

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

1991 if required: 

1992 json_schema['required'] = required 

1993 return json_schema 

1994 

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

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

1997 

1998 Args: 

1999 schema: The core schema. 

2000 

2001 Returns: 

2002 The generated JSON schema. 

2003 """ 

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

2005 

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

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

2008 

2009 Args: 

2010 schema: The core schema. 

2011 

2012 Returns: 

2013 The generated JSON schema. 

2014 """ 

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

2016 

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

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

2019 

2020 Args: 

2021 schema: The core schema. 

2022 

2023 Returns: 

2024 The generated JSON schema. 

2025 """ 

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

2027 content_json_schema = self.generate_inner(content_core_schema) 

2028 if self.mode == 'validation': 

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

2030 else: 

2031 # self.mode == 'serialization' 

2032 return content_json_schema 

2033 

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

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

2036 

2037 Args: 

2038 schema: The core schema. 

2039 

2040 Returns: 

2041 The generated JSON schema. 

2042 """ 

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

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

2045 return json_schema 

2046 

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

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

2049 

2050 Args: 

2051 schema: The core schema. 

2052 

2053 Returns: 

2054 The generated JSON schema. 

2055 """ 

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

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

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

2059 return json_schema 

2060 

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

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

2063 

2064 Args: 

2065 schema: The core schema. 

2066 

2067 Returns: 

2068 The generated JSON schema. 

2069 """ 

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

2071 

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

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

2074 

2075 Args: 

2076 schema: The core schema. 

2077 

2078 Returns: 

2079 The generated JSON schema. 

2080 """ 

2081 for definition in schema['definitions']: 

2082 try: 

2083 self.generate_inner(definition) 

2084 except PydanticInvalidForJsonSchema as e: # noqa: PERF203 

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

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

2087 continue 

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

2089 

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

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

2092 

2093 Args: 

2094 schema: The core schema. 

2095 

2096 Returns: 

2097 The generated JSON schema. 

2098 """ 

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

2100 _, ref_json_schema = self.get_cache_defs_ref_schema(core_ref) 

2101 return ref_json_schema 

2102 

2103 def ser_schema( 

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

2105 ) -> JsonSchemaValue | None: 

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

2107 

2108 Args: 

2109 schema: The core schema. 

2110 

2111 Returns: 

2112 The generated JSON schema. 

2113 """ 

2114 schema_type = schema['type'] 

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

2116 # PlainSerializerFunctionSerSchema or WrapSerializerFunctionSerSchema 

2117 return_schema = schema.get('return_schema') 

2118 if return_schema is not None: 

2119 return self.generate_inner(return_schema) 

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

2121 # FormatSerSchema or ToStringSerSchema 

2122 return self.str_schema(core_schema.str_schema()) 

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

2124 # ModelSerSchema 

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

2126 return None 

2127 

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

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

2130 

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

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

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

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

2135 

2136 Args: 

2137 schema: The core schema. 

2138 

2139 Returns: 

2140 The generated JSON schema. 

2141 """ 

2142 return {'type': 'string'} 

2143 

2144 # ### Utility methods 

2145 

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

2147 """Retrieves a title from a name. 

2148 

2149 Args: 

2150 name: The name to retrieve a title from. 

2151 

2152 Returns: 

2153 The title. 

2154 """ 

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

2156 

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

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

2159 

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

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

2162 

2163 Args: 

2164 schema: The schema to check. 

2165 

2166 Returns: 

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

2168 """ 

2169 if _core_utils.is_core_schema_field(schema): 

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

2171 field_schema = schema['return_schema'] 

2172 else: 

2173 field_schema = schema['schema'] 

2174 return self.field_title_should_be_set(field_schema) 

2175 

2176 elif _core_utils.is_core_schema(schema): 

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

2178 return False 

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

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

2181 if _core_utils.is_function_with_inner_schema(schema): 

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

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

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

2185 # schemas with refs should not 

2186 return False 

2187 return True # anything else should have title set 

2188 

2189 else: 

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

2191 

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

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

2194 

2195 Args: 

2196 name: The name to normalize. 

2197 

2198 Returns: 

2199 The normalized name. 

2200 """ 

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

2202 

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

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

2205 

2206 Args: 

2207 core_mode_ref: The core reference. 

2208 

2209 Returns: 

2210 The definitions key. 

2211 """ 

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

2213 core_ref, mode = core_mode_ref 

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

2215 # Remove IDs from each component 

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

2217 core_ref_no_id = ''.join(components) 

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

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

2220 short_ref = ''.join(components) 

2221 

2222 mode_title = _MODE_TITLE_MAPPING[mode] 

2223 

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

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

2226 # the id of the source type in the core_ref 

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

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

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

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

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

2232 occurrence_index = self._collision_index.get(module_qualname_id) 

2233 if occurrence_index is None: 

2234 self._collision_counter[module_qualname] += 1 

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

2236 

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

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

2239 

2240 self._prioritized_defsref_choices[module_qualname_occurrence_mode] = [ 

2241 name, 

2242 name_mode, 

2243 module_qualname, 

2244 module_qualname_mode, 

2245 module_qualname_occurrence, 

2246 module_qualname_occurrence_mode, 

2247 ] 

2248 

2249 return module_qualname_occurrence_mode 

2250 

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

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

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

2254 

2255 Args: 

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

2257 

2258 Returns: 

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

2260 """ 

2261 core_mode_ref = (core_ref, self.mode) 

2262 maybe_defs_ref = self.core_to_defs_refs.get(core_mode_ref) 

2263 if maybe_defs_ref is not None: 

2264 json_ref = self.core_to_json_refs[core_mode_ref] 

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

2266 

2267 defs_ref = self.get_defs_ref(core_mode_ref) 

2268 

2269 # populate the ref translation mappings 

2270 self.core_to_defs_refs[core_mode_ref] = defs_ref 

2271 self.defs_to_core_refs[defs_ref] = core_mode_ref 

2272 

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

2274 self.core_to_json_refs[core_mode_ref] = json_ref 

2275 self.json_to_defs_refs[json_ref] = defs_ref 

2276 ref_json_schema = {'$ref': json_ref} 

2277 return defs_ref, ref_json_schema 

2278 

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

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

2281 

2282 Args: 

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

2284 

2285 Returns: 

2286 The schema with redundant sibling keys removed. 

2287 """ 

2288 if '$ref' in json_schema: 

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

2290 json_schema = json_schema.copy() 

2291 

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

2293 if referenced_json_schema is None: 

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

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

2296 # any redundant override keys. 

2297 return json_schema 

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

2299 if k == '$ref': 

2300 continue 

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

2302 del json_schema[k] # redundant key 

2303 

2304 return json_schema 

2305 

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

2307 try: 

2308 def_ref = self.json_to_defs_refs[json_ref] 

2309 if def_ref in self._core_defs_invalid_for_json_schema: 

2310 raise self._core_defs_invalid_for_json_schema[def_ref] 

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

2312 except KeyError: 

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

2314 return None 

2315 raise 

2316 

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

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

2319 

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

2321 

2322 Args: 

2323 dft: The default value to encode. 

2324 

2325 Returns: 

2326 The encoded default value. 

2327 """ 

2328 from .type_adapter import TypeAdapter, _type_has_config 

2329 

2330 config = self._config 

2331 try: 

2332 default = ( 

2333 dft 

2334 if _type_has_config(type(dft)) 

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

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

2337 ) 

2338 ) 

2339 except PydanticSchemaGenerationError: 

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

2341 

2342 return pydantic_core.to_jsonable_python( 

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

2344 ) 

2345 

2346 def update_with_validations( 

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

2348 ) -> None: 

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

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

2351 

2352 Args: 

2353 json_schema: The JSON schema to update. 

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

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

2356 """ 

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

2358 if core_key in core_schema: 

2359 json_schema[json_schema_key] = core_schema[core_key] 

2360 

2361 class ValidationsMapping: 

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

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

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

2365 GenerateJsonSchema.ValidationsMapping) to change these mappings. 

2366 """ 

2367 

2368 numeric = { 

2369 'multiple_of': 'multipleOf', 

2370 'le': 'maximum', 

2371 'ge': 'minimum', 

2372 'lt': 'exclusiveMaximum', 

2373 'gt': 'exclusiveMinimum', 

2374 } 

2375 bytes = { 

2376 'min_length': 'minLength', 

2377 'max_length': 'maxLength', 

2378 } 

2379 string = { 

2380 'min_length': 'minLength', 

2381 'max_length': 'maxLength', 

2382 'pattern': 'pattern', 

2383 } 

2384 array = { 

2385 'min_length': 'minItems', 

2386 'max_length': 'maxItems', 

2387 } 

2388 object = { 

2389 'min_length': 'minProperties', 

2390 'max_length': 'maxProperties', 

2391 } 

2392 

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

2394 members = [] 

2395 for schema in schemas: 

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

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

2398 else: 

2399 members.append(schema) 

2400 members = _deduplicate_schemas(members) 

2401 if len(members) == 1: 

2402 return members[0] 

2403 return {'anyOf': members} 

2404 

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

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

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

2408 

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

2410 if isinstance(schema, dict): 

2411 if '$ref' in schema: 

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

2413 if not isinstance(json_ref, str): 

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

2415 already_visited = json_ref in json_refs 

2416 json_refs[json_ref] += 1 

2417 if already_visited: 

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

2419 try: 

2420 defs_ref = self.json_to_defs_refs[json_ref] 

2421 if defs_ref in self._core_defs_invalid_for_json_schema: 

2422 raise self._core_defs_invalid_for_json_schema[defs_ref] 

2423 _add_json_refs(self.definitions[defs_ref]) 

2424 except KeyError: 

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

2426 raise 

2427 

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

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

2430 # Skip examples that may contain arbitrary values and references 

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

2432 continue 

2433 _add_json_refs(v) 

2434 elif isinstance(schema, list): 

2435 for v in schema: 

2436 _add_json_refs(v) 

2437 

2438 _add_json_refs(json_schema) 

2439 return json_refs 

2440 

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

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

2443 

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

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

2446 message = self.render_warning_message(kind, detail) 

2447 if message is not None: 

2448 warnings.warn(message, PydanticJsonSchemaWarning) 

2449 

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

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

2452 

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

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

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

2456 

2457 Args: 

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

2459 

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

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

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

2463 

2464 Returns: 

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

2466 """ 

2467 if kind in self.ignored_warning_kinds: 

2468 return None 

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

2470 

2471 def _build_definitions_remapping(self) -> _DefinitionsRemapping: 

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

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

2474 for defs_ref in defs_refs: 

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

2476 defs_to_json[defs_ref] = json_ref 

2477 

2478 return _DefinitionsRemapping.from_prioritized_choices( 

2479 self._prioritized_defsref_choices, defs_to_json, self.definitions 

2480 ) 

2481 

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

2483 visited_defs_refs: set[DefsRef] = set() 

2484 unvisited_json_refs = _get_all_json_refs(schema) 

2485 while unvisited_json_refs: 

2486 next_json_ref = unvisited_json_refs.pop() 

2487 try: 

2488 next_defs_ref = self.json_to_defs_refs[next_json_ref] 

2489 if next_defs_ref in visited_defs_refs: 

2490 continue 

2491 visited_defs_refs.add(next_defs_ref) 

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

2493 except KeyError: 

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

2495 raise 

2496 

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

2498 

2499 

2500# ##### Start JSON Schema Generation Functions ##### 

2501 

2502 

2503def model_json_schema( 

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

2505 by_alias: bool = True, 

2506 ref_template: str = DEFAULT_REF_TEMPLATE, 

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

2508 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, 

2509 mode: JsonSchemaMode = 'validation', 

2510) -> dict[str, Any]: 

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

2512 

2513 Args: 

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

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

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

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

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

2519 

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

2521 keyword to combine schemas (the default). 

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

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

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

2525 `any_of`. 

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

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

2528 

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

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

2531 

2532 Returns: 

2533 The generated JSON Schema. 

2534 """ 

2535 from .main import BaseModel 

2536 

2537 schema_generator_instance = schema_generator( 

2538 by_alias=by_alias, ref_template=ref_template, union_format=union_format 

2539 ) 

2540 

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

2542 cls.__pydantic_core_schema__.rebuild() 

2543 

2544 if cls is BaseModel: 

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

2546 

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

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

2549 

2550 

2551def models_json_schema( 

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

2553 *, 

2554 by_alias: bool = True, 

2555 title: str | None = None, 

2556 description: str | None = None, 

2557 ref_template: str = DEFAULT_REF_TEMPLATE, 

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

2559 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, 

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

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

2562 

2563 Args: 

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

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

2566 title: The title of the generated JSON Schema. 

2567 description: The description of the generated JSON Schema. 

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

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

2570 

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

2572 keyword to combine schemas (the default). 

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

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

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

2576 `any_of`. 

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

2578 

2579 Returns: 

2580 A tuple where: 

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

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

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

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

2585 element, along with the optional title and description keys. 

2586 """ 

2587 for cls, _ in models: 

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

2589 cls.__pydantic_core_schema__.rebuild() 

2590 

2591 instance = schema_generator(by_alias=by_alias, ref_template=ref_template, union_format=union_format) 

2592 inputs: list[tuple[type[BaseModel] | type[PydanticDataclass], JsonSchemaMode, CoreSchema]] = [ 

2593 (m, mode, m.__pydantic_core_schema__) for m, mode in models 

2594 ] 

2595 json_schemas_map, definitions = instance.generate_definitions(inputs) 

2596 

2597 json_schema: dict[str, Any] = {} 

2598 if definitions: 

2599 json_schema['$defs'] = definitions 

2600 if title: 

2601 json_schema['title'] = title 

2602 if description: 

2603 json_schema['description'] = description 

2604 

2605 return json_schemas_map, json_schema 

2606 

2607 

2608# ##### End JSON Schema Generation Functions ##### 

2609 

2610 

2611_HashableJsonValue: TypeAlias = Union[ 

2612 int, float, str, bool, None, tuple['_HashableJsonValue', ...], tuple[tuple[str, '_HashableJsonValue'], ...] 

2613] 

2614 

2615 

2616def _deduplicate_schemas(schemas: Iterable[JsonDict]) -> list[JsonDict]: 

2617 return list({_make_json_hashable(schema): schema for schema in schemas}.values()) 

2618 

2619 

2620def _make_json_hashable(value: JsonValue) -> _HashableJsonValue: 

2621 if isinstance(value, dict): 

2622 return tuple(sorted((k, _make_json_hashable(v)) for k, v in value.items())) 

2623 elif isinstance(value, list): 

2624 return tuple(_make_json_hashable(v) for v in value) 

2625 else: 

2626 return value 

2627 

2628 

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

2630class WithJsonSchema: 

2631 """!!! abstract "Usage Documentation" 

2632 [`WithJsonSchema` Annotation](../concepts/json_schema.md#withjsonschema-annotation) 

2633 

2634 Add this as an annotation on a field to override the (base) JSON schema that would be generated for that field. 

2635 This provides a way to set a JSON schema for types that would otherwise raise errors when producing a JSON schema, 

2636 such as Callable, or types that have an is-instance core schema, without needing to go so far as creating a 

2637 custom subclass of pydantic.json_schema.GenerateJsonSchema. 

2638 Note that any _modifications_ to the schema that would normally be made (such as setting the title for model fields) 

2639 will still be performed. 

2640 

2641 If `mode` is set this will only apply to that schema generation mode, allowing you 

2642 to set different json schemas for validation and serialization. 

2643 """ 

2644 

2645 json_schema: JsonSchemaValue | None 

2646 mode: Literal['validation', 'serialization'] | None = None 

2647 

2648 def __get_pydantic_json_schema__( 

2649 self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler 

2650 ) -> JsonSchemaValue: 

2651 mode = self.mode or handler.mode 

2652 if mode != handler.mode: 

2653 return handler(core_schema) 

2654 if self.json_schema is None: 

2655 # This exception is handled in pydantic.json_schema.GenerateJsonSchema._named_required_fields_schema 

2656 raise PydanticOmit 

2657 else: 

2658 return self.json_schema.copy() 

2659 

2660 def __hash__(self) -> int: 

2661 return hash(type(self.mode)) 

2662 

2663 

2664class Examples: 

2665 """Add examples to a JSON schema. 

2666 

2667 If the JSON Schema already contains examples, the provided examples 

2668 will be appended. 

2669 

2670 If `mode` is set this will only apply to that schema generation mode, 

2671 allowing you to add different examples for validation and serialization. 

2672 """ 

2673 

2674 @overload 

2675 @deprecated('Using a dict for `examples` is deprecated since v2.9 and will be removed in v3.0. Use a list instead.') 

2676 def __init__( 

2677 self, examples: dict[str, Any], mode: Literal['validation', 'serialization'] | None = None 

2678 ) -> None: ... 

2679 

2680 @overload 

2681 def __init__(self, examples: list[Any], mode: Literal['validation', 'serialization'] | None = None) -> None: ... 

2682 

2683 def __init__( 

2684 self, examples: dict[str, Any] | list[Any], mode: Literal['validation', 'serialization'] | None = None 

2685 ) -> None: 

2686 if isinstance(examples, dict): 

2687 warnings.warn( 

2688 'Using a dict for `examples` is deprecated, use a list instead.', 

2689 PydanticDeprecatedSince29, 

2690 stacklevel=2, 

2691 ) 

2692 self.examples = examples 

2693 self.mode = mode 

2694 

2695 def __get_pydantic_json_schema__( 

2696 self, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler 

2697 ) -> JsonSchemaValue: 

2698 mode = self.mode or handler.mode 

2699 json_schema = handler(core_schema) 

2700 if mode != handler.mode: 

2701 return json_schema 

2702 examples = json_schema.get('examples') 

2703 if examples is None: 

2704 json_schema['examples'] = to_jsonable_python(self.examples) 

2705 if isinstance(examples, dict): 

2706 if isinstance(self.examples, list): 

2707 warnings.warn( 

2708 'Updating existing JSON Schema examples of type dict with examples of type list. ' 

2709 'Only the existing examples values will be retained. Note that dict support for ' 

2710 'examples is deprecated and will be removed in v3.0.', 

2711 UserWarning, 

2712 ) 

2713 json_schema['examples'] = to_jsonable_python( 

2714 [ex for value in examples.values() for ex in value] + self.examples 

2715 ) 

2716 else: 

2717 json_schema['examples'] = to_jsonable_python({**examples, **self.examples}) 

2718 if isinstance(examples, list): 

2719 if isinstance(self.examples, list): 

2720 json_schema['examples'] = to_jsonable_python(examples + self.examples) 

2721 elif isinstance(self.examples, dict): 

2722 warnings.warn( 

2723 'Updating existing JSON Schema examples of type list with examples of type dict. ' 

2724 'Only the examples values will be retained. Note that dict support for ' 

2725 'examples is deprecated and will be removed in v3.0.', 

2726 UserWarning, 

2727 ) 

2728 json_schema['examples'] = to_jsonable_python( 

2729 examples + [ex for value in self.examples.values() for ex in value] 

2730 ) 

2731 

2732 return json_schema 

2733 

2734 def __hash__(self) -> int: 

2735 return hash(type(self.mode)) 

2736 

2737 

2738def _get_all_json_refs(item: Any) -> set[JsonRef]: 

2739 """Get all the definitions references from a JSON schema.""" 

2740 refs: set[JsonRef] = set() 

2741 stack = [item] 

2742 

2743 while stack: 

2744 current = stack.pop() 

2745 if isinstance(current, dict): 

2746 for key, value in current.items(): 

2747 if key == 'examples' and isinstance(value, list): 

2748 # Skip examples that may contain arbitrary values and references 

2749 # (e.g. `{"examples": [{"$ref": "..."}]}`). Note: checking for value 

2750 # of type list is necessary to avoid skipping valid portions of the schema, 

2751 # for instance when "examples" is used as a property key. A more robust solution 

2752 # could be found, but would require more advanced JSON Schema parsing logic. 

2753 continue 

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

2755 refs.add(JsonRef(value)) 

2756 elif isinstance(value, dict): 

2757 stack.append(value) 

2758 elif isinstance(value, list): 

2759 stack.extend(value) 

2760 elif isinstance(current, list): 

2761 stack.extend(current) 

2762 

2763 return refs 

2764 

2765 

2766AnyType = TypeVar('AnyType') 

2767 

2768if TYPE_CHECKING: 

2769 SkipJsonSchema = Annotated[AnyType, ...] 

2770else: 

2771 

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

2773 class SkipJsonSchema: 

2774 """!!! abstract "Usage Documentation" 

2775 [`SkipJsonSchema` Annotation](../concepts/json_schema.md#skipjsonschema-annotation) 

2776 

2777 Add this as an annotation on a field to skip generating a JSON schema for that field. 

2778 

2779 Example: 

2780 ```python 

2781 from pprint import pprint 

2782 from typing import Union 

2783 

2784 from pydantic import BaseModel 

2785 from pydantic.json_schema import SkipJsonSchema 

2786 

2787 class Model(BaseModel): 

2788 a: Union[int, None] = None # (1)! 

2789 b: Union[int, SkipJsonSchema[None]] = None # (2)! 

2790 c: SkipJsonSchema[Union[int, None]] = None # (3)! 

2791 

2792 pprint(Model.model_json_schema()) 

2793 ''' 

2794 { 

2795 'properties': { 

2796 'a': { 

2797 'anyOf': [ 

2798 {'type': 'integer'}, 

2799 {'type': 'null'} 

2800 ], 

2801 'default': None, 

2802 'title': 'A' 

2803 }, 

2804 'b': { 

2805 'default': None, 

2806 'title': 'B', 

2807 'type': 'integer' 

2808 } 

2809 }, 

2810 'title': 'Model', 

2811 'type': 'object' 

2812 } 

2813 ''' 

2814 ``` 

2815 

2816 1. The integer and null types are both included in the schema for `a`. 

2817 2. The integer type is the only type included in the schema for `b`. 

2818 3. The entirety of the `c` field is omitted from the schema. 

2819 """ 

2820 

2821 def __class_getitem__(cls, item: AnyType) -> AnyType: 

2822 return Annotated[item, cls()] 

2823 

2824 def __get_pydantic_json_schema__( 

2825 self, core_schema: CoreSchema, handler: GetJsonSchemaHandler 

2826 ) -> JsonSchemaValue: 

2827 raise PydanticOmit 

2828 

2829 def __hash__(self) -> int: 

2830 return hash(type(self)) 

2831 

2832 

2833def _get_typed_dict_config(cls: type[Any] | None) -> ConfigDict: 

2834 if cls is not None: 

2835 try: 

2836 return _decorators.get_attribute_from_bases(cls, '__pydantic_config__') 

2837 except AttributeError: 

2838 pass 

2839 return {} 

2840 

2841 

2842def _get_ser_schema_for_default_value(schema: CoreSchema) -> core_schema.PlainSerializerFunctionSerSchema | None: 

2843 """Get a `'function-plain'` serialization schema that can be used to serialize a default value. 

2844 

2845 This takes into account having the serialization schema nested under validation schema(s). 

2846 """ 

2847 if ( 

2848 (ser_schema := schema.get('serialization')) 

2849 and ser_schema['type'] == 'function-plain' 

2850 and not ser_schema.get('info_arg') 

2851 ): 

2852 return ser_schema 

2853 if _core_utils.is_function_with_inner_schema(schema): 

2854 return _get_ser_schema_for_default_value(schema['schema'])