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

1301 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 collections.abc 

15import dataclasses 

16import inspect 

17import math 

18import os 

19import re 

20import warnings 

21from collections import Counter, defaultdict 

22from collections.abc import Callable, Hashable, Iterable, Sequence 

23from copy import deepcopy 

24from enum import Enum 

25from re import Pattern 

26from typing import ( 

27 TYPE_CHECKING, 

28 Annotated, 

29 Any, 

30 Literal, 

31 NewType, 

32 TypeAlias, 

33 TypeVar, 

34 cast, 

35 overload, 

36) 

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 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 _mock_val_ser, 

52 _schema_generation_shared, 

53 _typing_extra, 

54) 

55from .annotated_handlers import GetJsonSchemaHandler 

56from .config import JsonDict, JsonValue 

57from .errors import PydanticInvalidForJsonSchema, PydanticUserError 

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

180 # Map all alternatives after the remapped one to the remapped one 

181 # This ensures that intermediate simplifications are also remapped 

182 remapped_index = alternatives.index(remapped_defs_ref) 

183 for alt in alternatives[remapped_index:]: 

184 json_remapping[defs_to_json[alt]] = defs_to_json[remapped_defs_ref] 

185 remapping = _DefinitionsRemapping(defs_remapping, json_remapping) 

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

187 if definitions_schema == new_definitions_schema: 

188 # We've reached the fixed point 

189 return remapping 

190 definitions_schema = new_definitions_schema 

191 

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

193 

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

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

196 

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

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

199 

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

201 """ 

202 Recursively update the JSON schema replacing all $refs 

203 """ 

204 if isinstance(schema, str): 

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

206 return self.remap_json_ref(JsonRef(schema)) 

207 elif isinstance(schema, list): 

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

209 elif isinstance(schema, dict): 

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

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

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

213 elif key == '$defs': 

214 schema['$defs'] = { 

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

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

217 } 

218 else: 

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

220 return schema 

221 

222 

223class GenerateJsonSchema: 

224 """!!! abstract "Usage Documentation" 

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

226 

227 A class for generating JSON schemas. 

228 

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

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

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

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

233 

234 Attributes: 

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

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

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

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

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

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

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

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

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

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

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

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

247 definitions: Definitions in the schema. 

248 

249 Args: 

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

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

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

253 

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

255 keyword to combine schemas (the default). 

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

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

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

259 `any_of`. 

260 

261 Raises: 

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

263 """ 

264 

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

266 

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

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

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

270 

271 def __init__( 

272 self, 

273 by_alias: bool = True, 

274 ref_template: str = DEFAULT_REF_TEMPLATE, 

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

276 ) -> None: 

277 self.by_alias = by_alias 

278 self.ref_template = ref_template 

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

280 

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

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

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

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

285 

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

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

288 

289 self._mode: JsonSchemaMode = 'validation' 

290 

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

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

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

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

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

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

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

298 

299 self._schema_type_to_method = self.build_schema_type_to_method() 

300 

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

302 # so that they are available schemas that reference them 

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

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

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

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

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

308 

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

310 # of a single instance of a schema generator 

311 self._used = False 

312 

313 @property 

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

315 return self._config_wrapper_stack.tail 

316 

317 @property 

318 def mode(self) -> JsonSchemaMode: 

319 if self._config.json_schema_mode_override is not None: 

320 return self._config.json_schema_mode_override 

321 else: 

322 return self._mode 

323 

