|
9 | 9 | # See the License for the specific language governing permissions and
|
10 | 10 | # limitations under the License.
|
11 | 11 |
|
12 |
| -import functools |
13 |
| -import inspect |
| 12 | +import types |
14 | 13 | import warnings
|
15 |
| -from typing import Callable, Type, TypeVar |
| 14 | +from functools import wraps |
| 15 | +from typing import Any, Callable, Optional, TypeVar, cast |
16 | 16 |
|
17 |
| -from packaging import version |
| 17 | +TObj = TypeVar("TObj") |
18 | 18 |
|
19 | 19 |
|
20 | 20 | def warning_deprecated(msg: str) -> None:
|
| 21 | + """ |
| 22 | + Display a warning message indicating that a certain functionality is deprecated. |
| 23 | +
|
| 24 | + :param msg: The warning message to display. |
| 25 | + """ |
21 | 26 | # Note: must use FutureWarning in order not to get suppressed by default
|
22 | 27 | warnings.warn(msg, FutureWarning, stacklevel=2)
|
23 | 28 |
|
24 | 29 |
|
25 |
| -ClassOrFn = TypeVar("ClassOrFn", Callable, Type) |
| 30 | +def deprecated( |
| 31 | + msg: Optional[str] = None, start_version: Optional[str] = None, end_version: Optional[str] = None |
| 32 | +) -> Callable[[TObj], TObj]: |
| 33 | + """ |
| 34 | + Decorator to mark a function or class as deprecated. |
26 | 35 |
|
| 36 | + :param msg: Message to provide additional information about the deprecation. |
| 37 | + :param start_version: Start version from which the function or class is deprecated. |
| 38 | + :param end_version: End version until which the function or class is deprecated. |
27 | 39 |
|
28 |
| -class deprecated: |
| 40 | + :return: The decorator function. |
29 | 41 | """
|
30 |
| - A decorator for marking function calls or class instantiations as deprecated. A call to the marked function or an |
31 |
| - instantiation of an object of the marked class will trigger a `FutureWarning`. If a class is marked as |
32 |
| - @deprecated, only the instantiations will trigger a warning, but static attribute accesses or method calls will not. |
| 42 | + |
| 43 | + def decorator(obj: TObj) -> TObj: |
| 44 | + |
| 45 | + if isinstance(obj, types.FunctionType): |
| 46 | + |
| 47 | + @wraps(obj) |
| 48 | + def wrapper(*args: Any, **kwargs: Any) -> Any: |
| 49 | + name = f"function '{obj.__module__}.{obj.__name__}'" |
| 50 | + text = _generate_deprecation_message(name, msg, start_version, end_version) |
| 51 | + warning_deprecated(text) |
| 52 | + return obj(*args, **kwargs) |
| 53 | + |
| 54 | + return cast(TObj, wrapper) |
| 55 | + |
| 56 | + if isinstance(obj, type): |
| 57 | + original_init = obj.__init__ # type: ignore[misc] |
| 58 | + |
| 59 | + @wraps(original_init) |
| 60 | + def wrapped_init(*args: Any, **kwargs: Any) -> Any: |
| 61 | + name = f"class '{obj.__module__}.{obj.__name__}'" |
| 62 | + text = _generate_deprecation_message(name, msg, start_version, end_version) |
| 63 | + warning_deprecated(text) |
| 64 | + return original_init(*args, **kwargs) |
| 65 | + |
| 66 | + obj.__init__ = wrapped_init # type: ignore[misc] |
| 67 | + |
| 68 | + return cast(TObj, obj) |
| 69 | + |
| 70 | + raise TypeError("The @deprecated decorator can only be used on functions or classes.") |
| 71 | + |
| 72 | + return decorator |
| 73 | + |
| 74 | + |
| 75 | +def _generate_deprecation_message( |
| 76 | + name: str, text: Optional[str], start_version: Optional[str], end_version: Optional[str] |
| 77 | +) -> str: |
33 | 78 | """
|
| 79 | + Generate a deprecation message for a given name, with optional start and end versions. |
34 | 80 |
|
35 |
| - def __init__(self, msg: str = None, start_version: str = None, end_version: str = None): |
36 |
| - """ |
37 |
| - :param msg: Custom message to be added after the boilerplate deprecation text. |
38 |
| - """ |
39 |
| - self.msg = msg |
40 |
| - self.start_version = version.parse(start_version) if start_version is not None else None |
41 |
| - self.end_version = version.parse(end_version) if end_version is not None else None |
42 |
| - |
43 |
| - def __call__(self, fn_or_class: ClassOrFn) -> ClassOrFn: |
44 |
| - name = fn_or_class.__module__ + "." + fn_or_class.__name__ |
45 |
| - if inspect.isclass(fn_or_class): |
46 |
| - fn_or_class.__init__ = self._get_wrapper(fn_or_class.__init__, name) |
47 |
| - return fn_or_class |
48 |
| - return self._get_wrapper(fn_or_class, name) |
49 |
| - |
50 |
| - def _get_wrapper(self, fn_to_wrap: Callable, name: str) -> Callable: |
51 |
| - @functools.wraps(fn_to_wrap) |
52 |
| - def wrapped(*args, **kwargs): |
53 |
| - msg = f"Usage of {name} is deprecated " |
54 |
| - if self.start_version is not None: |
55 |
| - msg += f"starting from NNCF v{str(self.start_version)} " |
56 |
| - msg += "and will be removed in " |
57 |
| - if self.end_version is not None: |
58 |
| - msg += f"NNCF v{str(self.end_version)}." |
59 |
| - else: |
60 |
| - msg += "a future NNCF version." |
61 |
| - if self.msg is not None: |
62 |
| - msg += "\n" + self.msg |
63 |
| - warning_deprecated(msg) |
64 |
| - return fn_to_wrap(*args, **kwargs) |
65 |
| - |
66 |
| - return wrapped |
| 81 | + :param name: The name of the deprecated feature. |
| 82 | + :param text: Additional text to include in the deprecation message. |
| 83 | + :param start_version: The version from which the feature is deprecated. |
| 84 | + :param end_version: The version in which the feature will be removed. |
| 85 | + :return: The deprecation message. |
| 86 | + """ |
| 87 | + msg = ( |
| 88 | + f"Usage of {name} is deprecated {f'starting from NNCF v{start_version} ' if start_version else ''}" |
| 89 | + f"and will be removed in {f'NNCF v{end_version}.' if end_version else 'a future NNCF version.'}" |
| 90 | + ) |
| 91 | + if text: |
| 92 | + return "\n".join([msg, text]) |
| 93 | + return msg |
0 commit comments