From 20b7bd497c0f39c351da5ecd4a86ddd2c988f78b Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Mon, 2 Oct 2023 13:55:16 -0400 Subject: [PATCH] Add pending deprecation warning (#11133) This PR uses 2 dedicated LangChain warnings types for deprecations (mirroring python's built in deprecation and pending deprecation warnings). These deprecation types are unslienced during initialization in langchain achieving the same default behavior that we have with our current warnings approach. However, because these warnings have a dedicated type, users will be able to silence them selectively (I think this is strictly better than our current handling of warnings). The PR adds a deprecation warning to llm symbolic math. --------- Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> --- libs/langchain/langchain/__init__.py | 6 + libs/langchain/langchain/_api/__init__.py | 4 + libs/langchain/langchain/_api/deprecation.py | 178 ++++++++++-------- .../chains/llm_symbolic_math/__init__.py | 10 + .../tests/unit_tests/_api/test_deprecation.py | 6 +- 5 files changed, 122 insertions(+), 82 deletions(-) diff --git a/libs/langchain/langchain/__init__.py b/libs/langchain/langchain/__init__.py index c22d789cae..feef1a7ee8 100644 --- a/libs/langchain/langchain/__init__.py +++ b/libs/langchain/langchain/__init__.py @@ -4,6 +4,8 @@ import warnings from importlib import metadata from typing import TYPE_CHECKING, Any, Optional +from langchain._api.deprecation import surface_langchain_deprecation_warnings + if TYPE_CHECKING: from langchain.schema import BaseCache @@ -40,6 +42,10 @@ def _warn_on_import(name: str) -> None: ) +# Surfaces Deprecation and Pending Deprecation warnings from langchain. +surface_langchain_deprecation_warnings() + + def __getattr__(name: str) -> Any: if name == "MRKLChain": from langchain.agents import MRKLChain diff --git a/libs/langchain/langchain/_api/__init__.py b/libs/langchain/langchain/_api/__init__.py index 168a99bcfd..e013a72129 100644 --- a/libs/langchain/langchain/_api/__init__.py +++ b/libs/langchain/langchain/_api/__init__.py @@ -13,10 +13,14 @@ from .deprecation import ( LangChainDeprecationWarning, deprecated, suppress_langchain_deprecation_warning, + surface_langchain_deprecation_warnings, + warn_deprecated, ) __all__ = [ "deprecated", "LangChainDeprecationWarning", "suppress_langchain_deprecation_warning", + "surface_langchain_deprecation_warnings", + "warn_deprecated", ] diff --git a/libs/langchain/langchain/_api/deprecation.py b/libs/langchain/langchain/_api/deprecation.py index ccc8e39716..6919504351 100644 --- a/libs/langchain/langchain/_api/deprecation.py +++ b/libs/langchain/langchain/_api/deprecation.py @@ -21,84 +21,8 @@ class LangChainDeprecationWarning(DeprecationWarning): """A class for issuing deprecation warnings for LangChain users.""" -def _warn_deprecated( - since: str, - *, - message: str = "", - name: str = "", - alternative: str = "", - pending: bool = False, - obj_type: str = "", - addendum: str = "", - removal: 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. - """ - if pending and removal: - raise ValueError("A pending deprecation cannot have a scheduled removal") - - if not pending: - if not removal: - removal = f"in {removal}" if removal else "within ?? minor releases" - raise NotImplementedError( - f"Need to determine which default deprecation schedule to use. " - f"{removal}" - ) - else: - removal = f"in {removal}" - - if not message: - message = "" - - if obj_type: - message += f"The {obj_type} `{name}`" - else: - message += f"`{name}`" - - if pending: - message += " will be deprecated in a future version" - else: - message += f" was deprecated in LangChain {since}" - - if removal: - message += f" and will be removed {removal}" - - if alternative: - message += f". Use {alternative} instead." - - if addendum: - message += f" {addendum}" - - warning_cls = PendingDeprecationWarning if pending else LangChainDeprecationWarning - warning = warning_cls(message) - warnings.warn(warning, category=LangChainDeprecationWarning, stacklevel=2) +class LangChainPendingDeprecationWarning(PendingDeprecationWarning): + """A class for issuing deprecation warnings for LangChain users.""" # PUBLIC API @@ -262,7 +186,7 @@ def deprecated( def emit_warning() -> None: """Emit the warning.""" - _warn_deprecated( + warn_deprecated( since, message=_message, name=_name, @@ -318,4 +242,100 @@ def suppress_langchain_deprecation_warning() -> Generator[None, None, None]: """Context manager to suppress LangChainDeprecationWarning.""" with warnings.catch_warnings(): warnings.simplefilter("ignore", LangChainDeprecationWarning) + warnings.simplefilter("ignore", LangChainPendingDeprecationWarning) yield + + +def warn_deprecated( + since: str, + *, + message: str = "", + name: str = "", + alternative: str = "", + pending: bool = False, + obj_type: str = "", + addendum: str = "", + removal: 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. + """ + if pending and removal: + raise ValueError("A pending deprecation cannot have a scheduled removal") + + if not pending: + if not removal: + removal = f"in {removal}" if removal else "within ?? minor releases" + raise NotImplementedError( + f"Need to determine which default deprecation schedule to use. " + f"{removal}" + ) + else: + removal = f"in {removal}" + + if not message: + message = "" + + if obj_type: + message += f"The {obj_type} `{name}`" + else: + message += f"`{name}`" + + if pending: + message += " will be deprecated in a future version" + else: + message += f" was deprecated in LangChain {since}" + + if removal: + message += f" and will be removed {removal}" + + if alternative: + message += f". Use {alternative} instead." + + if addendum: + message += f" {addendum}" + + warning_cls = ( + LangChainPendingDeprecationWarning if pending else LangChainDeprecationWarning + ) + warning = warning_cls(message) + warnings.warn(warning, category=LangChainDeprecationWarning, stacklevel=2) + + +def surface_langchain_deprecation_warnings() -> None: + """Unmute LangChain deprecation warnings.""" + warnings.filterwarnings( + "default", + category=LangChainPendingDeprecationWarning, + ) + + warnings.filterwarnings( + "default", + category=LangChainDeprecationWarning, + ) diff --git a/libs/langchain/langchain/chains/llm_symbolic_math/__init__.py b/libs/langchain/langchain/chains/llm_symbolic_math/__init__.py index d6cde9105a..6ba7b01959 100644 --- a/libs/langchain/langchain/chains/llm_symbolic_math/__init__.py +++ b/libs/langchain/langchain/chains/llm_symbolic_math/__init__.py @@ -2,3 +2,13 @@ Heavily borrowed from llm_math, wrapper for SymPy """ +from langchain._api import warn_deprecated + +warn_deprecated( + since="0.0.304", + message=( + "On 2023-10-06 this module will be moved to langchain-experimental as " + "it relies on sympy https://github.com/sympy/sympy/issues/10805" + ), + pending=True, +) diff --git a/libs/langchain/tests/unit_tests/_api/test_deprecation.py b/libs/langchain/tests/unit_tests/_api/test_deprecation.py index 7b6b359cbf..cecb034d4d 100644 --- a/libs/langchain/tests/unit_tests/_api/test_deprecation.py +++ b/libs/langchain/tests/unit_tests/_api/test_deprecation.py @@ -3,7 +3,7 @@ from typing import Any, Dict import pytest -from langchain._api.deprecation import _warn_deprecated, deprecated +from langchain._api.deprecation import deprecated, warn_deprecated from langchain.pydantic_v1 import BaseModel @@ -55,7 +55,7 @@ def test_warn_deprecated(kwargs: Dict[str, Any], expected_message: str) -> None: with warnings.catch_warnings(record=True) as warning_list: warnings.simplefilter("always") - _warn_deprecated(**kwargs) + warn_deprecated(**kwargs) assert len(warning_list) == 1 warning = warning_list[0].message @@ -65,7 +65,7 @@ def test_warn_deprecated(kwargs: Dict[str, Any], expected_message: str) -> None: def test_undefined_deprecation_schedule() -> None: """This test is expected to fail until we defined a deprecation schedule.""" with pytest.raises(NotImplementedError): - _warn_deprecated("1.0.0", pending=False) + warn_deprecated("1.0.0", pending=False) @deprecated(since="2.0.0", removal="3.0.0", pending=False)