"""Helper functions for deprecating parts of the LangChain API.This module was adapted from matplotlibs _api/deprecation.py module:https://github.com/matplotlib/matplotlib/blob/main/lib/matplotlib/_api/deprecation.py.. warning:: This module is for internal use only. Do not use it in your own code. We may change the API at any time with no warning."""importcontextlibimportfunctoolsimportinspectimportwarningsfromcollections.abcimportGeneratorfromtypingimport(Any,Callable,TypeVar,Union,cast,)fromtyping_extensionsimportParamSpecfromlangchain_core._api.internalimportis_caller_internalclassLangChainDeprecationWarning(DeprecationWarning):"""A class for issuing deprecation warnings for LangChain users."""classLangChainPendingDeprecationWarning(PendingDeprecationWarning):"""A class for issuing deprecation warnings for LangChain users."""# PUBLIC API# Last Any should be FieldInfoV1 but this leads to circular importsT=TypeVar("T",bound=Union[type,Callable[...,Any],Any])def_validate_deprecation_params(pending:bool,removal:str,alternative:str,alternative_import:str,)->None:"""Validate the deprecation parameters."""ifpendingandremoval:msg="A pending deprecation cannot have a scheduled removal"raiseValueError(msg)ifalternativeandalternative_import:msg="Cannot specify both alternative and alternative_import"raiseValueError(msg)ifalternative_importand"."notinalternative_import:msg=("alternative_import must be a fully qualified module path. Got "f" {alternative_import}")raiseValueError(msg)defdeprecated(since:str,*,message:str="",name:str="",alternative:str="",alternative_import:str="",pending:bool=False,obj_type:str="",addendum:str="",removal:str="",package:str="",)->Callable[[T],T]:"""Decorator to mark a function, a class, or a property as deprecated. When deprecating a classmethod, a staticmethod, or a property, the ``@deprecated`` decorator should go *under* ``@classmethod`` and ``@staticmethod`` (i.e., `deprecated` should directly decorate the underlying callable), but *over* ``@property``. When deprecating a class ``C`` intended to be used as a base class in a multiple inheritance hierarchy, ``C`` *must* define an ``__init__`` method (if ``C`` instead inherited its ``__init__`` from its own base class, then ``@deprecated`` would mess up ``__init__`` inheritance when installing its own (deprecation-emitting) ``C.__init__``). Parameters are the same as for `warn_deprecated`, except that *obj_type* defaults to 'class' if decorating a class, 'attribute' if decorating a property, and 'function' otherwise. Args: since : str The release at which this API became deprecated. message : str, optional Override the default deprecation message. The %(since)s, %(name)s, %(alternative)s, %(obj_type)s, %(addendum)s, and %(removal)s format specifiers will be replaced by the values of the respective arguments passed to this function. name : str, optional The name of the deprecated object. alternative : str, optional An alternative API that the user may use in place of the deprecated API. The deprecation warning will tell the user about this alternative if provided. pending : bool, optional If True, uses a PendingDeprecationWarning instead of a DeprecationWarning. Cannot be used together with removal. obj_type : str, optional The object type being deprecated. addendum : str, optional Additional text appended directly to the final message. removal : str, optional The expected removal version. With the default (an empty string), a removal version is automatically computed from since. Set to other Falsy values to not schedule a removal date. Cannot be used together with pending. Examples: .. code-block:: python @deprecated('1.4.0') def the_function_to_deprecate(): pass """_validate_deprecation_params(pending,removal,alternative,alternative_import)defdeprecate(obj:T,*,_obj_type:str=obj_type,_name:str=name,_message:str=message,_alternative:str=alternative,_alternative_import:str=alternative_import,_pending:bool=pending,_addendum:str=addendum,_package:str=package,)->T:"""Implementation of the decorator returned by `deprecated`."""fromlangchain_core.utils.pydanticimportFieldInfoV1,FieldInfoV2defemit_warning()->None:"""Emit the warning."""warn_deprecated(since,message=_message,name=_name,alternative=_alternative,alternative_import=_alternative_import,pending=_pending,obj_type=_obj_type,addendum=_addendum,removal=removal,package=_package,)warned=Falsedefwarning_emitting_wrapper(*args:Any,**kwargs:Any)->Any:"""Wrapper for the original wrapped callable that emits a warning. Args: *args: The positional arguments to the function. **kwargs: The keyword arguments to the function. Returns: The return value of the function being wrapped. """nonlocalwarnedifnotwarnedandnotis_caller_internal():warned=Trueemit_warning()returnwrapped(*args,**kwargs)asyncdefawarning_emitting_wrapper(*args:Any,**kwargs:Any)->Any:"""Same as warning_emitting_wrapper, but for async functions."""nonlocalwarnedifnotwarnedandnotis_caller_internal():warned=Trueemit_warning()returnawaitwrapped(*args,**kwargs)_package=_packageorobj.__module__.split(".")[0].replace("_","-")ifisinstance(obj,type):ifnot_obj_type:_obj_type="class"wrapped=obj.__init__# type: ignore_name=_nameorobj.__qualname__old_doc=obj.__doc__deffinalize(wrapper:Callable[...,Any],new_doc:str)->T:"""Finalize the deprecation of a class."""# Can't set new_doc on some extension objects.withcontextlib.suppress(AttributeError):obj.__doc__=new_docdefwarn_if_direct_instance(self:Any,*args:Any,**kwargs:Any)->Any:"""Warn that the class is in beta."""nonlocalwarnedifnotwarnedandtype(self)isobjandnotis_caller_internal():warned=Trueemit_warning()returnwrapped(self,*args,**kwargs)obj.__init__=functools.wraps(obj.__init__)(# type: ignore[misc]warn_if_direct_instance)returncast("T",obj)elifisinstance(obj,FieldInfoV1):wrapped=Noneifnot_obj_type:_obj_type="attribute"ifnot_name:msg=f"Field {obj} must have a name to be deprecated."raiseValueError(msg)old_doc=obj.descriptiondeffinalize(wrapper:Callable[...,Any],new_doc:str)->T:returncast("T",FieldInfoV1(default=obj.default,default_factory=obj.default_factory,description=new_doc,alias=obj.alias,exclude=obj.exclude,),)elifisinstance(obj,FieldInfoV2):wrapped=Noneifnot_obj_type:_obj_type="attribute"ifnot_name:msg=f"Field {obj} must have a name to be deprecated."raiseValueError(msg)old_doc=obj.descriptiondeffinalize(wrapper:Callable[...,Any],new_doc:str)->T:returncast("T",FieldInfoV2(default=obj.default,default_factory=obj.default_factory,description=new_doc,alias=obj.alias,exclude=obj.exclude,),)elifisinstance(obj,property):ifnot_obj_type:_obj_type="attribute"wrapped=None_name=_nameorcast("Union[type, Callable]",obj.fget).__qualname__old_doc=obj.__doc__class_DeprecatedProperty(property):"""A deprecated property."""def__init__(self,fget:Union[Callable[[Any],Any],None]=None,fset:Union[Callable[[Any,Any],None],None]=None,fdel:Union[Callable[[Any],None],None]=None,doc:Union[str,None]=None,)->None:super().__init__(fget,fset,fdel,doc)self.__orig_fget=fgetself.__orig_fset=fsetself.__orig_fdel=fdeldef__get__(self,instance:Any,owner:Union[type,None]=None)->Any:ifinstanceisnotNoneorownerisnotNone:emit_warning()ifself.fgetisNone:returnNonereturnself.fget(instance)def__set__(self,instance:Any,value:Any)->None:ifinstanceisnotNone:emit_warning()ifself.fsetisnotNone:self.fset(instance,value)def__delete__(self,instance:Any)->None:ifinstanceisnotNone:emit_warning()ifself.fdelisnotNone:self.fdel(instance)def__set_name__(self,owner:Union[type,None],set_name:str)->None:nonlocal_nameif_name=="<lambda>":_name=set_namedeffinalize(wrapper:Callable[...,Any],new_doc:str)->T:"""Finalize the property."""returncast("T",_DeprecatedProperty(fget=obj.fget,fset=obj.fset,fdel=obj.fdel,doc=new_doc),)else:_name=_nameorcast("Union[type, Callable]",obj).__qualname__ifnot_obj_type:# edge case: when a function is within another function# within a test, this will call it a "method" not a "function"_obj_type="function"if"."notin_nameelse"method"wrapped=objold_doc=wrapped.__doc__deffinalize(wrapper:Callable[...,Any],new_doc:str)->T:"""Wrap the wrapped function using the wrapper and update the docstring. Args: wrapper: The wrapper function. new_doc: The new docstring. Returns: The wrapped function. """wrapper=functools.wraps(wrapped)(wrapper)wrapper.__doc__=new_docreturncast("T",wrapper)old_doc=inspect.cleandoc(old_docor"").strip("\n")# old_doc can be Noneifnotold_doc:old_doc=""# Modify the docstring to include a deprecation notice.if(_alternativeand_alternative.split(".")[-1].lower()==_alternative.split(".")[-1]):_alternative=f":meth:`~{_alternative}`"elif_alternative:_alternative=f":class:`~{_alternative}`"if(_alternative_importand_alternative_import.split(".")[-1].lower()==_alternative_import.split(".")[-1]):_alternative_import=f":meth:`~{_alternative_import}`"elif_alternative_import:_alternative_import=f":class:`~{_alternative_import}`"components=[_message,f"Use {_alternative} instead."if_alternativeelse"",f"Use ``{_alternative_import}`` instead."if_alternative_importelse"",_addendum,]details=" ".join([component.strip()forcomponentincomponentsifcomponent])package=_packageor(_name.split(".")[0].replace("_","-")if"."in_nameelseNone)ifremoval:ifremoval.startswith("1.")andpackageandpackage.startswith("langchain"):removal_str=f"It will not be removed until {package}=={removal}."else:removal_str=f"It will be removed in {package}=={removal}."else:removal_str=""new_doc=f"""\.. deprecated:: {since}{details}{removal_str}{old_doc}\"""ifinspect.iscoroutinefunction(obj):finalized=finalize(awarning_emitting_wrapper,new_doc)else:finalized=finalize(warning_emitting_wrapper,new_doc)returncast("T",finalized)returndeprecate@contextlib.contextmanagerdefsuppress_langchain_deprecation_warning()->Generator[None,None,None]:"""Context manager to suppress LangChainDeprecationWarning."""withwarnings.catch_warnings():warnings.simplefilter("ignore",LangChainDeprecationWarning)warnings.simplefilter("ignore",LangChainPendingDeprecationWarning)yielddefwarn_deprecated(since:str,*,message:str="",name:str="",alternative:str="",alternative_import:str="",pending:bool=False,obj_type:str="",addendum:str="",removal:str="",package:str="",)->None:"""Display a standardized deprecation. Arguments: since : str The release at which this API became deprecated. message : str, optional Override the default deprecation message. The %(since)s, %(name)s, %(alternative)s, %(obj_type)s, %(addendum)s, and %(removal)s format specifiers will be replaced by the values of the respective arguments passed to this function. name : str, optional The name of the deprecated object. alternative : str, optional An alternative API that the user may use in place of the deprecated API. The deprecation warning will tell the user about this alternative if provided. pending : bool, optional If True, uses a PendingDeprecationWarning instead of a DeprecationWarning. Cannot be used together with removal. obj_type : str, optional The object type being deprecated. addendum : str, optional Additional text appended directly to the final message. removal : str, optional The expected removal version. With the default (an empty string), a removal version is automatically computed from since. Set to other Falsy values to not schedule a removal date. Cannot be used together with pending. """ifnotpending:ifnotremoval:removal=f"in {removal}"ifremovalelse"within ?? minor releases"msg=(f"Need to determine which default deprecation schedule to use. "f"{removal}")raiseNotImplementedError(msg)else:removal=f"in {removal}"ifnotmessage:message=""_package=(packageorname.split(".")[0].replace("_","-")if"."innameelse"LangChain")ifobj_type:message+=f"The {obj_type} `{name}`"else:message+=f"`{name}`"ifpending:message+=" will be deprecated in a future version"else:message+=f" was deprecated in {_package}{since}"ifremoval:message+=f" and will be removed {removal}"ifalternative_import:alt_package=alternative_import.split(".")[0].replace("_","-")ifalt_package==_package:message+=f". Use {alternative_import} instead."else:alt_module,alt_name=alternative_import.rsplit(".",1)message+=(f". An updated version of the {obj_type} exists in the "f"{alt_package} package and should be used instead. To use it run "f"`pip install -U {alt_package}` and import as "f"`from {alt_module} import {alt_name}`.")elifalternative:message+=f". Use {alternative} instead."ifaddendum:message+=f" {addendum}"warning_cls=(LangChainPendingDeprecationWarningifpendingelseLangChainDeprecationWarning)warning=warning_cls(message)warnings.warn(warning,category=LangChainDeprecationWarning,stacklevel=4)defsurface_langchain_deprecation_warnings()->None:"""Unmute LangChain deprecation warnings."""warnings.filterwarnings("default",category=LangChainPendingDeprecationWarning,)warnings.filterwarnings("default",category=LangChainDeprecationWarning,)_P=ParamSpec("_P")_R=TypeVar("_R")defrename_parameter(*,since:str,removal:str,old:str,new:str,)->Callable[[Callable[_P,_R]],Callable[_P,_R]]:"""Decorator indicating that parameter *old* of *func* is renamed to *new*. The actual implementation of *func* should use *new*, not *old*. If *old* is passed to *func*, a DeprecationWarning is emitted, and its value is used, even if *new* is also passed by keyword. Example: .. code-block:: python @_api.rename_parameter("3.1", "bad_name", "good_name") def func(good_name): ... """defdecorator(f:Callable[_P,_R])->Callable[_P,_R]:@functools.wraps(f)defwrapper(*args:_P.args,**kwargs:_P.kwargs)->_R:ifnewinkwargsandoldinkwargs:msg=f"{f.__name__}() got multiple values for argument {new!r}"raiseTypeError(msg)ifoldinkwargs:warn_deprecated(since,removal=removal,message=f"The parameter `{old}` of `{f.__name__}` was "f"deprecated in {since} and will be removed "f"in {removal} Use `{new}` instead.",)kwargs[new]=kwargs.pop(old)returnf(*args,**kwargs)returnwrapperreturndecorator