1
# -*- test-case-name: twisted.python.test.test_deprecate -*-
2
# Copyright (c) 2008-2010 Twisted Matrix Laboratories.
3
# See LICENSE for details.
6
Deprecation framework for Twisted.
8
To mark a method or function as being deprecated do this::
10
def badAPI(self, first, second):
15
badAPI = deprecate(Version("Twisted", 8, 0, 0))(badAPI)
17
The newly-decorated badAPI will issue a warning when called. It will also have
18
a deprecation notice appended to its docstring.
20
To mark module-level attributes as being deprecated you can use::
22
badAttribute = "someValue"
26
deprecatedModuleAttribute(
27
Version("Twisted", 8, 0, 0),
28
"Use goodAttribute instead.",
29
"your.full.module.name",
32
The deprecated attributes will issue a warning whenever they are accessed. If
33
the attributes being deprecated are in the same module as the
34
L{deprecatedModuleAttribute} call is being made from, the C{__name__} global
35
can be used as the C{moduleName} parameter.
39
@type DEPRECATION_WARNING_FORMAT: C{str}
40
@var DEPRECATION_WARNING_FORMAT: The default deprecation warning string format
41
to use when one is not provided by the user.
47
'getDeprecationWarningString',
50
'deprecatedModuleAttribute',
55
from warnings import warn
57
from twisted.python.versions import getVersionString
58
from twisted.python.util import mergeFunctionMetadata
62
DEPRECATION_WARNING_FORMAT = '%(fqpn)s was deprecated in %(version)s'
65
# Notionally, part of twisted.python.reflect, but defining it there causes a
66
# cyclic dependency between this module and that module. Define it here,
67
# instead, and let reflect import it to re-expose to the public.
68
def _fullyQualifiedName(obj):
70
Return the fully qualified name of a module, class, method or function.
71
Classes and functions need to be module level ones to be correctly
77
if inspect.isclass(obj) or inspect.isfunction(obj):
78
moduleName = obj.__module__
79
return "%s.%s" % (moduleName, name)
80
elif inspect.ismethod(obj):
81
className = _fullyQualifiedName(obj.im_class)
82
return "%s.%s" % (className, name)
84
# Try to keep it looking like something in twisted.python.reflect.
85
_fullyQualifiedName.__module__ = 'twisted.python.reflect'
86
_fullyQualifiedName.__name__ = 'fullyQualifiedName'
89
def getWarningMethod():
91
Return the warning method currently used to record deprecation warnings.
97
def setWarningMethod(newMethod):
99
Set the warning method to use to record deprecation warnings.
101
The callable should take message, category and stacklevel. The return
109
def _getDeprecationDocstring(version):
110
return "Deprecated in %s." % getVersionString(version)
114
def _getDeprecationWarningString(fqpn, version, format=None):
116
Return a string indicating that the Python name was deprecated in the given
120
@param fqpn: Fully qualified Python name of the thing being deprecated
122
@type version: L{twisted.python.versions.Version}
123
@param version: Version that C{fqpn} was deprecated in
126
@param format: A user-provided format to interpolate warning values into,
127
or L{DEPRECATION_WARNING_FORMAT} if C{None} is given
130
@return: A textual description of the deprecation
133
format = DEPRECATION_WARNING_FORMAT
136
'version': getVersionString(version)}
140
def getDeprecationWarningString(callableThing, version, format=None):
142
Return a string indicating that the callable was deprecated in the given
145
@type callableThing: C{callable}
146
@param callableThing: Callable object to be deprecated
148
@type version: L{twisted.python.versions.Version}
149
@param version: Version that C{fqpn} was deprecated in
152
@param format: A user-provided format to interpolate warning values into,
153
or L{DEPRECATION_WARNING_FORMAT} if C{None} is given
156
@return: A textual description of the deprecation
158
return _getDeprecationWarningString(
159
_fullyQualifiedName(callableThing), version, format)
163
def deprecated(version):
165
Return a decorator that marks callables as deprecated.
167
@type version: L{twisted.python.versions.Version}
168
@param version: The version in which the callable will be marked as
169
having been deprecated. The decorated function will be annotated
170
with this version, having it set as its C{deprecatedVersion}
173
def deprecationDecorator(function):
175
Decorator that marks C{function} as deprecated.
177
warningString = getDeprecationWarningString(function, version)
179
def deprecatedFunction(*args, **kwargs):
184
return function(*args, **kwargs)
186
deprecatedFunction = mergeFunctionMetadata(
187
function, deprecatedFunction)
188
_appendToDocstring(deprecatedFunction,
189
_getDeprecationDocstring(version))
190
deprecatedFunction.deprecatedVersion = version
191
return deprecatedFunction
193
return deprecationDecorator
197
def _appendToDocstring(thingWithDoc, textToAppend):
199
Append the given text to the docstring of C{thingWithDoc}.
201
If C{thingWithDoc} has no docstring, then the text just replaces the
202
docstring. If it has a single-line docstring then it appends a blank line
203
and the message text. If it has a multi-line docstring, then in appends a
204
blank line a the message text, and also does the indentation correctly.
206
if thingWithDoc.__doc__:
207
docstringLines = thingWithDoc.__doc__.splitlines()
211
if len(docstringLines) == 0:
212
docstringLines.append(textToAppend)
213
elif len(docstringLines) == 1:
214
docstringLines.extend(['', textToAppend, ''])
216
spaces = docstringLines.pop()
217
docstringLines.extend(['',
218
spaces + textToAppend,
220
thingWithDoc.__doc__ = '\n'.join(docstringLines)
224
class _ModuleProxy(object):
226
Python module wrapper to hook module-level attribute access.
228
Access to deprecated attributes first checks L{_deprecatedAttributes}, if
229
the attribute does not appear there then access falls through to L{_module},
230
the wrapped module object.
232
@type _module: C{module}
233
@ivar _module: Module on which to hook attribute access.
235
@type _deprecatedAttributes: C{dict} mapping C{str} to
236
L{_DeprecatedAttribute}
237
@ivar _deprecatedAttributes: Mapping of attribute names to objects that
238
retrieve the module attribute's original value.
240
def __init__(self, module):
241
object.__setattr__(self, '_module', module)
242
object.__setattr__(self, '_deprecatedAttributes', {})
247
Get a string containing the type of the module proxy and a
248
representation of the wrapped module object.
250
_module = object.__getattribute__(self, '_module')
251
return '<%s module=%r>' % (
256
def __setattr__(self, name, value):
258
Set an attribute on the wrapped module object.
260
_module = object.__getattribute__(self, '_module')
261
setattr(_module, name, value)
264
def __getattribute__(self, name):
266
Get an attribute on the wrapped module object.
268
If the specified name has been deprecated then a warning is issued.
270
_module = object.__getattribute__(self, '_module')
271
_deprecatedAttributes = object.__getattribute__(
272
self, '_deprecatedAttributes')
274
getter = _deprecatedAttributes.get(name)
275
if getter is not None:
278
value = getattr(_module, name)
283
class _DeprecatedAttribute(object):
285
Wrapper for deprecated attributes.
287
This is intended to be used by L{_ModuleProxy}. Calling
288
L{_DeprecatedAttribute.get} will issue a warning and retrieve the
289
underlying attribute's value.
291
@type module: C{module}
292
@ivar module: The original module instance containing this attribute
295
@ivar fqpn: Fully qualified Python name for the deprecated attribute
297
@type version: L{twisted.python.versions.Version}
298
@ivar version: Version that the attribute was deprecated in
300
@type message: C{str}
301
@ivar message: Deprecation message
303
def __init__(self, module, name, version, message):
305
Initialise a deprecated name wrapper.
309
self.fqpn = module.__name__ + '.' + name
310
self.version = version
311
self.message = message
316
Get the underlying attribute value and issue a deprecation warning.
318
message = _getDeprecationWarningString(self.fqpn, self.version,
319
DEPRECATION_WARNING_FORMAT + ': ' + self.message)
320
warn(message, DeprecationWarning, stacklevel=3)
321
return getattr(self.module, self.__name__)
325
def _deprecateAttribute(proxy, name, version, message):
327
Mark a module-level attribute as being deprecated.
329
@type proxy: L{_ModuleProxy}
330
@param proxy: The module proxy instance proxying the deprecated attributes
333
@param name: Attribute name
335
@type version: L{twisted.python.versions.Version}
336
@param version: Version that the attribute was deprecated in
338
@type message: C{str}
339
@param message: Deprecation message
341
_module = object.__getattribute__(proxy, '_module')
342
attr = _DeprecatedAttribute(_module, name, version, message)
343
# Add a deprecated attribute marker for this module's attribute. When this
344
# attribute is accessed via _ModuleProxy a warning is emitted.
345
_deprecatedAttributes = object.__getattribute__(
346
proxy, '_deprecatedAttributes')
347
_deprecatedAttributes[name] = attr
351
def deprecatedModuleAttribute(version, message, moduleName, name):
353
Declare a module-level attribute as being deprecated.
355
@type version: L{twisted.python.versions.Version}
356
@param version: Version that the attribute was deprecated in
358
@type message: C{str}
359
@param message: Deprecation message
361
@type moduleName: C{str}
362
@param moduleName: Fully-qualified Python name of the module containing
363
the deprecated attribute; if called from the same module as the
364
attributes are being deprecated in, using the C{__name__} global can
368
@param name: Attribute name to deprecate
370
module = sys.modules[moduleName]
371
if not isinstance(module, _ModuleProxy):
372
module = _ModuleProxy(module)
373
sys.modules[moduleName] = module
375
_deprecateAttribute(module, name, version, message)