Commit bb81242f authored by Noé Gaumont's avatar Noé Gaumont 🐙
Browse files

refactor(logilab.common.deprecation): add types

parent ff3479ad6c95
Pipeline #8736 passed with stages
in 3 minutes and 43 seconds
......@@ -23,9 +23,11 @@ import os
import sys
from warnings import warn
from functools import WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
from typing import Any, Callable, Dict, Optional, Iterable
from typing_extensions import Protocol
def lazy_wraps(wrapped):
def lazy_wraps(wrapped: Callable) -> Callable:
"""
This is the equivalent of the @wraps decorator of functools except it won't
try to grabs attributes of the targeted function on decoration but on access.
......@@ -44,19 +46,19 @@ def lazy_wraps(wrapped):
>>> def wrapper(*args, **kwargs): ...
"""
def update_wrapper_attributes(wrapper):
def __getattribute__(self, attribute):
def update_wrapper_attributes(wrapper: Callable) -> Callable:
def __getattribute__(self, attribute: str) -> Any:
if attribute in WRAPPER_ASSIGNMENTS:
return getattr(wrapped, attribute)
return super(self.__class__, self).__getattribute__(attribute)
wrapper.__getattribute__ = __getattribute__
wrapper.__getattribute__ = __getattribute__ # type: ignore
for attribute in WRAPPER_UPDATES:
getattr(wrapper, attribute).update(getattr(wrapped, attribute, {}))
wrapper.__wrapped__ = wrapped
wrapper.__wrapped__ = wrapped # type: ignore
return wrapper
......@@ -67,16 +69,16 @@ class DeprecationWrapper(object):
"""proxy to print a warning on access to any attribute of the wrapped object
"""
def __init__(self, proxied, msg=None, version=None):
def __init__(self, proxied, msg: Optional[str] = None, version: Optional[str] = None) -> None:
self._proxied = proxied
self._msg = msg
self.version = version
self._msg: str = msg if msg else ""
self.version: Optional[str] = version
def __getattr__(self, attr):
def __getattr__(self, attr: str) -> Any:
send_warning(self._msg, stacklevel=3, version=self.version)
return getattr(self._proxied, attr)
def __setattr__(self, attr, value):
def __setattr__(self, attr: str, value: Any) -> None:
if attr in ("_proxied", "_msg"):
self.__dict__[attr] = value
else:
......@@ -84,7 +86,7 @@ class DeprecationWrapper(object):
setattr(self._proxied, attr, value)
def _get_module_name(number=1):
def _get_module_name(number: int = 1) -> str:
"""
automagically try to determine the package name from which the warning has
been triggered by loop other calling frames.
......@@ -111,7 +113,12 @@ def _get_module_name(number=1):
return file_name
def send_warning(reason, version=None, stacklevel=2, module_name=None):
def send_warning(
reason: str,
version: Optional[str] = None,
stacklevel: int = 2,
module_name: Optional[str] = None,
) -> None:
"""Display a deprecation message only if the version is older than the
compatible version.
"""
......@@ -125,7 +132,9 @@ def send_warning(reason, version=None, stacklevel=2, module_name=None):
warn(reason, DeprecationWarning, stacklevel=stacklevel)
def callable_renamed(old_name, new_function, version=None):
def callable_renamed(
old_name: str, new_function: Callable, version: Optional[str] = None
) -> Callable:
"""use to tell that a callable has been renamed.
It returns a callable wrapper, so that when its called a warning is printed
......@@ -154,10 +163,12 @@ def callable_renamed(old_name, new_function, version=None):
return wrapped
renamed = callable_renamed(old_name="renamed", new_function=callable_renamed)
renamed: Callable[[str, Callable, Optional[str]], Callable] = callable_renamed(
old_name="renamed", new_function=callable_renamed
)
def argument_removed(old_argument_name, version=None):
def argument_removed(old_argument_name: str, version: Optional[str] = None) -> Callable:
"""
callable decorator to allow getting backward compatibility for renamed keyword arguments.
......@@ -170,7 +181,7 @@ def argument_removed(old_argument_name, version=None):
42
"""
def _wrap(func):
def _wrap(func: Callable) -> Callable:
@lazy_wraps(func)
def check_kwargs(*args, **kwargs):
if old_argument_name in kwargs:
......@@ -192,15 +203,17 @@ def argument_removed(old_argument_name, version=None):
@argument_removed("name")
@argument_removed("doc")
def callable_deprecated(reason=None, version=None, stacklevel=2):
def callable_deprecated(
reason: Optional[str] = None, version: Optional[str] = None, stacklevel: int = 2
) -> Callable:
"""Display a deprecation message only if the version is older than the
compatible version.
"""
def decorator(func):
def decorator(func: Callable) -> Callable:
@lazy_wraps(func)
def wrapped(*args, **kwargs):
message = reason or 'The function "%s" is deprecated'
def wrapped(*args, **kwargs) -> Callable:
message: str = reason or 'The function "%s" is deprecated'
if "%s" in message:
message %= func.__name__
......@@ -212,13 +225,24 @@ def callable_deprecated(reason=None, version=None, stacklevel=2):
return decorator
deprecated = callable_renamed(old_name="deprecated", new_function=callable_deprecated)
class CallableDeprecatedCallable(Protocol):
def __call__(
self, reason: Optional[str] = None, version: Optional[str] = None, stacklevel: int = 2
) -> Callable:
...
def class_deprecated(old_name, parent, class_dict):
class DeprecatedClass(*parent):
deprecated: CallableDeprecatedCallable = callable_renamed(
old_name="deprecated", new_function=callable_deprecated
)
def class_deprecated(old_name: str, parent: Iterable[type], class_dict: Dict[str, Any]) -> type:
class DeprecatedClass(*parent): # type: ignore
def __init__(self, *args, **kwargs):
msg = class_dict.get("__deprecation_warning__", f"{old_name} is deprecated")
msg: str = class_dict.get(
"__deprecation_warning__", f"{old_name} is deprecated"
) # type: ignore
send_warning(
msg,
stacklevel=class_dict.get("__deprecation_warning_stacklevel__", 3),
......@@ -234,7 +258,7 @@ def class_deprecated(old_name, parent, class_dict):
return DeprecatedClass
def attribute_renamed(old_name, new_name, version=None):
def attribute_renamed(old_name: str, new_name: str, version: Optional[str] = None) -> Callable:
"""
class decorator to allow getting backward compatibility for renamed attributes.
......@@ -257,21 +281,21 @@ def attribute_renamed(old_name, new_name, version=None):
True
"""
def _class_wrap(klass):
def _class_wrap(klass: type) -> type:
reason = (
f"{klass.__name__}.{old_name} has been renamed and is deprecated, use "
f"{klass.__name__}.{new_name} instead"
)
def _get_old(self):
def _get_old(self) -> Any:
send_warning(reason, stacklevel=3, version=version, module_name=klass.__module__)
return getattr(self, new_name)
def _set_old(self, value):
def _set_old(self, value: Any) -> None:
send_warning(reason, stacklevel=3, version=version, module_name=klass.__module__)
setattr(self, new_name, value)
def _del_old(self):
def _del_old(self) -> None:
send_warning(reason, stacklevel=3, version=version, module_name=klass.__module__)
delattr(self, new_name)
......@@ -282,7 +306,7 @@ def attribute_renamed(old_name, new_name, version=None):
return _class_wrap
def argument_renamed(old_name, new_name, version=None):
def argument_renamed(old_name: str, new_name: str, version: Optional[str] = None) -> Callable:
"""
callable decorator to allow getting backward compatibility for renamed keyword arguments.
......@@ -296,9 +320,9 @@ def argument_renamed(old_name, new_name, version=None):
42
"""
def _wrap(func):
def _wrap(func: Callable) -> Callable:
@lazy_wraps(func)
def check_kwargs(*args, **kwargs):
def check_kwargs(*args, **kwargs) -> Callable:
if old_name in kwargs and new_name in kwargs:
raise ValueError(
f"argument {old_name} of callable {func.__name__} has been "
......@@ -326,7 +350,9 @@ def argument_renamed(old_name, new_name, version=None):
@argument_renamed(old_name="modpath", new_name="module_path")
@argument_renamed(old_name="objname", new_name="object_name")
def callable_moved(module_name, object_name, version=None, stacklevel=2):
def callable_moved(
module_name: str, object_name: str, version: Optional[str] = None, stacklevel: int = 2
) -> Callable:
"""use to tell that a callable has been moved to a new module.
It returns a callable wrapper, so that when its called a warning is printed
......@@ -352,10 +378,18 @@ def callable_moved(module_name, object_name, version=None, stacklevel=2):
return callnew
moved = callable_renamed(old_name="moved", new_function=callable_moved)
moved: Callable[[Optional[str], Optional[str], int], Callable] = callable_renamed(
old_name="moved", new_function=callable_moved
)
def class_renamed(old_name, new_class, message=None, version=None, module_name=None):
def class_renamed(
old_name: str,
new_class: type,
message: Optional[str] = None,
version: Optional[str] = None,
module_name: Optional[str] = None,
) -> type:
"""automatically creates a class which fires a DeprecationWarning
when instantiated.
......@@ -365,7 +399,7 @@ def class_renamed(old_name, new_class, message=None, version=None, module_name=N
s = Set()
>>>
"""
class_dict = {}
class_dict: Dict[str, Any] = {}
if message is None:
message = "%s is deprecated, use %s instead" % (old_name, new_class.__name__)
......@@ -381,7 +415,12 @@ def class_renamed(old_name, new_class, message=None, version=None, module_name=N
return class_deprecated(old_name, (new_class,), class_dict)
def class_moved(new_class, old_name=None, message=None, version=None):
def class_moved(
new_class: type,
old_name: Optional[str] = None,
message: Optional[str] = None,
version: Optional[str] = None,
) -> type:
"""nice wrapper around class_renamed when a class has been moved into
another module
"""
......
......@@ -100,7 +100,7 @@ import re
import sys
import os.path as osp
from time import process_time, time
from re import Match
from re import Match # type: ignore
import warnings
import types
import inspect
......
......@@ -46,7 +46,7 @@ __docformat__ = "restructuredtext en"
import sys
import re
import os.path as osp
from re import Pattern, Match
from re import Pattern, Match # type: ignore
from warnings import warn
from unicodedata import normalize as _uninormalize
from typing import Any, Optional, Tuple, List, Callable, Dict, Union
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment