Spaces:
Sleeping
Sleeping
File size: 10,570 Bytes
6a86ad5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
"""
General SymPy exceptions and warnings.
"""
import warnings
import contextlib
from textwrap import dedent
class SymPyDeprecationWarning(DeprecationWarning):
r"""
A warning for deprecated features of SymPy.
See the :ref:`deprecation-policy` document for details on when and how
things should be deprecated in SymPy.
Note that simply constructing this class will not cause a warning to be
issued. To do that, you must call the :func`sympy_deprecation_warning`
function. For this reason, it is not recommended to ever construct this
class directly.
Explanation
===========
The ``SymPyDeprecationWarning`` class is a subclass of
``DeprecationWarning`` that is used for all deprecations in SymPy. A
special subclass is used so that we can automatically augment the warning
message with additional metadata about the version the deprecation was
introduced in and a link to the documentation. This also allows users to
explicitly filter deprecation warnings from SymPy using ``warnings``
filters (see :ref:`silencing-sympy-deprecation-warnings`).
Additionally, ``SymPyDeprecationWarning`` is enabled to be shown by
default, unlike normal ``DeprecationWarning``\s, which are only shown by
default in interactive sessions. This ensures that deprecation warnings in
SymPy will actually be seen by users.
See the documentation of :func:`sympy_deprecation_warning` for a
description of the parameters to this function.
To mark a function as deprecated, you can use the :func:`@deprecated
<sympy.utilities.decorator.deprecated>` decorator.
See Also
========
sympy.utilities.exceptions.sympy_deprecation_warning
sympy.utilities.exceptions.ignore_warnings
sympy.utilities.decorator.deprecated
sympy.testing.pytest.warns_deprecated_sympy
"""
def __init__(self, message, *, deprecated_since_version, active_deprecations_target):
super().__init__(message, deprecated_since_version,
active_deprecations_target)
self.message = message
if not isinstance(deprecated_since_version, str):
raise TypeError(f"'deprecated_since_version' should be a string, got {deprecated_since_version!r}")
self.deprecated_since_version = deprecated_since_version
self.active_deprecations_target = active_deprecations_target
if any(i in active_deprecations_target for i in '()='):
raise ValueError("active_deprecations_target be the part inside of the '(...)='")
self.full_message = f"""
{dedent(message).strip()}
See https://docs.sympy.org/latest/explanation/active-deprecations.html#{active_deprecations_target}
for details.
This has been deprecated since SymPy version {deprecated_since_version}. It
will be removed in a future version of SymPy.
"""
def __str__(self):
return self.full_message
def __repr__(self):
return f"{self.__class__.__name__}({self.message!r}, deprecated_since_version={self.deprecated_since_version!r}, active_deprecations_target={self.active_deprecations_target!r})"
def __eq__(self, other):
return isinstance(other, SymPyDeprecationWarning) and self.args == other.args
# Make pickling work. The by default, it tries to recreate the expression
# from its args, but this doesn't work because of our keyword-only
# arguments.
@classmethod
def _new(cls, message, deprecated_since_version,
active_deprecations_target):
return cls(message, deprecated_since_version=deprecated_since_version, active_deprecations_target=active_deprecations_target)
def __reduce__(self):
return (self._new, (self.message, self.deprecated_since_version, self.active_deprecations_target))
# Python by default hides DeprecationWarnings, which we do not want.
warnings.simplefilter("once", SymPyDeprecationWarning)
def sympy_deprecation_warning(message, *, deprecated_since_version,
active_deprecations_target, stacklevel=3):
r'''
Warn that a feature is deprecated in SymPy.
See the :ref:`deprecation-policy` document for details on when and how
things should be deprecated in SymPy.
To mark an entire function or class as deprecated, you can use the
:func:`@deprecated <sympy.utilities.decorator.deprecated>` decorator.
Parameters
==========
message : str
The deprecation message. This may span multiple lines and contain
code examples. Messages should be wrapped to 80 characters. The
message is automatically dedented and leading and trailing whitespace
stripped. Messages may include dynamic content based on the user
input, but avoid using ``str(expression)`` if an expression can be
arbitrary, as it might be huge and make the warning message
unreadable.
deprecated_since_version : str
The version of SymPy the feature has been deprecated since. For new
deprecations, this should be the version in `sympy/release.py
<https://github.com/sympy/sympy/blob/master/sympy/release.py>`_
without the ``.dev``. If the next SymPy version ends up being
different from this, the release manager will need to update any
``SymPyDeprecationWarning``\s using the incorrect version. This
argument is required and must be passed as a keyword argument.
(example: ``deprecated_since_version="1.10"``).
active_deprecations_target : str
The Sphinx target corresponding to the section for the deprecation in
the :ref:`active-deprecations` document (see
``doc/src/explanation/active-deprecations.md``). This is used to
automatically generate a URL to the page in the warning message. This
argument is required and must be passed as a keyword argument.
(example: ``active_deprecations_target="deprecated-feature-abc"``)
stacklevel : int, default: 3
The ``stacklevel`` parameter that is passed to ``warnings.warn``. If
you create a wrapper that calls this function, this should be
increased so that the warning message shows the user line of code that
produced the warning. Note that in some cases there will be multiple
possible different user code paths that could result in the warning.
In that case, just choose the smallest common stacklevel.
Examples
========
>>> from sympy.utilities.exceptions import sympy_deprecation_warning
>>> def is_this_zero(x, y=0):
... """
... Determine if x = 0.
...
... Parameters
... ==========
...
... x : Expr
... The expression to check.
...
... y : Expr, optional
... If provided, check if x = y.
...
... .. deprecated:: 1.1
...
... The ``y`` argument to ``is_this_zero`` is deprecated. Use
... ``is_this_zero(x - y)`` instead.
...
... """
... from sympy import simplify
...
... if y != 0:
... sympy_deprecation_warning("""
... The y argument to is_zero() is deprecated. Use is_zero(x - y) instead.""",
... deprecated_since_version="1.1",
... active_deprecations_target='is-this-zero-y-deprecation')
... return simplify(x - y) == 0
>>> is_this_zero(0)
True
>>> is_this_zero(1, 1) # doctest: +SKIP
<stdin>:1: SymPyDeprecationWarning:
<BLANKLINE>
The y argument to is_zero() is deprecated. Use is_zero(x - y) instead.
<BLANKLINE>
See https://docs.sympy.org/latest/explanation/active-deprecations.html#is-this-zero-y-deprecation
for details.
<BLANKLINE>
This has been deprecated since SymPy version 1.1. It
will be removed in a future version of SymPy.
<BLANKLINE>
is_this_zero(1, 1)
True
See Also
========
sympy.utilities.exceptions.SymPyDeprecationWarning
sympy.utilities.exceptions.ignore_warnings
sympy.utilities.decorator.deprecated
sympy.testing.pytest.warns_deprecated_sympy
'''
w = SymPyDeprecationWarning(message,
deprecated_since_version=deprecated_since_version,
active_deprecations_target=active_deprecations_target)
warnings.warn(w, stacklevel=stacklevel)
@contextlib.contextmanager
def ignore_warnings(warningcls):
'''
Context manager to suppress warnings during tests.
.. note::
Do not use this with SymPyDeprecationWarning in the tests.
warns_deprecated_sympy() should be used instead.
This function is useful for suppressing warnings during tests. The warns
function should be used to assert that a warning is raised. The
ignore_warnings function is useful in situation when the warning is not
guaranteed to be raised (e.g. on importing a module) or if the warning
comes from third-party code.
This function is also useful to prevent the same or similar warnings from
being issue twice due to recursive calls.
When the warning is coming (reliably) from SymPy the warns function should
be preferred to ignore_warnings.
>>> from sympy.utilities.exceptions import ignore_warnings
>>> import warnings
Here's a warning:
>>> with warnings.catch_warnings(): # reset warnings in doctest
... warnings.simplefilter('error')
... warnings.warn('deprecated', UserWarning)
Traceback (most recent call last):
...
UserWarning: deprecated
Let's suppress it with ignore_warnings:
>>> with warnings.catch_warnings(): # reset warnings in doctest
... warnings.simplefilter('error')
... with ignore_warnings(UserWarning):
... warnings.warn('deprecated', UserWarning)
(No warning emitted)
See Also
========
sympy.utilities.exceptions.SymPyDeprecationWarning
sympy.utilities.exceptions.sympy_deprecation_warning
sympy.utilities.decorator.deprecated
sympy.testing.pytest.warns_deprecated_sympy
'''
# Absorbs all warnings in warnrec
with warnings.catch_warnings(record=True) as warnrec:
# Make sure our warning doesn't get filtered
warnings.simplefilter("always", warningcls)
# Now run the test
yield
# Reissue any warnings that we aren't testing for
for w in warnrec:
if not issubclass(w.category, warningcls):
warnings.warn_explicit(w.message, w.category, w.filename, w.lineno)
|