123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- # -*- coding: utf-8 -*-
- """
- Classic deprecation warning
- ===========================
- Classic ``@deprecated`` decorator to deprecate old python classes, functions or methods.
- .. _The Warnings Filter: https://docs.python.org/3/library/warnings.html#the-warnings-filter
- """
- import functools
- import inspect
- import platform
- import warnings
- import wrapt
- try:
- # If the C extension for wrapt was compiled and wrapt/_wrappers.pyd exists, then the
- # stack level that should be passed to warnings.warn should be 2. However, if using
- # a pure python wrapt, an extra stacklevel is required.
- import wrapt._wrappers
- _routine_stacklevel = 2
- _class_stacklevel = 2
- except ImportError:
- _routine_stacklevel = 3
- if platform.python_implementation() == "PyPy":
- _class_stacklevel = 2
- else:
- _class_stacklevel = 3
- string_types = (type(b''), type(u''))
- class ClassicAdapter(wrapt.AdapterFactory):
- """
- Classic adapter -- *for advanced usage only*
- This adapter is used to get the deprecation message according to the wrapped object type:
- class, function, standard method, static method, or class method.
- This is the base class of the :class:`~deprecated.sphinx.SphinxAdapter` class
- which is used to update the wrapped object docstring.
- You can also inherit this class to change the deprecation message.
- In the following example, we change the message into "The ... is deprecated.":
- .. code-block:: python
- import inspect
- from deprecated.classic import ClassicAdapter
- from deprecated.classic import deprecated
- class MyClassicAdapter(ClassicAdapter):
- def get_deprecated_msg(self, wrapped, instance):
- if instance is None:
- if inspect.isclass(wrapped):
- fmt = "The class {name} is deprecated."
- else:
- fmt = "The function {name} is deprecated."
- else:
- if inspect.isclass(instance):
- fmt = "The class method {name} is deprecated."
- else:
- fmt = "The method {name} is deprecated."
- if self.reason:
- fmt += " ({reason})"
- if self.version:
- fmt += " -- Deprecated since version {version}."
- return fmt.format(name=wrapped.__name__,
- reason=self.reason or "",
- version=self.version or "")
- Then, you can use your ``MyClassicAdapter`` class like this in your source code:
- .. code-block:: python
- @deprecated(reason="use another function", adapter_cls=MyClassicAdapter)
- def some_old_function(x, y):
- return x + y
- """
- def __init__(self, reason="", version="", action=None, category=DeprecationWarning, extra_stacklevel=0):
- """
- Construct a wrapper adapter.
- :type reason: str
- :param reason:
- Reason message which documents the deprecation in your library (can be omitted).
- :type version: str
- :param version:
- Version of your project which deprecates this feature.
- If you follow the `Semantic Versioning <https://semver.org/>`_,
- the version number has the format "MAJOR.MINOR.PATCH".
- :type action: Literal["default", "error", "ignore", "always", "module", "once"]
- :param action:
- A warning filter used to activate or not the deprecation warning.
- Can be one of "error", "ignore", "always", "default", "module", or "once".
- If ``None`` or empty, the global filtering mechanism is used.
- See: `The Warnings Filter`_ in the Python documentation.
- :type category: Type[Warning]
- :param category:
- The warning category to use for the deprecation warning.
- By default, the category class is :class:`~DeprecationWarning`,
- you can inherit this class to define your own deprecation warning category.
- :type extra_stacklevel: int
- :param extra_stacklevel:
- Number of additional stack levels to consider instrumentation rather than user code.
- With the default value of 0, the warning refers to where the class was instantiated
- or the function was called.
- .. versionchanged:: 1.2.15
- Add the *extra_stacklevel* parameter.
- """
- self.reason = reason or ""
- self.version = version or ""
- self.action = action
- self.category = category
- self.extra_stacklevel = extra_stacklevel
- super(ClassicAdapter, self).__init__()
- def get_deprecated_msg(self, wrapped, instance):
- """
- Get the deprecation warning message for the user.
- :param wrapped: Wrapped class or function.
- :param instance: The object to which the wrapped function was bound when it was called.
- :return: The warning message.
- """
- if instance is None:
- if inspect.isclass(wrapped):
- fmt = "Call to deprecated class {name}."
- else:
- fmt = "Call to deprecated function (or staticmethod) {name}."
- else:
- if inspect.isclass(instance):
- fmt = "Call to deprecated class method {name}."
- else:
- fmt = "Call to deprecated method {name}."
- if self.reason:
- fmt += " ({reason})"
- if self.version:
- fmt += " -- Deprecated since version {version}."
- return fmt.format(name=wrapped.__name__, reason=self.reason or "", version=self.version or "")
- def __call__(self, wrapped):
- """
- Decorate your class or function.
- :param wrapped: Wrapped class or function.
- :return: the decorated class or function.
- .. versionchanged:: 1.2.4
- Don't pass arguments to :meth:`object.__new__` (other than *cls*).
- .. versionchanged:: 1.2.8
- The warning filter is not set if the *action* parameter is ``None`` or empty.
- """
- if inspect.isclass(wrapped):
- old_new1 = wrapped.__new__
- def wrapped_cls(cls, *args, **kwargs):
- msg = self.get_deprecated_msg(wrapped, None)
- stacklevel = _class_stacklevel + self.extra_stacklevel
- if self.action:
- with warnings.catch_warnings():
- warnings.simplefilter(self.action, self.category)
- warnings.warn(msg, category=self.category, stacklevel=stacklevel)
- else:
- warnings.warn(msg, category=self.category, stacklevel=stacklevel)
- if old_new1 is object.__new__:
- return old_new1(cls)
- # actually, we don't know the real signature of *old_new1*
- return old_new1(cls, *args, **kwargs)
- wrapped.__new__ = staticmethod(wrapped_cls)
- elif inspect.isroutine(wrapped):
- @wrapt.decorator
- def wrapper_function(wrapped_, instance_, args_, kwargs_):
- msg = self.get_deprecated_msg(wrapped_, instance_)
- stacklevel = _routine_stacklevel + self.extra_stacklevel
- if self.action:
- with warnings.catch_warnings():
- warnings.simplefilter(self.action, self.category)
- warnings.warn(msg, category=self.category, stacklevel=stacklevel)
- else:
- warnings.warn(msg, category=self.category, stacklevel=stacklevel)
- return wrapped_(*args_, **kwargs_)
- return wrapper_function(wrapped)
- else:
- raise TypeError(repr(type(wrapped)))
- return wrapped
- def deprecated(*args, **kwargs):
- """
- This is a decorator which can be used to mark functions
- as deprecated. It will result in a warning being emitted
- when the function is used.
- **Classic usage:**
- To use this, decorate your deprecated function with **@deprecated** decorator:
- .. code-block:: python
- from deprecated import deprecated
- @deprecated
- def some_old_function(x, y):
- return x + y
- You can also decorate a class or a method:
- .. code-block:: python
- from deprecated import deprecated
- class SomeClass(object):
- @deprecated
- def some_old_method(self, x, y):
- return x + y
- @deprecated
- class SomeOldClass(object):
- pass
- You can give a *reason* message to help the developer to choose another function/class,
- and a *version* number to specify the starting version number of the deprecation.
- .. code-block:: python
- from deprecated import deprecated
- @deprecated(reason="use another function", version='1.2.0')
- def some_old_function(x, y):
- return x + y
- The *category* keyword argument allow you to specify the deprecation warning class of your choice.
- By default, :exc:`DeprecationWarning` is used, but you can choose :exc:`FutureWarning`,
- :exc:`PendingDeprecationWarning` or a custom subclass.
- .. code-block:: python
- from deprecated import deprecated
- @deprecated(category=PendingDeprecationWarning)
- def some_old_function(x, y):
- return x + y
- The *action* keyword argument allow you to locally change the warning filtering.
- *action* can be one of "error", "ignore", "always", "default", "module", or "once".
- If ``None``, empty or missing, the global filtering mechanism is used.
- See: `The Warnings Filter`_ in the Python documentation.
- .. code-block:: python
- from deprecated import deprecated
- @deprecated(action="error")
- def some_old_function(x, y):
- return x + y
- The *extra_stacklevel* keyword argument allows you to specify additional stack levels
- to consider instrumentation rather than user code. With the default value of 0, the
- warning refers to where the class was instantiated or the function was called.
- """
- if args and isinstance(args[0], string_types):
- kwargs['reason'] = args[0]
- args = args[1:]
- if args and not callable(args[0]):
- raise TypeError(repr(type(args[0])))
- if args:
- adapter_cls = kwargs.pop('adapter_cls', ClassicAdapter)
- adapter = adapter_cls(**kwargs)
- wrapped = args[0]
- return adapter(wrapped)
- return functools.partial(deprecated, **kwargs)
|