File size: 5,764 Bytes
6219ec9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
"""
A module that implements tooling to enable easy warnings about deprecations.
"""

import logging
import warnings
from typing import Any, Optional, TextIO, Type, Union

from pip._vendor.packaging.version import parse

from pip import __version__ as current_version  # NOTE: tests patch this name.

DEPRECATION_MSG_PREFIX = "DEPRECATION: "


class PipDeprecationWarning(Warning):
    pass


_original_showwarning: Any = None


# Warnings <-> Logging Integration
def _showwarning(
    message: Union[Warning, str],
    category: Type[Warning],
    filename: str,
    lineno: int,
    file: Optional[TextIO] = None,
    line: Optional[str] = None,
) -> None:
    if file is not None:
        if _original_showwarning is not None:
            _original_showwarning(message, category, filename, lineno, file, line)
    elif issubclass(category, PipDeprecationWarning):
        # We use a specially named logger which will handle all of the
        # deprecation messages for pip.
        logger = logging.getLogger("pip._internal.deprecations")
        logger.warning(message)
    else:
        _original_showwarning(message, category, filename, lineno, file, line)


def install_warning_logger() -> None:
    # Enable our Deprecation Warnings
    warnings.simplefilter("default", PipDeprecationWarning, append=True)

    global _original_showwarning

    if _original_showwarning is None:
        _original_showwarning = warnings.showwarning
        warnings.showwarning = _showwarning


def deprecated(
    *,
    reason: str,
    replacement: Optional[str],
    gone_in: Optional[str],
    feature_flag: Optional[str] = None,
    issue: Optional[int] = None,
) -> None:
    """Helper to deprecate existing functionality.

    reason:
        Textual reason shown to the user about why this functionality has
        been deprecated. Should be a complete sentence.
    replacement:
        Textual suggestion shown to the user about what alternative
        functionality they can use.
    gone_in:
        The version of pip does this functionality should get removed in.
        Raises an error if pip's current version is greater than or equal to
        this.
    feature_flag:
        Command-line flag of the form --use-feature={feature_flag} for testing
        upcoming functionality.
    issue:
        Issue number on the tracker that would serve as a useful place for
        users to find related discussion and provide feedback.
    """

    # Determine whether or not the feature is already gone in this version.
    is_gone = gone_in is not None and parse(current_version) >= parse(gone_in)

    message_parts = [
        (reason, f"{DEPRECATION_MSG_PREFIX}{{}}"),
        (
            gone_in,
            "pip {} will enforce this behaviour change."
            if not is_gone
            else "Since pip {}, this is no longer supported.",
        ),
        (
            replacement,
            "A possible replacement is {}.",
        ),
        (
            feature_flag,
            "You can use the flag --use-feature={} to test the upcoming behaviour."
            if not is_gone
            else None,
        ),
        (
            issue,
            "Discussion can be found at https://github.com/pypa/pip/issues/{}",
        ),
    ]

    message = " ".join(
        format_str.format(value)
        for value, format_str in message_parts
        if format_str is not None and value is not None
    )

    # Raise as an error if this behaviour is deprecated.
    if is_gone:
        raise PipDeprecationWarning(message)

    warnings.warn(message, category=PipDeprecationWarning, stacklevel=2)


class LegacyInstallReason:
    def __init__(
        self,
        reason: str,
        replacement: Optional[str] = None,
        gone_in: Optional[str] = None,
        feature_flag: Optional[str] = None,
        issue: Optional[int] = None,
        emit_after_success: bool = False,
        emit_before_install: bool = False,
    ):
        self._reason = reason
        self._replacement = replacement
        self._gone_in = gone_in
        self._feature_flag = feature_flag
        self._issue = issue
        self.emit_after_success = emit_after_success
        self.emit_before_install = emit_before_install

    def emit_deprecation(self, name: str) -> None:
        deprecated(
            reason=self._reason.format(name=name),
            replacement=self._replacement,
            gone_in=self._gone_in,
            feature_flag=self._feature_flag,
            issue=self._issue,
        )


LegacyInstallReasonFailedBdistWheel = LegacyInstallReason(
    reason=(
        "{name} was installed using the legacy 'setup.py install' "
        "method, because a wheel could not be built for it."
    ),
    replacement="to fix the wheel build issue reported above",
    gone_in="23.1",
    issue=8368,
    emit_after_success=True,
)


LegacyInstallReasonMissingWheelPackage = LegacyInstallReason(
    reason=(
        "{name} is being installed using the legacy "
        "'setup.py install' method, because it does not have a "
        "'pyproject.toml' and the 'wheel' package "
        "is not installed."
    ),
    replacement="to enable the '--use-pep517' option",
    gone_in="23.1",
    issue=8559,
    emit_before_install=True,
)

LegacyInstallReasonNoBinaryForcesSetuptoolsInstall = LegacyInstallReason(
    reason=(
        "{name} is being installed using the legacy "
        "'setup.py install' method, because the '--no-binary' option was enabled "
        "for it and this currently disables local wheel building for projects that "
        "don't have a 'pyproject.toml' file."
    ),
    replacement="to enable the '--use-pep517' option",
    gone_in="23.1",
    issue=11451,
    emit_before_install=True,
)