Coverage for /usr/lib/python3/dist-packages/sympy/utilities/exceptions.py: 41%

37 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1""" 

2General SymPy exceptions and warnings. 

3""" 

4 

5import warnings 

6import contextlib 

7 

8from textwrap import dedent 

9 

10 

11class SymPyDeprecationWarning(DeprecationWarning): 

12 r""" 

13 A warning for deprecated features of SymPy. 

14 

15 See the :ref:`deprecation-policy` document for details on when and how 

16 things should be deprecated in SymPy. 

17 

18 Note that simply constructing this class will not cause a warning to be 

19 issued. To do that, you must call the :func`sympy_deprecation_warning` 

20 function. For this reason, it is not recommended to ever construct this 

21 class directly. 

22 

23 Explanation 

24 =========== 

25 

26 The ``SymPyDeprecationWarning`` class is a subclass of 

27 ``DeprecationWarning`` that is used for all deprecations in SymPy. A 

28 special subclass is used so that we can automatically augment the warning 

29 message with additional metadata about the version the deprecation was 

30 introduced in and a link to the documentation. This also allows users to 

31 explicitly filter deprecation warnings from SymPy using ``warnings`` 

32 filters (see :ref:`silencing-sympy-deprecation-warnings`). 

33 

34 Additionally, ``SymPyDeprecationWarning`` is enabled to be shown by 

35 default, unlike normal ``DeprecationWarning``\s, which are only shown by 

36 default in interactive sessions. This ensures that deprecation warnings in 

37 SymPy will actually be seen by users. 

38 

39 See the documentation of :func:`sympy_deprecation_warning` for a 

40 description of the parameters to this function. 

41 

42 To mark a function as deprecated, you can use the :func:`@deprecated 

43 <sympy.utilities.decorator.deprecated>` decorator. 

44 

45 See Also 

46 ======== 

47 sympy.utilities.exceptions.sympy_deprecation_warning 

48 sympy.utilities.exceptions.ignore_warnings 

49 sympy.utilities.decorator.deprecated 

50 sympy.testing.pytest.warns_deprecated_sympy 

51 

52 """ 

53 def __init__(self, message, *, deprecated_since_version, active_deprecations_target): 

54 

55 super().__init__(message, deprecated_since_version, 

56 active_deprecations_target) 

57 self.message = message 

58 if not isinstance(deprecated_since_version, str): 

59 raise TypeError(f"'deprecated_since_version' should be a string, got {deprecated_since_version!r}") 

60 self.deprecated_since_version = deprecated_since_version 

61 self.active_deprecations_target = active_deprecations_target 

62 if any(i in active_deprecations_target for i in '()='): 

63 raise ValueError("active_deprecations_target be the part inside of the '(...)='") 

64 

65 self.full_message = f""" 

66 

67{dedent(message).strip()} 

68 

69See https://docs.sympy.org/latest/explanation/active-deprecations.html#{active_deprecations_target} 

70for details. 

71 

72This has been deprecated since SymPy version {deprecated_since_version}. It 

73will be removed in a future version of SymPy. 

74""" 

75 

76 def __str__(self): 

77 return self.full_message 

78 

79 def __repr__(self): 

80 return f"{self.__class__.__name__}({self.message!r}, deprecated_since_version={self.deprecated_since_version!r}, active_deprecations_target={self.active_deprecations_target!r})" 

81 

82 def __eq__(self, other): 

83 return isinstance(other, SymPyDeprecationWarning) and self.args == other.args 

84 

85 # Make pickling work. The by default, it tries to recreate the expression 

86 # from its args, but this doesn't work because of our keyword-only 

87 # arguments. 

88 @classmethod 

89 def _new(cls, message, deprecated_since_version, 

90 active_deprecations_target): 

91 return cls(message, deprecated_since_version=deprecated_since_version, active_deprecations_target=active_deprecations_target) 

92 

93 def __reduce__(self): 

94 return (self._new, (self.message, self.deprecated_since_version, self.active_deprecations_target)) 

95 

96# Python by default hides DeprecationWarnings, which we do not want. 

97warnings.simplefilter("once", SymPyDeprecationWarning) 

98 

99def sympy_deprecation_warning(message, *, deprecated_since_version, 

100 active_deprecations_target, stacklevel=3): 

