Skip to content

Commit 91dac71

Browse files
committed
cr
1 parent af9d07e commit 91dac71

File tree

2 files changed

+163
-83
lines changed

2 files changed

+163
-83
lines changed

libs/core/langchain_core/_api/deprecation.py

Lines changed: 78 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def _build_deprecation_message(
3333
alternative: str = "",
3434
alternative_import: str = "",
3535
) -> str:
36-
"""Build a simple deprecation message for __deprecated__ attribute (PEP 702).
36+
"""Build a simple deprecation message for `__deprecated__` attribute.
3737
3838
Args:
3939
alternative: An alternative API name.
@@ -102,60 +102,57 @@ def deprecated(
102102
) -> Callable[[T], T]:
103103
"""Decorator to mark a function, a class, or a property as deprecated.
104104
105-
When deprecating a classmethod, a staticmethod, or a property, the
106-
`@deprecated` decorator should go *under* `@classmethod` and
107-
`@staticmethod` (i.e., `deprecated` should directly decorate the
108-
underlying callable), but *over* `@property`.
105+
When deprecating a classmethod, a staticmethod, or a property, the `@deprecated`
106+
decorator should go *under* `@classmethod` and `@staticmethod` (i.e., `deprecated`
107+
should directly decorate the underlying callable), but *over* `@property`.
109108
110-
When deprecating a class `C` intended to be used as a base class in a
111-
multiple inheritance hierarchy, `C` *must* define an `__init__` method
112-
(if `C` instead inherited its `__init__` from its own base class, then
113-
`@deprecated` would mess up `__init__` inheritance when installing its
114-
own (deprecation-emitting) `C.__init__`).
109+
When deprecating a class `C` intended to be used as a base class in a multiple
110+
inheritance hierarchy, `C` *must* define an `__init__` method (if `C` instead
111+
inherited its `__init__` from its own base class, then `@deprecated` would mess up
112+
`__init__` inheritance when installing its own (deprecation-emitting) `C.__init__`).
115113
116-
Parameters are the same as for `warn_deprecated`, except that *obj_type*
117-
defaults to 'class' if decorating a class, 'attribute' if decorating a
118-
property, and 'function' otherwise.
114+
Parameters are the same as for `warn_deprecated`, except that *obj_type* defaults to
115+
'class' if decorating a class, 'attribute' if decorating a property, and 'function'
116+
otherwise.
119117
120118
Args:
121-
since:
122-
The release at which this API became deprecated.
123-
message:
124-
Override the default deprecation message. The %(since)s,
125-
%(name)s, %(alternative)s, %(obj_type)s, %(addendum)s,
126-
and %(removal)s format specifiers will be replaced by the
119+
since: The release at which this API became deprecated.
120+
message: Override the default deprecation message.
121+
122+
The `%(since)s`, `%(name)s`, `%(alternative)s`, `%(obj_type)s`,
123+
`%(addendum)s`, and `%(removal)s` format specifiers will be replaced by the
127124
values of the respective arguments passed to this function.
128-
name:
129-
The name of the deprecated object.
130-
alternative:
131-
An alternative API that the user may use in place of the
132-
deprecated API. The deprecation warning will tell the user
133-
about this alternative if provided.
134-
alternative_import:
135-
An alternative import that the user may use instead.
136-
pending:
137-
If `True`, uses a `PendingDeprecationWarning` instead of a
138-
DeprecationWarning. Cannot be used together with removal.
139-
obj_type:
140-
The object type being deprecated.
141-
addendum:
142-
Additional text appended directly to the final message.
143-
removal:
144-
The expected removal version. With the default (an empty
145-
string), a removal version is automatically computed from
146-
since. Set to other Falsy values to not schedule a removal
147-
date. Cannot be used together with pending.
148-
package:
149-
The package of the deprecated object.
125+
name: The name of the deprecated object.
126+
alternative: An alternative API that the user may use in place of the deprecated
127+
API.
128+
129+
The deprecation warning will tell the user about this alternative if
130+
provided.
131+
alternative_import: An alternative import that the user may use instead.
132+
pending: If `True`, uses a `PendingDeprecationWarning` instead of a
133+
`DeprecationWarning`.
134+
135+
Cannot be used together with removal.
136+
obj_type: The object type being deprecated.
137+
addendum: Additional text appended directly to the final message.
138+
removal: The expected removal version.
139+
140+
With the default (an empty string), a removal version is automatically
141+
computed from since. Set to other Falsy values to not schedule a removal
142+
date.
143+
144+
Cannot be used together with pending.
145+
package: The package of the deprecated object.
150146
151147
Returns:
152148
A decorator to mark a function or class as deprecated.
153149
154-
```python
155-
@deprecated("1.4.0")
156-
def the_function_to_deprecate():
157-
pass
158-
```
150+
Example:
151+
```python
152+
@deprecated("1.4.0")
153+
def the_function_to_deprecate():
154+
pass
155+
```
159156
"""
160157
_validate_deprecation_params(
161158
removal, alternative, alternative_import, pending=pending
@@ -246,8 +243,8 @@ def warn_if_direct_instance(
246243
)
247244
# Set __deprecated__ for PEP 702 (IDE/type checker support)
248245
obj.__deprecated__ = _build_deprecation_message( # type: ignore[attr-defined]
249-
alternative=_alternative,
250-
alternative_import=_alternative_import,
246+
alternative=alternative,
247+
alternative_import=alternative_import,
251248
)
252249
return obj
253250

@@ -346,8 +343,8 @@ def finalize(wrapper: Callable[..., Any], new_doc: str) -> T: # noqa: ARG001
346343
)
347344
# Set __deprecated__ for PEP 702 (IDE/type checker support)
348345
prop.__deprecated__ = _build_deprecation_message( # type: ignore[attr-defined]
349-
alternative=_alternative,
350-
alternative_import=_alternative_import,
346+
alternative=alternative,
347+
alternative_import=alternative_import,
351348
)
352349
return cast("T", prop)
353350

