| | """ |
| | 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 |
| |
|
| | |
| | |
| | |
| | @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)) |
| |
|
| | |
| | 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 |
| | |
| | ''' |
| | |
| | with warnings.catch_warnings(record=True) as warnrec: |
| | |
| | warnings.simplefilter("always", warningcls) |
| | |
| | yield |
| |
|
| | |
| | for w in warnrec: |
| | if not issubclass(w.category, warningcls): |
| | warnings.warn_explicit(w.message, w.category, w.filename, w.lineno) |
| |
|