123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- """
- 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)
|