libs/core/langchain_core/_api/deprecation.py PYTHON 638 lines View on github.com → Search inside
1"""Helper functions for deprecating parts of the LangChain API.23This module was adapted from matplotlib's [`_api/deprecation.py`](https://github.com/matplotlib/matplotlib/blob/main/lib/matplotlib/_api/deprecation.py)4module.56!!! warning78    This module is for internal use only. Do not use it in your own code. We may change9    the API at any time with no warning.10"""1112import contextlib13import functools14import inspect15import sys16import warnings17from collections.abc import Callable, Generator18from contextvars import ContextVar19from typing import (20    TYPE_CHECKING,21    Any,22    ParamSpec,23    TypeGuard,24    TypeVar,25    cast,26)2728from pydantic.fields import FieldInfo2930from langchain_core._api.internal import is_caller_internal3132if TYPE_CHECKING:33    from pydantic.v1.fields import FieldInfo as FieldInfoV1343536def _is_pydantic_v1_field_info(obj: Any) -> TypeGuard["FieldInfoV1"]:37    """Check if `obj` is a `pydantic.v1.fields.FieldInfo` without forcing import.3839    Importing `pydantic.v1` emits a `UserWarning` on Python 3.14+. Skipping the40    import entirely when no caller has constructed a v1 `FieldInfo` keeps that41    warning out of `langchain_core`'s import path. If a caller did construct one,42    `pydantic.v1.fields` is already in `sys.modules` and isinstance is safe.43    """44    mod = sys.modules.get("pydantic.v1.fields")45    if mod is None:46        return False47    return isinstance(obj, mod.FieldInfo)484950def _build_deprecation_message(51    *,52    alternative: str = "",53    alternative_import: str = "",54) -> str:55    """Build a simple deprecation message for `__deprecated__` attribute.5657    Args:58        alternative: An alternative API name.59        alternative_import: A fully qualified import path for the alternative.6061    Returns:62        A deprecation message string for IDE/type checker display.63    """64    if alternative_import:65        return f"Use {alternative_import} instead."66    if alternative:67        return f"Use {alternative} instead."68    return "Deprecated."697071class LangChainDeprecationWarning(DeprecationWarning):72    """A class for issuing deprecation warnings for LangChain users."""737475class LangChainPendingDeprecationWarning(PendingDeprecationWarning):76    """A class for issuing deprecation warnings for LangChain users."""777879# Tracks when callers intentionally silence LangChain deprecation warnings.80# Suppressed warnings should not consume a deprecated callable's one-time81# warning state; otherwise an internal compatibility path can prevent the first82# user-visible call from warning.83_SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING = ContextVar(84    "_SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING", default=False85)868788# PUBLIC API899091# Bound is `Any` (not `FieldInfoV1`) because importing `pydantic.v1` at module92# scope emits a `UserWarning` on Python 3.14+; v1 `FieldInfo` support is handled93# at runtime via `_is_pydantic_v1_field_info`.94T = TypeVar("T", bound=type | Callable[..., Any] | Any)959697def _validate_deprecation_params(98    removal: str,99    alternative: str,100    alternative_import: str,101    *,102    pending: bool,103) -> None:104    """Validate the deprecation parameters."""105    if pending and removal:106        msg = "A pending deprecation cannot have a scheduled removal"107        raise ValueError(msg)108    if alternative and alternative_import:109        msg = "Cannot specify both alternative and alternative_import"110        raise ValueError(msg)111112    if alternative_import and "." not in alternative_import:113        msg = (114            "alternative_import must be a fully qualified module path. Got "115            f" {alternative_import}"116        )117        raise ValueError(msg)118119120def deprecated(121    since: str,122    *,123    message: str = "",124    name: str = "",125    alternative: str = "",126    alternative_import: str = "",127    pending: bool = False,128    obj_type: str = "",129    addendum: str = "",130    removal: str = "",131    package: str = "",132) -> Callable[[T], T]:133    """Decorator to mark a function, a class, or a property as deprecated.134135    When deprecating a classmethod, a staticmethod, or a property, the `@deprecated`136    decorator should go *under* `@classmethod` and `@staticmethod` (i.e., `deprecated`137    should directly decorate the underlying callable), but *over* `@property`.138139    When deprecating a class `C` intended to be used as a base class in a multiple140    inheritance hierarchy, `C` *must* define an `__init__` method (if `C` instead141    inherited its `__init__` from its own base class, then `@deprecated` would mess up142    `__init__` inheritance when installing its own (deprecation-emitting) `C.__init__`).143144    Parameters are the same as for `warn_deprecated`, except that *obj_type* defaults to145    'class' if decorating a class, 'attribute' if decorating a property, and 'function'146    otherwise.147148    Args:149        since: The release at which this API became deprecated.150        message: Override the default deprecation message.151152            The `%(since)s`, `%(name)s`, `%(alternative)s`, `%(obj_type)s`,153            `%(addendum)s`, and `%(removal)s` format specifiers will be replaced by the154            values of the respective arguments passed to this function.155        name: The name of the deprecated object.156        alternative: An alternative API that the user may use in place of the deprecated157            API.158159            The deprecation warning will tell the user about this alternative if160            provided.161        alternative_import: An alternative import that the user may use instead.162        pending: If `True`, uses a `PendingDeprecationWarning` instead of a163            `DeprecationWarning`.164165            Cannot be used together with removal.166        obj_type: The object type being deprecated.167        addendum: Additional text appended directly to the final message.168        removal: The expected removal version.169170            With the default (an empty string), no removal version is shown in the171            warning message.172173            Cannot be used together with pending.174        package: The package of the deprecated object.175176    Returns:177        A decorator to mark a function or class as deprecated.178179    Example:180        ```python181        @deprecated("1.4.0")182        def the_function_to_deprecate():183            pass184        ```185    """186    _validate_deprecation_params(187        removal, alternative, alternative_import, pending=pending188    )189190    def deprecate(191        obj: T,192        *,193        _obj_type: str = obj_type,194        _name: str = name,195        _message: str = message,196        _alternative: str = alternative,197        _alternative_import: str = alternative_import,198        _pending: bool = pending,199        _addendum: str = addendum,200        _package: str = package,201    ) -> T:202        """Implementation of the decorator returned by `deprecated`."""203204        def emit_warning() -> None:205            """Emit the warning."""206            warn_deprecated(207                since,208                message=_message,209                name=_name,210                alternative=_alternative,211                alternative_import=_alternative_import,212                pending=_pending,213                obj_type=_obj_type,214                addendum=_addendum,215                removal=removal,216                package=_package,217            )218219        warned = False220221        def warning_emitting_wrapper(*args: Any, **kwargs: Any) -> Any:222            """Wrapper for the original wrapped callable that emits a warning.223224            Args:225                *args: The positional arguments to the function.226                **kwargs: The keyword arguments to the function.227228            Returns:229                The return value of the function being wrapped.230            """231            nonlocal warned232            if not warned and not is_caller_internal():233                emit_warning()234                # Only mark the warning as emitted if it was not intentionally235                # suppressed by `suppress_langchain_deprecation_warning()`.236                warned = not _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.get()237            return wrapped(*args, **kwargs)238239        async def awarning_emitting_wrapper(*args: Any, **kwargs: Any) -> Any:240            """Same as warning_emitting_wrapper, but for async functions."""241            nonlocal warned242            if not warned and not is_caller_internal():243                emit_warning()244                # Only mark the warning as emitted if it was not intentionally245                # suppressed by `suppress_langchain_deprecation_warning()`.246                warned = not _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.get()247            return await wrapped(*args, **kwargs)248249        _package = _package or obj.__module__.split(".")[0].replace("_", "-")250251        if isinstance(obj, type):252            if not _obj_type:253                _obj_type = "class"254            wrapped = obj.__init__  # type: ignore[misc]255            _name = _name or obj.__qualname__256            old_doc = obj.__doc__257258            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:259                """Finalize the deprecation of a class."""260                # Can't set new_doc on some extension objects.261                with contextlib.suppress(AttributeError):262                    obj.__doc__ = new_doc263264                def warn_if_direct_instance(265                    self: Any, *args: Any, **kwargs: Any266                ) -> Any:267                    """Warn that the class is in beta."""268                    nonlocal warned269                    if not warned and type(self) is obj and not is_caller_internal():270                        emit_warning()271                        # Only mark the warning as emitted if it was not intentionally272                        # suppressed by `suppress_langchain_deprecation_warning()`.273                        warned = not _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.get()274                    return wrapped(self, *args, **kwargs)275276                obj.__init__ = functools.wraps(obj.__init__)(  # type: ignore[misc]277                    warn_if_direct_instance278                )279                # Set __deprecated__ for PEP 702 (IDE/type checker support)280                obj.__deprecated__ = _build_deprecation_message(  # type: ignore[attr-defined]281                    alternative=alternative,282                    alternative_import=alternative_import,283                )284                return obj285286        elif _is_pydantic_v1_field_info(obj):287            wrapped = None288            if not _obj_type:289                _obj_type = "attribute"290            if not _name:291                msg = f"Field {obj} must have a name to be deprecated."292                raise ValueError(msg)293            old_doc = obj.description294295            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:296                from pydantic.v1.fields import FieldInfo as FieldInfoV1  # noqa: PLC0415297298                return cast(299                    "T",300                    FieldInfoV1(301                        default=obj.default,302                        default_factory=obj.default_factory,303                        description=new_doc,304                        alias=obj.alias,305                        exclude=obj.exclude,306                    ),307                )308309        elif isinstance(obj, FieldInfo):310            wrapped = None311            if not _obj_type:312                _obj_type = "attribute"313            if not _name:314                msg = f"Field {obj} must have a name to be deprecated."315                raise ValueError(msg)316            old_doc = obj.description317318            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:319                return cast(320                    "T",321                    FieldInfo(322                        default=obj.default,323                        default_factory=obj.default_factory,324                        description=new_doc,325                        alias=obj.alias,326                        exclude=obj.exclude,327                    ),328                )329330        elif isinstance(obj, property):331            if not _obj_type:332                _obj_type = "attribute"333            wrapped = None334            _name = _name or cast("type | Callable", obj.fget).__qualname__335            old_doc = obj.__doc__336337            class _DeprecatedProperty(property):338                """A deprecated property."""339340                def __init__(341                    self,342                    fget: Callable[[Any], Any] | None = None,343                    fset: Callable[[Any, Any], None] | None = None,344                    fdel: Callable[[Any], None] | None = None,345                    doc: str | None = None,346                ) -> None:347                    super().__init__(fget, fset, fdel, doc)348                    self.__orig_fget = fget349                    self.__orig_fset = fset350                    self.__orig_fdel = fdel351352                def __get__(self, instance: Any, owner: type | None = None) -> Any:353                    if instance is not None or owner is not None:354                        emit_warning()355                    if self.fget is None:356                        return None357                    return self.fget(instance)358359                def __set__(self, instance: Any, value: Any) -> None:360                    if instance is not None:361                        emit_warning()362                    if self.fset is not None:363                        self.fset(instance, value)364365                def __delete__(self, instance: Any) -> None:366                    if instance is not None:367                        emit_warning()368                    if self.fdel is not None:369                        self.fdel(instance)370371                def __set_name__(self, owner: type | None, set_name: str) -> None:372                    nonlocal _name373                    if _name == "<lambda>":374                        _name = set_name375376            def finalize(_: Callable[..., Any], new_doc: str, /) -> T:377                """Finalize the property."""378                prop = _DeprecatedProperty(379                    fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc380                )381                # Set __deprecated__ for PEP 702 (IDE/type checker support)382                prop.__deprecated__ = _build_deprecation_message(  # type: ignore[attr-defined]383                    alternative=alternative,384                    alternative_import=alternative_import,385                )386                return cast("T", prop)387388        else:389            _name = _name or cast("type | Callable", obj).__qualname__390            if not _obj_type:391                # edge case: when a function is within another function392                # within a test, this will call it a "method" not a "function"393                _obj_type = "function" if "." not in _name else "method"394            wrapped = obj395            old_doc = wrapped.__doc__396397            def finalize(wrapper: Callable[..., Any], new_doc: str, /) -> T:398                """Wrap the wrapped function using the wrapper and update the docstring.399400                Args:401                    wrapper: The wrapper function.402                    new_doc: The new docstring.403404                Returns:405                    The wrapped function.406                """407                wrapper = functools.wraps(wrapped)(wrapper)408                wrapper.__doc__ = new_doc409                # Set __deprecated__ for PEP 702 (IDE/type checker support)410                wrapper.__deprecated__ = _build_deprecation_message(  # type: ignore[attr-defined]411                    alternative=alternative,412                    alternative_import=alternative_import,413                )414                return cast("T", wrapper)415416        old_doc = inspect.cleandoc(old_doc or "").strip("\n")417418        # old_doc can be None419        if not old_doc:420            old_doc = ""421422        # Modify the docstring to include a deprecation notice.423        if (424            _alternative425            and _alternative.rsplit(".", maxsplit=1)[-1].lower()426            == _alternative.rsplit(".", maxsplit=1)[-1]427        ) or _alternative:428            _alternative = f"`{_alternative}`"429430        if (431            _alternative_import432            and _alternative_import.rsplit(".", maxsplit=1)[-1].lower()433            == _alternative_import.rsplit(".", maxsplit=1)[-1]434        ) or _alternative_import:435            _alternative_import = f"`{_alternative_import}`"436437        components = [438            _message,439            f"Use {_alternative} instead." if _alternative else "",440            f"Use {_alternative_import} instead." if _alternative_import else "",441            _addendum,442        ]443        details = " ".join([component.strip() for component in components if component])444        package = _package or (445            _name.split(".")[0].replace("_", "-") if "." in _name else None446        )447        if removal:448            if removal.startswith("1.") and package and package.startswith("langchain"):449                removal_str = f"It will not be removed until {package}=={removal}."450            else:451                removal_str = f"It will be removed in {package}=={removal}."452        else:453            removal_str = ""454        new_doc = f"""\455!!! deprecated "{since} {details} {removal_str}"456457{old_doc}\458"""459460        if inspect.iscoroutinefunction(obj):461            return finalize(awarning_emitting_wrapper, new_doc)462        return finalize(warning_emitting_wrapper, new_doc)463464    return deprecate465466467@contextlib.contextmanager468def suppress_langchain_deprecation_warning() -> Generator[None, None, None]:469    """Context manager to suppress `LangChainDeprecationWarning`."""470    token = _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.set(True)471    try:472        with warnings.catch_warnings():473            warnings.simplefilter("ignore", LangChainDeprecationWarning)474            warnings.simplefilter("ignore", LangChainPendingDeprecationWarning)475            yield476    finally:477        _SUPPRESSING_LANGCHAIN_DEPRECATION_WARNING.reset(token)478479480def warn_deprecated(481    since: str,482    *,483    message: str = "",484    name: str = "",485    alternative: str = "",486    alternative_import: str = "",487    pending: bool = False,488    obj_type: str = "",489    addendum: str = "",490    removal: str = "",491    package: str = "",492) -> None:493    """Display a standardized deprecation.494495    Args:496        since: The release at which this API became deprecated.497        message: Override the default deprecation message.498499            The `%(since)s`, `%(name)s`, `%(alternative)s`, `%(obj_type)s`,500            `%(addendum)s`, and `%(removal)s` format specifiers will be replaced by the501            values of the respective arguments passed to this function.502        name: The name of the deprecated object.503        alternative: An alternative API that the user may use in place of the504            deprecated API.505506            The deprecation warning will tell the user about this alternative if507            provided.508        alternative_import: An alternative import that the user may use instead.509        pending: If `True`, uses a `PendingDeprecationWarning` instead of a510            `DeprecationWarning`.511512            Cannot be used together with removal.513        obj_type: The object type being deprecated.514        addendum: Additional text appended directly to the final message.515        removal: The expected removal version.516517            With the default (an empty string), no removal version is shown in the518            warning message.519520            Cannot be used together with pending.521        package: The package of the deprecated object.522    """523    if not pending and removal:524        removal = f"in {removal}"525526    if not message:527        message = ""528        package_ = (529            package or name.split(".", maxsplit=1)[0].replace("_", "-")530            if "." in name531            else "LangChain"532        )533534        if obj_type:535            message += f"The {obj_type} `{name}`"536        else:537            message += f"`{name}`"538539        if pending:540            message += " will be deprecated in a future version"541        else:542            message += f" was deprecated in {package_} {since}"543544            if removal:545                message += f" and will be removed {removal}"546547        if alternative_import:548            alt_package = alternative_import.split(".", maxsplit=1)[0].replace("_", "-")549            if alt_package == package_:550                message += f". Use {alternative_import} instead."551            else:552                alt_module, alt_name = alternative_import.rsplit(".", 1)553                message += (554                    f". An updated version of the {obj_type} exists in the "555                    f"{alt_package} package and should be used instead. To use it run "556                    f"`pip install -U {alt_package}` and import as "557                    f"`from {alt_module} import {alt_name}`."558                )559        elif alternative:560            message += f". Use {alternative} instead."561562        if addendum:563            message += f" {addendum}"564565    warning_cls = (566        LangChainPendingDeprecationWarning if pending else LangChainDeprecationWarning567    )568    warning = warning_cls(message)569    warnings.warn(warning, category=LangChainDeprecationWarning, stacklevel=4)570571572def surface_langchain_deprecation_warnings() -> None:573    """Unmute LangChain deprecation warnings."""574    warnings.filterwarnings(575        "default",576        category=LangChainPendingDeprecationWarning,577    )578579    warnings.filterwarnings(580        "default",581        category=LangChainDeprecationWarning,582    )583584585_P = ParamSpec("_P")586_R = TypeVar("_R")587588589def rename_parameter(590    *,591    since: str,592    removal: str,593    old: str,594    new: str,595) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]:596    """Decorator indicating that parameter *old* of *func* is renamed to *new*.597598    The actual implementation of *func* should use *new*, not *old*. If *old* is passed599    to *func*, a `DeprecationWarning` is emitted, and its value is used, even if *new*600    is also passed by keyword.601602    Args:603        since: The version in which the parameter was renamed.604        removal: The version in which the old parameter will be removed.605        old: The old parameter name.606        new: The new parameter name.607608    Returns:609        A decorator indicating that a parameter was renamed.610611    Example:612        ```python613        @_api.rename_parameter("3.1", "bad_name", "good_name")614        def func(good_name): ...615        ```616    """617618    def decorator(f: Callable[_P, _R]) -> Callable[_P, _R]:619        @functools.wraps(f)620        def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:621            if new in kwargs and old in kwargs:622                msg = f"{f.__name__}() got multiple values for argument {new!r}"623                raise TypeError(msg)624            if old in kwargs:625                warn_deprecated(626                    since,627                    removal=removal,628                    message=f"The parameter `{old}` of `{f.__name__}` was "629                    f"deprecated in {since} and will be removed "630                    f"in {removal} Use `{new}` instead.",631                )632                kwargs[new] = kwargs.pop(old)633            return f(*args, **kwargs)634635        return wrapper636637    return decorator

