summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/polyfactory/utils/deprecation.py
blob: 576c4ac08aeaedcb21f6e355a8e26d4b2755fa01 (plain)
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
from __future__ import annotations

import inspect
from functools import wraps
from typing import Any, Callable, Literal, TypeVar
from warnings import warn

from typing_extensions import ParamSpec

__all__ = ("deprecated", "warn_deprecation", "check_for_deprecated_parameters")


T = TypeVar("T")
P = ParamSpec("P")
DeprecatedKind = Literal["function", "method", "classmethod", "attribute", "property", "class", "parameter", "import"]


def warn_deprecation(
    version: str,
    deprecated_name: str,
    kind: DeprecatedKind,
    *,
    removal_in: str | None = None,
    alternative: str | None = None,
    info: str | None = None,
    pending: bool = False,
) -> None:
    """Warn about a call to a (soon to be) deprecated function.

    Args:
        version: Polyfactory version where the deprecation will occur.
        deprecated_name: Name of the deprecated function.
        removal_in: Polyfactory version where the deprecated function will be removed.
        alternative: Name of a function that should be used instead.
        info: Additional information.
        pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``.
        kind: Type of the deprecated thing.
    """
    parts = []

    if kind == "import":
        access_type = "Import of"
    elif kind in {"function", "method"}:
        access_type = "Call to"
    else:
        access_type = "Use of"

    if pending:
        parts.append(f"{access_type} {kind} awaiting deprecation {deprecated_name!r}")
    else:
        parts.append(f"{access_type} deprecated {kind} {deprecated_name!r}")

    parts.extend(
        (
            f"Deprecated in polyfactory {version}",
            f"This {kind} will be removed in {removal_in or 'the next major version'}",
        )  # noqa: COM812
    )
    if alternative:
        parts.append(f"Use {alternative!r} instead")

    if info:
        parts.append(info)

    text = ". ".join(parts)
    warning_class = PendingDeprecationWarning if pending else DeprecationWarning

    warn(text, warning_class, stacklevel=2)


def deprecated(
    version: str,
    *,
    removal_in: str | None = None,
    alternative: str | None = None,
    info: str | None = None,
    pending: bool = False,
    kind: Literal["function", "method", "classmethod", "property"] | None = None,
) -> Callable[[Callable[P, T]], Callable[P, T]]:
    """Create a decorator wrapping a function, method or property with a warning call about a (pending) deprecation.

    Args:
        version: Polyfactory version where the deprecation will occur.
        removal_in: Polyfactory version where the deprecated function will be removed.
        alternative: Name of a function that should be used instead.
        info: Additional information.
        pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``.
        kind: Type of the deprecated callable. If ``None``, will use ``inspect`` to figure
            out if it's a function or method.

    Returns:
        A decorator wrapping the function call with a warning
    """

    def decorator(func: Callable[P, T]) -> Callable[P, T]:
        @wraps(func)
        def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
            warn_deprecation(
                version=version,
                deprecated_name=func.__name__,
                info=info,
                alternative=alternative,
                pending=pending,
                removal_in=removal_in,
                kind=kind or ("method" if inspect.ismethod(func) else "function"),
            )
            return func(*args, **kwargs)

        return wrapped

    return decorator


def check_for_deprecated_parameters(
    version: str,
    *,
    parameters: tuple[tuple[str, Any], ...],
    default_value: Any = None,
    removal_in: str | None = None,
    alternative: str | None = None,
    info: str | None = None,
    pending: bool = False,
) -> None:
    """Warn about a call to a (soon to be) deprecated argument to a function.

    Args:
        version: Polyfactory version where the deprecation will occur.
        parameters: Parameters to trigger warning if used.
        default_value: Default value for parameter to detect if supplied or not.
        removal_in: Polyfactory version where the deprecated function will be removed.
        alternative: Name of a function that should be used instead.
        info: Additional information.
        pending: Use ``PendingDeprecationWarning`` instead of ``DeprecationWarning``.
        kind: Type of the deprecated callable. If ``None``, will use ``inspect`` to figure
            out if it's a function or method.
    """
    for parameter_name, value in parameters:
        if value == default_value:
            continue

        warn_deprecation(
            version=version,
            deprecated_name=parameter_name,
            info=info,
            alternative=alternative,
            pending=pending,
            removal_in=removal_in,
            kind="parameter",
        )