Skip to content

Commit 9ce254f

Browse files
committed
Add decorator for deprecated classes
1 parent e48106c commit 9ce254f

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

redisvl/utils/utils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,46 @@ def wrapper(*args, **kwargs):
173173
return decorator
174174

175175

176+
def deprecated_class(name: Optional[str] = None, replacement: Optional[str] = None):
177+
"""
178+
Decorator to mark a class as deprecated.
179+
180+
When the decorated class is instantiated, the decorator will emit a
181+
deprecation warning.
182+
183+
Args:
184+
name: Optional custom name for the class in the warning message.
185+
If not provided, uses the class's __name__.
186+
replacement: Optional message describing what to use instead.
187+
188+
Example:
189+
@deprecated_class(replacement="Use NewClass instead.")
190+
class OldClass:
191+
pass
192+
"""
193+
194+
def decorator(cls):
195+
class_name = name or cls.__name__
196+
warning_message = (
197+
f"Class {class_name} is deprecated and will be "
198+
"removed in the next major release. "
199+
)
200+
if replacement:
201+
warning_message += replacement
202+
203+
original_init = cls.__init__
204+
205+
@wraps(original_init)
206+
def new_init(self, *args, **kwargs):
207+
warn(warning_message, category=DeprecationWarning, stacklevel=2)
208+
original_init(self, *args, **kwargs)
209+
210+
cls.__init__ = new_init
211+
return cls
212+
213+
return decorator
214+
215+
176216
def sync_wrapper(fn: Callable[[], Coroutine[Any, Any, Any]]) -> Callable[[], None]:
177217
def wrapper():
178218
# Check if the interpreter is shutting down

tests/unit/test_utils.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
assert_no_warnings,
1717
denorm_cosine_distance,
1818
deprecated_argument,
19+
deprecated_class,
1920
deprecated_function,
2021
lazy_import,
2122
norm_cosine_distance,
@@ -534,6 +535,94 @@ def test_logging_configuration_not_overridden(self):
534535
), f"Date format changed: was present before: {has_date_pre}, present after: {has_date_post}"
535536

536537

538+
class TestDeprecatedClass:
539+
def test_deprecated_class_warning_with_replacement(self):
540+
@deprecated_class(replacement="Use NewClass instead.")
541+
class OldClass:
542+
def __init__(self, value):
543+
self.value = value
544+
545+
with pytest.warns(DeprecationWarning) as record:
546+
obj = OldClass(42)
547+
548+
assert len(record) == 1
549+
assert str(record[0].message) == (
550+
"Class OldClass is deprecated and will be removed in the next major release. "
551+
"Use NewClass instead."
552+
)
553+
assert obj.value == 42
554+
555+
def test_deprecated_class_warning_without_replacement(self):
556+
@deprecated_class()
557+
class OldClass:
558+
def __init__(self, value):
559+
self.value = value
560+
561+
with pytest.warns(DeprecationWarning) as record:
562+
obj = OldClass(42)
563+
564+
assert len(record) == 1
565+
assert str(record[0].message) == (
566+
"Class OldClass is deprecated and will be removed in the next major release. "
567+
)
568+
assert obj.value == 42
569+
570+
def test_deprecated_class_with_custom_name(self):
571+
@deprecated_class(name="CustomOldClass", replacement="Use NewClass instead.")
572+
class OldClass:
573+
pass
574+
575+
with pytest.warns(DeprecationWarning) as record:
576+
OldClass()
577+
578+
assert len(record) == 1
579+
assert str(record[0].message) == (
580+
"Class CustomOldClass is deprecated and will be removed in the next major release. "
581+
"Use NewClass instead."
582+
)
583+
584+
def test_deprecated_class_preserves_functionality(self):
585+
@deprecated_class(replacement="Use NewClass instead.")
586+
class OldClass:
587+
def __init__(self, x, y):
588+
self.x = x
589+
self.y = y
590+
591+
def add(self):
592+
return self.x + self.y
593+
594+
with pytest.warns(DeprecationWarning):
595+
obj = OldClass(10, 20)
596+
597+
assert obj.x == 10
598+
assert obj.y == 20
599+
assert obj.add() == 30
600+
601+
def test_deprecated_class_with_inheritance(self):
602+
@deprecated_class(replacement="Use NewBase instead.")
603+
class OldBase:
604+
def __init__(self, value):
605+
self.value = value
606+
607+
class Derived(OldBase):
608+
def __init__(self, value, extra):
609+
super().__init__(value)
610+
self.extra = extra
611+
612+
# Creating an instance of the deprecated base class should warn
613+
with pytest.warns(DeprecationWarning):
614+
base_obj = OldBase(42)
615+
616+
# Creating an instance of the derived class should also warn
617+
# because it calls the deprecated __init__
618+
with pytest.warns(DeprecationWarning):
619+
derived_obj = Derived(42, "extra")
620+
621+
assert base_obj.value == 42
622+
assert derived_obj.value == 42
623+
assert derived_obj.extra == "extra"
624+
625+
537626
class TestLazyImport:
538627
def test_import_standard_library(self):
539628
"""Test lazy importing of a standard library module"""

0 commit comments

Comments
 (0)