Overuse may indicate design issues; consider polymorphism
if not inspect.isclass(cls) or isinstance(cls, GenericAlias):
1"""Utilities for pydantic."""23from __future__ import annotations45import inspect6import textwrap7import warnings8from contextlib import nullcontext9from functools import lru_cache, wraps10from types import GenericAlias11from typing import (12 TYPE_CHECKING,13 Any,14 TypeVar,15 cast,16 overload,17)1819import pydantic20from packaging import version2122# root_validator is deprecated but we need it for backward compatibility of @pre_init23from pydantic import ( # type: ignore[deprecated]24 BaseModel,25 ConfigDict,26 Field,27 PydanticDeprecationWarning,28 RootModel,29 root_validator,30)31from pydantic import (32 create_model as _create_model_base,33)34from pydantic.fields import FieldInfo as FieldInfoV235from pydantic.json_schema import (36 DEFAULT_REF_TEMPLATE,37 GenerateJsonSchema,38 JsonSchemaMode,39 JsonSchemaValue,40)41from pydantic.v1 import BaseModel as BaseModelV142from pydantic.v1 import create_model as create_model_v143from typing_extensions import deprecated, override4445if TYPE_CHECKING:46 from collections.abc import Callable4748 from pydantic.v1.fields import ModelField49 from pydantic_core import core_schema5051PYDANTIC_VERSION = version.parse(pydantic.__version__)525354@deprecated("Use PYDANTIC_VERSION.major instead.")55def get_pydantic_major_version() -> int:56 """DEPRECATED - Get the major version of Pydantic.5758 Use `PYDANTIC_VERSION.major` instead.5960 Returns:61 The major version of Pydantic.62 """63 return PYDANTIC_VERSION.major646566PYDANTIC_MAJOR_VERSION = PYDANTIC_VERSION.major67PYDANTIC_MINOR_VERSION = PYDANTIC_VERSION.minor6869IS_PYDANTIC_V1 = False70IS_PYDANTIC_V2 = True7172PydanticBaseModel = BaseModel73TypeBaseModel = type[BaseModel]7475TBaseModel = TypeVar("TBaseModel", bound=PydanticBaseModel)767778def is_pydantic_v1_subclass(cls: type) -> bool:79 """Check if the given class is Pydantic v1-like.8081 Returns:82 `True` if the given class is a subclass of Pydantic `BaseModel` 1.x.83 """84 return issubclass(cls, BaseModelV1)858687def is_pydantic_v2_subclass(cls: type) -> bool:88 """Check if the given class is Pydantic v2-like.8990 Returns:91 `True` if the given class is a subclass of Pydantic `BaseModel` 2.x.92 """93 return issubclass(cls, BaseModel)949596def is_basemodel_subclass(cls: type) -> bool:97 """Check if the given class is a subclass of Pydantic `BaseModel`.9899 Check if the given class is a subclass of any of the following:100101 * `pydantic.BaseModel` in Pydantic 2.x102 * `pydantic.v1.BaseModel` in Pydantic 2.x103104 Returns:105 `True` if the given class is a subclass of Pydantic `BaseModel`.106 """107 # Before we can use issubclass on the cls we need to check if it is a class108 if not inspect.isclass(cls) or isinstance(cls, GenericAlias):109 return False110111 return issubclass(cls, (BaseModel, BaseModelV1))112113114def is_basemodel_instance(obj: Any) -> bool:115 """Check if the given class is an instance of Pydantic `BaseModel`.116117 Check if the given class is an instance of any of the following:118119 * `pydantic.BaseModel` in Pydantic 2.x120 * `pydantic.v1.BaseModel` in Pydantic 2.x121122 Returns:123 `True` if the given class is an instance of Pydantic `BaseModel`.124 """125 return isinstance(obj, (BaseModel, BaseModelV1))126127128# How to type hint this?129def pre_init(func: Callable) -> Any:130 """Decorator to run a function before model initialization.131132 Args:133 func: The function to run before model initialization.134135 Returns:136 The decorated function.137 """138 with warnings.catch_warnings():139 warnings.filterwarnings(action="ignore", category=PydanticDeprecationWarning)140141 # Ideally we would use @model_validator(mode="before") but this would change the142 # order of the validators. See https://github.com/pydantic/pydantic/discussions/7434.143 # So we keep root_validator for backward compatibility.144 @root_validator(pre=True) # type: ignore[deprecated]145 @wraps(func)146 def wrapper(cls: type[BaseModel], values: dict[str, Any]) -> Any:147 """Decorator to run a function before model initialization.148149 Args:150 cls: The model class.151 values: The values to initialize the model with.152153 Returns:154 The values to initialize the model with.155 """156 # Insert default values157 fields = cls.model_fields158 for name, field_info in fields.items():159 # Check if allow_population_by_field_name is enabled160 # If yes, then set the field name to the alias161 if (162 hasattr(cls, "Config")163 and hasattr(cls.Config, "allow_population_by_field_name")164 and cls.Config.allow_population_by_field_name165 and field_info.alias in values166 ):167 values[name] = values.pop(field_info.alias)168 if (169 hasattr(cls, "model_config")170 and cls.model_config.get("populate_by_name")171 and field_info.alias in values172 ):173 values[name] = values.pop(field_info.alias)174175 if (176 name not in values or values[name] is None177 ) and not field_info.is_required():178 if field_info.default_factory is not None:179 values[name] = field_info.default_factory() # type: ignore[call-arg]180 else:181 values[name] = field_info.default182183 # Call the decorated function184 return func(cls, values)185186 return wrapper187188189class _IgnoreUnserializable(GenerateJsonSchema):190 """A JSON schema generator that ignores unknown types.191192 https://docs.pydantic.dev/latest/concepts/json_schema/#customizing-the-json-schema-generation-process193 """194195 @override196 def handle_invalid_for_json_schema(197 self, schema: core_schema.CoreSchema, error_info: str198 ) -> JsonSchemaValue:199 return {}200201202def _create_subset_model_v1(203 name: str,204 model: type[BaseModelV1],205 field_names: list,206 *,207 descriptions: dict | None = None,208 fn_description: str | None = None,209) -> type[BaseModelV1]:210 """Create a Pydantic model with only a subset of model's fields."""211 fields = {}212213 for field_name in field_names:214 # Using pydantic v1 so can access __fields__ as a dict.215 field = model.__fields__[field_name]216 t = (217 # this isn't perfect but should work for most functions218 field.outer_type_219 if field.required and not field.allow_none220 else field.outer_type_ | None221 )222 if descriptions and field_name in descriptions:223 field.field_info.description = descriptions[field_name]224 fields[field_name] = (t, field.field_info)225226 rtn = cast("type[BaseModelV1]", create_model_v1(name, **fields)) # type: ignore[call-overload]227 rtn.__doc__ = textwrap.dedent(fn_description or model.__doc__ or "")228 return rtn229230231def _create_subset_model_v2(232 name: str,233 model: type[BaseModel],234 field_names: list[str],235 *,236 descriptions: dict | None = None,237 fn_description: str | None = None,238) -> type[BaseModel]:239 """Create a Pydantic model with a subset of the model fields."""240 descriptions_ = descriptions or {}241 fields = {}242 for field_name in field_names:243 field = model.model_fields[field_name]244 description = descriptions_.get(field_name, field.description)245 field_kwargs: dict[str, Any] = {"description": description}246 if field.default_factory is not None:247 field_kwargs["default_factory"] = field.default_factory248 else:249 field_kwargs["default"] = field.default250 field_info = FieldInfoV2(**field_kwargs)251 if field.metadata:252 field_info.metadata = field.metadata253 fields[field_name] = (field.annotation, field_info)254255 rtn = cast(256 "type[BaseModel]",257 _create_model_base( # type: ignore[call-overload]258 name, **fields, __config__=ConfigDict(arbitrary_types_allowed=True)259 ),260 )261262 # TODO(0.3): Determine if there is a more "pydantic" way to preserve annotations.263 # This is done to preserve __annotations__ when working with pydantic 2.x264 # and using the Annotated type with TypedDict.265 # Comment out the following line, to trigger the relevant test case.266 selected_annotations = [267 (name, annotation)268 for name, annotation in model.__annotations__.items()269 if name in field_names270 ]271272 rtn.__annotations__ = dict(selected_annotations)273 rtn.__doc__ = textwrap.dedent(fn_description or model.__doc__ or "")274 return rtn275276277# Private functionality to create a subset model that's compatible across278# different versions of pydantic.279# Handles pydantic versions 2.x. including v1 of pydantic in 2.x.280# However, can't find a way to type hint this.281def _create_subset_model(282 name: str,283 model: TypeBaseModel,284 field_names: list[str],285 *,286 descriptions: dict | None = None,287 fn_description: str | None = None,288) -> type[BaseModel]:289 """Create subset model using the same pydantic version as the input model.290291 Returns:292 The created subset model.293 """294 if issubclass(model, BaseModelV1):295 return _create_subset_model_v1(296 name,297 model,298 field_names,299 descriptions=descriptions,300 fn_description=fn_description,301 )302 return _create_subset_model_v2(303 name,304 model,305 field_names,306 descriptions=descriptions,307 fn_description=fn_description,308 )309310311@overload312def get_fields(model: type[BaseModel]) -> dict[str, FieldInfoV2]: ...313314315@overload316def get_fields(model: BaseModel) -> dict[str, FieldInfoV2]: ...317318319@overload320def get_fields(model: type[BaseModelV1]) -> dict[str, ModelField]: ...321322323@overload324def get_fields(model: BaseModelV1) -> dict[str, ModelField]: ...325326327def get_fields(328 model: type[BaseModel | BaseModelV1] | BaseModel | BaseModelV1,329) -> dict[str, FieldInfoV2] | dict[str, ModelField]:330 """Return the field names of a Pydantic model.331332 Args:333 model: The Pydantic model or instance.334335 Raises:336 TypeError: If the model is not a Pydantic model.337 """338 if not isinstance(model, type):339 model = type(model)340 if issubclass(model, BaseModel):341 return model.model_fields342 if issubclass(model, BaseModelV1):343 return model.__fields__344 msg = f"Expected a Pydantic model. Got {model}"345 raise TypeError(msg)346347348_SchemaConfig = ConfigDict(349 arbitrary_types_allowed=True, frozen=True, protected_namespaces=()350)351352NO_DEFAULT = object()353354355def _create_root_model(356 name: str,357 type_: Any,358 module_name: str | None = None,359 default_: object = NO_DEFAULT,360) -> type[BaseModel]:361 """Create a base class."""362363 def schema(364 cls: type[BaseModelV1],365 by_alias: bool = True, # noqa: FBT001,FBT002366 ref_template: str = DEFAULT_REF_TEMPLATE,367 ) -> dict[str, Any]:368 super_cls = cast("type[BaseModelV1]", super(cls, cls))369 schema_ = super_cls.schema(by_alias=by_alias, ref_template=ref_template)370 schema_["title"] = name371 return schema_372373 def model_json_schema(374 cls: type[BaseModel],375 by_alias: bool = True, # noqa: FBT001,FBT002376 ref_template: str = DEFAULT_REF_TEMPLATE,377 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,378 mode: JsonSchemaMode = "validation",379 ) -> dict[str, Any]:380 super_cls = cast("type[BaseModel]", super(cls, cls))381 schema_ = super_cls.model_json_schema(382 by_alias=by_alias,383 ref_template=ref_template,384 schema_generator=schema_generator,385 mode=mode,386 )387 schema_["title"] = name388 return schema_389390 base_class_attributes = {391 "__annotations__": {"root": type_},392 "model_config": ConfigDict(arbitrary_types_allowed=True),393 "schema": classmethod(schema),394 "model_json_schema": classmethod(model_json_schema),395 "__module__": module_name or "langchain_core.runnables.utils",396 }397398 if default_ is not NO_DEFAULT:399 base_class_attributes["root"] = default_400 with warnings.catch_warnings():401 try:402 if (403 isinstance(type_, type)404 and not isinstance(type_, GenericAlias)405 and issubclass(type_, BaseModelV1)406 ):407 warnings.filterwarnings(408 action="ignore", category=PydanticDeprecationWarning409 )410 except TypeError:411 pass412 custom_root_type = type(name, (RootModel,), base_class_attributes)413 return cast("type[BaseModel]", custom_root_type)414415416@lru_cache(maxsize=256)417def _create_root_model_cached(418 model_name: str,419 type_: Any,420 *,421 module_name: str | None = None,422 default_: object = NO_DEFAULT,423) -> type[BaseModel]:424 return _create_root_model(425 model_name, type_, default_=default_, module_name=module_name426 )427428429@lru_cache(maxsize=256)430def _create_model_cached(431 model_name: str,432 /,433 **field_definitions: Any,434) -> type[BaseModel]:435 return _create_model_base(436 model_name,437 __config__=_SchemaConfig,438 **_remap_field_definitions(field_definitions),439 )440441442def create_model(443 model_name: str,444 module_name: str | None = None,445 /,446 **field_definitions: Any,447) -> type[BaseModel]:448 """Create a Pydantic model with the given field definitions.449450 Please use `create_model_v2` instead of this function.451452 Args:453 model_name: The name of the model.454 module_name: The name of the module where the model is defined.455456 This is used by Pydantic to resolve any forward references.457 **field_definitions: The field definitions for the model.458459 Returns:460 The created model.461 """462 kwargs = {}463 if "__root__" in field_definitions:464 kwargs["root"] = field_definitions.pop("__root__")465466 return create_model_v2(467 model_name,468 module_name=module_name,469 field_definitions=field_definitions,470 **kwargs,471 )472473474# Reserved names should capture all the `public` names / methods that are475# used by BaseModel internally. This will keep the reserved names up-to-date.476# For reference, the reserved names are:477# "construct", "copy", "dict", "from_orm", "json", "parse_file", "parse_obj",478# "parse_raw", "schema", "schema_json", "update_forward_refs", "validate",479# "model_computed_fields", "model_config", "model_construct", "model_copy",480# "model_dump", "model_dump_json", "model_extra", "model_fields",481# "model_fields_set", "model_json_schema", "model_parametrized_name",482# "model_post_init", "model_rebuild", "model_validate", "model_validate_json",483# "model_validate_strings"484_RESERVED_NAMES = {key for key in dir(BaseModel) if not key.startswith("_")}485486487def _remap_field_definitions(field_definitions: dict[str, Any]) -> dict[str, Any]:488 """This remaps fields to avoid colliding with internal pydantic fields."""489 remapped = {}490 for key, value in field_definitions.items():491 if key.startswith("_") or key in _RESERVED_NAMES:492 # Let's add a prefix to avoid colliding with internal pydantic fields493 if isinstance(value, FieldInfoV2):494 msg = (495 f"Remapping for fields starting with '_' or fields with a name "496 f"matching a reserved name {_RESERVED_NAMES} is not supported if "497 f" the field is a pydantic Field instance. Got {key}."498 )499 raise NotImplementedError(msg)500 type_, default_ = value501 remapped[f"private_{key}"] = (502 type_,503 Field(504 default=default_,505 alias=key,506 serialization_alias=key,507 title=key.lstrip("_").replace("_", " ").title(),508 ),509 )510 else:511 remapped[key] = value512 return remapped513514515def create_model_v2(516 model_name: str,517 *,518 module_name: str | None = None,519 field_definitions: dict[str, Any] | None = None,520 root: Any | None = None,521) -> type[BaseModel]:522 """Create a Pydantic model with the given field definitions.523524 !!! warning525526 Do not use outside of langchain packages. This API is subject to change at any527 time.528529 Args:530 model_name: The name of the model.531 module_name: The name of the module where the model is defined.532533 This is used by Pydantic to resolve any forward references.534 field_definitions: The field definitions for the model.535 root: Type for a root model (`RootModel`)536537 Returns:538 The created model.539 """540 field_definitions = field_definitions or {}541542 if root:543 if field_definitions:544 msg = (545 "When specifying __root__ no other "546 f"fields should be provided. Got {field_definitions}"547 )548 raise NotImplementedError(msg)549550 if isinstance(root, tuple):551 kwargs = {"type_": root[0], "default_": root[1]}552 else:553 kwargs = {"type_": root}554555 try:556 named_root_model = _create_root_model_cached(557 model_name, module_name=module_name, **kwargs558 )559 except TypeError:560 # something in the arguments into _create_root_model_cached is not hashable561 named_root_model = _create_root_model(562 model_name,563 module_name=module_name,564 **kwargs,565 )566 return named_root_model567568 # No root, just field definitions569 names = set(field_definitions.keys())570571 capture_warnings = False572573 for name in names:574 # Also if any non-reserved name is used (e.g., model_id or model_name)575 if name.startswith("model"):576 capture_warnings = True577578 with warnings.catch_warnings() if capture_warnings else nullcontext():579 if capture_warnings:580 warnings.filterwarnings(action="ignore")581 try:582 return _create_model_cached(model_name, **field_definitions)583 except TypeError:584 # something in field definitions is not hashable585 return _create_model_base(586 model_name,587 __config__=_SchemaConfig,588 **_remap_field_definitions(field_definitions),589 )
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.