r47001 - Merge deprecated-property-8124: Make @deprecatedProperty, to make deprecated properties
hawkowl-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org Tue, 15 Mar 2016 21:54:35 -0600 (MDT)
| Newsgroups | gmane.comp.python.twisted.commits |
|---|---|
| Message-ID | <[email protected]> |
Author: hawkowl
Date: Tue Mar 15 21:54:30 2016
New Revision: 47001
Added:
trunk/twisted/topfiles/8124.misc
Modified:
trunk/twisted/python/deprecate.py
trunk/twisted/python/test/test_deprecate.py
Log:
Merge deprecated-property-8124: Make @deprecatedProperty, to make deprecated properties
Author: adiroiban, hawkowl
Reviewer: glyph
Fixes: #8124
Modified: trunk/twisted/python/deprecate.py
==============================================================================
--- trunk/twisted/python/deprecate.py (original)
+++ trunk/twisted/python/deprecate.py Tue Mar 15 21:54:30 2016
@@ -5,7 +5,7 @@
"""
Deprecation framework for Twisted.
-To mark a method or function as being deprecated do this::
+To mark a method, function, or class as being deprecated do this::
from twisted.python.versions import Version
from twisted.python.deprecate import deprecated
@@ -17,8 +17,35 @@
'''
...
-The newly-decorated badAPI will issue a warning when called. It will also have
-a deprecation notice appended to its docstring.
+ @deprecated(Version("Twisted", 16, 0, 0))
+ class BadClass(object):
+ '''
+ Docstring for BadClass.
+ '''
+
+The newly-decorated badAPI will issue a warning when called, and BadClass will
+issue a warning when instantiated. Both will also have a deprecation notice
+appended to their docstring.
+
+To deprecate properties you can use::
+
+ from twisted.python.versions import Version
+ from twisted.python.deprecate import deprecatedProperty
+
+ class OtherwiseUndeprecatedClass(object):
+
+ @deprecatedProperty(Version('Twisted', 16, 0, 0))
+ def badProperty(self):
+ '''
+ Docstring for badProperty.
+ '''
+
+ @badProperty.setter
+ def badProperty(self, value):
+ '''
+ Setter sill also raise the deprecation warning.
+ '''
+
To mark module-level attributes as being deprecated you can use::
@@ -48,6 +75,7 @@
__all__ = [
'deprecated',
+ 'deprecatedProperty',
'getDeprecationWarningString',
'getWarningMethod',
'setWarningMethod',
@@ -61,6 +89,7 @@
from functools import wraps
from twisted.python.versions import getVersionString
+from twisted.python.compat import _PY3
DEPRECATION_WARNING_FORMAT = '%(fqpn)s was deprecated in %(version)s'
@@ -239,7 +268,8 @@
def deprecated(version, replacement=None):
"""
- Return a decorator that marks callables as deprecated.
+ Return a decorator that marks callables as deprecated. To deprecate a
+ property, see L{deprecatedProperty}.
@type version: L{twisted.python.versions.Version}
@param version: The version in which the callable will be marked as
@@ -279,6 +309,89 @@
+def deprecatedProperty(version, replacement=None):
+ """
+ Return a decorator that marks a property as deprecated. To deprecate a
+ regular callable or class, see L{deprecated}.
+
+ @type version: L{twisted.python.versions.Version}
+ @param version: The version in which the callable will be marked as
+ having been deprecated. The decorated function will be annotated
+ with this version, having it set as its C{deprecatedVersion}
+ attribute.
+
+ @param version: the version that the callable was deprecated in.
+ @type version: L{twisted.python.versions.Version}
+
+ @param replacement: what should be used in place of the callable.
+ Either pass in a string, which will be inserted into the warning
+ message, or a callable, which will be expanded to its full import
+ path.
+ @type replacement: C{str} or callable
+
+ @return: A new property with deprecated setter and getter.
+ @rtype: C{property}
+
+ @since: 16.1.0
+ """
+
+ class _DeprecatedProperty(property):
+ """
+ Extension of the build-in property to allow deprecated setters.
+ """
+
+ def _deprecatedWrapper(self, function):
+ @wraps(function)
+ def deprecatedFunction(*args, **kwargs):
+ warn(
+ self.warningString,
+ DeprecationWarning,
+ stacklevel=2)
+ return function(*args, **kwargs)
+ return deprecatedFunction
+
+
+ def setter(self, function):
+ return property.setter(self, self._deprecatedWrapper(function))
+
+
+ def deprecationDecorator(function):
+ if _PY3:
+ warningString = getDeprecationWarningString(
+ function, version, None, replacement)
+ else:
+ # Because Python 2 sucks, we need to implement our own here -- lack
+ # of __qualname__ means that we kinda have to stack walk. It maybe
+ # probably works. Probably. -Amber
+ functionName = function.__name__
+ className = inspect.stack()[1][3] # wow hax
+ moduleName = function.__module__
+
+ fqdn = "%s.%s.%s" % (moduleName, className, functionName)
+
+ warningString = _getDeprecationWarningString(
+ fqdn, version, None, replacement)
+
+ @wraps(function)
+ def deprecatedFunction(*args, **kwargs):
+ warn(
+ warningString,
+ DeprecationWarning,
+ stacklevel=2)
+ return function(*args, **kwargs)
+
+ _appendToDocstring(deprecatedFunction,
+ _getDeprecationDocstring(version, replacement))
+ deprecatedFunction.deprecatedVersion = version
+
+ result = _DeprecatedProperty(deprecatedFunction)
+ result.warningString = warningString
+ return result
+
+ return deprecationDecorator
+
+
+
def getWarningMethod():
"""
Return the warning method currently used to record deprecation warnings.
Modified: trunk/twisted/python/test/test_deprecate.py
==============================================================================
--- trunk/twisted/python/test/test_deprecate.py (original)
+++ trunk/twisted/python/test/test_deprecate.py Tue Mar 15 21:54:30 2016
@@ -22,7 +22,8 @@
getDeprecationWarningString,
deprecated, _appendToDocstring, _getDeprecationDocstring,
_fullyQualifiedName as fullyQualifiedName,
- _passed, _mutuallyExclusiveArguments
+ _passed, _mutuallyExclusiveArguments,
+ deprecatedProperty,
)
from twisted.python.versions import Version
@@ -530,6 +531,7 @@
"Unexpected warning string: %r" % (msg,))
+
def dummyCallable():
"""
Do nothing.
@@ -686,6 +688,143 @@
__name__))
+
+@deprecated(Version('Twisted', 1, 2, 3))
+class DeprecatedClass(object):
+ """
+ Class which is entirely deprecated without having a replacement.
+ """
+
+
+
+class ClassWithDeprecatedProperty(object):
+ """
+ Class with a single deprecated property.
+ """
+
+ _someProtectedValue = None
+
+ @deprecatedProperty(Version('Twisted', 1, 2, 3))
+ def someProperty(self):
+ """
+ Getter docstring.
+
+ @return: The property.
+ """
+ return self._someProtectedValue
+
+
+ @someProperty.setter
+ def someProperty(self, value):
+ """
+ Setter docstring.
+ """
+ self._someProtectedValue = value
+
+
+
+class DeprecatedDecoratorTests(SynchronousTestCase):
+ """
+ Tests for deprecated decorators.
+ """
+
+ def assertDocstring(self, target, expected):
+ """
+ Check that C{target} object has the C{expected} docstring lines.
+
+ @param target: Object which is checked.
+ @type target: C{anything}
+
+ @param expected: List of lines, ignoring empty lines or leading or
+ trailing spaces.
+ @type expected: L{list} or L{str}
+ """
+ self.assertEqual(
+ expected,
+ [x.strip() for x in target.__doc__.splitlines() if x.strip()]
+ )
+
+
+ def test_propertyGetter(self):
+ """
+ When L{deprecatedProperty} is used on a C{property}, accesses raise a
+ L{DeprecationWarning} and getter docstring is updated to inform the
+ version in which it was deprecated. C{deprecatedVersion} attribute is
+ also set to inform the deprecation version.
+ """
+ obj = ClassWithDeprecatedProperty()
+
+ obj.someProperty
+
+ self.assertDocstring(
+ ClassWithDeprecatedProperty.someProperty,
+ [
+ 'Getter docstring.',
+ '@return: The property.',
+ 'Deprecated in Twisted 1.2.3.',
+ ],
+ )
+ ClassWithDeprecatedProperty.someProperty.deprecatedVersion = Version(
+ 'Twisted', 1, 2, 3)
+
+ message = (
+ 'twisted.python.test.test_deprecate.ClassWithDeprecatedProperty.'
+ 'someProperty was deprecated in Twisted 1.2.3'
+ )
+ warnings = self.flushWarnings([self.test_propertyGetter])
+ self.assertEqual(1, len(warnings))
+ self.assertEqual(DeprecationWarning, warnings[0]['category'])
+ self.assertEqual(message, warnings[0]['message'])
+
+
+ def test_propertySetter(self):
+ """
+ When L{deprecatedProperty} is used on a C{property}, setter accesses
+ raise a L{DeprecationWarning}.
+ """
+ newValue = object()
+ obj = ClassWithDeprecatedProperty()
+
+ obj.someProperty = newValue
+
+ self.assertIs(newValue, obj._someProtectedValue)
+ message = (
+ 'twisted.python.test.test_deprecate.ClassWithDeprecatedProperty.'
+ 'someProperty was deprecated in Twisted 1.2.3'
+ )
+ warnings = self.flushWarnings([self.test_propertySetter])
+ self.assertEqual(1, len(warnings))
+ self.assertEqual(DeprecationWarning, warnings[0]['category'])
+ self.assertEqual(message, warnings[0]['message'])
+
+
+ def test_class(self):
+ """
+ When L{deprecated} is used on a class, instantiations raise a
+ L{DeprecationWarning} and class's docstring is updated to inform the
+ version in which it was deprecated. C{deprecatedVersion} attribute is
+ also set to inform the deprecation version.
+ """
+ DeprecatedClass()
+
+ self.assertDocstring(
+ DeprecatedClass,
+ [('Class which is entirely deprecated without having a '
+ 'replacement.'),
+ 'Deprecated in Twisted 1.2.3.'],
+ )
+ DeprecatedClass.deprecatedVersion = Version('Twisted', 1, 2, 3)
+
+ message = (
+ 'twisted.python.test.test_deprecate.DeprecatedClass '
+ 'was deprecated in Twisted 1.2.3'
+ )
+ warnings = self.flushWarnings([self.test_class])
+ self.assertEqual(1, len(warnings))
+ self.assertEqual(DeprecationWarning, warnings[0]['category'])
+ self.assertEqual(message, warnings[0]['message'])
+
+
def test_deprecatedReplacement(self):
"""
L{deprecated} takes an additional replacement parameter that can be used