~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/symbol_versioning.py

Close logging handler on disabling the test log. This will remove the
handler from the internal list inside python's logging module,
preventing shutdown from closing it twice.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
2
 
#   Authors: Robert Collins <robert.collins@canonical.com> and others
 
1
# Copyright (C) 2006 by Canonical Ltd
 
2
#   Authors: Robert Collins <robert.collins@canonical.com>
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
5
5
# it under the terms of the GNU General Public License as published by
21
21
"""
22
22
 
23
23
__all__ = ['deprecated_function',
24
 
           'deprecated_in',
25
 
           'deprecated_list',
26
24
           'deprecated_method',
27
25
           'DEPRECATED_PARAMETER',
28
26
           'deprecated_passed',
29
 
           'set_warning_method',
30
 
           'warn',
31
 
           'zero_seven',
 
27
           'warn', 'set_warning_method', 'zero_seven',
32
28
           'zero_eight',
33
 
           'zero_nine',
34
 
           'zero_ten',
35
 
           'zero_eleven',
36
 
           'zero_twelve',
37
 
           'zero_thirteen',
38
 
           'zero_fourteen',
39
 
           'zero_fifteen',
40
 
           'zero_sixteen',
41
 
           'zero_seventeen',
42
 
           'zero_eighteen',
43
 
           'zero_ninety',
44
 
           'zero_ninetyone',
45
 
           'zero_ninetytwo',
46
 
           'zero_ninetythree',
47
 
           'one_zero',
48
 
           'one_one',
49
 
           'one_two',
50
 
           'one_three',
51
 
           'one_four',
52
 
           'one_five',
53
 
           'one_six',
54
29
           ]
55
30
 
56
31
from warnings import warn
57
32
 
58
 
import bzrlib
59
 
 
60
33
 
61
34
DEPRECATED_PARAMETER = "A deprecated parameter marker."
62
35
zero_seven = "%s was deprecated in version 0.7."
63
36
zero_eight = "%s was deprecated in version 0.8."
64
 
zero_nine = "%s was deprecated in version 0.9."
65
 
zero_ten = "%s was deprecated in version 0.10."
66
 
zero_eleven = "%s was deprecated in version 0.11."
67
 
zero_twelve = "%s was deprecated in version 0.12."
68
 
zero_thirteen = "%s was deprecated in version 0.13."
69
 
zero_fourteen = "%s was deprecated in version 0.14."
70
 
zero_fifteen = "%s was deprecated in version 0.15."
71
 
zero_sixteen = "%s was deprecated in version 0.16."
72
 
zero_seventeen = "%s was deprecated in version 0.17."
73
 
zero_eighteen = "%s was deprecated in version 0.18."
74
 
zero_ninety = "%s was deprecated in version 0.90."
75
 
zero_ninetyone = "%s was deprecated in version 0.91."
76
 
zero_ninetytwo = "%s was deprecated in version 0.92."
77
 
one_zero = "%s was deprecated in version 1.0."
78
 
zero_ninetythree = one_zero # Maintained for backwards compatibility
79
 
one_one = "%s was deprecated in version 1.1."
80
 
one_two = "%s was deprecated in version 1.2."
81
 
one_three = "%s was deprecated in version 1.3."
82
 
one_four = "%s was deprecated in version 1.4."
83
 
one_five = "%s was deprecated in version 1.5."
84
 
one_six = "%s was deprecated in version 1.6."
85
 
 
86
 
 
87
 
def deprecated_in(version_tuple):
88
 
    """Generate a message that something was deprecated in a release.
89
 
 
90
 
    >>> deprecated_in((1, 4, 0))
91
 
    '%s was deprecated in version 1.4.'
92
 
    """
93
 
    return ("%%s was deprecated in version %s."
94
 
            % bzrlib._format_version_tuple(version_tuple))
95
37
 
96
38
 
97
39
def set_warning_method(method):
108
50
# add that on top of the primitives, once we have all three written
109
51
# - RBC 20050105
110
52
 
111
 
 
112
 
def deprecation_string(a_callable, deprecation_version):
113
 
    """Generate an automatic deprecation string for a_callable.
114
 
 
115
 
    :param a_callable: The callable to substitute into deprecation_version.
116
 
    :param deprecation_version: A deprecation format warning string. This should
117
 
        have a single %s operator in it. a_callable will be turned into a nice
118
 
        python symbol and then substituted into deprecation_version.
