Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / python / deprecate.py
1 # -*- test-case-name: twisted.python.test.test_deprecate -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 Deprecation framework for Twisted.
7
8 To mark a method or function as being deprecated do this::
9
10     from twisted.python.versions import Version
11     from twisted.python.deprecate import deprecated
12
13     @deprecated(Version("Twisted", 8, 0, 0))
14     def badAPI(self, first, second):
15         '''
16         Docstring for badAPI.
17         '''
18         ...
19
20 The newly-decorated badAPI will issue a warning when called. It will also have
21 a deprecation notice appended to its docstring.
22
23 To mark module-level attributes as being deprecated you can use::
24
25     badAttribute = "someValue"
26
27     ...
28
29     deprecatedModuleAttribute(
30         Version("Twisted", 8, 0, 0),
31         "Use goodAttribute instead.",
32         "your.full.module.name",
33         "badAttribute")
34
35 The deprecated attributes will issue a warning whenever they are accessed. If
36 the attributes being deprecated are in the same module as the
37 L{deprecatedModuleAttribute} call is being made from, the C{__name__} global
38 can be used as the C{moduleName} parameter.
39
40 See also L{Version}.
41
42 @type DEPRECATION_WARNING_FORMAT: C{str}
43 @var DEPRECATION_WARNING_FORMAT: The default deprecation warning string format
44     to use when one is not provided by the user.
45 """
46
47
48 __all__ = [
49     'deprecated',
50     'getDeprecationWarningString',
51     'getWarningMethod',
52     'setWarningMethod',
53     'deprecatedModuleAttribute',
54     ]
55
56
57 import sys, inspect
58 from warnings import warn, warn_explicit
59 from dis import findlinestarts
60
61 from twisted.python.versions import getVersionString
62 from twisted.python.util import mergeFunctionMetadata
63
64
65
66 DEPRECATION_WARNING_FORMAT = '%(fqpn)s was deprecated in %(version)s'
67
68
69 # Notionally, part of twisted.python.reflect, but defining it there causes a
70 # cyclic dependency between this module and that module.  Define it here,
71 # instead, and let reflect import it to re-expose to the public.
72 def _fullyQualifiedName(obj):
73     """
74     Return the fully qualified name of a module, class, method or function.
75     Classes and functions need to be module level ones to be correctly
76     qualified.
77
78     @rtype: C{str}.
79     """
80     name = obj.__name__
81     if inspect.isclass(obj) or inspect.isfunction(obj):
82         moduleName = obj.__module__
83         return "%s.%s" % (moduleName, name)
84     elif inspect.ismethod(obj):
85         className = _fullyQualifiedName(obj.im_class)
86         return "%s.%s" % (className, name)
87     return name
88 # Try to keep it looking like something in twisted.python.reflect.
89 _fullyQualifiedName.__module__ = 'twisted.python.reflect'
90 _fullyQualifiedName.__name__ = 'fullyQualifiedName'
91
92
93
94 def getWarningMethod():
95     """
96     Return the warning method currently used to record deprecation warnings.
97     """
98     return warn
99
100
101
102 def setWarningMethod(newMethod):
103     """
104     Set the warning method to use to record deprecation warnings.
105
106     The callable should take message, category and stacklevel. The return
107     value is ignored.
108     """
109     global warn
110     warn = newMethod
111
112
113
114 def _getDeprecationDocstring(version, replacement=None):
115     """
116     Generate an addition to a deprecated object's docstring that explains its
117     deprecation.
118
119     @param version: the version it was deprecated.
120     @type version: L{Version}
121
122     @param replacement: The replacement, if specified.
123     @type replacement: C{str} or callable
124
125     @return: a string like "Deprecated in Twisted 27.2.0; please use
126         twisted.timestream.tachyon.flux instead."
127     """
128     doc = "Deprecated in %s" % (getVersionString(version),)
129     if replacement:
130         doc = "%s; %s" % (doc, _getReplacementString(replacement))
131     return doc + "."
132
133
134
135 def _getReplacementString(replacement):
136     """
137     Surround a replacement for a deprecated API with some polite text exhorting
138     the user to consider it as an alternative.
139
140     @type replacement: C{str} or callable
141
142     @return: a string like "please use twisted.python.modules.getModule
143         instead".
144     """
145     if callable(replacement):
146         replacement = _fullyQualifiedName(replacement)
147     return "please use %s instead" % (replacement,)
148
149
150
151 def _getDeprecationWarningString(fqpn, version, format=None, replacement=None):
152     """
153     Return a string indicating that the Python name was deprecated in the given
154     version.
155
156     @param fqpn: Fully qualified Python name of the thing being deprecated
157     @type fqpn: C{str}
158
159     @param version: Version that C{fqpn} was deprecated in.
160     @type version: L{twisted.python.versions.Version}
161
162     @param format: A user-provided format to interpolate warning values into, or
163         L{DEPRECATION_WARNING_FORMAT
164         <twisted.python.deprecate.DEPRECATION_WARNING_FORMAT>} if C{None} is
165         given.
166     @type format: C{str}
167
168     @param replacement: what should be used in place of C{fqpn}. Either pass in
169         a string, which will be inserted into the warning message, or a
170         callable, which will be expanded to its full import path.
171     @type replacement: C{str} or callable
172
173     @return: A textual description of the deprecation
174     @rtype: C{str}
175     """
176     if format is None:
177         format = DEPRECATION_WARNING_FORMAT
178     warningString = format % {
179         'fqpn': fqpn,
180         'version': getVersionString(version)}
181     if replacement:
182         warningString = "%s; %s" % (
183             warningString, _getReplacementString(replacement))
184     return warningString
185
186
187
188 def getDeprecationWarningString(callableThing, version, format=None,
189                                 replacement=None):
190     """
191     Return a string indicating that the callable was deprecated in the given
192     version.
193
194     @type callableThing: C{callable}
195     @param callableThing: Callable object to be deprecated
196
197     @type version: L{twisted.python.versions.Version}
198     @param version: Version that C{callableThing} was deprecated in
199
200     @type format: C{str}
201     @param format: A user-provided format to interpolate warning values into,
202         or L{DEPRECATION_WARNING_FORMAT
203         <twisted.python.deprecate.DEPRECATION_WARNING_FORMAT>} if C{None} is
204         given
205
206     @param callableThing: A callable to be deprecated.
207
208     @param version: The L{twisted.python.versions.Version} that the callable
209         was deprecated in.
210
211     @param replacement: what should be used in place of the callable. Either
212         pass in a string, which will be inserted into the warning message,
213         or a callable, which will be expanded to its full import path.
214     @type replacement: C{str} or callable
215
216     @return: A string describing the deprecation.
217     @rtype: C{str}
218     """
219     return _getDeprecationWarningString(
220         _fullyQualifiedName(callableThing), version, format, replacement)
221
222
223
224 def deprecated(version, replacement=None):
225     """
226     Return a decorator that marks callables as deprecated.
227
228     @type version: L{twisted.python.versions.Version}
229     @param version: The version in which the callable will be marked as
230         having been deprecated.  The decorated function will be annotated
231         with this version, having it set as its C{deprecatedVersion}
232         attribute.
233
234     @param version: the version that the callable was deprecated in.
235     @type version: L{twisted.python.versions.Version}
236
237     @param replacement: what should be used in place of the callable. Either
238         pass in a string, which will be inserted into the warning message,
239         or a callable, which will be expanded to its full import path.
240     @type replacement: C{str} or callable
241     """
242     def deprecationDecorator(function):
243         """
244         Decorator that marks C{function} as deprecated.
245         """
246         warningString = getDeprecationWarningString(
247             function, version, None, replacement)
248
249         def deprecatedFunction(*args, **kwargs):
250             warn(
251                 warningString,
252                 DeprecationWarning,
253                 stacklevel=2)
254             return function(*args, **kwargs)
255
256         deprecatedFunction = mergeFunctionMetadata(
257             function, deprecatedFunction)
258         _appendToDocstring(deprecatedFunction,
259                            _getDeprecationDocstring(version, replacement))
260         deprecatedFunction.deprecatedVersion = version
261         return deprecatedFunction
262
263     return deprecationDecorator
264
265
266
267 def _appendToDocstring(thingWithDoc, textToAppend):
268     """
269     Append the given text to the docstring of C{thingWithDoc}.
270
271     If C{thingWithDoc} has no docstring, then the text just replaces the
272     docstring. If it has a single-line docstring then it appends a blank line
273     and the message text. If it has a multi-line docstring, then in appends a
274     blank line a the message text, and also does the indentation correctly.
275     """
276     if thingWithDoc.__doc__:
277         docstringLines = thingWithDoc.__doc__.splitlines()
278     else:
279         docstringLines = []
280
281     if len(docstringLines) == 0:
282         docstringLines.append(textToAppend)
283     elif len(docstringLines) == 1:
284         docstringLines.extend(['', textToAppend, ''])
285     else:
286         spaces = docstringLines.pop()
287         docstringLines.extend(['',
288                                spaces + textToAppend,
289                                spaces])
290     thingWithDoc.__doc__ = '\n'.join(docstringLines)
291
292
293
294 class _InternalState(object):
295     """
296     An L{_InternalState} is a helper object for a L{_ModuleProxy}, so that it
297     can easily access its own attributes, bypassing its logic for delegating to
298     another object that it's proxying for.
299
300     @ivar proxy: a L{ModuleProxy}
301     """
302     def __init__(self, proxy):
303         object.__setattr__(self, 'proxy', proxy)
304
305
306     def __getattribute__(self, name):
307         return object.__getattribute__(object.__getattribute__(self, 'proxy'),
308                                        name)
309
310
311     def __setattr__(self, name, value):
312         return object.__setattr__(object.__getattribute__(self, 'proxy'),
313                                   name, value)
314
315
316
317 class _ModuleProxy(object):
318     """
319     Python module wrapper to hook module-level attribute access.
320
321     Access to deprecated attributes first checks
322     L{_ModuleProxy._deprecatedAttributes}, if the attribute does not appear
323     there then access falls through to L{_ModuleProxy._module}, the wrapped
324     module object.
325
326     @ivar _module: Module on which to hook attribute access.
327     @type _module: C{module}
328
329     @ivar _deprecatedAttributes: Mapping of attribute names to objects that
330         retrieve the module attribute's original value.
331     @type _deprecatedAttributes: C{dict} mapping C{str} to
332         L{_DeprecatedAttribute}
333
334     @ivar _lastWasPath: Heuristic guess as to whether warnings about this
335         package should be ignored for the next call.  If the last attribute
336         access of this module was a C{getattr} of C{__path__}, we will assume
337         that it was the import system doing it and we won't emit a warning for
338         the next access, even if it is to a deprecated attribute.  The CPython
339         import system always tries to access C{__path__}, then the attribute
340         itself, then the attribute itself again, in both successful and failed
341         cases.
342     @type _lastWasPath: C{bool}
343     """
344     def __init__(self, module):
345         state = _InternalState(self)
346         state._module = module
347         state._deprecatedAttributes = {}
348         state._lastWasPath = False
349
350
351     def __repr__(self):
352         """
353         Get a string containing the type of the module proxy and a
354         representation of the wrapped module object.
355         """
356         state = _InternalState(self)
357         return '<%s module=%r>' % (type(self).__name__, state._module)
358
359
360     def __setattr__(self, name, value):
361         """
362         Set an attribute on the wrapped module object.
363         """
364         state = _InternalState(self)
365         state._lastWasPath = False
366         setattr(state._module, name, value)
367
368
369     def __getattribute__(self, name):
370         """
371         Get an attribute from the module object, possibly emitting a warning.
372
373         If the specified name has been deprecated, then a warning is issued.
374         (Unless certain obscure conditions are met; see
375         L{_ModuleProxy._lastWasPath} for more information about what might quash
376         such a warning.)
377         """
378         state = _InternalState(self)
379         if state._lastWasPath:
380             deprecatedAttribute = None
381         else:
382             deprecatedAttribute = state._deprecatedAttributes.get(name)
383
384         if deprecatedAttribute is not None:
385             # If we have a _DeprecatedAttribute object from the earlier lookup,
386             # allow it to issue the warning.
387             value = deprecatedAttribute.get()
388         else:
389             # Otherwise, just retrieve the underlying value directly; it's not
390             # deprecated, there's no warning to issue.
391             value = getattr(state._module, name)
392         if name == '__path__':
393             state._lastWasPath = True
394         else:
395             state._lastWasPath = False
396         return value
397
398
399
400 class _DeprecatedAttribute(object):
401     """
402     Wrapper for deprecated attributes.
403
404     This is intended to be used by L{_ModuleProxy}. Calling
405     L{_DeprecatedAttribute.get} will issue a warning and retrieve the
406     underlying attribute's value.
407
408     @type module: C{module}
409     @ivar module: The original module instance containing this attribute
410
411     @type fqpn: C{str}
412     @ivar fqpn: Fully qualified Python name for the deprecated attribute
413
414     @type version: L{twisted.python.versions.Version}
415     @ivar version: Version that the attribute was deprecated in
416
417     @type message: C{str}
418     @ivar message: Deprecation message
419     """
420     def __init__(self, module, name, version, message):
421         """
422         Initialise a deprecated name wrapper.
423         """
424         self.module = module
425         self.__name__ = name
426         self.fqpn = module.__name__ + '.' + name
427         self.version = version
428         self.message = message
429
430
431     def get(self):
432         """
433         Get the underlying attribute value and issue a deprecation warning.
434         """
435         # This might fail if the deprecated thing is a module inside a package.
436         # In that case, don't emit the warning this time.  The import system
437         # will come back again when it's not an AttributeError and we can emit
438         # the warning then.
439         result = getattr(self.module, self.__name__)
440         message = _getDeprecationWarningString(self.fqpn, self.version,
441             DEPRECATION_WARNING_FORMAT + ': ' + self.message)
442         warn(message, DeprecationWarning, stacklevel=3)
443         return result
444
445
446
447 def _deprecateAttribute(proxy, name, version, message):
448     """
449     Mark a module-level attribute as being deprecated.
450
451     @type proxy: L{_ModuleProxy}
452     @param proxy: The module proxy instance proxying the deprecated attributes
453
454     @type name: C{str}
455     @param name: Attribute name
456
457     @type version: L{twisted.python.versions.Version}
458     @param version: Version that the attribute was deprecated in
459
460     @type message: C{str}
461     @param message: Deprecation message
462     """
463     _module = object.__getattribute__(proxy, '_module')
464     attr = _DeprecatedAttribute(_module, name, version, message)
465     # Add a deprecated attribute marker for this module's attribute. When this
466     # attribute is accessed via _ModuleProxy a warning is emitted.
467     _deprecatedAttributes = object.__getattribute__(
468         proxy, '_deprecatedAttributes')
469     _deprecatedAttributes[name] = attr
470
471
472
473 def deprecatedModuleAttribute(version, message, moduleName, name):
474     """
475     Declare a module-level attribute as being deprecated.
476
477     @type version: L{twisted.python.versions.Version}
478     @param version: Version that the attribute was deprecated in
479
480     @type message: C{str}
481     @param message: Deprecation message
482
483     @type moduleName: C{str}
484     @param moduleName: Fully-qualified Python name of the module containing
485         the deprecated attribute; if called from the same module as the
486         attributes are being deprecated in, using the C{__name__} global can
487         be helpful
488
489     @type name: C{str}
490     @param name: Attribute name to deprecate
491     """
492     module = sys.modules[moduleName]
493     if not isinstance(module, _ModuleProxy):
494         module = _ModuleProxy(module)
495         sys.modules[moduleName] = module
496
497     _deprecateAttribute(module, name, version, message)
498
499
500 def warnAboutFunction(offender, warningString):
501     """
502     Issue a warning string, identifying C{offender} as the responsible code.
503
504     This function is used to deprecate some behavior of a function.  It differs
505     from L{warnings.warn} in that it is not limited to deprecating the behavior
506     of a function currently on the call stack.
507
508     @param function: The function that is being deprecated.
509
510     @param warningString: The string that should be emitted by this warning.
511     @type warningString: C{str}
512
513     @since: 11.0
514     """
515     # inspect.getmodule() is attractive, but somewhat
516     # broken in Python < 2.6.  See Python bug 4845.
517     offenderModule = sys.modules[offender.__module__]
518     filename = inspect.getabsfile(offenderModule)
519     lineStarts = list(findlinestarts(offender.func_code))
520     lastLineNo = lineStarts[-1][1]
521     globals = offender.func_globals
522
523     kwargs = dict(
524         category=DeprecationWarning,
525         filename=filename,
526         lineno=lastLineNo,
527         module=offenderModule.__name__,
528         registry=globals.setdefault("__warningregistry__", {}),
529         module_globals=None)
530
531     if sys.version_info[:2] < (2, 5):
532         kwargs.pop('module_globals')
533
534     warn_explicit(warningString, **kwargs)