191 lines
5.6 KiB
Python
191 lines
5.6 KiB
Python
|
from contextlib import contextmanager
|
||
|
import functools
|
||
|
import sys
|
||
|
import threading
|
||
|
|
||
|
from .base import Logger, DEBUG
|
||
|
from .helpers import string_types
|
||
|
|
||
|
|
||
|
class _SlowContextNotifier(object):
|
||
|
|
||
|
def __init__(self, threshold, func):
|
||
|
self.timer = threading.Timer(threshold, func)
|
||
|
|
||
|
def __enter__(self):
|
||
|
self.timer.start()
|
||
|
return self
|
||
|
|
||
|
def __exit__(self, *_):
|
||
|
self.timer.cancel()
|
||
|
|
||
|
|
||
|
_slow_logger = Logger('Slow')
|
||
|
|
||
|
|
||
|
def logged_if_slow(*args, **kwargs):
|
||
|
"""Context manager that logs if operations within take longer than
|
||
|
`threshold` seconds.
|
||
|
|
||
|
:param threshold: Number of seconds (or fractions thereof) allwoed before
|
||
|
logging occurs. The default is 1 second.
|
||
|
:param logger: :class:`~logbook.Logger` to use. The default is a 'slow'
|
||
|
logger.
|
||
|
:param level: Log level. The default is `DEBUG`.
|
||
|
:param func: (Deprecated). Function to call to perform logging.
|
||
|
|
||
|
The remaining parameters are passed to the
|
||
|
:meth:`~logbook.base.LoggerMixin.log` method.
|
||
|
"""
|
||
|
threshold = kwargs.pop('threshold', 1)
|
||
|
func = kwargs.pop('func', None)
|
||
|
if func is None:
|
||
|
logger = kwargs.pop('logger', _slow_logger)
|
||
|
level = kwargs.pop('level', DEBUG)
|
||
|
func = functools.partial(logger.log, level, *args, **kwargs)
|
||
|
else:
|
||
|
if 'logger' in kwargs or 'level' in kwargs:
|
||
|
raise TypeError("If using deprecated func parameter, 'logger' and"
|
||
|
" 'level' arguments cannot be passed.")
|
||
|
func = functools.partial(func, *args, **kwargs)
|
||
|
|
||
|
return _SlowContextNotifier(threshold, func)
|
||
|
|
||
|
|
||
|
class _Local(threading.local):
|
||
|
enabled = True
|
||
|
|
||
|
_local = _Local()
|
||
|
|
||
|
|
||
|
@contextmanager
|
||
|
def suppressed_deprecations():
|
||
|
"""Disables deprecation messages temporarily
|
||
|
|
||
|
>>> with suppressed_deprecations():
|
||
|
... call_some_deprecated_logic()
|
||
|
|
||
|
.. versionadded:: 0.12
|
||
|
"""
|
||
|
prev_enabled = _local.enabled
|
||
|
_local.enabled = False
|
||
|
try:
|
||
|
yield
|
||
|
finally:
|
||
|
_local.enabled = prev_enabled
|
||
|
|
||
|
|
||
|
_deprecation_logger = Logger("deprecation")
|
||
|
_deprecation_locations = set()
|
||
|
|
||
|
|
||
|
def forget_deprecation_locations():
|
||
|
_deprecation_locations.clear()
|
||
|
|
||
|
|
||
|
def _write_deprecations_if_needed(message, frame_correction):
|
||
|
if not _local.enabled:
|
||
|
return
|
||
|
caller_location = _get_caller_location(frame_correction=frame_correction+1)
|
||
|
if caller_location not in _deprecation_locations:
|
||
|
_deprecation_logger.warning(message, frame_correction=frame_correction+1)
|
||
|
_deprecation_locations.add(caller_location)
|
||
|
|
||
|
|
||
|
def log_deprecation_message(message, frame_correction=0):
|
||
|
_write_deprecations_if_needed("Deprecation message: {0}".format(message), frame_correction=frame_correction+1)
|
||
|
|
||
|
|
||
|
class _DeprecatedFunction(object):
|
||
|
|
||
|
def __init__(self, func, message, obj=None, objtype=None):
|
||
|
super(_DeprecatedFunction, self).__init__()
|
||
|
self._func = func
|
||
|
self._message = message
|
||
|
self._obj = obj
|
||
|
self._objtype = objtype
|
||
|
|
||
|
def _get_underlying_func(self):
|
||
|
returned = self._func
|
||
|
if isinstance(returned, classmethod):
|
||
|
if hasattr(returned, '__func__'):
|
||
|
returned = returned.__func__
|
||
|
else:
|
||
|
returned = returned.__get__(self._objtype).__func__
|
||
|
return returned
|
||
|
|
||
|
def __call__(self, *args, **kwargs):
|
||
|
func = self._get_underlying_func()
|
||
|
warning = "{0} is deprecated.".format(self._get_func_str())
|
||
|
if self._message is not None:
|
||
|
warning += " {0}".format(self._message)
|
||
|
_write_deprecations_if_needed(warning, frame_correction=+1)
|
||
|
if self._obj is not None:
|
||
|
return func(self._obj, *args, **kwargs)
|
||
|
elif self._objtype is not None:
|
||
|
return func(self._objtype, *args, **kwargs)
|
||
|
return func(*args, **kwargs)
|
||
|
|
||
|
def _get_func_str(self):
|
||
|
func = self._get_underlying_func()
|
||
|
if self._objtype is not None:
|
||
|
return '{0}.{1}'.format(self._objtype.__name__, func.__name__)
|
||
|
return '{0}.{1}'.format(func.__module__, func.__name__)
|
||
|
|
||
|
def __get__(self, obj, objtype):
|
||
|
return self.bound_to(obj, objtype)
|
||
|
|
||
|
def bound_to(self, obj, objtype):
|
||
|
return _DeprecatedFunction(self._func, self._message, obj=obj,
|
||
|
objtype=objtype)
|
||
|
|
||
|
@property
|
||
|
def __name__(self):
|
||
|
return self._get_underlying_func().__name__
|
||
|
|
||
|
@property
|
||
|
def __doc__(self):
|
||
|
returned = self._get_underlying_func().__doc__
|
||
|
if returned: # pylint: disable=no-member
|
||
|
returned += "\n.. deprecated\n" # pylint: disable=no-member
|
||
|
if self._message:
|
||
|
returned += " {0}".format(
|
||
|
self._message) # pylint: disable=no-member
|
||
|
return returned
|
||
|
|
||
|
@__doc__.setter
|
||
|
def __doc__(self, doc):
|
||
|
self._get_underlying_func().__doc__ = doc
|
||
|
|
||
|
|
||
|
def deprecated(func=None, message=None):
|
||
|
"""Marks the specified function as deprecated, and emits a warning when
|
||
|
it's called.
|
||
|
|
||
|
>>> @deprecated(message='No longer supported')
|
||
|
... def deprecated_func():
|
||
|
... pass
|
||
|
|
||
|
This will cause a warning log to be emitted when the function gets called,
|
||
|
with the correct filename/lineno.
|
||
|
|
||
|
.. versionadded:: 0.12
|
||
|
"""
|
||
|
if isinstance(func, string_types):
|
||
|
assert message is None
|
||
|
message = func
|
||
|
func = None
|
||
|
|
||
|
if func is None:
|
||
|
return functools.partial(deprecated, message=message)
|
||
|
|
||
|
return _DeprecatedFunction(func, message)
|
||
|
|
||
|
|
||
|
def _get_caller_location(frame_correction):
|
||
|
frame = sys._getframe(frame_correction + 1) # pylint: disable=protected-access
|
||
|
try:
|
||
|
return (frame.f_code.co_name, frame.f_lineno)
|
||
|
finally:
|
||
|
del frame
|