Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/pandas/_testing/_warnings.py: 23%

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

71 statements  

1from __future__ import annotations 

2 

3from contextlib import ( 

4 contextmanager, 

5 nullcontext, 

6) 

7import inspect 

8import re 

9import sys 

10from typing import ( 

11 TYPE_CHECKING, 

12 Literal, 

13 cast, 

14) 

15import warnings 

16 

17from pandas.compat import PY311 

18 

19if TYPE_CHECKING: 

20 from collections.abc import ( 

21 Generator, 

22 Sequence, 

23 ) 

24 

25 

26@contextmanager 

27def assert_produces_warning( 

28 expected_warning: type[Warning] | bool | tuple[type[Warning], ...] | None = Warning, 

29 filter_level: Literal[ 

30 "error", "ignore", "always", "default", "module", "once" 

31 ] = "always", 

32 check_stacklevel: bool = True, 

33 raise_on_extra_warnings: bool = True, 

34 match: str | None = None, 

35) -> Generator[list[warnings.WarningMessage], None, None]: 

36 """ 

37 Context manager for running code expected to either raise a specific warning, 

38 multiple specific warnings, or not raise any warnings. Verifies that the code 

39 raises the expected warning(s), and that it does not raise any other unexpected 

40 warnings. It is basically a wrapper around ``warnings.catch_warnings``. 

41 

42 Parameters 

43 ---------- 

44 expected_warning : {Warning, False, tuple[Warning, ...], None}, default Warning 

45 The type of Exception raised. ``exception.Warning`` is the base 

46 class for all warnings. To raise multiple types of exceptions, 

47 pass them as a tuple. To check that no warning is returned, 

48 specify ``False`` or ``None``. 

49 filter_level : str or None, default "always" 

50 Specifies whether warnings are ignored, displayed, or turned 

51 into errors. 

52 Valid values are: 

53 

54 * "error" - turns matching warnings into exceptions 

55 * "ignore" - discard the warning 

56 * "always" - always emit a warning 

57 * "default" - print the warning the first time it is generated 

58 from each location 

59 * "module" - print the warning the first time it is generated 

60 from each module 

61 * "once" - print the warning the first time it is generated 

62 

63 check_stacklevel : bool, default True 

64 If True, displays the line that called the function containing 

65 the warning to show were the function is called. Otherwise, the 

66 line that implements the function is displayed. 

67 raise_on_extra_warnings : bool, default True 

68 Whether extra warnings not of the type `expected_warning` should 

69 cause the test to fail. 

70 match : str, optional 

71 Match warning message. 

72 

73 Examples 

74 -------- 

75 >>> import warnings 

76 >>> with assert_produces_warning(): 

77 ... warnings.warn(UserWarning()) 

78 ... 

79 >>> with assert_produces_warning(False): 

80 ... warnings.warn(RuntimeWarning()) 

81 ... 

82 Traceback (most recent call last): 

83 ... 

84 AssertionError: Caused unexpected warning(s): ['RuntimeWarning']. 

85 >>> with assert_produces_warning(UserWarning): 

86 ... warnings.warn(RuntimeWarning()) 

87 Traceback (most recent call last): 

88 ... 

89 AssertionError: Did not see expected warning of class 'UserWarning'. 

90 

91 ..warn:: This is *not* thread-safe. 

92 """ 

93 __tracebackhide__ = True 

94 

95 with warnings.catch_warnings(record=True) as w: 

96 warnings.simplefilter(filter_level) 

97 try: 

98 yield w 

99 finally: 

100 if expected_warning: 

101 expected_warning = cast(type[Warning], expected_warning) 

102 _assert_caught_expected_warning( 

103 caught_warnings=w, 

104 expected_warning=expected_warning, 

105 match=match, 

106 check_stacklevel=check_stacklevel, 

107 ) 

108 if raise_on_extra_warnings: 

109 _assert_caught_no_extra_warnings( 

110 caught_warnings=w, 

111 expected_warning=expected_warning, 

112 ) 

113 

114 

115def maybe_produces_warning(warning: type[Warning], condition: bool, **kwargs): 

116 """ 

117 Return a context manager that possibly checks a warning based on the condition 

118 """ 

119 if condition: 

120 return assert_produces_warning(warning, **kwargs) 

121 else: 

122 return nullcontext() 

123 

124 