324 def build_schema_type_to_method( 

325 self, 

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

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

328 

329 Returns: 

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

331 

332 Raises: 

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

334 """ 

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

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

337 for key in core_schema_types: 

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

339 try: 

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

341 except AttributeError as e: # pragma: no cover 

342 if os.getenv('PYDANTIC_PRIVATE_ALLOW_UNHANDLED_SCHEMA_TYPES'): 

343 continue 

344 raise TypeError( 

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

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

347 ) from e 

348 return mapping 

349 

350 def generate_definitions( 

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

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

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

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

355 

356 Args: 

357 inputs: A sequence of tuples, where: 

358 

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

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

361 - The third element is a core schema. 

362 

363 Returns: 

364 A tuple where: 

365 

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

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

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

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

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

371 

372 Raises: 

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

374 """ 

375 if self._used: 

376 raise PydanticUserError( 

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

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

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

380 ) 

381 

382 for _, mode, schema in inputs: 

383 self._mode = mode 

384 self.generate_inner(schema) 

385 

386 definitions_remapping = self._build_definitions_remapping() 

387 

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

389 for key, mode, schema in inputs: 

390 self._mode = mode 

391 json_schema = self.generate_inner(schema) 

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

393 

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

395 json_schema = definitions_remapping.remap_json_schema(json_schema) 

396 self._used = True 

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

398 

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

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

401 

402 Args: 

403 schema: A Pydantic model. 

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

405 

406 Returns: 

407 A JSON schema representing the specified schema. 

408 

409 Raises: 

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

411 """ 

412 self._mode = mode 

413 if self._used: 

414 raise PydanticUserError( 

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

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

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

418 ) 

419 

420 json_schema: JsonSchemaValue = self.generate_inner(schema) 

421 json_ref_counts = self.get_json_ref_counts(json_schema) 

422 

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

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

425 ref_json_schema = self.get_schema_from_definitions(ref) 

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

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

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

429 json_ref_counts[ref] -= 1 

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

431 ref = None 

432 

433 self._garbage_collect_definitions(json_schema) 

434 definitions_remapping = self._build_definitions_remapping() 

435 

436 if self.definitions: 

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

438 

439 json_schema = definitions_remapping.remap_json_schema(json_schema) 

440 

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

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

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

444 

445 self._used = True 

446 return self.sort(json_schema) 

447 

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

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

450 

451 Args: 

452 schema: The given core schema. 

453 

454 Returns: 

455 The generated JSON schema. 

456 

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

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

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

460 """ 

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

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

463 # on types that should have different JSON schemas 

464 if 'ref' in schema: 

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

466 core_mode_ref = (core_ref, self.mode) 

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

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

469 

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

471 if 'ref' in core_schema: 

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

473 defs_ref, ref_json_schema = self.get_cache_defs_ref_schema(core_ref) 

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

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

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

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

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

479 self.definitions[defs_ref] = json_schema 

480 self._core_defs_invalid_for_json_schema.pop(defs_ref, None) 

481 json_schema = ref_json_schema 

482 return json_schema 

483 

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

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

486 

487 Args: 

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

489 

490 Returns: 

491 The generated JSON schema. 

492 

493 Raises: 

494 TypeError: If an unexpected schema type is encountered. 

495 """ 

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

497 json_schema: JsonSchemaValue | None = None 

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

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

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

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

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

503 json_schema = self.ser_schema(ser_schema) 

504 

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

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

507 if ( 

508 json_schema is not None 

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

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

511 ): 

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

513 if json_schema is None: 

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

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

516 json_schema = generate_for_schema_type(schema_or_field) 

517 else: 

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

519 return json_schema 

520 

521 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, handler_func) 

522 

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

524 

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

526 

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

528 

529 def js_updates_handler_func( 

530 schema_or_field: CoreSchemaOrField, 

531 current_handler: GetJsonSchemaHandler = current_handler, 

532 ) -> JsonSchemaValue: 

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

534 return json_schema 

535 

536 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_updates_handler_func) 

537 

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

539 

540 def js_extra_handler_func( 

541 schema_or_field: CoreSchemaOrField, 

542 current_handler: GetJsonSchemaHandler = current_handler, 

543 ) -> JsonSchemaValue: 

544 json_schema = current_handler(schema_or_field) 

545 if isinstance(js_extra, dict): 

546 json_schema.update(to_jsonable_python(js_extra)) 

547 elif callable(js_extra): 

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

549 js_extra(json_schema) # type: ignore 

550 return json_schema 

551 

552 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, js_extra_handler_func) 

553 

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

555 

556 def new_handler_func( 

557 schema_or_field: CoreSchemaOrField, 

558 current_handler: GetJsonSchemaHandler = current_handler, 

559 js_modify_function: GetJsonSchemaFunction = js_modify_function, 

560 ) -> JsonSchemaValue: 

561 json_schema = js_modify_function(schema_or_field, current_handler) 

562 if _core_utils.is_core_schema(schema_or_field): 

563 json_schema = populate_defs(schema_or_field, json_schema) 

564 original_schema = current_handler.resolve_ref_schema(json_schema) 

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

566 if ref and json_schema: 

567 original_schema.update(json_schema) 

568 return original_schema 

569 

570 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func) 

571 

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

573 

574 def new_handler_func( 

575 schema_or_field: CoreSchemaOrField, 

576 current_handler: GetJsonSchemaHandler = current_handler, 

577 js_modify_function: GetJsonSchemaFunction = js_modify_function, 

578 ) -> JsonSchemaValue: 

579 return js_modify_function(schema_or_field, current_handler) 

580 

581 current_handler = _schema_generation_shared.GenerateJsonSchemaHandler(self, new_handler_func) 

582 

583 json_schema = current_handler(schema) 

584 if _core_utils.is_core_schema(schema): 

585 json_schema = populate_defs(schema, json_schema) 

586 return json_schema 

587 

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

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

590 

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

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

593 """ 

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

595 keys = value.keys() 

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

597 keys = sorted(keys) 

598 for key in keys: 

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

600 return sorted_dict 

601 

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

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

604 if isinstance(value, dict): 

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

606 keys = value.keys() 

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

608 keys = sorted(keys) 

609 for key in keys: 

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

611 return sorted_dict 

612 elif isinstance(value, list): 

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

614 return sorted_list 

615 else: 

616 return value 

617 

618 # ### Schema generation methods 

619 

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

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

622 

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

624 

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

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

627 

628 Args: 

629 schema: The core schema. 

630 

631 Returns: 

632 The generated JSON schema. 

633 """ 

634 return {} 

635 

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

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

638 

639 Args: 

640 schema: The core schema. 

641 

642 Returns: 

643 The generated JSON schema. 

644 """ 

645 return {'type': 'null'} 

646 

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

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

649 

650 Args: 

651 schema: The core schema. 

652 

653 Returns: 

654 The generated JSON schema. 

655 """ 

656 return {'type': 'boolean'} 

657 

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

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

660 

661 Args: 

662 schema: The core schema. 

663 

664 Returns: 

665 The generated JSON schema. 

666 """ 

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

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

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

670 return json_schema 

671 

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

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

674 

675 Args: 

676 schema: The core schema. 

677 

678 Returns: 

679 The generated JSON schema. 

680 """ 

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

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

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

684 return json_schema 

685 

686 def fraction_schema(self, schema: core_schema.FractionSchema) -> JsonSchemaValue: 

687 """Generates a JSON schema that matches a fraction value. 

688 

689 Args: 

690 schema: The core schema. 

691 

692 Returns: 

693 The generated JSON schema. 

694 

695 """ 

696 json_schema: JsonSchemaValue = {'type': 'string', 'format': 'fraction'} 

697 if self.mode == 'validation': 

698 le = schema.get('le') 

699 ge = schema.get('ge') 

700 lt = schema.get('lt') 

701 gt = schema.get('gt') 

702 json_schema = { 

703 'anyOf': [ 

704 self.float_schema( 

705 core_schema.float_schema( 

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

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

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

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

710 ) 

711 ), 

712 json_schema, 

713 ], 

714 } 

715 return json_schema 

716 

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

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

719 

720 Args: 

721 schema: The core schema. 

722 

723 Returns: 

724 The generated JSON schema. 

725 """ 

726 str_schema = core_schema.str_schema() 

727 pattern = self.get_decimal_pattern(schema) 

728 if pattern is not None: 

729 str_schema['pattern'] = pattern 

730 json_schema = self.str_schema(str_schema) 

731 if self.mode == 'validation': 

732 multiple_of = schema.get('multiple_of') 

733 le = schema.get('le') 

734 ge = schema.get('ge') 

735 lt = schema.get('lt') 

736 gt = schema.get('gt') 

737 json_schema = { 

738 'anyOf': [ 

739 self.float_schema( 

740 core_schema.float_schema( 

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

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

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

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

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

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

747 ) 

748 ), 

749 json_schema, 

750 ], 

751 } 

752 return json_schema 

753 

754 def get_decimal_pattern(self, schema: core_schema.DecimalSchema) -> str | None: 

755 """Get the regular expression pattern to apply to the string representation of a decimal. 

756 

757 By default, no pattern is applied (`None` is returned). Subclasses can override this method 

758 to return a custom pattern, or [`build_decimal_pattern()`][pydantic.json_schema.GenerateJsonSchema.build_decimal_pattern] 

759 can be used to get a pattern reflecting the `max_digits` and `decimal_places` constraints: 

760 

761 ```python 

762 from pydantic.json_schema import GenerateJsonSchema 

763 

764 class MyGenerateJsonSchema(GenerateJsonSchema): 

765 def get_decimal_pattern(self, schema): 

766 return self.build_decimal_pattern(schema) 

767 ``` 

768 

769 Args: 

770 schema: The core schema. 

771 

772 Returns: 

773 The regular expression pattern, or `None` if no pattern should be applied. 

774 """ 

775 return None 

776 

777 def build_decimal_pattern(self, schema: core_schema.DecimalSchema) -> str: 

778 """Build a regular expression pattern for the string representation of a decimal. 

779 

780 The pattern takes into account the `max_digits` and `decimal_places` constraints, and the current 

781 mode: 

782 

783 - In `'validation'` mode, the pattern accepts the string syntax understood by the [`Decimal`][decimal.Decimal] 

784 constructor. The `max_digits` and `decimal_places` constraints are only reflected for strings 

785 *without* an exponent: strings using an exponent are accepted as long as they are syntactically valid, 

786 as the constraints can't be expressed with a regular expression in this case. 

787 - In `'serialization'` mode, the pattern exactly describes the strings produced by pydantic 

788 (i.e. by `str(decimal)`) for decimals satisfying the constraints, 

789 including the ones using scientific notation (e.g. `'1E-7'`, `'1.5E+3'`). 

790 

791 In both modes, trailing zeros in the decimal part are not counted (e.g. `'1.10'` is accepted with 

792 `decimal_places=1`), consistent with the validation behavior. If `allow_inf_nan` is enabled (on the 

793 core schema or in the configuration), the string representations of infinity and NaN are also accepted. 

794 

795 Args: 

796 schema: The core schema. 

797 

798 Returns: 

799 The regular expression pattern. 

800 """ 

801 max_digits = schema.get('max_digits') 

802 decimal_places = schema.get('decimal_places') 

803 allow_inf_nan = schema.get('allow_inf_nan') 

804 if allow_inf_nan is None: 

805 allow_inf_nan = self._config.config_dict.get('allow_inf_nan', False) 

806 if self.mode == 'validation': 

807 return _decimal_validation_pattern(max_digits, decimal_places, allow_inf_nan) 

808 else: 

809 return _decimal_serialization_pattern(max_digits, decimal_places, allow_inf_nan) 

810 

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

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

813 

814 Args: 

815 schema: The core schema. 

816 

817 Returns: 

818 The generated JSON schema. 

819 """ 

820 json_schema: JsonSchemaValue = {'type': 'string'} 

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

822 if 'minLength' not in json_schema and self._config.str_min_length: 

823 json_schema['minLength'] = self._config.str_min_length 

824 if 'maxLength' not in json_schema and self._config.str_max_length is not None: 

825 json_schema['maxLength'] = self._config.str_max_length 

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

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

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

829 return json_schema 

830 

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

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

833 

834 Args: 

835 schema: The core schema. 

836 

837 Returns: 

838 The generated JSON schema. 

839 """ 

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

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

842 return json_schema 

843 

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

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

846 

847 Args: 

848 schema: The core schema. 

849 

850 Returns: 

851 The generated JSON schema. 

852 """ 

853 return self._common_temporal_schema('date', self._config.ser_json_temporal) 

854 

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

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

857 

858 Args: 

859 schema: The core schema. 

860 

861 Returns: 

862 The generated JSON schema. 

863 """ 

864 return self._common_temporal_schema('time', self._config.ser_json_temporal) 

865 

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

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

868 

869 Args: 

870 schema: The core schema. 

871 

872 Returns: 

873 The generated JSON schema. 

874 """ 

875 return self._common_temporal_schema('date-time', self._config.ser_json_temporal) 

876 

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

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

879 

880 Args: 

881 schema: The core schema. 

882 

883 Returns: 

884 The generated JSON schema. 

885 """ 

886 if 'ser_json_temporal' in self._config.config_dict: 

887 temporal_format = self._config.ser_json_temporal 

888 else: 

889 # `ser_json_temporal` supersedes `ser_json_timedelta`, which only applies when the former isn't 

890 # explicitly set. 

891 temporal_format = 'seconds' if self._config.ser_json_timedelta == 'float' else 'iso8601' 

892 return self._common_temporal_schema('duration', temporal_format) 

893 

894 def _common_temporal_schema( 

895 self, format: str, temporal_format: Literal['iso8601', 'seconds', 'milliseconds'] 

896 ) -> JsonSchemaValue: 

897 if self.mode == 'serialization' and temporal_format != 'iso8601': 

898 # Both `'seconds'` and `'milliseconds'` serialize to a number: 

899 return {'type': 'number'} 

900 return {'type': 'string', 'format': format} 

901 

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

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

904 

905 Args: 

906 schema: The core schema. 

907 

908 Returns: 

909 The generated JSON schema. 

910 """ 

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

912 

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

914 if len(expected) == 1: 

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

916 else: 

917 result['enum'] = expected 

918 

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

920 if types == {str}: 

921 result['type'] = 'string' 

922 elif types == {int}: 

923 result['type'] = 'integer' 

924 elif types == {float}: 

925 result['type'] = 'number' 

926 elif types == {bool}: 

927 result['type'] = 'boolean' 

928 elif types == {list}: 

929 result['type'] = 'array' 

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

931 result['type'] = 'null' 

932 return result 

933 

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

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

936 

937 Args: 

938 schema: The core schema. 

939 

940 Returns: 

941 The generated JSON schema. 

942 """ 

943 inner_schema = schema.get('schema') 

944 if inner_schema is None: 

945 raise PydanticOmit 

946 return self.generate_inner(inner_schema) 

947 

948 def ellipsis_schema(self, schema: core_schema.EllipsisSchema) -> JsonSchemaValue: 

949 """Handles JSON schema generation for a core schema that checks if a value is the [`Ellipsis`][] literal. 

950 

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

952 

953 Args: 

954 schema: The core schema. 

955 

956 Returns: 

957 The generated JSON schema. 

958 """ 

959 return self.handle_invalid_for_json_schema(schema, 'core_schema.EllipsisSchema') 

960 

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

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

963 

964 Args: 

965 schema: The core schema. 

966 

967 Returns: 

968 The generated JSON schema. 

969 """ 

970 enum_type = schema['cls'] 

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

972 if ( 

973 description == 'An enumeration.' 

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

975 description = None 

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

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

978 

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

980 

981 result['enum'] = expected 

982 

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

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

985 result['type'] = 'string' 

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

987 result['type'] = 'integer' 

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

989 result['type'] = 'number' 

990 elif types == {bool}: 

991 result['type'] = 'boolean' 

992 elif types == {list}: 

993 result['type'] = 'array' 

994 

995 return result 

996 

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

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

999 

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

1001 

1002 Args: 

1003 schema: The core schema. 

1004 

1005 Returns: 

1006 The generated JSON schema. 

1007 """ 

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

1009 

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

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

1012 

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

1014 

1015 Args: 

1016 schema: The core schema. 

1017 

1018 Returns: 

1019 The generated JSON schema. 

1020 """ 

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

1022 return {} 

1023 

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

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

1026 

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

1028 

1029 Args: 

1030 schema: The core schema. 

1031 

1032 Returns: 

1033 The generated JSON schema. 

1034 """ 

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

1036 

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

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

1039 

1040 Args: 

1041 schema: The core schema. 

1042 

1043 Returns: 

1044 The generated JSON schema. 

1045 """ 

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

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

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

1049 return json_schema 

1050 

1051 def deque_schema(self, schema: core_schema.DequeSchema) -> JsonSchemaValue: 

1052 """Returns a schema that matches a deque schema. 

1053 

1054 Args: 

1055 schema: The core schema. 

1056 

1057 Returns: 

1058 The generated JSON schema. 

1059 """ 

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

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

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

1063 return json_schema 

1064 

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

1066 @final 

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

1068 """Replaced by `tuple_schema`.""" 

1069 warnings.warn( 

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

1071 PydanticDeprecatedSince26, 

1072 stacklevel=2, 

1073 ) 

1074 return self.tuple_schema(schema) 

1075 

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

1077 @final 

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

1079 """Replaced by `tuple_schema`.""" 

1080 warnings.warn( 

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

1082 PydanticDeprecatedSince26, 

1083 stacklevel=2, 

1084 ) 

1085 return self.tuple_schema(schema) 

1086 

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

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

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

1090 

1091 Args: 

1092 schema: The core schema. 

1093 

1094 Returns: 

1095 The generated JSON schema. 

1096 """ 

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

1098 if 'variadic_item_index' in schema: 

1099 variadic_item_index = schema['variadic_item_index'] 

1100 if variadic_item_index > 0: 

1101 json_schema['minItems'] = variadic_item_index 

1102 json_schema['prefixItems'] = [ 

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

1104 ] 

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

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

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

1108 else: 

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

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

1111 # for now 

1112 json_schema['items'] = True 

1113 else: 

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

1115 if prefixItems: 

1116 json_schema['prefixItems'] = prefixItems 

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

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

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

1120 return json_schema 

1121 

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

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

1124 

1125 Args: 

1126 schema: The core schema. 

1127 

1128 Returns: 

1129 The generated JSON schema. 

1130 """ 

1131 return self._common_set_schema(schema) 

1132 

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

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

1135 

1136 Args: 

1137 schema: The core schema. 

1138 

1139 Returns: 

1140 The generated JSON schema. 

1141 """ 

1142 return self._common_set_schema(schema) 

1143 

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

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

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

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

1148 return json_schema 

1149 

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

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

1152 

1153 Args: 

1154 schema: The schema. 

1155 

1156 Returns: 

1157 The generated JSON schema. 

1158 """ 

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

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

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

1162 return json_schema 

1163 

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

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

1166 

1167 Args: 

1168 schema: The core schema. 

1169 

1170 Returns: 

1171 The generated JSON schema. 

1172 """ 

1173 return self._common_dict_schema(schema) 

1174 

1175 def frozendict_schema(self, schema: core_schema.FrozenDictSchema) -> JsonSchemaValue: 

1176 """Generates a JSON schema that matches a frozendict schema. 

1177 

1178 Args: 

1179 schema: The core schema. 

1180 

1181 Returns: 

1182 The generated JSON schema. 

1183 """ 

1184 return self._common_dict_schema(schema) 

1185 

1186 def ordered_dict_schema(self, schema: core_schema.OrderedDictSchema) -> JsonSchemaValue: 

1187 """Generates a JSON schema that matches an `OrderedDict` schema. 

1188 

1189 Args: 

1190 schema: The core schema. 

1191 

1192 Returns: 

1193 The generated JSON schema. 

1194 """ 

1195 return self._common_dict_schema(schema) 

1196 

1197 def counter_schema(self, schema: core_schema.CounterSchema) -> JsonSchemaValue: 

1198 """Generates a JSON schema that matches a `Counter` schema. 

1199 

1200 Args: 

1201 schema: The core schema. 

1202 

1203 Returns: 

1204 The generated JSON schema. 

1205 """ 

1206 return self._common_dict_schema(schema) 

1207 

1208 def _common_dict_schema( 

1209 self, 

1210 schema: core_schema.DictSchema 

1211 | core_schema.FrozenDictSchema 

1212 | core_schema.OrderedDictSchema 

1213 | core_schema.CounterSchema, 

1214 ) -> JsonSchemaValue: 

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

1216 

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

1218 if '$ref' not in keys_schema: 

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

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

1221 keys_schema.pop('title', None) 

1222 else: 

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

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

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

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

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

1228 keys_pattern = None 

1229 

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

1231 # don't give a title to additionalProperties: 

1232 values_schema.pop('title', None) 

1233 

1234 if values_schema or keys_pattern is not None: 

1235 if keys_pattern is None: 

1236 json_schema['additionalProperties'] = values_schema 

1237 else: 

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

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

1240 json_schema['additionalProperties'] = True 

1241 

1242 if ( 

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

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

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

1246 or '$ref' in keys_schema 

1247 ): 

1248 keys_schema.pop('type', None) 

1249 json_schema['propertyNames'] = keys_schema 

1250 

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

1252 return json_schema 

1253 

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

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

1256 

1257 Args: 

1258 schema: The core schema. 

1259 

1260 Returns: 

1261 The generated JSON schema. 

1262 """ 

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

1264 return self.generate_inner(input_schema) 

1265 

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

1267 

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

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

1270 

1271 Args: 

1272 schema: The core schema. 

1273 

1274 Returns: 

1275 The generated JSON schema. 

1276 """ 

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

1278 

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

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

1281 

1282 Args: 

1283 schema: The core schema. 

1284 

1285 Returns: 

1286 The generated JSON schema. 

1287 """ 

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

1289 return self.generate_inner(input_schema) 

1290 

1291 return self.handle_invalid_for_json_schema( 

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

1293 ) 

1294 

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

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

1297 

1298 Args: 

1299 schema: The core schema. 

1300 

1301 Returns: 

1302 The generated JSON schema. 

1303 """ 

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

1305 return self.generate_inner(input_schema) 

1306 

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

1308 

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

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

1311 

1312 Args: 

1313 schema: The core schema. 

1314 

1315 Returns: 

1316 The generated JSON schema. 

1317 """ 

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

1319 

1320 default = self.get_default_value(schema) 

1321 if default is NoDefault or default is MISSING: 

1322 return json_schema 

1323 

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

1325 # JSON Schemas viewed in serialization mode: 

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

1327 if self.mode == 'serialization': 

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

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

1330 if ( 

1331 ser_schema is not None 

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

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

1334 ): 

1335 try: 

1336 default = ser_func(default) # type: ignore 

1337 except Exception: 

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

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

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

1341 # this pattern for now. 

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

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

1344 self.emit_warning( 

1345 'non-serializable-default', 

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

1347 ) 

1348 return json_schema 

1349 

1350 # Sort set/frozenset defaults to ensure deterministic JSON schema generation 

1351 # We only sort if len > 1 because sets of size 0 or 1 are already deterministic 

1352 if isinstance(default, collections.abc.Set) and len(default) > 1: 

1353 try: 

1354 default = sorted(default) 

1355 except TypeError: # pragma: no cover 

1356 # If items aren't comparable (e.g. mixed types), we can't sort them. 

1357 pass 

1358 

1359 try: 

1360 encoded_default = self.encode_default(default) 

1361 except pydantic_core.PydanticSerializationError: 

1362 self.emit_warning( 

1363 'non-serializable-default', 

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

1365 ) 

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

1367 return json_schema 

1368 

1369 json_schema['default'] = encoded_default 

1370 return json_schema 

1371 

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

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

1374 

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

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

1377 

1378 Args: 

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

1380 

1381 Returns: 

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

1383 value is available. 

1384 """ 

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

1386 

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

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

1389 

1390 Args: 

1391 schema: The core schema. 

1392 

1393 Returns: 

1394 The generated JSON schema. 

1395 """ 

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

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

1398 

1399 if inner_json_schema == null_schema: 

1400 return null_schema 

1401 else: 

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

1403 

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

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

1406 

1407 Args: 

1408 schema: The core schema. 

1409 

1410 Returns: 

1411 The generated JSON schema. 

1412 """ 

1413 generated: list[JsonSchemaValue] = [] 

1414 

1415 for choice in core_schema.iter_union_choices(schema): 

1416 try: 

1417 generated.append(self.generate_inner(choice)) 

1418 except PydanticOmit: # noqa: PERF203 

1419 continue 

1420 except PydanticInvalidForJsonSchema as exc: 

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

1422 if len(generated) == 1: 

1423 return generated[0] 

1424 return self.get_union_of_schemas(generated) 

1425 

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

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

1428 

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

1430 

1431 Args: 

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

1433 

1434 Returns: 

1435 The JSON Schema representing the union of schemas. 

1436 """ 

1437 if self.union_format == 'primitive_type_array': 

1438 types: list[str] = [] 

1439 for schema in schemas: 

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

1441 if schema_types is None: 

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

1443 break 

1444 if not isinstance(schema_types, list): 

1445 schema_types = [schema_types] 

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

1447 break 

1448 if len(schema) != 1: 

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

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

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

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

1453 break 

1454 

1455 types.extend(schema_types) 

1456 else: 

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

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

1459 

1460 return self.get_flattened_anyof(schemas) 

1461 

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

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

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

1465 the value. 

1466 

1467 Args: 

1468 schema: The core schema. 

1469 

1470 Returns: 

1471 The generated JSON schema. 

1472 """ 

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

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

1475 if isinstance(k, Enum): 

1476 k = k.value 

1477 elif isinstance(k, bool): 

1478 # Use the JSON representation so that the discriminator mapping 

1479 # can be matched against the serialized payload value 

1480 k = 'true' if k else 'false' 

1481 elif k is None: 

1482 k = 'null' 

1483 try: 

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

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

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

1487 except PydanticOmit: 

1488 continue 

1489 except PydanticInvalidForJsonSchema as exc: 

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

1491 

1492 one_of_choices = _deduplicate_schemas(generated.values()) 

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

1494 

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

1496 openapi_discriminator = self._extract_discriminator(schema, one_of_choices) 

1497 if openapi_discriminator is not None: 

1498 json_schema['discriminator'] = { 

1499 'propertyName': openapi_discriminator, 

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

1501 } 

1502 

1503 return json_schema 

1504 

1505 def _extract_discriminator( 

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

1507 ) -> str | None: 

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

1509 schema.""" 

1510 openapi_discriminator: str | None = None 

1511 

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

1513 return schema['discriminator'] 

1514 

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

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

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

1518 return schema['discriminator'][0] 

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

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

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

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

1523 for alias_path in schema['discriminator']: 

1524 if not isinstance(alias_path, list): 

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

1526 if len(alias_path) != 1: 

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

1528 alias = alias_path[0] 

1529 if not isinstance(alias, str): 

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

1531 alias_is_present_on_all_choices = True 

1532 for choice in one_of_choices: 

1533 try: 

1534 choice = self.resolve_ref_schema(choice) 

1535 except RuntimeError as exc: 

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

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

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

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

1540 choice = {} 

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

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

1543 alias_is_present_on_all_choices = False 

1544 break 

1545 if alias_is_present_on_all_choices: 

1546 openapi_discriminator = alias 

1547 break 

1548 return openapi_discriminator 

1549 

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

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

1552 

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

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

1555 

1556 Args: 

1557 schema: The core schema. 

1558 

1559 Returns: 

1560 The generated JSON schema. 

1561 """ 

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

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

1564 

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

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

1567 strict schema. 

1568 

1569 Args: 

1570 schema: The core schema. 

1571 

1572 Returns: 

1573 The generated JSON schema. 

1574 """ 

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

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

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

1578 # because one of the following two branches failed. 

1579 if use_strict: 

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

1581 else: 

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

1583 

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

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

1586 Python schema. 

1587 

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

1589 this method. 

1590 

1591 Args: 

1592 schema: The core schema. 

1593 

1594 Returns: 

1595 The generated JSON schema. 

1596 """ 

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

1598 

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

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

1601 

1602 Args: 

1603 schema: The core schema. 

1604 

1605 Returns: 

1606 The generated JSON schema. 

1607 """ 

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

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

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

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

1612 if self.field_is_present(field) 

1613 ] 

1614 if self.mode == 'serialization': 

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

1616 cls = schema.get('cls') 

1617 config = _get_typed_dict_config(cls) 

1618 with self._config_wrapper_stack.push(config): 

1619 json_schema = self._named_required_fields_schema(named_required_fields) 

1620 

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

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

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

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

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

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

1627 if 'extras_schema' in schema and schema['extras_schema'] != core_schema.any_schema(): 

1628 allow_additional_props = self.generate_inner(schema['extras_schema']) 

1629 else: 

1630 allow_additional_props = True 

1631 

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

1633 json_schema['additionalProperties'] = False 

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

1635 json_schema['additionalProperties'] = allow_additional_props 

1636 

1637 if cls is not None: 

1638 # `_update_class_schema()` will not override 

1639 # `additionalProperties` if already present: 

1640 self._update_class_schema(json_schema, cls, config or {}) 

1641 elif 'additionalProperties' not in json_schema: 

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

1643 if extra == 'forbid': 

1644 json_schema['additionalProperties'] = False 

1645 elif extra == 'allow': 

1646 json_schema['additionalProperties'] = allow_additional_props 

1647 

1648 return json_schema 

1649 

1650 @staticmethod 

1651 def _name_required_computed_fields( 

1652 computed_fields: list[ComputedField], 

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

1654 return [ 

1655 (field['property_name'], field.get('serialization_exclude_if') is None, field) for field in computed_fields 

1656 ] 

1657 

1658 def _named_required_fields_schema( 

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

1660 ) -> JsonSchemaValue: 

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

1662 required_fields: list[str] = [] 

1663 for name, required, field in named_required_fields: 

1664 if self.by_alias: 

1665 name = self._get_alias_name(field, name) 

1666 try: 

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

1668 except PydanticOmit: 

1669 continue 

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

1671 title = self.get_title_from_name(name) 

1672 field_json_schema['title'] = title 

1673 field_json_schema = self.handle_ref_overrides(field_json_schema) 

1674 properties[name] = field_json_schema 

1675 if required: 

1676 required_fields.append(name) 

1677 

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

1679 if required_fields: 

1680 json_schema['required'] = required_fields 

1681 return json_schema 

1682 

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

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

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

1686 elif self.mode == 'validation': 

1687 alias = name if not self._config.validate_by_alias else field.get('validation_alias', name) 

1688 else: 

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

1690 if isinstance(alias, str): 

1691 name = alias 

1692 elif isinstance(alias, list): 

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

1694 for path in alias: 

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

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

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

1698 name = path[0] 

1699 break 

1700 else: 

1701 assert_never(alias) 

1702 return name 

1703 

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

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

1706 

1707 Args: 

1708 schema: The core schema. 

1709 

1710 Returns: 

1711 The generated JSON schema. 

1712 """ 

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

1714 

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

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

1717 

1718 Args: 

1719 schema: The core schema. 

1720 

1721 Returns: 

1722 The generated JSON schema. 

1723 """ 

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

1725 

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

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

1728 

1729 Args: 

1730 schema: The core schema. 

1731 

1732 Returns: 

1733 The generated JSON schema. 

1734 """ 

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

1736 

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

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

1739 

1740 Args: 

1741 schema: The core schema. 

1742 

1743 Returns: 

1744 The generated JSON schema. 

1745 """ 

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

1747 

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

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

1750 

1751 Args: 

1752 schema: The core schema. 

1753 

1754 Returns: 

1755 The generated JSON schema. 

1756 """ 

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

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

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

1760 config = cls.model_config 

1761 

1762 with self._config_wrapper_stack.push(config): 

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

1764 

1765 self._update_class_schema(json_schema, cls, config) 

1766 

1767 return json_schema 

1768 

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

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

1771 

1772 * title 

1773 * description 

1774 * additional properties 

1775 * json_schema_extra 

1776 * deprecated 

1777 

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

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

1780 """ 

1781 from ._internal._dataclasses import is_stdlib_dataclass 

1782 from .main import BaseModel 

1783 

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

1785 json_schema.setdefault('title', config_title) 

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

1787 title = model_title_generator(cls) 

1788 if not isinstance(title, str): 

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

1790 json_schema.setdefault('title', title) 

1791 if 'title' not in json_schema: 

1792 json_schema['title'] = cls.__name__ 

1793 

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

1795 if cls is BaseModel: 

1796 docstring = None 

1797 elif is_stdlib_dataclass(cls): # For Pydantic dataclasses, we already handle this at class creation 

1798 # The `dataclass` module generates a `__doc__` based on the `inspect.signature()` 

1799 # result, which we don't want to use as a description. Such `__doc__` startswith 

1800 # `cls.__name__(`, which could lead to mistakenly discarding it if for some reason 

1801 # an explicitly set class docstring follows the same pattern, but this is unlikely 

1802 # to happen. 

1803 doc = cls.__doc__ 

1804 docstring = None if doc is None or doc.startswith(f'{cls.__name__}(') else doc 

1805 else: 

1806 docstring = cls.__doc__ 

1807 

1808 if docstring: 

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

1810 

1811 extra = config.get('extra') 

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

1813 if extra == 'allow': 

1814 json_schema['additionalProperties'] = True 

1815 elif extra == 'forbid': 

1816 json_schema['additionalProperties'] = False 

1817 

1818 json_schema_extra = config.get('json_schema_extra') 

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

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

1821 if json_schema_extra and root_json_schema_extra: 

1822 raise ValueError( 

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

1824 ' field must not be set simultaneously' 

1825 ) 

1826 if root_json_schema_extra: 

1827 json_schema_extra = root_json_schema_extra 

1828 

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

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

1831 json_schema_extra = json_schema_extra.__get__(cls) 

1832 

1833 if isinstance(json_schema_extra, dict): 

1834 json_schema.update(json_schema_extra) 

1835 elif callable(json_schema_extra): 

1836 if len(_typing_extra.signature_no_eval(json_schema_extra).parameters) > 1: 

1837 json_schema_extra = cast(Callable[[JsonDict, type[Any]], None], json_schema_extra) 

1838 json_schema_extra(json_schema, cls) 

1839 else: 

1840 json_schema_extra = cast(Callable[[JsonDict], None], json_schema_extra) 

1841 json_schema_extra(json_schema) 

1842 elif json_schema_extra is not None: 

1843 raise ValueError( 

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

1845 ) 

1846 

1847 if hasattr(cls, '__deprecated__'): 

1848 json_schema['deprecated'] = True 

1849 

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

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

1852 

1853 Args: 

1854 json_schema: The schema to resolve. 

1855 

1856 Returns: 

1857 The resolved schema. 

1858 

1859 Raises: 

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

1861 """ 

1862 while '$ref' in json_schema: 

1863 ref = json_schema['$ref'] 

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

1865 if schema_to_update is None: 

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

1867 json_schema = schema_to_update 

1868 return json_schema 

1869 

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

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

1872 

1873 Args: 

1874 schema: The core schema. 

1875 

1876 Returns: 

1877 The generated JSON schema. 

1878 """ 

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

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

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

1882 if self.field_is_present(field) 

1883 ] 

1884 if self.mode == 'serialization': 

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

1886 json_schema = self._named_required_fields_schema(named_required_fields) 

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

1888 if extras_schema is not None: 

1889 schema_to_update = self.resolve_ref_schema(json_schema) 

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

1891 return json_schema 

1892 

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

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

1895 

1896 Args: 

1897 field: The schema for the field itself. 

1898 

1899 Returns: 

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

1901 """ 

1902 if self.mode == 'serialization': 

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

1904 # override this method and return True 

1905 return not field.get('serialization_exclude') 

1906 elif self.mode == 'validation': 

1907 return True 

1908 else: 

1909 assert_never(self.mode) 

1910 

1911 def field_is_required( 

1912 self, 

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

1914 total: bool, 

1915 ) -> bool: 

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

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

1918 

1919 Args: 

1920 field: The schema for the field itself. 

1921 total: Only applies to `TypedDictField`s. 

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

1923 explicitly specify `required=False` are required. 

1924 

1925 Returns: 

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

1927 """ 

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

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

1930 else: 

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

1932 

1933 if self.mode == 'serialization': 

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

1935 if self._config.json_schema_serialization_defaults_required: 

1936 return not has_exclude_if 

1937 else: 

1938 return required and not has_exclude_if 

1939 else: 

1940 return required 

1941 

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

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

1944 

1945 Args: 

1946 schema: The core schema. 

1947 

1948 Returns: 

1949 The generated JSON schema. 

1950 """ 

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

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

1953 for field in schema['fields'] 

1954 if self.field_is_present(field) 

1955 ] 

1956 if self.mode == 'serialization': 

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

1958 return self._named_required_fields_schema(named_required_fields) 

1959 

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

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

1962 

1963 Args: 

1964 schema: The core schema. 

1965 

1966 Returns: 

1967 The generated JSON schema. 

1968 """ 

1969 

1970 cls = schema['cls'] 

1971 # If (stdlib) dataclass doesn't have config, the parent's config is used (as during core schema generation): 

1972 config = cast('ConfigDict | None', getattr(cls, '__pydantic_config__', None)) 

1973 

1974 with self._config_wrapper_stack.push(config): 

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

1976 

1977 # Only the parent's `extra` config value is relevant to the class schema. It is propagated 

1978 # to the core config (and used for validation), so we reuse it: 

1979 class_config = config or {} 

1980 if 'extra' not in class_config and (extra := schema.get('config', {}).get('extra_fields_behavior')) is not None: 

1981 class_config = cast('ConfigDict', {**class_config, 'extra': extra}) 

1982 

1983 self._update_class_schema(json_schema, cls, class_config) 

1984 

1985 return json_schema 

1986 

1987 def named_tuple_field_schema(self, schema: core_schema.NamedTupleField) -> JsonSchemaValue: 

1988 """Generates a JSON schema that matches a schema that defines a named tuple field. 

