refactor(core): improve beta decorator (#32505)

This is better than using a subclass as returning a `property` works
with `ClassWithBetaMethods.beta_property.__doc__`

Co-authored-by: Mason Daugherty <mason@langchain.dev>
This commit is contained in:
Christophe Bornet
2025-09-09 00:06:48 +02:00
committed by GitHub
parent c3b28c769a
commit 714f74a847
2 changed files with 33 additions and 48 deletions

View File

@@ -156,50 +156,24 @@ def beta(
_name = _name or obj.fget.__qualname__
old_doc = obj.__doc__
class _BetaProperty(property):
"""A beta property."""
def _fget(instance: Any) -> Any:
if instance is not None:
emit_warning()
return obj.fget(instance)
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 = fget
self.__orig_fset = fset
self.__orig_fdel = fdel
self.__doc__ = doc
def _fset(instance: Any, value: Any) -> None:
if instance is not None:
emit_warning()
obj.fset(instance, value)
def __get__(
self, instance: Any, owner: Union[type, None] = None
) -> Any:
if instance is not None or owner is not None:
emit_warning()
return self.fget(instance)
def _fdel(instance: Any) -> None:
if instance is not None:
emit_warning()
obj.fdel(instance)
def __set__(self, instance: Any, value: Any) -> None:
if instance is not None:
emit_warning()
return self.fset(instance, value)
def __delete__(self, instance: Any) -> None:
if instance is not None:
emit_warning()
return self.fdel(instance)
def __set_name__(self, owner: Union[type, None], set_name: str) -> None:
nonlocal _name
if _name == "<lambda>":
_name = set_name
def finalize(wrapper: Callable[..., Any], new_doc: str) -> Any: # noqa: ARG001
def finalize(_wrapper: Callable[..., Any], new_doc: str) -> Any:
"""Finalize the property."""
return _BetaProperty(
fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc
)
return property(fget=_fget, fset=_fset, fdel=_fdel, doc=new_doc)
else:
_name = _name or obj.__qualname__
if not _obj_type:

View File

@@ -91,12 +91,20 @@ class ClassWithBetaMethods:
"""Original doc."""
return "This is a beta staticmethod."
@beta() # type: ignore[prop-decorator]
@property
def beta_property(self) -> str:
"""Original doc."""
return "This is a beta property."
@beta_property.setter
def beta_property(self, _value: str) -> None:
pass
@beta() # type: ignore[misc]
@beta_property.deleter
def beta_property(self) -> None:
pass
def test_beta_function() -> None:
"""Test beta function."""
@@ -222,14 +230,17 @@ def test_beta_property() -> None:
obj = ClassWithBetaMethods()
assert obj.beta_property == "This is a beta property."
assert len(warning_list) == 1
warning = warning_list[0].message
obj.beta_property = "foo"
assert str(warning) == (
"The attribute `ClassWithBetaMethods.beta_property` is in beta. "
"It is actively being worked on, so the API may change."
)
doc = ClassWithBetaMethods.__dict__["beta_property"].__doc__
del obj.beta_property
assert len(warning_list) == 3
for warning in warning_list:
assert str(warning.message) == (
"The attribute `ClassWithBetaMethods.beta_property` is in beta. "
"It is actively being worked on, so the API may change."
)
doc = ClassWithBetaMethods.beta_property.__doc__
assert isinstance(doc, str)
assert doc.startswith(".. beta::")