Add typing to scrapy/utils/deprecate.py.

This commit is contained in:
Andrey Rakhmatullin 2023-05-07 18:50:23 +04:00
parent d400f1ac06
commit 9661a5c491
1 changed files with 36 additions and 23 deletions

View File

@ -2,12 +2,12 @@
import inspect
import warnings
from typing import List, Tuple
from typing import Any, List, Optional, Tuple, Type, overload
from scrapy.exceptions import ScrapyDeprecationWarning
def attribute(obj, oldattr, newattr, version="0.12"):
def attribute(obj: Any, oldattr: str, newattr: str, version: str = "0.12") -> None:
cname = obj.__class__.__name__
warnings.warn(
f"{cname}.{oldattr} attribute is deprecated and will be no longer supported "
@ -18,16 +18,16 @@ def attribute(obj, oldattr, newattr, version="0.12"):
def create_deprecated_class(
name,
new_class,
clsdict=None,
warn_category=ScrapyDeprecationWarning,
warn_once=True,
old_class_path=None,
new_class_path=None,
subclass_warn_message="{cls} inherits from deprecated class {old}, please inherit from {new}.",
instance_warn_message="{cls} is deprecated, instantiate {new} instead.",
):
name: str,
new_class: type,
clsdict: Optional[dict[str, Any]] = None,
warn_category: Type[Warning] = ScrapyDeprecationWarning,
warn_once: bool = True,
old_class_path: Optional[str] = None,
new_class_path: Optional[str] = None,
subclass_warn_message: str = "{cls} inherits from deprecated class {old}, please inherit from {new}.",
instance_warn_message: str = "{cls} is deprecated, instantiate {new} instead.",
) -> type:
"""
Return a "deprecated" class that causes its subclasses to issue a warning.
Subclasses of ``new_class`` are considered subclasses of this class.
@ -53,17 +53,20 @@ def create_deprecated_class(
OldName.
"""
class DeprecatedClass(new_class.__class__):
deprecated_class = None
warned_on_subclass = False
# https://github.com/python/mypy/issues/4177
class DeprecatedClass(new_class.__class__): # type: ignore[misc, name-defined]
deprecated_class: Optional[type] = None
warned_on_subclass: bool = False
def __new__(metacls, name, bases, clsdict_):
def __new__(
metacls, name: str, bases: Tuple[type, ...], clsdict_: dict[str, Any]
) -> type:
cls = super().__new__(metacls, name, bases, clsdict_)
if metacls.deprecated_class is None:
metacls.deprecated_class = cls
return cls
def __init__(cls, name, bases, clsdict_):
def __init__(cls, name: str, bases: Tuple[type, ...], clsdict_: dict[str, Any]):
meta = cls.__class__
old = meta.deprecated_class
if old in bases and not (warn_once and meta.warned_on_subclass):
@ -81,10 +84,10 @@ def create_deprecated_class(
# see https://www.python.org/dev/peps/pep-3119/#overloading-isinstance-and-issubclass
# and https://docs.python.org/reference/datamodel.html#customizing-instance-and-subclass-checks
# for implementation details
def __instancecheck__(cls, inst):
def __instancecheck__(cls, inst: Any) -> bool:
return any(cls.__subclasscheck__(c) for c in (type(inst), inst.__class__))
def __subclasscheck__(cls, sub):
def __subclasscheck__(cls, sub: type) -> bool:
if cls is not DeprecatedClass.deprecated_class:
# we should do the magic only if second `issubclass` argument
# is the deprecated class itself - subclasses of the
@ -98,7 +101,7 @@ def create_deprecated_class(
mro = getattr(sub, "__mro__", ())
return any(c in {cls, new_class} for c in mro)
def __call__(cls, *args, **kwargs):
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
old = DeprecatedClass.deprecated_class
if cls is old:
msg = instance_warn_message.format(
@ -125,7 +128,7 @@ def create_deprecated_class(
return deprecated_cls
def _clspath(cls, forced=None):
def _clspath(cls: type, forced: Optional[str] = None) -> str:
if forced is not None:
return forced
return f"{cls.__module__}.{cls.__name__}"
@ -134,7 +137,17 @@ def _clspath(cls, forced=None):
DEPRECATION_RULES: List[Tuple[str, str]] = []
def update_classpath(path):
@overload
def update_classpath(path: str) -> str:
...
@overload
def update_classpath(path: Any) -> Any:
...
def update_classpath(path: Any) -> Any:
"""Update a deprecated path from an object with its new location"""
for prefix, replacement in DEPRECATION_RULES:
if isinstance(path, str) and path.startswith(prefix):
@ -147,7 +160,7 @@ def update_classpath(path):
return path
def method_is_overridden(subclass, base_class, method_name):
def method_is_overridden(subclass: type, base_class: type, method_name: str) -> bool:
"""
Return True if a method named ``method_name`` of a ``base_class``
is overridden in a ``subclass``.