Ensure functions have docstrings for documentation
def evaluate_forwardref(
1import re2import warnings3from collections.abc import Sequence4from copy import copy5from dataclasses import dataclass, is_dataclass6from enum import Enum7from functools import lru_cache8from typing import (9 Annotated,10 Any,11 Literal,12 Union,13 cast,14 get_args,15 get_origin,16)1718from fastapi._compat import lenient_issubclass, shared19from fastapi.openapi.constants import REF_TEMPLATE20from fastapi.types import IncEx, ModelNameMap, UnionType21from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model22from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError23from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation24from pydantic import ValidationError as ValidationError25from pydantic._internal import _typing_extra as _pydantic_typing_extra26from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined]27 GetJsonSchemaHandler as GetJsonSchemaHandler,28)29from pydantic.fields import FieldInfo as FieldInfo30from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema31from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue32from pydantic_core import CoreSchema as CoreSchema33from pydantic_core import PydanticUndefined34from pydantic_core import Url as Url35from pydantic_core.core_schema import (36 with_info_plain_validator_function as with_info_plain_validator_function,37)3839RequiredParam = PydanticUndefined40Undefined = PydanticUndefined414243def evaluate_forwardref(44 value: Any,45 globalns: dict[str, Any] | None = None,46 localns: dict[str, Any] | None = None,47) -> Any:48 # eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530)49 try_eval_type = getattr(_pydantic_typing_extra, "try_eval_type", None)50 if try_eval_type is not None:51 return try_eval_type(value, globalns, localns)[0]52 return _pydantic_typing_extra.eval_type_lenient( # ty: ignore[deprecated]53 value, globalns, localns54 )555657class GenerateJsonSchema(_GenerateJsonSchema):58 # TODO: remove when this is merged (or equivalent): https://github.com/pydantic/pydantic/pull/1284159 # and dropping support for any version of Pydantic before that one (so, in a very long time)60 def bytes_schema(self, schema: CoreSchema) -> JsonSchemaValue:61 json_schema = {"type": "string", "contentMediaType": "application/octet-stream"}62 bytes_mode = (63 self._config.ser_json_bytes64 if self.mode == "serialization"65 else self._config.val_json_bytes66 )67 if bytes_mode == "base64":68 json_schema["contentEncoding"] = "base64"69 self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes)70 return json_schema717273# TODO: remove when dropping support for Pydantic < v2.12.374_Attrs = {75 "default": ...,76 "default_factory": None,77 "alias": None,78 "alias_priority": None,79 "validation_alias": None,80 "serialization_alias": None,81 "title": None,82 "field_title_generator": None,83 "description": None,84 "examples": None,85 "exclude": None,86 "exclude_if": None,87 "discriminator": None,88 "deprecated": None,89 "json_schema_extra": None,90 "frozen": None,91 "validate_default": None,92 "repr": True,93 "init": None,94 "init_var": None,95 "kw_only": None,96}979899# TODO: remove when dropping support for Pydantic < v2.12.3100def asdict(field_info: FieldInfo) -> dict[str, Any]:101 attributes = {}102 for attr in _Attrs:103 value = getattr(field_info, attr, Undefined)104 if value is not Undefined:105 attributes[attr] = value106 return {107 "annotation": field_info.annotation,108 "metadata": field_info.metadata,109 "attributes": attributes,110 }111112113@dataclass114class ModelField:115 field_info: FieldInfo116 name: str117 mode: Literal["validation", "serialization"] = "validation"118 config: ConfigDict | None = None119120 @property121 def alias(self) -> str:122 a = self.field_info.alias123 return a if a is not None else self.name124125 @property126 def validation_alias(self) -> str | None:127 va = self.field_info.validation_alias128 if isinstance(va, str) and va:129 return va130 return None131132 @property133 def serialization_alias(self) -> str | None:134 sa = self.field_info.serialization_alias135 return sa or None136137 @property138 def default(self) -> Any:139 return self.get_default()140141 def __post_init__(self) -> None:142 with warnings.catch_warnings():143 # Pydantic >= 2.12.0 warns about field specific metadata that is unused144 # (e.g. `TypeAdapter(Annotated[int, Field(alias='b')])`). In some cases, we145 # end up building the type adapter from a model field annotation so we146 # need to ignore the warning:147 if shared.PYDANTIC_VERSION_MINOR_TUPLE >= (2, 12):148 from pydantic.warnings import UnsupportedFieldAttributeWarning149150 warnings.simplefilter(151 "ignore", category=UnsupportedFieldAttributeWarning152 )153 # TODO: remove after setting the min Pydantic to v2.12.3154 # that adds asdict(), and use self.field_info.asdict() instead155 field_dict = asdict(self.field_info)156 annotated_args = (157 field_dict["annotation"],158 *field_dict["metadata"],159 # this FieldInfo needs to be created again so that it doesn't include160 # the old field info metadata and only the rest of the attributes161 Field(**field_dict["attributes"]),162 )163 self._type_adapter: TypeAdapter[Any] = TypeAdapter(164 Annotated[annotated_args], # ty: ignore[invalid-type-form]165 config=self.config,166 )167168 def get_default(self) -> Any:169 if self.field_info.is_required():170 return Undefined171 return self.field_info.get_default(call_default_factory=True)172173 def validate(174 self,175 value: Any,176 values: dict[str, Any] = {}, # noqa: B006177 *,178 loc: tuple[int | str, ...] = (),179 ) -> tuple[Any, list[dict[str, Any]]]:180 try:181 return (182 self._type_adapter.validate_python(value, from_attributes=True),183 [],184 )185 except ValidationError as exc:186 return None, _regenerate_error_with_loc(187 errors=exc.errors(include_url=False), loc_prefix=loc188 )189190 def serialize(191 self,192 value: Any,193 *,194 mode: Literal["json", "python"] = "json",195 include: IncEx | None = None,196 exclude: IncEx | None = None,197 by_alias: bool = True,198 exclude_unset: bool = False,199 exclude_defaults: bool = False,200 exclude_none: bool = False,201 ) -> Any:202 # What calls this code passes a value that already called203 # self._type_adapter.validate_python(value)204 return self._type_adapter.dump_python(205 value,206 mode=mode,207 include=include,208 exclude=exclude,209 by_alias=by_alias,210 exclude_unset=exclude_unset,211 exclude_defaults=exclude_defaults,212 exclude_none=exclude_none,213 )214215 def serialize_json(216 self,217 value: Any,218 *,219 include: IncEx | None = None,220 exclude: IncEx | None = None,221 by_alias: bool = True,222 exclude_unset: bool = False,223 exclude_defaults: bool = False,224 exclude_none: bool = False,225 ) -> bytes:226 # What calls this code passes a value that already called227 # self._type_adapter.validate_python(value)228 # This uses Pydantic's dump_json() which serializes directly to JSON229 # bytes in one pass (via Rust), avoiding the intermediate Python dict230 # step of dump_python(mode="json") + json.dumps().231 return self._type_adapter.dump_json(232 value,233 include=include,234 exclude=exclude,235 by_alias=by_alias,236 exclude_unset=exclude_unset,237 exclude_defaults=exclude_defaults,238 exclude_none=exclude_none,239 )240241 def __hash__(self) -> int:242 # Each ModelField is unique for our purposes, to allow making a dict from243 # ModelField to its JSON Schema.244 return id(self)245246247def _has_computed_fields(field: ModelField) -> bool:248 computed_fields = field._type_adapter.core_schema.get("schema", {}).get(249 "computed_fields", []250 )251 return len(computed_fields) > 0252253254def get_schema_from_model_field(255 *,256 field: ModelField,257 model_name_map: ModelNameMap,258 field_mapping: dict[259 tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue260 ],261 separate_input_output_schemas: bool = True,262) -> dict[str, Any]:263 override_mode: Literal["validation"] | None = (264 None265 if (separate_input_output_schemas or _has_computed_fields(field))266 else "validation"267 )268 field_alias = (269 (field.validation_alias or field.alias)270 if field.mode == "validation"271 else (field.serialization_alias or field.alias)272 )273274 # This expects that GenerateJsonSchema was already used to generate the definitions275 json_schema = field_mapping[(field, override_mode or field.mode)]276 if "$ref" not in json_schema:277 # TODO remove when deprecating Pydantic v1278 # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207279 json_schema["title"] = field.field_info.title or field_alias.title().replace(280 "_", " "281 )282 return json_schema283284285def get_definitions(286 *,287 fields: Sequence[ModelField],288 model_name_map: ModelNameMap,289 separate_input_output_schemas: bool = True,290) -> tuple[291 dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue],292 dict[str, dict[str, Any]],293]:294 schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)295 validation_fields = [field for field in fields if field.mode == "validation"]296 serialization_fields = [field for field in fields if field.mode == "serialization"]297 flat_validation_models = get_flat_models_from_fields(298 validation_fields, known_models=set()299 )300 flat_serialization_models = get_flat_models_from_fields(301 serialization_fields, known_models=set()302 )303 flat_validation_model_fields = [304 ModelField(305 field_info=FieldInfo(annotation=model),306 name=model.__name__,307 mode="validation",308 )309 for model in flat_validation_models310 ]311 flat_serialization_model_fields = [312 ModelField(313 field_info=FieldInfo(annotation=model),314 name=model.__name__,315 mode="serialization",316 )317 for model in flat_serialization_models318 ]319 flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields320 input_types = {f.field_info.annotation for f in fields}321 unique_flat_model_fields = {322 f for f in flat_model_fields if f.field_info.annotation not in input_types323 }324 inputs = [325 (326 field,327 (328 field.mode329 if (separate_input_output_schemas or _has_computed_fields(field))330 else "validation"331 ),332 field._type_adapter.core_schema,333 )334 for field in list(fields) + list(unique_flat_model_fields)335 ]336 field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs)337 for item_def in cast(dict[str, dict[str, Any]], definitions).values():338 if "description" in item_def:339 item_description = cast(str, item_def["description"]).split("\f")[0]340 item_def["description"] = item_description341 # definitions: dict[DefsRef, dict[str, Any]]342 # but mypy complains about general str in other places that are not declared as343 # DefsRef, although DefsRef is just str:344 # DefsRef = NewType('DefsRef', str)345 # So, a cast to simplify the types here346 return field_mapping, cast(dict[str, dict[str, Any]], definitions)347348349def is_scalar_field(field: ModelField) -> bool:350 from fastapi import params351352 return shared.field_annotation_is_scalar(353 field.field_info.annotation354 ) and not isinstance(field.field_info, params.Body)355356357def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:358 cls = type(field_info)359 merged_field_info = cls.from_annotation(annotation)360 new_field_info = copy(field_info)361 new_field_info.metadata = merged_field_info.metadata362 new_field_info.annotation = merged_field_info.annotation363 return new_field_info364365366def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:367 origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation368 if origin_type is Union or origin_type is UnionType: # Handle optional sequences369 union_args = get_args(field.field_info.annotation)370 for union_arg in union_args:371 if union_arg is type(None):372 continue373 origin_type = get_origin(union_arg) or union_arg374 break375 assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type] # ty: ignore[invalid-argument-type]376 return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index] # ty: ignore[invalid-return-type]377378379def get_missing_field_error(loc: tuple[int | str, ...]) -> dict[str, Any]:380 error = ValidationError.from_exception_data(381 "Field required", [{"type": "missing", "loc": loc, "input": {}}]382 ).errors(include_url=False)[0]383 error["input"] = None384 return error # type: ignore[return-value] # ty: ignore[invalid-return-type]385386387def create_body_model(388 *, fields: Sequence[ModelField], model_name: str389) -> type[BaseModel]:390 field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields}391 BodyModel: type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload] # ty: ignore[no-matching-overload]392 return BodyModel393394395def get_model_fields(model: type[BaseModel]) -> list[ModelField]:396 model_fields: list[ModelField] = []397 for name, field_info in model.model_fields.items():398 type_ = field_info.annotation399 if lenient_issubclass(type_, (BaseModel, dict)) or is_dataclass(type_):400 model_config = None401 else:402 model_config = model.model_config403 model_fields.append(404 ModelField(405 field_info=field_info,406 name=name,407 config=model_config,408 )409 )410 return model_fields411412413@lru_cache414def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]:415 return get_model_fields(model)416417418# Duplicate of several schema functions from Pydantic v1 to make them compatible with419# Pydantic v2 and allow mixing the models420421TypeModelOrEnum = type["BaseModel"] | type[Enum]422TypeModelSet = set[TypeModelOrEnum]423424425def normalize_name(name: str) -> str:426 return re.sub(r"[^a-zA-Z0-9.\-_]", "_", name)427428429def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str]:430 name_model_map = {}431 for model in unique_models:432 model_name = normalize_name(model.__name__)433 name_model_map[model_name] = model434 return {v: k for k, v in name_model_map.items()}435436437def get_flat_models_from_model(438 model: type["BaseModel"], known_models: TypeModelSet | None = None439) -> TypeModelSet:440 known_models = known_models or set()441 fields = get_model_fields(model)442 get_flat_models_from_fields(fields, known_models=known_models)443 return known_models444445446def get_flat_models_from_annotation(447 annotation: Any, known_models: TypeModelSet448) -> TypeModelSet:449 origin = get_origin(annotation)450 if origin is not None:451 for arg in get_args(annotation):452 if lenient_issubclass(arg, (BaseModel, Enum)):453 if arg not in known_models:454 known_models.add(arg) # type: ignore[arg-type]455 if lenient_issubclass(arg, BaseModel):456 get_flat_models_from_model(arg, known_models=known_models)457 else:458 get_flat_models_from_annotation(arg, known_models=known_models)459 return known_models460461462def get_flat_models_from_field(463 field: ModelField, known_models: TypeModelSet464) -> TypeModelSet:465 field_type = field.field_info.annotation466 if lenient_issubclass(field_type, BaseModel):467 if field_type in known_models:468 return known_models469 known_models.add(field_type)470 get_flat_models_from_model(field_type, known_models=known_models)471 elif lenient_issubclass(field_type, Enum):472 known_models.add(field_type)473 else:474 get_flat_models_from_annotation(field_type, known_models=known_models)475 return known_models476477478def get_flat_models_from_fields(479 fields: Sequence[ModelField], known_models: TypeModelSet480) -> TypeModelSet:481 for field in fields:482 get_flat_models_from_field(field, known_models=known_models)483 return known_models484485486def _regenerate_error_with_loc(487 *, errors: Sequence[Any], loc_prefix: tuple[str | int, ...]488) -> list[dict[str, Any]]:489 updated_loc_errors: list[Any] = [490 {**err, "loc": loc_prefix + err.get("loc", ())} for err in errors491 ]492493 return updated_loc_errors
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.