Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions libs/core/langchain_core/_api/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@
from langchain_core._api.internal import is_caller_internal


def _build_deprecation_message(
*,
alternative: str = "",
alternative_import: str = "",
) -> str:
"""Build a simple deprecation message for __deprecated__ attribute (PEP 702).

Args:
alternative: An alternative API name.
alternative_import: A fully qualified import path for the alternative.

Returns:
A deprecation message string for IDE/type checker display.
"""
if alternative_import:
return f"Use {alternative_import} instead."
if alternative:
return f"Use {alternative} instead."
return "Deprecated."
Comment on lines +31 to +49
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new __deprecated__ attribute functionality lacks test coverage. Tests should verify that:

  1. The __deprecated__ attribute is set correctly on deprecated functions, classes, and properties
  2. The attribute contains the expected message format (plain text without markdown backticks)
  3. The attribute is set with the correct alternative/alternative_import values when provided

Example test:

def test_deprecated_function_has_pep702_attribute():
    @deprecated(since="2.0.0", alternative="new_function")
    def old_function():
        pass
    
    assert hasattr(old_function, '__deprecated__')
    assert old_function.__deprecated__ == "Use new_function instead."

Copilot uses AI. Check for mistakes.


class LangChainDeprecationWarning(DeprecationWarning):
"""A class for issuing deprecation warnings for LangChain users."""

Expand Down Expand Up @@ -223,6 +244,11 @@ def warn_if_direct_instance(
obj.__init__ = functools.wraps(obj.__init__)( # type: ignore[misc]
warn_if_direct_instance
)
# Set __deprecated__ for PEP 702 (IDE/type checker support)
obj.__deprecated__ = _build_deprecation_message( # type: ignore[attr-defined]
alternative=_alternative,
alternative_import=_alternative_import,
Comment on lines +249 to +250
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __deprecated__ attribute is being set with _alternative and _alternative_import values that have been modified to include markdown backticks (lines 389-401). This means the PEP 702 deprecation message shown in IDEs will contain backticks like "Use some_function instead."

PEP 702's __deprecated__ attribute is meant for display in IDEs and type checkers, not for documentation, so it should use plain text without markdown formatting. The _build_deprecation_message calls should use the original, unmodified values of alternative and alternative_import parameters from the outer scope, not _alternative and _alternative_import which have been wrapped in backticks.

Suggested change
alternative=_alternative,
alternative_import=_alternative_import,
alternative=alternative,
alternative_import=alternative_import,

Copilot uses AI. Check for mistakes.
)
return obj

elif isinstance(obj, FieldInfoV1):
Expand Down Expand Up @@ -315,12 +341,15 @@ def __set_name__(self, owner: type | None, set_name: str) -> None:

def finalize(wrapper: Callable[..., Any], new_doc: str) -> T: # noqa: ARG001
"""Finalize the property."""
return cast(
"T",
_DeprecatedProperty(
fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc
),
prop = _DeprecatedProperty(
fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc
)
# Set __deprecated__ for PEP 702 (IDE/type checker support)
prop.__deprecated__ = _build_deprecation_message( # type: ignore[attr-defined]
alternative=_alternative,
alternative_import=_alternative_import,
)
Comment on lines +347 to +351
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as lines 247-251: _alternative and _alternative_import have been modified to include markdown backticks before being passed to _build_deprecation_message. Use the original alternative and alternative_import parameters instead.

Copilot uses AI. Check for mistakes.
return cast("T", prop)

else:
_name = _name or cast("type | Callable", obj).__qualname__
Expand All @@ -343,6 +372,11 @@ def finalize(wrapper: Callable[..., Any], new_doc: str) -> T:
"""
wrapper = functools.wraps(wrapped)(wrapper)
wrapper.__doc__ = new_doc
# Set __deprecated__ for PEP 702 (IDE/type checker support)
wrapper.__deprecated__ = _build_deprecation_message( # type: ignore[attr-defined]
alternative=_alternative,
alternative_import=_alternative_import,
Comment on lines +377 to +378
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as lines 247-251 and 347-351: _alternative and _alternative_import have been modified to include markdown backticks before being passed to _build_deprecation_message. Use the original alternative and alternative_import parameters instead.

Suggested change
alternative=_alternative,
alternative_import=_alternative_import,
alternative=alternative,
alternative_import=alternative_import,

Copilot uses AI. Check for mistakes.
)
return cast("T", wrapper)

old_doc = inspect.cleandoc(old_doc or "").strip("\n")
Expand Down
Loading