1989 

1990 Args: 

1991 schema: The core schema. 

1992 

1993 Returns: 

1994 The generated JSON schema. 

1995 """ 

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

1997 

1998 def named_tuple_schema(self, schema: core_schema.NamedTupleSchema) -> JsonSchemaValue: 

1999 """Generates a JSON schema that matches a schema that defines a named tuple. 

2000 

2001 Args: 

2002 schema: The core schema. 

2003 

2004 Returns: 

2005 The generated JSON schema. 

2006 """ 

2007 prefix_items: list[JsonSchemaValue] = [] 

2008 min_items = 0 

2009 

2010 for field in schema['fields']: 

2011 name = field['name'] 

2012 if self.by_alias: 

2013 alias = field.get('validation_alias') 

2014 if isinstance(alias, str): 

2015 name = alias 

2016 

2017 field_schema = self.generate_inner(field['schema']).copy() 

2018 if 'title' not in field_schema and self.field_title_should_be_set(field['schema']): 

2019 field_schema['title'] = self.get_title_from_name(name) 

2020 prefix_items.append(field_schema) 

2021 

2022 if field['schema']['type'] != 'default': 

2023 # This assumes that if the field has a default value, 

2024 # the inner schema must be of type WithDefaultSchema. 

2025 min_items += 1 

2026 

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

2028 if prefix_items: 

2029 json_schema['prefixItems'] = prefix_items 

2030 if min_items: 

2031 json_schema['minItems'] = min_items 

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

2033 

2034 return json_schema 

2035 

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

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

2038 

2039 Args: 

2040 schema: The core schema. 

2041 

2042 Returns: 

2043 The generated JSON schema. 

2044 """ 

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