125def _assert_caught_expected_warning( 

126 *, 

127 caught_warnings: Sequence[warnings.WarningMessage], 

128 expected_warning: type[Warning], 

129 match: str | None, 

130 check_stacklevel: bool, 

131) -> None: 

132 """Assert that there was the expected warning among the caught warnings.""" 

133 saw_warning = False 

134 matched_message = False 

135 unmatched_messages = [] 

136 

137 for actual_warning in caught_warnings: 

138 if issubclass(actual_warning.category, expected_warning): 

139 saw_warning = True 

140 

141 if check_stacklevel: 

142 _assert_raised_with_correct_stacklevel(actual_warning) 

143 

144 if match is not None: 

145 if re.search(match, str(actual_warning.message)): 

146 matched_message = True 

147 else: 

148 unmatched_messages.append(actual_warning.message) 

149 

150 if not saw_warning: 

151 raise AssertionError( 

152 f"Did not see expected warning of class " 

153 f"{repr(expected_warning.__name__)}" 

154 ) 

155 

156 if match and not matched_message: 

157 raise AssertionError( 

158 f"Did not see warning {repr(expected_warning.__name__)} " 

159 f"matching '{match}'. The emitted warning messages are " 

160 f"{unmatched_messages}" 

161 ) 

162 

163 

164def _assert_caught_no_extra_warnings( 

165 *, 

166 caught_warnings: Sequence[warnings.WarningMessage], 

167 expected_warning: type[Warning] | bool | tuple[type[Warning], ...] | None, 

168) -> None: 

169 """Assert that no extra warnings apart from the expected ones are caught.""" 

170 extra_warnings = [] 

171 

172 for actual_warning in caught_warnings: 

173 if _is_unexpected_warning(actual_warning, expected_warning): 

174 # GH#38630 pytest.filterwarnings does not suppress these. 

175 if actual_warning.category == ResourceWarning: 

176 # GH 44732: Don't make the CI flaky by filtering SSL-related 

177 # ResourceWarning from dependencies 

178 if "unclosed <ssl.SSLSocket" in str(actual_warning.message): 

179 continue 

180 # GH 44844: Matplotlib leaves font files open during the entire process 

181 # upon import. Don't make CI flaky if ResourceWarning raised 

182 # due to these open files. 

183 if any("matplotlib" in mod for mod in sys.modules): 

184 continue 

185 if PY311 and actual_warning.category == EncodingWarning: 

186 # EncodingWarnings are checked in the CI 

187 # pyproject.toml errors on EncodingWarnings in pandas 

188 # Ignore EncodingWarnings from other libraries 

189 continue 

190 extra_warnings.append( 

191 ( 

192 actual_warning.category.__name__, 

193 actual_warning.message, 

194 actual_warning.filename, 

195 actual_warning.lineno, 

196 ) 

197 ) 

198 

199 if extra_warnings: 

200 raise AssertionError(f"Caused unexpected warning(s): {repr(extra_warnings)}") 

201 

202 

203def _is_unexpected_warning( 

204 actual_warning: warnings.WarningMessage, 

205 expected_warning: type[Warning] | bool | tuple[type[Warning], ...] | None, 

206) -> bool: 

207 """Check if the actual warning issued is unexpected.""" 

208 if actual_warning and not expected_warning: 

209 return True 

210 expected_warning = cast(type[Warning], expected_warning) 

211 return bool(not issubclass(actual_warning.category, expected_warning)) 

212 

213 

214def _assert_raised_with_correct_stacklevel( 

215 actual_warning: warnings.WarningMessage, 

216) -> None: 

217 # https://stackoverflow.com/questions/17407119/python-inspect-stack-is-slow 

218 frame = inspect.currentframe() 

219 for _ in range(4): 

220 frame = frame.f_back # type: ignore[union-attr] 

221 try: 

222 caller_filename = inspect.getfile(frame) # type: ignore[arg-type] 

223 finally: 

224 # See note in 

225 # https://docs.python.org/3/library/inspect.html#inspect.Traceback 

226 del frame 

227 msg = ( 

228 "Warning not set with correct stacklevel. " 

229 f"File where warning is raised: {actual_warning.filename} != " 

230 f"{caller_filename}. Warning message: {actual_warning.message}" 

231 ) 

232 assert actual_warning.filename == caller_filename, msg