Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/utils/deprecation.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

54 statements  

1""" 

2A module that implements tooling to enable easy warnings about deprecations. 

3""" 

4 

5from __future__ import annotations 

6 

7import logging 

8import os 

9import warnings 

10from collections.abc import Generator 

11from contextlib import contextmanager 

12from contextvars import ContextVar 

13from typing import Any, TextIO 

14 

15from pip._vendor.packaging.version import parse 

16from pip._vendor.pyproject_hooks import BuildBackendWarning 

17from pip._vendor.rich.text import Text 

18 

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

20 

21DEPRECATION_MSG_PREFIX = "DEPRECATION: " 

22 

23 

24class PipDeprecationWarning(Warning): 

25 include_source: bool = False 

26 

27 

28_original_showwarning: Any = None 

29_build_backend_warning_level = ContextVar( 

30 "build_backend_warning_level", default=logging.DEBUG 

31) 

32 

33 

34@contextmanager 

35def build_backend_warnings(level: int) -> Generator[None, None, None]: 

36 """Set the logging level for warnings from this build backend call.""" 

37 token = _build_backend_warning_level.set(level) 

38 try: 

39 yield 

40 finally: 

41 _build_backend_warning_level.reset(token) 

42 

43 

44# Warnings <-> Logging Integration 

45def _showwarning( 

46 message: Warning | str, 

47 category: type[Warning], 

48 filename: str, 

49 lineno: int, 

50 file: TextIO | None = None, 

51 line: str | None = None, 

52) -> None: 

53 if file is not None: 

54 if _original_showwarning is not None: 

55 _original_showwarning(message, category, filename, lineno, file, line) 

56 elif issubclass(category, PipDeprecationWarning): 

57 # We use a specially named logger which will handle all of the 

58 # deprecation messages for pip. 

59 logger = logging.getLogger("pip._internal.deprecations") 

60 if isinstance(message, PipDeprecationWarning) and message.include_source: 

61 logger.warning("%s (%s:%s)", message, filename, lineno) 

62 else: 

63 logger.warning(message) 

64 elif issubclass(category, BuildBackendWarning): 

65 logger = logging.getLogger("pip._internal.build_backend") 

66 formatted = warnings.formatwarning(message, category, filename, lineno, line) 

67 logger.log( 

68 _build_backend_warning_level.get(), 

69 "%s", 

70 Text(formatted.rstrip()), 

71 extra={"rich": True}, 

72 ) 

73 else: 

74 _original_showwarning(message, category, filename, lineno, file, line) 

75 

76 

77def install_warning_logger() -> None: 

78 # Enable our Deprecation Warnings 

79 # If we're running pip test suite, promote the PipDeprecationWarning into errors. 

80 if os.environ.get("_PIP_TEST_ENV", None): 

81 warnings.simplefilter("error", PipDeprecationWarning) 

82 else: 

83 warnings.simplefilter("default", PipDeprecationWarning, append=True) 

84 

85 global _original_showwarning 

86 

87 if _original_showwarning is None: 

88 _original_showwarning = warnings.showwarning 

89 warnings.showwarning = _showwarning 

90 

91 

92def deprecated( 

93 *, 

94 reason: str, 

95 replacement: str | None, 

96 gone_in: str | None, 

97 feature_flag: str | None = None, 

98 issue: int | None = None, 

99 stacklevel: int = 2, 

100 include_source: bool = False, 

101) -> None: 

102 """Helper to deprecate existing functionality. 

103 

104 reason: 

105 Textual reason shown to the user about why this functionality has 

106 been deprecated. Should be a complete sentence. 

107 replacement: 

108 Textual suggestion shown to the user about what alternative 

109 functionality they can use. 

110 gone_in: 

111 The version of pip does this functionality should get removed in. 

112 Raises an error if pip's current version is greater than or equal to 

113 this. 

114 feature_flag: 

115 Command-line flag of the form --use-feature={feature_flag} for testing 

116 upcoming functionality. 

117 issue: 

118 Issue number on the tracker that would serve as a useful place for 

119 users to find related discussion and provide feedback. 

120 stacklevel: 

121 How many frames up the call stack to attribute the warning to. 

122 Defaults to 2 (the caller of deprecated()). 

123 include_source: 

124 If True, include the source filename and line number in the warning 

125 output. Useful when the warning originates from external code. 

126 """ 

127 

128 # Determine whether or not the feature is already gone in this version. 

129 is_gone = gone_in is not None and parse(current_version) >= parse(gone_in) 

130 

131 message_parts = [ 

132 (reason, f"{DEPRECATION_MSG_PREFIX}{{}}"), 

133 ( 

134 gone_in, 

135 ( 

136 "pip {} will enforce this behaviour change." 

137 if not is_gone 

138 else "Since pip {}, this is no longer supported." 

139 ), 

140 ), 

141 ( 

142 replacement, 

143 "A possible replacement is {}.", 

144 ), 

145 ( 

146 feature_flag, 

147 ( 

148 "You can use the flag --use-feature={} to test the upcoming behaviour." 

149 if not is_gone 

150 else None 

151 ), 

152 ), 

153 ( 

154 issue, 

155 "Discussion can be found at https://github.com/pypa/pip/issues/{}", 

156 ), 

157 ] 

158 

159 message = " ".join( 

160 format_str.format(value) 

161 for value, format_str in message_parts 

162 if format_str is not None and value is not None 

163 ) 

164 

165 # Raise as an error if this behaviour is deprecated. 

166 if is_gone: 

167 raise PipDeprecationWarning(message) 

168 

169 warning = PipDeprecationWarning(message) 

170 warning.include_source = include_source 

171 warnings.warn(warning, stacklevel=stacklevel)