119
 
    """
120
 
    # We also want to handle old-style classes, in particular exception, and
121
 
    # they don't have an im_class attribute.
122
 
    if getattr(a_callable, 'im_class', None) is None:
123
 
        symbol = "%s.%s" % (a_callable.__module__,
124
 
                            a_callable.__name__)
125
 
    else:
126
 
        symbol = "%s.%s.%s" % (a_callable.im_class.__module__,
127
 
                               a_callable.im_class.__name__,
128
 
                               a_callable.__name__
129
 
                               )
130
 
    return deprecation_version % symbol
131
 
 
132
 
 
133
53
def deprecated_function(deprecation_version):
134
54
    """Decorate a function so that use of it will trigger a warning."""
135
55
 
138
58
        
139
59
        def decorated_function(*args, **kwargs):
140
60
            """This is the decorated function."""
141
 
            warn(deprecation_string(callable, deprecation_version),
142
 
                DeprecationWarning, stacklevel=2)
 
61
            symbol = "%s.%s" % (callable.__module__, 
 
62
                                callable.__name__
 
63
                                )
 
64
            warn(deprecation_version % symbol, DeprecationWarning, stacklevel=2)
143
65
            return callable(*args, **kwargs)
144
66
        _populate_decorated(callable, deprecation_version, "function",
145
67
                            decorated_function)
149
71
 
150
72
def deprecated_method(deprecation_version):
151
73
    """Decorate a method so that use of it will trigger a warning.
152
 
 
153
 
    To deprecate a static or class method, use 
154
 
 
155
 
        @staticmethod
156
 
        @deprecated_function
157
 
        def ...
158
74
    
159
75
    To deprecate an entire class, decorate __init__.
160
76
    """
164
80
        
165
81
        def decorated_method(self, *args, **kwargs):
166
82
            """This is the decorated method."""
167
 
            if callable.__name__ == '__init__':
168
 
                symbol = "%s.%s" % (self.__class__.__module__,
169
 
                                    self.__class__.__name__,
170
 
                                    )
171
 
            else:
172
 
                symbol = "%s.%s.%s" % (self.__class__.__module__,
173
 
                                       self.__class__.__name__,
174
 
                                       callable.__name__
175
 
                                       )
 
83
            symbol = "%s.%s.%s" % (self.__class__.__module__, 
 
84
                                   self.__class__.__name__,
 
85
                                   callable.__name__
 
86
                                   )
176
87
            warn(deprecation_version % symbol, DeprecationWarning, stacklevel=2)
177
88
            return callable(self, *args, **kwargs)
178
89
        _populate_decorated(callable, deprecation_version, "method",
191
102
    # we cannot just forward to a new method name.I.e. in the following
192
103
    # examples we would want to have callers that pass any value to 'bad' be
193
104
    # given a warning - because we have applied:
194
 
    # @deprecated_parameter('bad', deprecated_in((1, 5, 0))
 
105
    # @deprecated_parameter('bad', zero_seven)
195
106
    #
196
107
    # def __init__(self, bad=None)
197
108
    # def __init__(self, bad, other)
202
113
 
203
114
def _decorate_docstring(callable, deprecation_version, label,
204
115
                        decorated_callable):
205
 
    if callable.__doc__:
206
 
        docstring_lines = callable.__doc__.split('\n')
207
 
    else:
208
 
        docstring_lines = []
 
116
    docstring_lines = callable.__doc__.split('\n')
209
117
    if len(docstring_lines) == 0:
210
118
        decorated_callable.__doc__ = deprecation_version % ("This " + label)
211
119
    elif len(docstring_lines) == 1:
232
140
    decorated_callable.__module__ = callable.__module__
233
141
    decorated_callable.__name__ = callable.__name__
234
142
    decorated_callable.is_deprecated = True
235
 
 
236
 
 
237
 
def _dict_deprecation_wrapper(wrapped_method):
238
 
    """Returns a closure that emits a warning and calls the superclass"""
239
 
    def cb(dep_dict, *args, **kwargs):
240
 
        msg = 'access to %s' % (dep_dict._variable_name, )
241
 
        msg = dep_dict._deprecation_version % (msg,)
242
 
        if dep_dict._advice:
243
 
            msg += ' ' + dep_dict._advice
244
 
        warn(msg, DeprecationWarning, stacklevel=2)
245
 
        return wrapped_method(dep_dict, *args, **kwargs)
246
 
    return cb
247
 
 
248
 
 
249
 
class DeprecatedDict(dict):
250
 
    """A dictionary that complains when read or written."""
251
 
 
252
 
    is_deprecated = True
253
 
 
254
 
    def __init__(self,
255
 
        deprecation_version,
256
 
        variable_name,
257
 
        initial_value,
258
 
        advice,
259
 
        ):
260
 
        """Create a dict that warns when read or modified.
261
 
 
262
 
        :param deprecation_version: string for the warning format to raise,
263
 
            typically from deprecated_in()
264
 
        :param initial_value: The contents of the dict
265
 
        :param variable_name: This allows better warnings to be printed
266
 
        :param advice: String of advice on what callers should do instead 
267
 
            of using this variable.
