Skip to content

Commit

Permalink
Merge deprecated-property-8124: Make @deprecatedProperty, to make dep…
Browse files Browse the repository at this point in the history
…recated properties

Author: adiroiban, hawkowl
Reviewer: glyph
Fixes: twisted#8124

git-svn-id: svn://svn.twistedmatrix.com/svn/Twisted/trunk@47001 bbbe8e31-12d6-0310-92fd-ac37d47ddeeb
  • Loading branch information
hawkowl committed Mar 16, 2016
1 parent 4b23d59 commit f074ba3
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 5 deletions.
121 changes: 117 additions & 4 deletions twisted/python/deprecate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,8 +17,35 @@ def badAPI(self, first, second):
'''
...
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::
Expand Down Expand Up @@ -48,6 +75,7 @@ def badAPI(self, first, second):

__all__ = [
'deprecated',
'deprecatedProperty',
'getDeprecationWarningString',
'getWarningMethod',
'setWarningMethod',
Expand All @@ -61,6 +89,7 @@ def badAPI(self, first, second):
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'

Expand Down Expand Up @@ -239,7 +268,8 @@ def _appendToDocstring(thingWithDoc, textToAppend):

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
Expand Down Expand Up @@ -279,6 +309,89 @@ def deprecatedFunction(*args, **kwargs):



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.
Expand Down
141 changes: 140 additions & 1 deletion twisted/python/test/test_deprecate.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
getDeprecationWarningString,
deprecated, _appendToDocstring, _getDeprecationDocstring,
_fullyQualifiedName as fullyQualifiedName,
_passed, _mutuallyExclusiveArguments
_passed, _mutuallyExclusiveArguments,
deprecatedProperty,
)

from twisted.python.versions import Version
Expand Down Expand Up @@ -530,6 +531,7 @@ def test_filteredOnceWarning(self):
"Unexpected warning string: %r" % (msg,))



def dummyCallable():
"""
Do nothing.
Expand Down Expand Up @@ -686,6 +688,143 @@ def test_getDeprecationWarningStringReplacementWithCallable(self):
__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
Expand Down
Empty file added twisted/topfiles/8124.misc
Empty file.

0 comments on commit f074ba3

Please sign in to comment.