diff --git a/pelican/utils.py b/pelican/utils.py index 48ed0757..9c3bd7be 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -6,6 +6,7 @@ import os import re import pytz import shutil +import traceback import logging import errno import locale @@ -122,6 +123,48 @@ class memoized(object): '''Support instance methods.''' return partial(self.__call__, obj) + +def deprecated_attribute(old, new, since=None, remove=None, doc=None): + """Attribute deprecation decorator for gentle upgrades + + For example: + + class MyClass (object): + @deprecated_attribute( + old='abc', new='xyz', since=(3, 2, 0), remove=(4, 1, 3)) + def abc(): return None + + def __init__(self): + xyz = 5 + + Note that the decorator needs a dummy method to attach to, but the + content of the dummy method is ignored. + """ + def _warn(): + version = '.'.join(six.text_type(x) for x in since) + message = ['{} has been deprecated since {}'.format(old, version)] + if remove: + version = '.'.join(six.text_type(x) for x in remove) + message.append( + ' and will be removed by version {}'.format(version)) + message.append('. Use {} instead.'.format(new)) + logger.warning(''.join(message)) + logger.debug(''.join( + six.text_type(x) for x in traceback.format_stack())) + + def fget(self): + _warn() + return getattr(self, new) + + def fset(self, value): + _warn() + setattr(self, new, value) + + def decorator(dummy): + return property(fget=fget, fset=fset, doc=doc) + + return decorator + def get_date(string): """Return a datetime object from a string. diff --git a/tests/test_utils.py b/tests/test_utils.py index ea4f839c..75e87c04 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,6 +11,17 @@ from pelican.utils import NoFilesError class TestUtils(unittest.TestCase): + _new_attribute = 'new_value' + + @utils.deprecated_attribute( + old='_old_attribute', new='_new_attribute', + since=(3, 1, 0), remove=(4, 1, 3)) + def _old_attribute(): return None + + def test_deprecated_attribute(self): + value = self._old_attribute + self.assertEquals(value, self._new_attribute) + # TODO: check log warning def test_get_date(self): # valid ones