]>
Commit | Line | Data |
---|---|---|
e0df8241 JR |
1 | """ |
2 | A module that implements tooling to enable easy warnings about deprecations. | |
3 | """ | |
4 | ||
5 | import logging | |
6 | import warnings | |
7 | from typing import Any, Optional, TextIO, Type, Union | |
8 | ||
9 | from pip._vendor.packaging.version import parse | |
10 | ||
11 | from pip import __version__ as current_version # NOTE: tests patch this name. | |
12 | ||
13 | DEPRECATION_MSG_PREFIX = "DEPRECATION: " | |
14 | ||
15 | ||
16 | class PipDeprecationWarning(Warning): | |
17 | pass | |
18 | ||
19 | ||
20 | _original_showwarning: Any = None | |
21 | ||
22 | ||
23 | # Warnings <-> Logging Integration | |
24 | def _showwarning( | |
25 | message: Union[Warning, str], | |
26 | category: Type[Warning], | |
27 | filename: str, | |
28 | lineno: int, | |
29 | file: Optional[TextIO] = None, | |
30 | line: Optional[str] = None, | |
31 | ) -> None: | |
32 | if file is not None: | |
33 | if _original_showwarning is not None: | |
34 | _original_showwarning(message, category, filename, lineno, file, line) | |
35 | elif issubclass(category, PipDeprecationWarning): | |
36 | # We use a specially named logger which will handle all of the | |
37 | # deprecation messages for pip. | |
38 | logger = logging.getLogger("pip._internal.deprecations") | |
39 | logger.warning(message) | |
40 | else: | |
41 | _original_showwarning(message, category, filename, lineno, file, line) | |
42 | ||
43 | ||
44 | def install_warning_logger() -> None: | |
45 | # Enable our Deprecation Warnings | |
46 | warnings.simplefilter("default", PipDeprecationWarning, append=True) | |
47 | ||
48 | global _original_showwarning | |
49 | ||
50 | if _original_showwarning is None: | |
51 | _original_showwarning = warnings.showwarning | |
52 | warnings.showwarning = _showwarning | |
53 | ||
54 | ||
55 | def deprecated( | |
56 | *, | |
57 | reason: str, | |
58 | replacement: Optional[str], | |
59 | gone_in: Optional[str], | |
60 | feature_flag: Optional[str] = None, | |
61 | issue: Optional[int] = None, | |
62 | ) -> None: | |
63 | """Helper to deprecate existing functionality. | |
64 | ||
65 | reason: | |
66 | Textual reason shown to the user about why this functionality has | |
67 | been deprecated. Should be a complete sentence. | |
68 | replacement: | |
69 | Textual suggestion shown to the user about what alternative | |
70 | functionality they can use. | |
71 | gone_in: | |
72 | The version of pip does this functionality should get removed in. | |
73 | Raises an error if pip's current version is greater than or equal to | |
74 | this. | |
75 | feature_flag: | |
76 | Command-line flag of the form --use-feature={feature_flag} for testing | |
77 | upcoming functionality. | |
78 | issue: | |
79 | Issue number on the tracker that would serve as a useful place for | |
80 | users to find related discussion and provide feedback. | |
81 | """ | |
82 | ||
83 | # Determine whether or not the feature is already gone in this version. | |
84 | is_gone = gone_in is not None and parse(current_version) >= parse(gone_in) | |
85 | ||
86 | message_parts = [ | |
87 | (reason, f"{DEPRECATION_MSG_PREFIX}{{}}"), | |
88 | ( | |
89 | gone_in, | |
90 | "pip {} will enforce this behaviour change." | |
91 | if not is_gone | |
92 | else "Since pip {}, this is no longer supported.", | |
93 | ), | |
94 | ( | |
95 | replacement, | |
96 | "A possible replacement is {}.", | |
97 | ), | |
98 | ( | |
99 | feature_flag, | |
100 | "You can use the flag --use-feature={} to test the upcoming behaviour." | |
101 | if not is_gone | |
102 | else None, | |
103 | ), | |
104 | ( | |
105 | issue, | |
106 | "Discussion can be found at https://github.com/pypa/pip/issues/{}", | |
107 | ), | |
108 | ] | |
109 | ||
110 | message = " ".join( | |
111 | format_str.format(value) | |
112 | for value, format_str in message_parts | |
113 | if format_str is not None and value is not None | |
114 | ) | |
115 | ||
116 | # Raise as an error if this behaviour is deprecated. | |
117 | if is_gone: | |
118 | raise PipDeprecationWarning(message) | |
119 | ||
120 | warnings.warn(message, category=PipDeprecationWarning, stacklevel=2) |