2046 

2047 arguments = schema['arguments_schema'] 

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

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

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

2051 var_args_schema = schema.get('var_args_schema') 

2052 var_kwargs_schema = schema.get('var_kwargs_schema') 

2053 

2054 if prefer_positional: 

2055 positional_possible = not kw_only_arguments and not var_kwargs_schema 

2056 if positional_possible: 

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

2058 

2059 keyword_possible = not p_only_arguments and not var_args_schema 

2060 if keyword_possible: 

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

2062 

2063 if not prefer_positional: 

2064 positional_possible = not kw_only_arguments and not var_kwargs_schema 

2065 if positional_possible: 

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

2067 

2068 raise PydanticInvalidForJsonSchema( 

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

2070 ) 

2071 

2072 def kw_arguments_schema( 

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

2074 ) -> JsonSchemaValue: 

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

2076 

2077 Args: 

2078 arguments: The core schema. 

2079 

2080 Returns: 

2081 The generated JSON schema. 

2082 """ 

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

2084 required: list[str] = [] 

2085 for argument in arguments: 

2086 name = self.get_argument_name(argument) 

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

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

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

2090 properties[name] = argument_schema 

2091 

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

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

2094 # the inner schema must be of type WithDefaultSchema. 

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

2096 required.append(name) 

2097 

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

2099 if required: 

2100 json_schema['required'] = required 

2101 

2102 if var_kwargs_schema: 

2103 additional_properties_schema = self.generate_inner(var_kwargs_schema) 

2104 if additional_properties_schema: 

2105 json_schema['additionalProperties'] = additional_properties_schema 

2106 else: 

2107 json_schema['additionalProperties'] = False 

2108 return json_schema 

2109 

2110 def p_arguments_schema( 

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

2112 ) -> JsonSchemaValue: 

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

2114 

2115 Args: 

2116 arguments: The core schema. 

2117 

2118 Returns: 

2119 The generated JSON schema. 

2120 """ 

2121 prefix_items: list[JsonSchemaValue] = [] 

2122 min_items = 0 

2123 

2124 for argument in arguments: 

2125 name = self.get_argument_name(argument) 

2126 

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

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

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

2130 prefix_items.append(argument_schema) 

2131 

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

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

2134 # the inner schema must be of type WithDefaultSchema. 

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

2136 min_items += 1 

2137 

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

2139 if prefix_items: 

2140 json_schema['prefixItems'] = prefix_items 

2141 if min_items: 

2142 json_schema['minItems'] = min_items 

2143 

2144 if var_args_schema: 

2145 items_schema = self.generate_inner(var_args_schema) 

2146 if items_schema: 

2147 json_schema['items'] = items_schema 

2148 else: 

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

2150 

2151 return json_schema 

2152 

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

2154 """Retrieves the name of an argument. 

2155 

2156 Args: 

2157 argument: The core schema. 

2158 

2159 Returns: 

2160 The name of the argument. 

2161 """ 

2162 name = argument['name'] 

2163 if self.by_alias: 

2164 alias = argument.get('alias') 

2165 if isinstance(alias, str): 

2166 name = alias 

2167 else: 

2168 pass # might want to do something else? 

2169 return name 

2170 

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

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

2173 

2174 Args: 

2175 schema: The core schema. 

2176 

2177 Returns: 

2178 The generated JSON schema. 

2179 """ 

2180 arguments = schema['arguments_schema'] 

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

2182 required: list[str] = [] 

2183 for argument in arguments: 

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

2185 name = self.get_argument_name(argument) 

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

2187 if mode == 'var_args': 

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

2189 elif mode == 'var_kwargs_uniform': 

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

2191 

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

2193 properties[name] = argument_schema 

2194 

2195 if ( 

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

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

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

2199 ): 

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

2201 # the inner schema must be of type WithDefaultSchema. 

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

2203 required.append(name) 

2204 

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

2206 if required: 

2207 json_schema['required'] = required 

2208 return json_schema 

2209 

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

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

2212 

2213 Args: 

2214 schema: The core schema. 

2215 

2216 Returns: 

2217 The generated JSON schema. 

2218 """ 

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

2220 

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

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

2223 

2224 Args: 

2225 schema: The core schema. 

2226 

2227 Returns: 

2228 The generated JSON schema. 

2229 """ 

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

2231 

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

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

2234 

2235 Args: 

2236 schema: The core schema. 

2237 

2238 Returns: 

2239 The generated JSON schema. 

2240 """ 

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

2242 content_json_schema = self.generate_inner(content_core_schema) 

2243 if self.mode == 'validation': 

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

2245 else: 

2246 # self.mode == 'serialization' 

2247 return content_json_schema 

2248 

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

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

2251 

2252 Args: 

2253 schema: The core schema. 

2254 

2255 Returns: 

2256 The generated JSON schema. 

2257 """ 

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

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

2260 return json_schema 

2261 

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

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

2264 

2265 Args: 

2266 schema: The core schema. 

2267 

2268 Returns: 

2269 The generated JSON schema. 

2270 """ 

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

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

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

2274 return json_schema 

2275 

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

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

2278 

2279 Args: 

2280 schema: The core schema. 

2281 

2282 Returns: 

2283 The generated JSON schema. 

2284 """ 

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

2286 

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

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

2289 

2290 Args: 

2291 schema: The core schema. 

2292 

2293 Returns: 

2294 The generated JSON schema. 

2295 """ 

2296 for definition in schema['definitions']: 

2297 try: 

2298 self.generate_inner(definition) 

2299 except PydanticInvalidForJsonSchema as e: # noqa: PERF203 

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

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

2302 continue 

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

2304 

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

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

2307 

2308 Args: 

