"""Utilities for pydantic."""from__future__importannotationsimportinspectimporttextwrapimportwarningsfromcontextlibimportnullcontextfromfunctoolsimportlru_cache,wrapsfromtypesimportGenericAliasfromtypingimport(TYPE_CHECKING,Any,Callable,Optional,TypeVar,Union,cast,overload,)importpydanticfrompydanticimport(BaseModel,ConfigDict,PydanticDeprecationWarning,RootModel,root_validator,)frompydanticimport(create_modelas_create_model_base,)frompydantic.fieldsimportFieldInfoasFieldInfoV2frompydantic.json_schemaimport(DEFAULT_REF_TEMPLATE,GenerateJsonSchema,JsonSchemaMode,JsonSchemaValue,)ifTYPE_CHECKING:frompydantic_coreimportcore_schema
[docs]defget_pydantic_major_version()->int:"""Get the major version of Pydantic."""try:importpydanticreturnint(pydantic.__version__.split(".")[0])exceptImportError:return0
def_get_pydantic_minor_version()->int:"""Get the minor version of Pydantic."""try:importpydanticreturnint(pydantic.__version__.split(".")[1])exceptImportError:return0PYDANTIC_MAJOR_VERSION=get_pydantic_major_version()PYDANTIC_MINOR_VERSION=_get_pydantic_minor_version()ifPYDANTIC_MAJOR_VERSION==1:frompydantic.fieldsimportFieldInfoasFieldInfoV1PydanticBaseModel=pydantic.BaseModelTypeBaseModel=type[BaseModel]elifPYDANTIC_MAJOR_VERSION==2:frompydantic.v1.fieldsimportFieldInfoasFieldInfoV1# type: ignore[assignment]# Union type needs to be last assignment to PydanticBaseModel to make mypy happy.PydanticBaseModel=Union[BaseModel,pydantic.BaseModel]# type: ignore[assignment,misc]TypeBaseModel=Union[type[BaseModel],type[pydantic.BaseModel]]# type: ignore[misc]else:msg=f"Unsupported Pydantic version: {PYDANTIC_MAJOR_VERSION}"raiseValueError(msg)TBaseModel=TypeVar("TBaseModel",bound=PydanticBaseModel)
[docs]defis_pydantic_v1_subclass(cls:type)->bool:"""Check if the installed Pydantic version is 1.x-like."""ifPYDANTIC_MAJOR_VERSION==1:returnTrueelifPYDANTIC_MAJOR_VERSION==2:frompydantic.v1importBaseModelasBaseModelV1ifissubclass(cls,BaseModelV1):returnTruereturnFalse
[docs]defis_pydantic_v2_subclass(cls:type)->bool:"""Check if the installed Pydantic version is 1.x-like."""frompydanticimportBaseModelreturnPYDANTIC_MAJOR_VERSION==2andissubclass(cls,BaseModel)
[docs]defis_basemodel_subclass(cls:type)->bool:"""Check if the given class is a subclass of Pydantic BaseModel. Check if the given class is a subclass of any of the following: * pydantic.BaseModel in Pydantic 1.x * pydantic.BaseModel in Pydantic 2.x * pydantic.v1.BaseModel in Pydantic 2.x """# Before we can use issubclass on the cls we need to check if it is a classifnotinspect.isclass(cls)orisinstance(cls,GenericAlias):returnFalseifPYDANTIC_MAJOR_VERSION==1:frompydanticimportBaseModelasBaseModelV1Properifissubclass(cls,BaseModelV1Proper):returnTrueelifPYDANTIC_MAJOR_VERSION==2:frompydanticimportBaseModelasBaseModelV2frompydantic.v1importBaseModelasBaseModelV1ifissubclass(cls,BaseModelV2):returnTrueifissubclass(cls,BaseModelV1):returnTrueelse:msg=f"Unsupported Pydantic version: {PYDANTIC_MAJOR_VERSION}"raiseValueError(msg)returnFalse
[docs]defis_basemodel_instance(obj:Any)->bool:"""Check if the given class is an instance of Pydantic BaseModel. Check if the given class is an instance of any of the following: * pydantic.BaseModel in Pydantic 1.x * pydantic.BaseModel in Pydantic 2.x * pydantic.v1.BaseModel in Pydantic 2.x """ifPYDANTIC_MAJOR_VERSION==1:frompydanticimportBaseModelasBaseModelV1Properifisinstance(obj,BaseModelV1Proper):returnTrueelifPYDANTIC_MAJOR_VERSION==2:frompydanticimportBaseModelasBaseModelV2frompydantic.v1importBaseModelasBaseModelV1ifisinstance(obj,BaseModelV2):returnTrueifisinstance(obj,BaseModelV1):returnTrueelse:msg=f"Unsupported Pydantic version: {PYDANTIC_MAJOR_VERSION}"raiseValueError(msg)returnFalse
# How to type hint this?
[docs]defpre_init(func:Callable)->Any:"""Decorator to run a function before model initialization. Args: func (Callable): The function to run before model initialization. Returns: Any: The decorated function. """withwarnings.catch_warnings():warnings.filterwarnings(action="ignore",category=PydanticDeprecationWarning)@root_validator(pre=True)@wraps(func)defwrapper(cls:type[BaseModel],values:dict[str,Any])->dict[str,Any]:"""Decorator to run a function before model initialization. Args: cls (Type[BaseModel]): The model class. values (Dict[str, Any]): The values to initialize the model with. Returns: Dict[str, Any]: The values to initialize the model with. """# Insert default valuesfields=cls.model_fieldsforname,field_infoinfields.items():# Check if allow_population_by_field_name is enabled# If yes, then set the field name to the aliasif(hasattr(cls,"Config")andhasattr(cls.Config,"allow_population_by_field_name")andcls.Config.allow_population_by_field_nameandfield_info.aliasinvalues):values[name]=values.pop(field_info.alias)if(hasattr(cls,"model_config")andcls.model_config.get("populate_by_name")andfield_info.aliasinvalues):values[name]=values.pop(field_info.alias)if(namenotinvaluesorvalues[name]isNone)andnotfield_info.is_required():iffield_info.default_factoryisnotNone:values[name]=field_info.default_factory()# type: ignoreelse:values[name]=field_info.default# Call the decorated functionreturnfunc(cls,values)returnwrapper
class_IgnoreUnserializable(GenerateJsonSchema):"""A JSON schema generator that ignores unknown types. https://docs.pydantic.dev/latest/concepts/json_schema/#customizing-the-json-schema-generation-process """defhandle_invalid_for_json_schema(self,schema:core_schema.CoreSchema,error_info:str)->JsonSchemaValue:return{}def_create_subset_model_v1(name:str,model:type[BaseModel],field_names:list,*,descriptions:Optional[dict]=None,fn_description:Optional[str]=None,)->type[BaseModel]:"""Create a pydantic model with only a subset of model's fields."""ifPYDANTIC_MAJOR_VERSION==1:frompydanticimportcreate_modelelifPYDANTIC_MAJOR_VERSION==2:frompydantic.v1importcreate_model# type: ignoreelse:msg=f"Unsupported pydantic version: {PYDANTIC_MAJOR_VERSION}"raiseNotImplementedError(msg)fields={}forfield_nameinfield_names:# Using pydantic v1 so can access __fields__ as a dict.field=model.__fields__[field_name]# type: ignoret=(# this isn't perfect but should work for most functionsfield.outer_type_iffield.requiredandnotfield.allow_noneelseOptional[field.outer_type_])ifdescriptionsandfield_nameindescriptions:field.field_info.description=descriptions[field_name]fields[field_name]=(t,field.field_info)rtn=create_model(name,**fields)# type: ignorertn.__doc__=textwrap.dedent(fn_descriptionormodel.__doc__or"")returnrtndef_create_subset_model_v2(name:str,model:type[pydantic.BaseModel],field_names:list[str],*,descriptions:Optional[dict]=None,fn_description:Optional[str]=None,)->type[pydantic.BaseModel]:"""Create a pydantic model with a subset of the model fields."""frompydanticimportcreate_modelfrompydantic.fieldsimportFieldInfodescriptions_=descriptionsor{}fields={}forfield_nameinfield_names:field=model.model_fields[field_name]# type: ignoredescription=descriptions_.get(field_name,field.description)field_info=FieldInfo(description=description,default=field.default)iffield.metadata:field_info.metadata=field.metadatafields[field_name]=(field.annotation,field_info)rtn=create_model(# type: ignorename,**fields,__config__=ConfigDict(arbitrary_types_allowed=True))# TODO(0.3): Determine if there is a more "pydantic" way to preserve annotations.# This is done to preserve __annotations__ when working with pydantic 2.x# and using the Annotated type with TypedDict.# Comment out the following line, to trigger the relevant test case.selected_annotations=[(name,annotation)forname,annotationinmodel.__annotations__.items()ifnameinfield_names]rtn.__annotations__=dict(selected_annotations)rtn.__doc__=textwrap.dedent(fn_descriptionormodel.__doc__or"")returnrtn# Private functionality to create a subset model that's compatible across# different versions of pydantic.# Handles pydantic versions 1.x and 2.x. including v1 of pydantic in 2.x.# However, can't find a way to type hint this.def_create_subset_model(name:str,model:TypeBaseModel,field_names:list[str],*,descriptions:Optional[dict]=None,fn_description:Optional[str]=None,)->type[BaseModel]:"""Create subset model using the same pydantic version as the input model."""ifPYDANTIC_MAJOR_VERSION==1:return_create_subset_model_v1(name,model,field_names,descriptions=descriptions,fn_description=fn_description,)elifPYDANTIC_MAJOR_VERSION==2:frompydantic.v1importBaseModelasBaseModelV1ifissubclass(model,BaseModelV1):return_create_subset_model_v1(name,model,field_names,descriptions=descriptions,fn_description=fn_description,)else:return_create_subset_model_v2(name,model,field_names,descriptions=descriptions,fn_description=fn_description,)else:msg=f"Unsupported pydantic version: {PYDANTIC_MAJOR_VERSION}"raiseNotImplementedError(msg)ifPYDANTIC_MAJOR_VERSION==2:frompydanticimportBaseModelasBaseModelV2frompydantic.v1importBaseModelasBaseModelV1@overloaddefget_fields(model:type[BaseModelV2])->dict[str,FieldInfoV2]:...@overloaddefget_fields(model:BaseModelV2)->dict[str,FieldInfoV2]:...@overloaddefget_fields(model:type[BaseModelV1])->dict[str,FieldInfoV1]:...@overloaddefget_fields(model:BaseModelV1)->dict[str,FieldInfoV1]:...defget_fields(model:Union[BaseModelV2,BaseModelV1,type[BaseModelV2],type[BaseModelV1],],)->Union[dict[str,FieldInfoV2],dict[str,FieldInfoV1]]:"""Get the field names of a Pydantic model."""ifhasattr(model,"model_fields"):returnmodel.model_fields# type: ignoreelifhasattr(model,"__fields__"):returnmodel.__fields__# type: ignoreelse:msg=f"Expected a Pydantic model. Got {type(model)}"raiseTypeError(msg)elifPYDANTIC_MAJOR_VERSION==1:frompydanticimportBaseModelasBaseModelV1_
[docs]defget_fields(# type: ignore[no-redef]model:Union[type[BaseModelV1_],BaseModelV1_],)->dict[str,FieldInfoV1]:"""Get the field names of a Pydantic model."""returnmodel.__fields__# type: ignore
else:msg=f"Unsupported Pydantic version: {PYDANTIC_MAJOR_VERSION}"raiseValueError(msg)_SchemaConfig=ConfigDict(arbitrary_types_allowed=True,frozen=True,protected_namespaces=())NO_DEFAULT=object()def_create_root_model(name:str,type_:Any,module_name:Optional[str]=None,default_:object=NO_DEFAULT,)->type[BaseModel]:"""Create a base class."""defschema(cls:type[BaseModel],by_alias:bool=True,ref_template:str=DEFAULT_REF_TEMPLATE,)->dict[str,Any]:# Complains about schema not being defined in superclassschema_=super(cls,cls).schema(# type: ignore[misc]by_alias=by_alias,ref_template=ref_template)schema_["title"]=namereturnschema_defmodel_json_schema(cls:type[BaseModel],by_alias:bool=True,ref_template:str=DEFAULT_REF_TEMPLATE,schema_generator:type[GenerateJsonSchema]=GenerateJsonSchema,mode:JsonSchemaMode="validation",)->dict[str,Any]:# Complains about model_json_schema not being defined in superclassschema_=super(cls,cls).model_json_schema(# type: ignore[misc]by_alias=by_alias,ref_template=ref_template,schema_generator=schema_generator,mode=mode,)schema_["title"]=namereturnschema_base_class_attributes={"__annotations__":{"root":type_},"model_config":ConfigDict(arbitrary_types_allowed=True),"schema":classmethod(schema),"model_json_schema":classmethod(model_json_schema),"__module__":module_nameor"langchain_core.runnables.utils",}ifdefault_isnotNO_DEFAULT:base_class_attributes["root"]=default_withwarnings.catch_warnings():try:if(isinstance(type_,type)andnotisinstance(type_,GenericAlias)andissubclass(type_,BaseModelV1)):warnings.filterwarnings(action="ignore",category=PydanticDeprecationWarning)exceptTypeError:passcustom_root_type=type(name,(RootModel,),base_class_attributes)returncast("type[BaseModel]",custom_root_type)@lru_cache(maxsize=256)def_create_root_model_cached(model_name:str,type_:Any,*,module_name:Optional[str]=None,default_:object=NO_DEFAULT,)->type[BaseModel]:return_create_root_model(model_name,type_,default_=default_,module_name=module_name)@lru_cache(maxsize=256)def_create_model_cached(__model_name:str,**field_definitions:Any,)->type[BaseModel]:return_create_model_base(__model_name,__config__=_SchemaConfig,**_remap_field_definitions(field_definitions),)
[docs]defcreate_model(__model_name:str,__module_name:Optional[str]=None,**field_definitions:Any,)->type[BaseModel]:"""Create a pydantic model with the given field definitions. Please use create_model_v2 instead of this function. Args: __model_name: The name of the model. __module_name: The name of the module where the model is defined. This is used by Pydantic to resolve any forward references. **field_definitions: The field definitions for the model. Returns: Type[BaseModel]: The created model. """kwargs={}if"__root__"infield_definitions:kwargs["root"]=field_definitions.pop("__root__")returncreate_model_v2(__model_name,module_name=__module_name,field_definitions=field_definitions,**kwargs,)
# Reserved names should capture all the `public` names / methods that are# used by BaseModel internally. This will keep the reserved names up-to-date.# For reference, the reserved names are:# "construct", "copy", "dict", "from_orm", "json", "parse_file", "parse_obj",# "parse_raw", "schema", "schema_json", "update_forward_refs", "validate",# "model_computed_fields", "model_config", "model_construct", "model_copy",# "model_dump", "model_dump_json", "model_extra", "model_fields",# "model_fields_set", "model_json_schema", "model_parametrized_name",# "model_post_init", "model_rebuild", "model_validate", "model_validate_json",# "model_validate_strings"_RESERVED_NAMES={keyforkeyindir(BaseModel)ifnotkey.startswith("_")}def_remap_field_definitions(field_definitions:dict[str,Any])->dict[str,Any]:"""This remaps fields to avoid colliding with internal pydantic fields."""frompydanticimportFieldfrompydantic.fieldsimportFieldInforemapped={}forkey,valueinfield_definitions.items():ifkey.startswith("_")orkeyin_RESERVED_NAMES:# Let's add a prefix to avoid colliding with internal pydantic fieldsifisinstance(value,FieldInfo):msg=(f"Remapping for fields starting with '_' or fields with a name "f"matching a reserved name {_RESERVED_NAMES} is not supported if "f" the field is a pydantic Field instance. Got {key}.")raiseNotImplementedError(msg)type_,default_=valueremapped[f"private_{key}"]=(type_,Field(default=default_,alias=key,serialization_alias=key,title=key.lstrip("_").replace("_"," ").title(),),)else:remapped[key]=valuereturnremapped
[docs]defcreate_model_v2(model_name:str,*,module_name:Optional[str]=None,field_definitions:Optional[dict[str,Any]]=None,root:Optional[Any]=None,)->type[BaseModel]:"""Create a pydantic model with the given field definitions. Attention: Please do not use outside of langchain packages. This API is subject to change at any time. Args: model_name: The name of the model. module_name: The name of the module where the model is defined. This is used by Pydantic to resolve any forward references. field_definitions: The field definitions for the model. root: Type for a root model (RootModel) Returns: Type[BaseModel]: The created model. """field_definitions=cast("dict[str, Any]",field_definitionsor{})# type: ignore[no-redef]ifroot:iffield_definitions:msg=("When specifying __root__ no other "f"fields should be provided. Got {field_definitions}")raiseNotImplementedError(msg)ifisinstance(root,tuple):kwargs={"type_":root[0],"default_":root[1]}else:kwargs={"type_":root}try:named_root_model=_create_root_model_cached(model_name,module_name=module_name,**kwargs)exceptTypeError:# something in the arguments into _create_root_model_cached is not hashablenamed_root_model=_create_root_model(model_name,module_name=module_name,**kwargs,)returnnamed_root_model# No root, just field definitionsnames=set(field_definitions.keys())capture_warnings=Falsefornameinnames:# Also if any non-reserved name is used (e.g., model_id or model_name)ifname.startswith("model"):capture_warnings=Truewithwarnings.catch_warnings()ifcapture_warningselsenullcontext():# type: ignore[attr-defined]ifcapture_warnings:warnings.filterwarnings(action="ignore")try:return_create_model_cached(model_name,**field_definitions)exceptTypeError:# something in field definitions is not hashablereturn_create_model_base(model_name,__config__=_SchemaConfig,**_remap_field_definitions(field_definitions),)