Source code for pyiron_snippets.deprecate

"""
A utility  class for deprecating code.
"""

import functools
import types
import warnings
from copy import copy


[docs] class Deprecator: """ Decorator class to mark functions and methods as deprecated with a uniform warning message at the time the function is called. The message has the form {function_name} is deprecated: {message}. It is not guaranteed to be in service after {version}. unless `pending=True` was given. Then the message will be {function_name} will be deprecated in a future version: {message}. If message and version are not initialized or given during the decorating call the respective parts are left out from the message. >>> from pyiron_snippets.deprecate import Deprecator >>> >>> deprecate = Deprecator() >>> @deprecate ... def foo(a, b): ... pass >>> foo(1, 2) # doctest: +SKIP DeprecationWarning: __main__.foo is deprecated >>> @deprecate("use bar() instead") ... def foo(a, b): ... pass >>> foo(1, 2) # doctest: +SKIP DeprecationWarning: __main__.foo is deprecated: use bar instead >>> @deprecate("use bar() instead", version="0.4.0") ... def foo(a, b): ... pass >>> foo(1, 2) # doctest: +SKIP DeprecationWarning: __main__.foo is deprecated: use bar instead. It is not guaranteed to be in service in vers. 0.4.0 >>> deprecate = Deprecator(message="I say no!", version="0.5.0") >>> @deprecate ... def foo(a, b): ... pass >>> foo(1, 2) # doctest: +SKIP DeprecationWarning: __main__.foo is deprecated: I say no! It is not guaranteed to be in service in vers. 0.5.0` Alternatively the decorator can also be called with `arguments` set to a dictionary mapping names of keyword arguments to deprecation messages. In this case the warning will only be emitted when the decorated function is called with arguments in that dictionary. >>> deprecate = Deprecator() >>> @deprecate(arguments={"bar": "use baz instead."}) ... def foo(bar=None, baz=None): ... pass >>> foo(baz=True) >>> foo(bar=True) # doctest: +SKIP DeprecationWarning: __main__.foo(bar=True) is deprecated: use baz instead. As a short-cut, it is also possible to pass the values in the arguments dict directly as keyword arguments to the decorator. >>> @deprecate(bar="use baz instead.") ... def foo(bar=None, baz=None): ... pass >>> foo(baz=True) >>> foo(bar=True) # doctest: +SKIP DeprecationWarning: __main__.foo(bar=True) is deprecated: use baz instead. """ def __init__(self, message=None, version=None, pending=False): """ Initialize default values for deprecation message and version. Args: message (str): default deprecation message version (str): default version after which the function might be removed pending (bool): only warn about future deprecation, warning category will be PendingDeprecationWarning instead of DeprecationWarning """ self.message = message self.version = version self.category = PendingDeprecationWarning if pending else DeprecationWarning def __copy__(self): cp = type(self)(message=self.message, version=self.version) cp.category = self.category return cp def __call__(self, message=None, version=None, arguments=None, **kwargs): depr = copy(self) if isinstance(message, types.FunctionType): return depr.__deprecate_function(message) else: depr.message = message depr.version = version depr.arguments = arguments if arguments is not None else {} depr.arguments.update(kwargs) return depr.wrap def _build_message(self): if self.category is PendingDeprecationWarning: message_format = "{} will be deprecated" else: message_format = "{} is deprecated" if self.message is not None: message_format += f": {self.message}." else: message_format += "." if self.version is not None: message_format += ( f" It is not guaranteed to be in service in vers. {self.version}" ) return message_format def __deprecate_function(self, function): message = self._build_message().format( f"{function.__module__}.{function.__name__}" ) @functools.wraps(function) def decorated(*args, **kwargs): warnings.warn(message, category=self.category, stacklevel=2) return function(*args, **kwargs) return decorated def __deprecate_argument(self, function): message_format = self._build_message() @functools.wraps(function) def decorated(*args, **kwargs): for kw in kwargs: if kw in self.arguments: warnings.warn( message_format.format( f"{function.__module__}.{function.__qualname__}" f"({kw}={kwargs[kw]})" ), category=self.category, stacklevel=2, ) return function(*args, **kwargs) return decorated
[docs] def wrap(self, function): """ Wrap the given function to emit a DeprecationWarning at call time. The warning message is constructed from the given message and version. If :attr:`.arguments` is set then the warning is only emitted, when the decorated function is called with keyword arguments found in that dictionary. Args: function (callable): function to mark as deprecated Return: function: raises DeprecationWarning when given function is called """ if not self.arguments: return self.__deprecate_function(function) else: return self.__deprecate_argument(function)
deprecate = Deprecator() deprecate_soon = Deprecator(pending=True)