101 r''' 

102 Warn that a feature is deprecated in SymPy. 

103 

104 See the :ref:`deprecation-policy` document for details on when and how 

105 things should be deprecated in SymPy. 

106 

107 To mark an entire function or class as deprecated, you can use the 

108 :func:`@deprecated <sympy.utilities.decorator.deprecated>` decorator. 

109 

110 Parameters 

111 ========== 

112 

113 message: str 

114 

115 The deprecation message. This may span multiple lines and contain 

116 code examples. Messages should be wrapped to 80 characters. The 

117 message is automatically dedented and leading and trailing whitespace 

118 stripped. Messages may include dynamic content based on the user 

119 input, but avoid using ``str(expression)`` if an expression can be 

120 arbitrary, as it might be huge and make the warning message 

121 unreadable. 

122 

123 deprecated_since_version: str 

124 

125 The version of SymPy the feature has been deprecated since. For new 

126 deprecations, this should be the version in `sympy/release.py 

127 <https://github.com/sympy/sympy/blob/master/sympy/release.py>`_ 

128 without the ``.dev``. If the next SymPy version ends up being 

129 different from this, the release manager will need to update any 

130 ``SymPyDeprecationWarning``\s using the incorrect version. This 

131 argument is required and must be passed as a keyword argument. 

132 (example: ``deprecated_since_version="1.10"``). 

133 

134 active_deprecations_target: str 

135 

136 The Sphinx target corresponding to the section for the deprecation in 

137 the :ref:`active-deprecations` document (see 

138 ``doc/src/explanation/active-deprecations.md``). This is used to 

139 automatically generate a URL to the page in the warning message. This 

140 argument is required and must be passed as a keyword argument. 

141 (example: ``active_deprecations_target="deprecated-feature-abc"``) 

142 

143 stacklevel: int (default: 3) 

144 

145 The ``stacklevel`` parameter that is passed to ``warnings.warn``. If 

146 you create a wrapper that calls this function, this should be 

147 increased so that the warning message shows the user line of code that 

148 produced the warning. Note that in some cases there will be multiple 

149 possible different user code paths that could result in the warning. 

150 In that case, just choose the smallest common stacklevel. 

151 

152 Examples 

153 ======== 

154 

155 >>> from sympy.utilities.exceptions import sympy_deprecation_warning 

156 >>> def is_this_zero(x, y=0): 

157 ... """ 

158 ... Determine if x = 0. 

159 ... 

160 ... Parameters 

161 ... ========== 

162 ... 

163 ... x : Expr 

164 ... The expression to check. 

165 ... 

166 ... y : Expr, optional 

167 ... If provided, check if x = y. 

168 ... 

169 ... .. deprecated:: 1.1 

170 ... 

171 ... The ``y`` argument to ``is_this_zero`` is deprecated. Use 

172 ... ``is_this_zero(x - y)`` instead. 

173 ... 

174 ... """ 

175 ... from sympy import simplify 

176 ... 

177 ... if y != 0: 

178 ... sympy_deprecation_warning(""" 

179 ... The y argument to is_zero() is deprecated. Use is_zero(x - y) instead.""", 

180 ... deprecated_since_version="1.1", 

181 ... active_deprecations_target='is-this-zero-y-deprecation') 

182 ... return simplify(x - y) == 0 

183 >>> is_this_zero(0) 

184 True 

185 >>> is_this_zero(1, 1) # doctest: +SKIP 

186 <stdin>:1: SymPyDeprecationWarning: 

187 <BLANKLINE> 

188 The y argument to is_zero() is deprecated. Use is_zero(x - y) instead. 

189 <BLANKLINE> 

190 See https://docs.sympy.org/latest/explanation/active-deprecations.html#is-this-zero-y-deprecation 

191 for details. 

192 <BLANKLINE> 

193 This has been deprecated since SymPy version 1.1. It 

194 will be removed in a future version of SymPy. 

195 <BLANKLINE> 

196 is_this_zero(1, 1) 

197 True 

198 

199 See Also 

200 ======== 

201 

202 sympy.utilities.exceptions.SymPyDeprecationWarning 

203 sympy.utilities.exceptions.ignore_warnings 

204 sympy.utilities.decorator.deprecated 

205 sympy.testing.pytest.warns_deprecated_sympy 

206 

207 ''' 

208 w = SymPyDeprecationWarning(message, 

209 deprecated_since_version=deprecated_since_version, 

210 active_deprecations_target=active_deprecations_target) 

211 warnings.warn(w, stacklevel=stacklevel) 

212 

213 

214@contextlib.contextmanager 

215def ignore_warnings(warningcls): 

216 ''' 

217 Context manager to suppress warnings during tests. 

218 

219 .. note:: 

220 

221 Do not use this with SymPyDeprecationWarning in the tests. 

222 warns_deprecated_sympy() should be used instead. 

223 

224 This function is useful for suppressing warnings during tests. The warns 

225 function should be used to assert that a warning is raised. The 

226 ignore_warnings function is useful in situation when the warning is not 

227 guaranteed to be raised (e.g. on importing a module) or if the warning 

228 comes from third-party code. 

229 

230 This function is also useful to prevent the same or similar warnings from 

231 being issue twice due to recursive calls. 

232 

233 When the warning is coming (reliably) from SymPy the warns function should 

234 be preferred to ignore_warnings. 

235 

236 >>> from sympy.utilities.exceptions import ignore_warnings 

237 >>> import warnings 

238 

239 Here's a warning: 

240 

241 >>> with warnings.catch_warnings(): # reset warnings in doctest 

242 ... warnings.simplefilter('error') 

243 ... warnings.warn('deprecated', UserWarning) 

244 Traceback (most recent call last): 

245 ... 

246 UserWarning: deprecated 

247 

248 Let's suppress it with ignore_warnings: 

249 

250 >>> with warnings.catch_warnings(): # reset warnings in doctest 

251 ... warnings.simplefilter('error') 

252 ... with ignore_warnings(UserWarning): 

253 ... warnings.warn('deprecated', UserWarning) 

254 

255 (No warning emitted) 

256 

257 See Also 

258 ======== 

259 sympy.utilities.exceptions.SymPyDeprecationWarning 

260 sympy.utilities.exceptions.sympy_deprecation_warning 

261 sympy.utilities.decorator.deprecated 

262 sympy.testing.pytest.warns_deprecated_sympy 

263 

264 ''' 

265 # Absorbs all warnings in warnrec 

266 with warnings.catch_warnings(record=True) as warnrec: 

267 # Make sure our warning doesn't get filtered 

268 warnings.simplefilter("always", warningcls) 

269 # Now run the test 

270 yield 

271 

272 # Reissue any warnings that we aren't testing for 

273 for w in warnrec: 

274 if not issubclass(w.category, warningcls): 

275 warnings.warn_explicit(w.message, w.category, w.filename, w.lineno)