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