@@ -374,8 +371,8 @@ def finalize(wrapper: Callable[..., Any], new_doc: str) -> T:
374371
wrapper.__doc__ = new_doc
375372
# Set __deprecated__ for PEP 702 (IDE/type checker support)
376373
wrapper.__deprecated__ = _build_deprecation_message( # type: ignore[attr-defined]
377-
alternative=_alternative,
378-
alternative_import=_alternative_import,
374+
alternative=alternative,
375+
alternative_import=alternative_import,
379376
)
380377
return cast("T", wrapper)
381378

@@ -432,7 +429,7 @@ def finalize(wrapper: Callable[..., Any], new_doc: str) -> T:
432429

433430
@contextlib.contextmanager
434431
def suppress_langchain_deprecation_warning() -> Generator[None, None, None]:
435-
"""Context manager to suppress LangChainDeprecationWarning."""
432+
"""Context manager to suppress `LangChainDeprecationWarning`."""
436433
with warnings.catch_warnings():
437434
warnings.simplefilter("ignore", LangChainDeprecationWarning)
438435
warnings.simplefilter("ignore", LangChainPendingDeprecationWarning)
@@ -455,35 +452,33 @@ def warn_deprecated(
455452
"""Display a standardized deprecation.
456453
457454
Args:
458-
since:
459-
The release at which this API became deprecated.
460-
message:
461-
Override the default deprecation message. The %(since)s,
462-
%(name)s, %(alternative)s, %(obj_type)s, %(addendum)s,
463-
and %(removal)s format specifiers will be replaced by the
455+
since: The release at which this API became deprecated.
456+
message: Override the default deprecation message.
457+
458+
The `%(since)s`, `%(name)s`, `%(alternative)s`, `%(obj_type)s`,
459+
`%(addendum)s`, and `%(removal)s` format specifiers will be replaced by the
464460
values of the respective arguments passed to this function.
465-
name:
466-
The name of the deprecated object.
467-
alternative:
468-
An alternative API that the user may use in place of the
469-
deprecated API. The deprecation warning will tell the user
470-
about this alternative if provided.
471-
alternative_import:
472-
An alternative import that the user may use instead.
473-
pending:
474-
If `True`, uses a `PendingDeprecationWarning` instead of a
475-
DeprecationWarning. Cannot be used together with removal.
476-
obj_type:
477-
The object type being deprecated.
478-
addendum:
479-
Additional text appended directly to the final message.
480-
removal:
481-
The expected removal version. With the default (an empty
482-
string), a removal version is automatically computed from
483-
since. Set to other Falsy values to not schedule a removal
484-
date. Cannot be used together with pending.
485-
package:
486-
The package of the deprecated object.
461+
name: The name of the deprecated object.
462+
alternative: An alternative API that the user may use in place of the
463+
deprecated API.
464+
465+
The deprecation warning will tell the user about this alternative if
466+
provided.
467+
alternative_import: An alternative import that the user may use instead.
468+
pending: If `True`, uses a `PendingDeprecationWarning` instead of a
469+
`DeprecationWarning`.
470+
471+
Cannot be used together with removal.
472+
obj_type: The object type being deprecated.
473+
addendum: Additional text appended directly to the final message.
474+
removal: The expected removal version.
475+
476+
With the default (an empty string), a removal version is automatically
477+
computed from since. Set to other Falsy values to not schedule a removal
478+
date.
479+
480+
Cannot be used together with pending.
481+
package: The package of the deprecated object.
487482
"""
488483
if not pending:
489484
if not removal:
@@ -568,8 +563,8 @@ def rename_parameter(
568563
"""Decorator indicating that parameter *old* of *func* is renamed to *new*.
569564
570565
The actual implementation of *func* should use *new*, not *old*. If *old* is passed
571-
to *func*, a DeprecationWarning is emitted, and its value is used, even if *new* is
572-
also passed by keyword.
566+
to *func*, a `DeprecationWarning` is emitted, and its value is used, even if *new*
567+
is also passed by keyword.
573568
574569
Args:
575570
since: The version in which the parameter was renamed.

libs/core/tests/unit_tests/_api/test_deprecation.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,88 @@ def a(self, new_name: str) -> str:
493493

494494
with pytest.raises(TypeError):
495495
assert foo.a("hello", old_name="hello") # type: ignore[call-arg]
496+
497+
498+
# Tests for PEP 702 __deprecated__ attribute
499+
500+
501+
def test_deprecated_function_has_pep702_attribute() -> None:
502+
"""Test that deprecated functions have `__deprecated__` attribute."""
503+
504+
@deprecated(since="2.0.0", removal="3.0.0", alternative="new_function")
505+
def old_function() -> str:
506+
"""Original doc."""
507+
return "old"
508+
509+
assert hasattr(old_function, "__deprecated__")
510+
assert old_function.__deprecated__ == "Use new_function instead."
511+
512+
513+
def test_deprecated_function_with_alternative_import_has_pep702_attribute() -> None:
514+
"""Test `__deprecated__` with `alternative_import`."""
515+
516+
@deprecated(
517+
since="2.0.0", removal="3.0.0", alternative_import="new_module.new_function"
518+
)
519+
def old_function() -> str:
520+
"""Original doc."""
521+
return "old"
522+
523+
assert hasattr(old_function, "__deprecated__")
524+
assert old_function.__deprecated__ == "Use new_module.new_function instead."
525+
526+
527+
def test_deprecated_function_without_alternative_has_pep702_attribute() -> None:
528+
"""Test `__deprecated__` without alternative shows `'Deprecated.'`."""
529+
530+
@deprecated(since="2.0.0", removal="3.0.0")
531+
def old_function() -> str:
532+
"""Original doc."""
533+
return "old"
534+
535+
assert hasattr(old_function, "__deprecated__")
536+
assert old_function.__deprecated__ == "Deprecated."
537+
538+
539+
def test_deprecated_class_has_pep702_attribute() -> None:
540+
"""Test that deprecated classes have `__deprecated__` attribute (PEP 702)."""
541+
542+
@deprecated(since="2.0.0", removal="3.0.0", alternative="NewClass")
543+
class OldClass:
544+
def __init__(self) -> None:
545+
"""Original doc."""
546+
547+
assert hasattr(OldClass, "__deprecated__")
548+
assert OldClass.__deprecated__ == "Use NewClass instead."
549+
550+
551+
def test_deprecated_class_without_alternative_has_pep702_attribute() -> None:
552+
"""Test `__deprecated__` on class without alternative."""
553+
554+
@deprecated(since="2.0.0", removal="3.0.0")
555+
class OldClass:
556+
def __init__(self) -> None:
557+
"""Original doc."""
558+
559+
assert hasattr(OldClass, "__deprecated__")
560+
assert OldClass.__deprecated__ == "Deprecated."
561+
562+
563+
def test_deprecated_property_has_pep702_attribute() -> None:
564+
"""Test that deprecated properties have `__deprecated__` attribute (PEP 702).
565+
566+
Note: When using @property over @deprecated (which is what works in practice),
567+
the `__deprecated__` attribute is set on the property's underlying `fget` function.
568+
"""
569+
570+
class MyClass:
571+
@property
572+
@deprecated(since="2.0.0", removal="3.0.0", alternative="new_property")
573+
def old_property(self) -> str:
574+
"""Original doc."""
575+
return "old"
576+
577+
prop = MyClass.__dict__["old_property"]
578+
# The __deprecated__ attribute is on the underlying fget function
579+
assert hasattr(prop.fget, "__deprecated__")
580+
assert prop.fget.__deprecated__ == "Use new_property instead."

0 commit comments

Comments
 (0)