268
 
        """
269
 
        self._deprecation_version = deprecation_version
270
 
        self._variable_name = variable_name
271
 
        self._advice = advice
272
 
        dict.__init__(self, initial_value)
273
 
 
274
 
    # This isn't every possible method but it should trap anyone using the
275
 
    # dict -- add more if desired
276
 
    __len__ = _dict_deprecation_wrapper(dict.__len__)
277
 
    __getitem__ = _dict_deprecation_wrapper(dict.__getitem__)
278
 
    __setitem__ = _dict_deprecation_wrapper(dict.__setitem__)
279
 
    __delitem__ = _dict_deprecation_wrapper(dict.__delitem__)
280
 
    keys = _dict_deprecation_wrapper(dict.keys)
281
 
    __contains__ = _dict_deprecation_wrapper(dict.__contains__)
282
 
 
283
 
 
284
 
def deprecated_list(deprecation_version, variable_name,
285
 
                    initial_value, extra=None):
286
 
    """Create a list that warns when modified
287
 
 
288
 
    :param deprecation_version: string for the warning format to raise,
289
 
        typically from deprecated_in()
290
 
    :param initial_value: The contents of the list
291
 
    :param variable_name: This allows better warnings to be printed
292
 
    :param extra: Extra info to print when printing a warning
293
 
    """
294
 
 
295
 
    subst_text = 'Modifying %s' % (variable_name,)
296
 
    msg = deprecation_version % (subst_text,)
297
 
    if extra:
298
 
        msg += ' ' + extra
299
 
 
300
 
    class _DeprecatedList(list):
301
 
        __doc__ = list.__doc__ + msg
302
 
 
303
 
        is_deprecated = True
304
 
 
305
 
        def _warn_deprecated(self, func, *args, **kwargs):
306
 
            warn(msg, DeprecationWarning, stacklevel=3)
307
 
            return func(self, *args, **kwargs)
308
 
            
309
 
        def append(self, obj):
310
 
            """appending to %s is deprecated""" % (variable_name,)
311
 
            return self._warn_deprecated(list.append, obj)
312
 
 
313
 
        def insert(self, index, obj):
314
 
            """inserting to %s is deprecated""" % (variable_name,)
315
 
            return self._warn_deprecated(list.insert, index, obj)
316
 
 
317
 
        def extend(self, iterable):
318
 
            """extending %s is deprecated""" % (variable_name,)
319
 
            return self._warn_deprecated(list.extend, iterable)
320
 
 
321
 
        def remove(self, value):
322
 
            """removing from %s is deprecated""" % (variable_name,)
323
 
            return self._warn_deprecated(list.remove, value)
324
 
 
325
 
        def pop(self, index=None):
326
 
            """pop'ing from from %s is deprecated""" % (variable_name,)
327
 
            if index:
328
 
                return self._warn_deprecated(list.pop, index)
329
 
            else:
330
 
                # Can't pass None
331
 
                return self._warn_deprecated(list.pop)
332
 
 
333
 
    return _DeprecatedList(initial_value)
334
 
 
335
 
 
336
 
def _check_for_filter(error_only):
337
 
    """Check if there is already a filter for deprecation warnings.
338
 
    
339
 
    :param error_only: Only match an 'error' filter
340
 
    :return: True if a filter is found, False otherwise
341
 
    """
342
 
    import warnings
343
 
    for filter in warnings.filters:
344
 
        if issubclass(DeprecationWarning, filter[2]):
345
 
            # This filter will effect DeprecationWarning
346
 
            if not error_only or filter[0] == 'error':
347
 
                return True
348
 
    return False
349
 
 
350
 
 
351
 
def suppress_deprecation_warnings(override=True):
352
 
    """Call this function to suppress all deprecation warnings.
353
 
 
354
 
    When this is a final release version, we don't want to annoy users with
355
 
    lots of deprecation warnings. We only want the deprecation warnings when
356
 
    running a dev or release candidate.
357
 
 
358
 
    :param override: If True, always set the ignore, if False, only set the
359
 
        ignore if there isn't already a filter.
360
 
    """
361
 
    import warnings
362
 
    if not override and _check_for_filter(error_only=False):
363
 
        # If there is already a filter effecting suppress_deprecation_warnings,
364
 
        # then skip it.
365
 
        return
366
 
    warnings.filterwarnings('ignore', category=DeprecationWarning)
367
 
 
368
 
 
369
 
def activate_deprecation_warnings(override=True):
370
 
    """Call this function to activate deprecation warnings.
371
 
 
372
 
    When running in a 'final' release we suppress deprecation warnings.
373
 
    However, the test suite wants to see them. So when running selftest, we
374
 
    re-enable the deprecation warnings.
375
 
 
376
 
    Note: warnings that have already been issued under 'ignore' will not be
377
 
    reported after this point. The 'warnings' module has already marked them as
378
 
    handled, so they don't get issued again.
379
 
 
380
 
    :param override: If False, only add a filter if there isn't an error filter
381
 
        already. (This slightly differs from suppress_deprecation_warnings, in
382
 
        because it always overrides everything but -Werror).
383
 
    """
384
 
    import warnings
385
 
    if not override and _check_for_filter(error_only=True):
386
 
        # DeprecationWarnings are already turned into errors, don't downgrade
387
 
        # them to 'default'.
388
 
        return
389
 
    warnings.filterwarnings('default', category=DeprecationWarning)