Code quality findings 16

Overuse may indicate design issues; consider polymorphism
isinstance-overuse
return isinstance(obj, mod.FieldInfo)
Ensure functions have docstrings for documentation
missing-docstring
def deprecated(
Ensure functions have docstrings for documentation
missing-docstring
def the_function_to_deprecate():
Ensure functions have docstrings for documentation
missing-docstring
def deprecate(
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(obj, type):
Ensure functions have docstrings for documentation
missing-docstring
def warn_if_direct_instance(
Use isinstance() for type checking instead of type()
type-check
if not warned and type(self) is obj and not is_caller_internal():
Ensure functions have docstrings for documentation
missing-docstring
def finalize(_: Callable[..., Any], new_doc: str, /) -> T:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(obj, FieldInfo):
Ensure functions have docstrings for documentation
missing-docstring
def finalize(_: Callable[..., Any], new_doc: str, /) -> T:
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(obj, property):
Ensure functions have docstrings for documentation
missing-docstring
def warn_deprecated(
Ensure functions have docstrings for documentation
missing-docstring
def rename_parameter(
Ensure functions have docstrings for documentation
missing-docstring
def func(good_name): ...
Ensure functions have docstrings for documentation
missing-docstring
def decorator(f: Callable[_P, _R]) -> Callable[_P, _R]:
Ensure functions have docstrings for documentation
missing-docstring
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.