2309 schema: The core schema. 

2310 

2311 Returns: 

2312 The generated JSON schema. 

2313 """ 

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

2315 _, ref_json_schema = self.get_cache_defs_ref_schema(core_ref) 

2316 return ref_json_schema 

2317 

2318 def ser_schema( 

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

2320 ) -> JsonSchemaValue | None: 

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

2322 

2323 Args: 

2324 schema: The core schema. 

2325 

2326 Returns: 

2327 The generated JSON schema. 

2328 """ 

2329 match schema: 

2330 case {'type': 'function-plain' | 'function-wrap'}: 

2331 # PlainSerializerFunctionSerSchema or WrapSerializerFunctionSerSchema 

2332 return_schema = schema.get('return_schema') 

2333 if return_schema is not None: 

2334 return self.generate_inner(return_schema) 

2335 case {'type': 'format' | 'to-string'}: 

2336 # FormatSerSchema or ToStringSerSchema 

2337 return self.str_schema(core_schema.str_schema()) 

2338 case {'type': 'model'}: 

2339 # ModelSerSchema 

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

2341 return None 

2342 

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

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

2345 

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

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

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

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

2350 

2351 Args: 

2352 schema: The core schema. 

2353 

2354 Returns: 

2355 The generated JSON schema. 

2356 """ 

2357 return {'type': 'string'} 

2358 

2359 # ### Utility methods 

2360 

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

2362 """Retrieves a title from a name. 

2363 

2364 Args: 

2365 name: The name to retrieve a title from. 

2366 

2367 Returns: 

2368 The title. 

2369 """ 

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

2371 

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

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

2374 

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

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

2377 

2378 Args: 

2379 schema: The schema to check. 

2380 

2381 Returns: 

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

2383 """ 

2384 if _core_utils.is_core_schema_field(schema): 

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

2386 field_schema = schema['return_schema'] 

2387 else: 

2388 field_schema = schema['schema'] 

2389 return self.field_title_should_be_set(field_schema) 

2390 

2391 elif _core_utils.is_core_schema(schema): 

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

2393 return False 

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

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

2396 if schema['type'] == 'missing-sentinel' and (inner_schema := schema.get('schema')) is not None: 

2397 return self.field_title_should_be_set(inner_schema) 

2398 if _core_utils.is_function_with_inner_schema(schema): 

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

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

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

2402 # schemas with refs should not 

2403 return False 

2404 return True # anything else should have title set 

2405 

2406 else: 

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

2408 

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

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

2411 

2412 Args: 

2413 name: The name to normalize. 

2414 

2415 Returns: 

2416 The normalized name. 

2417 """ 

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

2419 

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

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

2422 

2423 Args: 

2424 core_mode_ref: The core reference. 

2425 

2426 Returns: 

2427 The definitions key. 

2428 """ 

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

2430 core_ref, mode = core_mode_ref 

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

2432 # Remove IDs from each component 

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

2434 core_ref_no_id = ''.join(components) 

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

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

2437 short_ref = ''.join(components) 

2438 

2439 mode_title = _MODE_TITLE_MAPPING[mode] 

2440 

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

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

2443 # the id of the source type in the core_ref 

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

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

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

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

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

2449 occurrence_index = self._collision_index.get(module_qualname_id) 

2450 if occurrence_index is None: 

2451 self._collision_counter[module_qualname] += 1 

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

2453 

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

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

2456 

2457 self._prioritized_defsref_choices[module_qualname_occurrence_mode] = [ 

2458 name, 

2459 name_mode, 

2460 module_qualname, 

2461 module_qualname_mode, 

2462 module_qualname_occurrence, 

2463 module_qualname_occurrence_mode, 

2464 ] 

2465 

2466 return module_qualname_occurrence_mode 

2467 

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

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

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

2471 

2472 Args: 

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

2474 

2475 Returns: 

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

2477 """ 

2478 core_mode_ref = (core_ref, self.mode) 

2479 maybe_defs_ref = self.core_to_defs_refs.get(core_mode_ref) 

2480 if maybe_defs_ref is not None: 

2481 json_ref = self.core_to_json_refs[core_mode_ref] 

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

2483 

2484 defs_ref = self.get_defs_ref(core_mode_ref) 

2485 

2486 # populate the ref translation mappings 

2487 self.core_to_defs_refs[core_mode_ref] = defs_ref 

2488 self.defs_to_core_refs[defs_ref] = core_mode_ref 

2489 

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

2491 self.core_to_json_refs[core_mode_ref] = json_ref 

2492 self.json_to_defs_refs[json_ref] = defs_ref 

2493 ref_json_schema = {'$ref': json_ref} 

2494 return defs_ref, ref_json_schema 

2495 

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

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

2498 

2499 Args: 

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

2501 

2502 Returns: 

2503 The schema with redundant sibling keys removed. 

2504 """ 

2505 if '$ref' in json_schema: 

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

2507 json_schema = json_schema.copy() 

2508 

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

2510 if referenced_json_schema is None: 

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

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

2513 # any redundant override keys. 

2514 return json_schema 

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

2516 if k == '$ref': 

2517 continue 

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

2519 del json_schema[k] # redundant key 

2520 

2521 return json_schema 

2522 

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

2524 try: 

2525 def_ref = self.json_to_defs_refs[json_ref] 

2526 if def_ref in self._core_defs_invalid_for_json_schema: 

2527 raise self._core_defs_invalid_for_json_schema[def_ref] 

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

2529 except KeyError: 

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

2531 return None 

2532 raise 

2533 

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

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

2536 

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

2538 

2539 Args: 

2540 dft: The default value to encode. 

2541 

2542 Returns: 

2543 The encoded default value. 

2544 """ 

2545 from .type_adapter import TypeAdapter, _type_has_config 

2546 

2547 config = self._config.config_dict 

2548 if self.mode == 'validation' and ('ser_json_temporal' in config or 'ser_json_timedelta' in config): 

2549 # Temporal serialization formats aren't applied to validation JSON Schemas (see `_common_temporal_schema()`), 

2550 # so they shouldn't be applied to the default either: 

2551 config = cast( 

2552 'ConfigDict', 

2553 {k: v for k, v in config.items() if k not in ('ser_json_temporal', 'ser_json_timedelta')}, 

2554 ) 

2555 try: 

2556 # Types with their own config (e.g. models) can't be used with a config, so we serialize "as Any", 

2557 # which will serialize using their own serializer. 

2558 return TypeAdapter(Any if _type_has_config(type(dft)) else type(dft), config=config).dump_python( 

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

2560 ) 

2561 except Exception as e: 

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

2563 

2564 def update_with_validations( 

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

2566 ) -> None: 

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

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

2569 

2570 Args: 

2571 json_schema: The JSON schema to update. 

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

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

2574 """ 

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

2576 if core_key in core_schema: 

2577 json_schema[json_schema_key] = core_schema[core_key] 

2578 

2579 class ValidationsMapping: 

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

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

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

2583 GenerateJsonSchema.ValidationsMapping) to change these mappings. 

2584 """ 

2585 

2586 numeric = { 

2587 'multiple_of': 'multipleOf', 

2588 'le': 'maximum', 

2589 'ge': 'minimum', 

2590 'lt': 'exclusiveMaximum', 

2591 'gt': 'exclusiveMinimum', 

2592 } 

2593 bytes = { 

2594 'min_length': 'minLength', 

2595 'max_length': 'maxLength', 

2596 } 

2597 string = { 

2598 'min_length': 'minLength', 

2599 'max_length': 'maxLength', 

2600 'pattern': 'pattern', 

2601 } 

2602 array = { 

2603 'min_length': 'minItems', 

2604 'max_length': 'maxItems', 

2605 } 

2606 object = { 

2607 'min_length': 'minProperties', 

2608 'max_length': 'maxProperties', 

2609 } 

2610 

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

2612 members = [] 

2613 for schema in schemas: 

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

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

2616 else: 

2617 members.append(schema) 

2618 members = _deduplicate_schemas(members) 

2619 if len(members) == 1: 

2620 return members[0] 

2621 return {'anyOf': members} 

2622 

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

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

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

2626 

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

2628 if isinstance(schema, dict): 

2629 if '$ref' in schema: 

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

2631 if not isinstance(json_ref, str): 

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

2633 already_visited = json_ref in json_refs 

2634 json_refs[json_ref] += 1 

2635 if already_visited: 

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

2637 try: 

2638 defs_ref = self.json_to_defs_refs[json_ref] 

2639 if defs_ref in self._core_defs_invalid_for_json_schema: 

2640 raise self._core_defs_invalid_for_json_schema[defs_ref] 

2641 _add_json_refs(self.definitions[defs_ref]) 

2642 except KeyError: 

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

2644 raise 

2645 

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

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

2648 # Skip examples that may contain arbitrary values and references 

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

2650 continue 

2651 _add_json_refs(v) 

2652 elif isinstance(schema, list): 

2653 for v in schema: 

2654 _add_json_refs(v) 

2655 

2656 _add_json_refs(json_schema) 

2657 return json_refs 

2658 

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

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

2661 

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

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

2664 message = self.render_warning_message(kind, detail) 

2665 if message is not None: 

2666 warnings.warn(message, PydanticJsonSchemaWarning) 

2667 

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

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

2670 

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

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

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

2674 

2675 Args: 

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

2677 

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

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

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

2681 

2682 Returns: 

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

2684 """ 

2685 if kind in self.ignored_warning_kinds: 

2686 return None 

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

2688 

2689 def _build_definitions_remapping(self) -> _DefinitionsRemapping: 

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

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

2692 for defs_ref in defs_refs: 

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

2694 defs_to_json[defs_ref] = json_ref 

2695 

2696 return _DefinitionsRemapping.from_prioritized_choices( 

2697 self._prioritized_defsref_choices, defs_to_json, self.definitions 

2698 ) 

2699 

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

2701 visited_defs_refs: set[DefsRef] = set() 

