File: decorators.py

package info (click to toggle)
python-scrapy 2.14.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,308 kB
  • sloc: python: 55,321; xml: 199; makefile: 25; sh: 7
file content (135 lines) | stat: -rw-r--r-- 3,874 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from __future__ import annotations

import inspect
import warnings
from functools import wraps
from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, overload

from twisted.internet.defer import Deferred, maybeDeferred
from twisted.internet.threads import deferToThread

from scrapy.exceptions import ScrapyDeprecationWarning

if TYPE_CHECKING:
    from collections.abc import AsyncGenerator, Callable, Coroutine


_T = TypeVar("_T")
_P = ParamSpec("_P")


def deprecated(
    use_instead: Any = None,
) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]:
    """This is a decorator which can be used to mark functions
    as deprecated. It will result in a warning being emitted
    when the function is used."""

    def deco(func: Callable[_P, _T]) -> Callable[_P, _T]:
        @wraps(func)
        def wrapped(*args: _P.args, **kwargs: _P.kwargs) -> _T:
            message = f"Call to deprecated function {func.__name__}."
            if use_instead:
                message += f" Use {use_instead} instead."
            warnings.warn(message, category=ScrapyDeprecationWarning, stacklevel=2)
            return func(*args, **kwargs)

        return wrapped

    if callable(use_instead):
        deco = deco(use_instead)
        use_instead = None
    return deco


def defers(func: Callable[_P, _T]) -> Callable[_P, Deferred[_T]]:  # pragma: no cover
    """Decorator to make sure a function always returns a deferred"""
    warnings.warn(
        "@defers is deprecated, you can use maybeDeferred() directly if needed.",
        category=ScrapyDeprecationWarning,
        stacklevel=2,
    )

    @wraps(func)
    def wrapped(*a: _P.args, **kw: _P.kwargs) -> Deferred[_T]:
        return maybeDeferred(func, *a, **kw)

    return wrapped


def inthread(func: Callable[_P, _T]) -> Callable[_P, Deferred[_T]]:
    """Decorator to call a function in a thread and return a deferred with the
    result
    """

    @wraps(func)
    def wrapped(*a: _P.args, **kw: _P.kwargs) -> Deferred[_T]:
        return deferToThread(func, *a, **kw)

    return wrapped


@overload
def _warn_spider_arg(
    func: Callable[_P, Coroutine[Any, Any, _T]],
) -> Callable[_P, Coroutine[Any, Any, _T]]: ...


@overload
def _warn_spider_arg(
    func: Callable[_P, AsyncGenerator[_T]],
) -> Callable[_P, AsyncGenerator[_T]]: ...


@overload
def _warn_spider_arg(func: Callable[_P, _T]) -> Callable[_P, _T]: ...


def _warn_spider_arg(
    func: Callable[_P, _T],
) -> (
    Callable[_P, _T]
    | Callable[_P, Coroutine[Any, Any, _T]]
    | Callable[_P, AsyncGenerator[_T]]
):
    """Decorator to warn if a ``spider`` argument is passed to a function."""

    sig = inspect.signature(func)

    def check_args(*args: _P.args, **kwargs: _P.kwargs) -> None:
        bound = sig.bind(*args, **kwargs)
        if "spider" in bound.arguments:
            warnings.warn(
                f"Passing a 'spider' argument to {func.__qualname__}() is deprecated and "
                "the argument will be removed in a future Scrapy version.",
                category=ScrapyDeprecationWarning,
                stacklevel=3,
            )

    if inspect.iscoroutinefunction(func):

        @wraps(func)
        async def async_inner(*args: _P.args, **kwargs: _P.kwargs) -> _T:
            check_args(*args, **kwargs)
            return await func(*args, **kwargs)

        return async_inner

    if inspect.isasyncgenfunction(func):

        @wraps(func)
        async def asyncgen_inner(
            *args: _P.args, **kwargs: _P.kwargs
        ) -> AsyncGenerator[_T]:
            check_args(*args, **kwargs)
            async for item in func(*args, **kwargs):
                yield item

        return asyncgen_inner

    @wraps(func)
    def sync_inner(*args: _P.args, **kwargs: _P.kwargs) -> _T:
        check_args(*args, **kwargs)
        return func(*args, **kwargs)

    return sync_inner