2702 unvisited_json_refs = _get_all_json_refs(schema) 

2703 while unvisited_json_refs: 

2704 next_json_ref = unvisited_json_refs.pop() 

2705 try: 

2706 next_defs_ref = self.json_to_defs_refs[next_json_ref] 

2707 if next_defs_ref in visited_defs_refs: 

2708 continue 

2709 visited_defs_refs.add(next_defs_ref) 

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

2711 except KeyError: 

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

2713 raise 

2714 

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

2716 

2717 

2718# ##### Start JSON Schema Generation Functions ##### 

2719 

2720 

2721def model_json_schema( 

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

2723 by_alias: bool = True, 

2724 ref_template: str = DEFAULT_REF_TEMPLATE, 

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

2726 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, 

2727 mode: JsonSchemaMode = 'validation', 

2728) -> dict[str, Any]: 

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

2730 

2731 Args: 

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

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

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

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

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

2737 

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

2739 keyword to combine schemas (the default). 

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

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

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

2743 `any_of`. 

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

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

2746 

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

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

2749 

2750 Returns: 

2751 The generated JSON Schema. 

2752 """ 

2753 from .main import BaseModel 

2754 

2755 schema_generator_instance = schema_generator( 

2756 by_alias=by_alias, ref_template=ref_template, union_format=union_format 

2757 ) 

2758 

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

2760 cls.__pydantic_core_schema__.rebuild() 

2761 

2762 if cls is BaseModel: 

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

2764 

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

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

2767 

2768 

2769def models_json_schema( 

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

2771 *, 

2772 by_alias: bool = True, 

2773 title: str | None = None, 

2774 description: str | None = None, 

2775 ref_template: str = DEFAULT_REF_TEMPLATE, 

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

2777 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, 

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

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

2780 

2781 Args: 

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

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

2784 title: The title of the generated JSON Schema. 

2785 description: The description of the generated JSON Schema. 

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

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

2788 

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

2790 keyword to combine schemas (the default). 

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

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

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

2794 `any_of`. 

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

2796 

2797 Returns: 

2798 A tuple where: 

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

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

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

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

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

2804 """ 

2805 for cls, _ in models: 

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

2807 cls.__pydantic_core_schema__.rebuild() 

2808 

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

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

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

2812 ] 

2813 json_schemas_map, definitions = instance.generate_definitions(inputs) 

2814 

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

2816 if definitions: 

2817 json_schema['$defs'] = definitions 

2818 if title: 

2819 json_schema['title'] = title 

2820 if description: 

2821 json_schema['description'] = description 

2822 

2823 return json_schemas_map, json_schema 

2824 

2825 

2826# ##### End JSON Schema Generation Functions ##### 

2827 

2828 

2829# Decimal JSON Schema pattern utilities. 

2830# Note: while we could use `\d` to represent digits (`Decimal` objects allow Unicode decimal digits), we use [0-9] instead: 

2831# - to reduce complexity (`\d` expands to a large set, and with `max_digits`/`decimal_places` constraints set, it can blow 

2832# the compile limit on some engines). 

2833# - [0-9] is a portable pattern (e.g. `\d` is equivalent to `[0-9]` in ECMA (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Character_class_escape#d) 

2834# while it allows any Unicode decimal digit in Python. 

2835 

2836 

2837def _repeat(atom: str, min_count: int, max_count: int) -> str: 

2838 """Build a regex repetition of `atom` between `min_count` and `max_count` times (inclusive), using the shortest quantifier.""" 

2839 if min_count > max_count or max_count == 0: 

2840 return '' 

2841 if min_count == max_count: 

2842 return atom if min_count == 1 else f'{atom}{{{min_count}}}' 

2843 if (min_count, max_count) == (0, 1): 

2844 return f'{atom}?' 

2845 return f'{atom}{{{min_count},{max_count}}}' 

2846 

2847 

2848def _int_range_pattern(max_value: int) -> str: 

2849 """Build a regex matching the integers between 1 and `max_value` (inclusive), without leading zeros.""" 

2850 digits = str(max_value) 

2851 length = len(digits) 

2852 alts: list[str] = [f'[1-9]{_repeat("[0-9]", k - 1, k - 1)}' for k in range(1, length)] 

2853 for i, char in enumerate(digits): 

2854 d = int(char) 

2855 low = 1 if i == 0 else 0 

2856 high = d if i == length - 1 else d - 1 

2857 if high >= low: 

2858 char_class = str(low) if low == high else f'[{low}-{high}]' 

2859 alts.append(f'{digits[:i]}{char_class}{_repeat("[0-9]", length - i - 1, length - i - 1)}') 

2860 return alts[0] if len(alts) == 1 else f'(?:{"|".join(alts)})' 

2861 

2862 

2863def _decimal_validation_pattern(max_digits: int | None, decimal_places: int | None, allow_inf_nan: bool) -> str: 

2864 """Build the decimal pattern for the `'validation'` mode.""" 

2865 

2866 number = r'(?:[0-9]+\.?[0-9]*|\.[0-9]+)' # e.g. 1, 1., .5 

2867 exponent = r'[eE][+-]?[0-9]+' # e.g. e5, E5, e-7 

2868 # e.g. Infinity, inf, NaN, sNaN, NaN123 (case-insensitive). Spelled out explicitly, as inline flags 

2869 # aren't supported by all regular expression engines: 

2870 non_finite = r'|[iI][nN][fF](?:[iI][nN][iI][tT][yY])?|[sS]?[nN][aA][nN][0-9]*' if allow_inf_nan else '' 

2871 if max_digits is None and decimal_places is None: 

2872 return rf'^[+-]?(?:{number}(?:{exponent})?{non_finite})$' 

2873 

2874 alts: list[str] = [] 

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

2876 # If `decimal_places > max_digits`, no whole digits are allowed and `max_digits` bounds the decimal places: 

2877 whole = max(0, max_digits - decimal_places) 

2878 places = min(max_digits, decimal_places) 

2879 int_part = f'0*{_repeat("[0-9]", 1, whole)}' if whole else '0+' 

2880 opt_int_part = f'0*{_repeat("[0-9]", 0, whole)}' 

2881 opt_frac_part = rf'\.{_repeat("[0-9]", 0, places)}0*' 

2882 frac_part = rf'\.{_repeat("[0-9]", 1, places)}0*' if places else r'\.0+' 

2883 alts += [f'{int_part}(?:{opt_frac_part})?', f'{opt_int_part}{frac_part}'] 

2884 elif max_digits is not None: 

2885 # The whole part and decimal part must have at most `max_digits` digits in total, 

2886 # so we enumerate the possible widths of the whole part: 

2887 int_part = f'0*{_repeat("[0-9]", 1, max_digits)}' if max_digits else '0+' 

2888 alts.append(rf'{int_part}(?:\.0*)?') 

2889 alts += [ 

2890 rf'0*{_repeat("[0-9]", whole, whole)}\.{_repeat("[0-9]", 1, max_digits - whole)}0*' 

2891 for whole in range(max_digits) 

2892 ] or [r'0*\.0+'] 

2893 else: 

2894 assert decimal_places is not None 

2895 opt_frac_part = rf'\.{_repeat("[0-9]", 0, decimal_places)}0*' 

2896 frac_part = rf'\.{_repeat("[0-9]", 1, decimal_places)}0*' if decimal_places else r'\.0+' 

2897 alts += [f'[0-9]+(?:{opt_frac_part})?', f'[0-9]*{frac_part}'] 

2898 

2899 alts.append(f'{number}{exponent}') 

2900 return rf'^[+-]?(?:{"|".join(alts)}{non_finite})$' 

2901 

2902 

2903def _decimal_serialization_pattern(max_digits: int | None, decimal_places: int | None, allow_inf_nan: bool) -> str: 

2904 """Build the decimal pattern for the `'serialization'` mode. 

2905 

2906 Decimals are serialized using `str()`, producing either: 

2907 

2908 - The plain notation (`'123.45'`), when the exponent is lower than or equal to 0 and the adjusted 

2909 exponent is greater than or equal to -6. 

2910 - The scientific notation otherwise (`'1E+3'`, `'1.5E-7'`, `'0E-10'`), with a single digit before 

2911 the optional decimal point and an always signed exponent. 

2912 

2913 Trailing zeros of the decimal part are preserved by `str()`, but aren't counted by the validation logic. 

2914 """ 

2915 

2916 def frac_part(places: int) -> str: 

2917 return rf'\.{_repeat("[0-9]", 1, places)}0*' if places else r'\.0+' 

2918 

2919 alts: list[str] = [] 

2920 

2921 # Plain notation: 

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

2923 whole = max(0, max_digits - decimal_places) 

2924 places = min(max_digits, decimal_places) 

2925 int_part = f'(?:0|[1-9]{_repeat("[0-9]", 0, whole - 1)})' if whole else '0' 

2926 alts.append(f'{int_part}(?:{frac_part(places)})?') 

2927 elif max_digits is not None: 

2928 # The whole part and decimal part must have at most `max_digits` digits in total, 

2929 # so we enumerate the possible widths of the whole part: 

2930 alts.append(rf'0(?:\.{_repeat("[0-9]", 1, max_digits)}0*)?') 

2931 alts += [ 

2932 f'[1-9]{_repeat("[0-9]", whole - 1, whole - 1)}(?:{frac_part(max_digits - whole)})?' 

2933 for whole in range(1, max_digits + 1) 

2934 ] 

2935 elif decimal_places is not None: 

2936 alts.append(f'(?:0|[1-9][0-9]*)(?:{frac_part(decimal_places)})?') 

2937 else: 

2938 alts.append(r'(?:0|[1-9][0-9]*)(?:\.[0-9]+)?') 

2939 

2940 # Zero with an exponent (e.g. `'0E-10'`), which normalizes to zero and thus satisfies any constraint: 

2941 alts.append(r'0E[+-][1-9][0-9]*') 

2942 

2943 # Scientific notation with a positive exponent `k` (e.g. `'1.5E+3'`): the value has `k + 1` whole digits 

2944 # and no decimal places: 

2945 if max_digits is None: 

2946 alts.append(r'[1-9](?:\.[0-9]+)?E\+[1-9][0-9]*') 

2947 else: 

2948 max_exponent = max_digits - (decimal_places or 0) - 1 

2949 if max_exponent >= 1: 

2950 alts.append(rf'[1-9](?:\.[0-9]+)?E\+{_int_range_pattern(max_exponent)}') 

2951 

2952 # Scientific notation with a negative exponent `k` (`k >= 7`, e.g. `'1.5E-7'`): the value has no whole digits, 

2953 # and `k` + (the number of significant decimal digits of the coefficient) decimal places: 

2954 if max_digits is None and decimal_places is None: 

2955 alts.append(r'[1-9](?:\.[0-9]+)?E-[1-9][0-9]*') 

2956 else: 

2957 max_places = min(c for c in (max_digits, decimal_places) if c is not None) 

2958 alts += [f'[1-9](?:{frac_part(max_places - k)})?E-{k}' for k in range(7, max_places + 1)] 

2959 

2960 if allow_inf_nan: 

2961 # `str()` produces `'Infinity'`, `'NaN'` and `'sNaN'` (with an optional payload): 

2962 alts += ['Infinity', 's?NaN[0-9]*'] 

2963 

2964 return rf'^-?(?:{"|".join(alts)})$' 

2965 

2966 

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

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

2969 

2970 

2971_HashableJsonValue: TypeAlias = ( 

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

2973) 

2974 

2975 

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

2977 if isinstance(value, dict): 

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

2979 elif isinstance(value, list): 

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

2981 else: 

2982 return value 

2983 

2984 

2985@dataclasses.dataclass(slots=True) 

2986class WithJsonSchema: 

2987 """!!! abstract "Usage Documentation" 

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

2989 

2990 An annotation used to override the JSON Schema for a type. 

2991 

2992 This is useful when you want to set a JSON Schema for a type that don't produce any JSON Schemas by default 

2993 (e.g. [`Callable`][collections.abc.Callable]). 

2994 

2995 If `mode` is set this will only apply to that schema generation mode, allowing you to set different JSON Schemas for validation and serialization. 

2996 

2997 !!! note 

2998 If the `WithJsonSchema` annotation is coupled with the [`Field()`][pydantic.Field] function, the behavior overriding will vary depending on the location: 

2999 

3000 * If the [`Annotated`][typing.Annotated] metadata is specified at the "top-level" field, `Field()` metadata arguments 

3001 (excluding [constraints](../concepts/fields.md#field-constraints)) such as `title` and `description` will be applied on 

3002 top of the `WithJsonSchema`, no matter the order: 

3003 

3004 ```python 

3005 from typing import Annotated 

3006 

3007 from pydantic import BaseModel, Field, WithJsonSchema 

3008 

3009 class Model(BaseModel): 

3010 field: Annotated[ 

3011 int, 

3012 Field(title='My Field'), 

3013 WithJsonSchema({'type': 'integer', 'extra': 'data'}), 

3014 ] 

3015 

3016 Model.model_json_schema()['properties']['field'] 

3017 #> {'type': 'integer', 'extra': 'data', 'title': 'My Field'} 

3018 ``` 

3019 

3020 * If the [`Annotated`][typing.Annotated] metadata is specified on a specific inner type, `WithJsonSchema` will unconditionally 

3021 override the JSON Schema: 

3022 

3023 ```python 

3024 from typing import Annotated 

3025 

3026 from pydantic import BaseModel, Field, WithJsonSchema 

3027 

3028 class Model(BaseModel): 

3029 field: list[ 

3030 Annotated[ 

3031 int, 

3032 Field(title='My Field'), 

3033 WithJsonSchema({'type': 'integer', 'extra': 'data'}), 

3034 ] 

3035 ] 

3036 

3037 Model.model_json_schema()['properties']['field'] 

3038 #> {'items': {'extra': 'data', 'type': 'integer'}, 'title': 'Field', 'type': 'array'} 

3039 ``` 

3040 

3041 See also the documentation about [the annotated pattern](../concepts/fields.md#the-annotated-pattern). 

3042 """ 

3043 

3044 json_schema: JsonSchemaValue | None 

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

3046 

3047 def __get_pydantic_json_schema__( 

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

3049 ) -> JsonSchemaValue: 

3050 if self.mode is not None and self.mode != handler.mode: 

3051 return handler(core_schema) 

3052 if self.json_schema is None: 

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

3054 raise PydanticOmit 

3055 else: 

3056 return self.json_schema.copy() 

3057 

3058 def __hash__(self) -> int: 

3059 return hash(self.mode) 

3060 

3061 

3062class Examples: 

3063 """Add examples to a JSON schema. 

3064 

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

3066 will be appended. 

3067 

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

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

3070 """ 

3071 

3072 @overload 

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

3074 def __init__( 

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

3076 ) -> None: ... 

3077 

3078 @overload 

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

3080 

3081 def __init__( 

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

3083 ) -> None: 

3084 if isinstance(examples, dict): 

3085 warnings.warn( 

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

3087 PydanticDeprecatedSince29, 

3088 stacklevel=2, 

3089 ) 

3090 self.examples = examples 

3091 self.mode = mode 

3092 

3093 def __get_pydantic_json_schema__( 

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

3095 ) -> JsonSchemaValue: 

3096 mode = self.mode or handler.mode 

3097 json_schema = handler(core_schema) 

3098 if mode != handler.mode: 

3099 return json_schema 

3100 examples = json_schema.get('examples') 

3101 if examples is None: 

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

3103 if isinstance(examples, dict): 

3104 if isinstance(self.examples, list): 

3105 warnings.warn( 

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

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

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

3109 UserWarning, 

3110 ) 

3111 json_schema['examples'] = to_jsonable_python( 

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

3113 ) 

3114 else: 

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

3116 if isinstance(examples, list): 

3117 if isinstance(self.examples, list): 

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

3119 elif isinstance(self.examples, dict): 

3120 warnings.warn( 

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

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

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

3124 UserWarning, 

3125 ) 

3126 json_schema['examples'] = to_jsonable_python( 

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

3128 ) 

3129 

3130 return json_schema 

3131 

3132 def __hash__(self) -> int: 

3133 return hash(self.mode) 

3134 

3135 

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

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

3138 refs: set[JsonRef] = set() 

3139 stack = [item] 

3140 

3141 while stack: 

3142 current = stack.pop() 

3143 if isinstance(current, dict): 

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

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

3146 # Skip examples that may contain arbitrary values and references 

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

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

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

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

3151 continue 

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

3153 refs.add(JsonRef(value)) 

3154 elif isinstance(value, dict): 

3155 stack.append(value) 

3156 elif isinstance(value, list): 

3157 stack.extend(value) 

3158 elif isinstance(current, list): 

3159 stack.extend(current) 

3160 

3161 return refs 

3162 

3163 

3164AnyType = TypeVar('AnyType') 

3165 

3166if TYPE_CHECKING: 

3167 SkipJsonSchema = Annotated[AnyType, ...] 

3168else: 

3169 

3170 @dataclasses.dataclass(slots=True) 

3171 class SkipJsonSchema: 

3172 """!!! abstract "Usage Documentation" 

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

3174 

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

3176 

3177 Example: 

3178 ```python 

3179 from pprint import pprint 

3180 

3181 from pydantic import BaseModel 

3182 from pydantic.json_schema import SkipJsonSchema 

3183 

3184 class Model(BaseModel): 

3185 a: int | None = None # (1)! 

3186 b: int | SkipJsonSchema[None] = None # (2)! 

3187 c: SkipJsonSchema[int | None] = None # (3)! 

3188 

3189 pprint(Model.model_json_schema()) 

3190 ''' 

3191 { 

3192 'properties': { 

3193 'a': { 

3194 'anyOf': [ 

3195 {'type': 'integer'}, 

3196 {'type': 'null'} 

3197 ], 

3198 'default': None, 

3199 'title': 'A' 

3200 }, 

3201 'b': { 

3202 'default': None, 

3203 'title': 'B', 

3204 'type': 'integer' 

3205 } 

3206 }, 

3207 'title': 'Model', 

3208 'type': 'object' 

3209 } 

3210 ''' 

3211 ``` 

3212 

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

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

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

3216 """ 

3217 

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

3219 return Annotated[item, cls()] 

3220 

3221 def __get_pydantic_json_schema__( 

3222 self, core_schema: CoreSchema, handler: GetJsonSchemaHandler 

3223 ) -> JsonSchemaValue: 

3224 raise PydanticOmit 

3225 

3226 def __hash__(self) -> int: 

3227 return hash(type(self)) 

3228 

3229 

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

3231 # If typed dictionary doesn't have config, the parent's config is used (as during core schema generation), 

3232 # hence a default of `None`: 

3233 if cls is not None: 

3234 try: 

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

3236 except AttributeError: 

3237 pass 

3238 return None 

3239 

3240 

3241def _get_ser_schema_for_default_value(schema: CoreSchema) -> core_schema.PlainSerializerFunctionSerSchema | None: 

3242 """Get a `'function-plain'` serialization schema that can be used to serialize a default value. 

3243 

3244 This takes into account having the serialization schema nested under validation schema(s). 

3245 """ 

3246 if ( 

3247 (ser_schema := schema.get('serialization')) 

3248 and ser_schema['type'] == 'function-plain' 

3249 and not ser_schema.get('info_arg') 

3250 ): 

3251 return cast('core_schema.PlainSerializerFunctionSerSchema', ser_schema) 

3252 if _core_utils.is_function_with_inner_schema(schema): 

3253 return _get_ser_schema_for_default_value(schema['schema'])