/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/symbol_versioning.py

  • Committer: Robert Collins
  • Date: 2007-04-18 08:39:02 UTC
  • mto: (2425.1.2 integration)
  • mto: This revision was merged to the branch mainline in revision 2427.
  • Revision ID: robertc@robertcollins.net-20070418083902-4o66h9fk7zeisvwa
Command objects can now declare related help topics by having _see_also
set to a list of related topic. Updated the HACKING guide entry on
documentation to be more clear about how the help for commands is
generated and to reference this new feature. (Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
 
2
#   Authors: Robert Collins <robert.collins@canonical.com> and others
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
 
 
18
"""Symbol versioning
 
19
 
 
20
The methods here allow for api symbol versioning.
 
21
"""
 
22
 
 
23
__all__ = ['deprecated_function',
 
24
           'deprecated_list',
 
25
           'deprecated_method',
 
26
           'DEPRECATED_PARAMETER',
 
27
           'deprecated_passed',
 
28
           'warn', 'set_warning_method', 'zero_seven',
 
29
           'zero_eight',
 
30
           'zero_nine',
 
31
           'zero_ten',
 
32
           'zero_eleven',
 
33
           'zero_twelve',
 
34
           'zero_thirteen',
 
35
           'zero_fourteen',
 
36
           'zero_fifteen',
 
37
           'zero_sixteen',
 
38
           ]
 
39
 
 
40
from warnings import warn
 
41
 
 
42
 
 
43
DEPRECATED_PARAMETER = "A deprecated parameter marker."
 
44
zero_seven = "%s was deprecated in version 0.7."
 
45
zero_eight = "%s was deprecated in version 0.8."
 
46
zero_nine = "%s was deprecated in version 0.9."
 
47
zero_ten = "%s was deprecated in version 0.10."
 
48
zero_eleven = "%s was deprecated in version 0.11."
 
49
zero_twelve = "%s was deprecated in version 0.12."
 
50
zero_thirteen = "%s was deprecated in version 0.13."
 
51
zero_fourteen = "%s was deprecated in version 0.14."
 
52
zero_fifteen = "%s was deprecated in version 0.15."
 
53
zero_sixteen = "%s was deprecated in version 0.16."
 
54
 
 
55
 
 
56
def set_warning_method(method):
 
57
    """Set the warning method to be used by this module.
 
58
 
 
59
    It should take a message and a warning category as warnings.warn does.
 
60
    """
 
61
    global warn
 
62
    warn = method
 
63
 
 
64
 
 
65
# TODO - maybe this would be easier to use as one 'smart' method that
 
66
# guess if it is a method or a class or an attribute ? If so, we can
 
67
# add that on top of the primitives, once we have all three written
 
68
# - RBC 20050105
 
69
 
 
70
 
 
71
def deprecation_string(a_callable, deprecation_version):
 
72
    """Generate an automatic deprecation string for a_callable.
 
73
 
 
74
    :param a_callable: The callable to substitute into deprecation_version.
 
75
    :param deprecation_version: A deprecation format warning string. This should
 
76
        have a single %s operator in it. a_callable will be turned into a nice
 
77
        python symbol and then substituted into deprecation_version.
 
78
    """
 
79
    if getattr(a_callable, 'im_class', None) is None:
 
80
        symbol = "%s.%s" % (a_callable.__module__,
 
81
                            a_callable.__name__)
 
82
    else:
 
83
        symbol = "%s.%s.%s" % (a_callable.im_class.__module__,
 
84
                               a_callable.im_class.__name__,
 
85
                               a_callable.__name__
 
86
                               )
 
87
    return deprecation_version % symbol
 
88
 
 
89
 
 
90
def deprecated_function(deprecation_version):
 
91
    """Decorate a function so that use of it will trigger a warning."""
 
92
 
 
93
    def function_decorator(callable):
 
94
        """This is the function python calls to perform the decoration."""
 
95
        
 
96
        def decorated_function(*args, **kwargs):
 
97
            """This is the decorated function."""
 
98
            warn(deprecation_string(callable, deprecation_version),
 
99
                DeprecationWarning, stacklevel=2)
 
100
            return callable(*args, **kwargs)
 
101
        _populate_decorated(callable, deprecation_version, "function",
 
102
                            decorated_function)
 
103
        return decorated_function
 
104
    return function_decorator
 
105
 
 
106
 
 
107
def deprecated_method(deprecation_version):
 
108
    """Decorate a method so that use of it will trigger a warning.
 
109
    
 
110
    To deprecate an entire class, decorate __init__.
 
111
    """
 
112
 
 
113
    def method_decorator(callable):
 
114
        """This is the function python calls to perform the decoration."""
 
115
        
 
116
        def decorated_method(self, *args, **kwargs):
 
117
            """This is the decorated method."""
 
118
            symbol = "%s.%s.%s" % (self.__class__.__module__,
 
119
                                   self.__class__.__name__,
 
120
                                   callable.__name__
 
121
                                   )
 
122
            warn(deprecation_version % symbol, DeprecationWarning, stacklevel=2)
 
123
            return callable(self, *args, **kwargs)
 
124
        _populate_decorated(callable, deprecation_version, "method",
 
125
                            decorated_method)
 
126
        return decorated_method
 
127
    return method_decorator
 
128
 
 
129
 
 
130
def deprecated_passed(parameter_value):
 
131
    """Return True if parameter_value was used."""
 
132
    # FIXME: it might be nice to have a parameter deprecation decorator. 
 
133
    # it would need to handle positional and *args and **kwargs parameters,
 
134
    # which means some mechanism to describe how the parameter was being
 
135
    # passed before deprecation, and some way to deprecate parameters that
 
136
    # were not at the end of the arg list. Thats needed for __init__ where
 
137
    # we cannot just forward to a new method name.I.e. in the following
 
138
    # examples we would want to have callers that pass any value to 'bad' be
 
139
    # given a warning - because we have applied:
 
140
    # @deprecated_parameter('bad', zero_seven)
 
141
    #
 
142
    # def __init__(self, bad=None)
 
143
    # def __init__(self, bad, other)
 
144
    # def __init__(self, **kwargs)
 
145
    # RBC 20060116
 
146
    return not parameter_value is DEPRECATED_PARAMETER
 
147
 
 
148
 
 
149
def _decorate_docstring(callable, deprecation_version, label,
 
150
                        decorated_callable):
 
151
    if callable.__doc__:
 
152
        docstring_lines = callable.__doc__.split('\n')
 
153
    else:
 
154
        docstring_lines = []
 
155
    if len(docstring_lines) == 0:
 
156
        decorated_callable.__doc__ = deprecation_version % ("This " + label)
 
157
    elif len(docstring_lines) == 1:
 
158
        decorated_callable.__doc__ = (callable.__doc__ 
 
159
                                    + "\n"
 
160
                                    + "\n"
 
161
                                    + deprecation_version % ("This " + label)
 
162
                                    + "\n")
 
163
    else:
 
164
        spaces = len(docstring_lines[-1])
 
165
        new_doc = callable.__doc__
 
166
        new_doc += "\n" + " " * spaces
 
167
        new_doc += deprecation_version % ("This " + label)
 
168
        new_doc += "\n" + " " * spaces
 
169
        decorated_callable.__doc__ = new_doc
 
170
 
 
171
 
 
172
def _populate_decorated(callable, deprecation_version, label,
 
173
                        decorated_callable):
 
174
    """Populate attributes like __name__ and __doc__ on the decorated callable.
 
175
    """
 
176
    _decorate_docstring(callable, deprecation_version, label,
 
177
                        decorated_callable)
 
178
    decorated_callable.__module__ = callable.__module__
 
179
    decorated_callable.__name__ = callable.__name__
 
180
    decorated_callable.is_deprecated = True
 
181
 
 
182
 
 
183
def _dict_deprecation_wrapper(wrapped_method):
 
184
    """Returns a closure that emits a warning and calls the superclass"""
 
185
    def cb(dep_dict, *args, **kwargs):
 
186
        msg = 'access to %s' % (dep_dict._variable_name, )
 
187
        msg = dep_dict._deprecation_version % (msg,)
 
188
        if dep_dict._advice:
 
189
            msg += ' ' + dep_dict._advice
 
190
        warn(msg, DeprecationWarning, stacklevel=2)
 
191
        return wrapped_method(dep_dict, *args, **kwargs)
 
192
    return cb
 
193
 
 
194
 
 
195
class DeprecatedDict(dict):
 
196
    """A dictionary that complains when read or written."""
 
197
 
 
198
    is_deprecated = True
 
199
 
 
200
    def __init__(self,
 
201
        deprecation_version,
 
202
        variable_name,
 
203
        initial_value,
 
204
        advice,
 
205
        ):
 
206
        """Create a dict that warns when read or modified.
 
207
 
 
208
        :param deprecation_version: something like zero_nine
 
209
        :param initial_value: The contents of the dict
 
210
        :param variable_name: This allows better warnings to be printed
 
211
        :param advice: String of advice on what callers should do instead 
 
212
            of using this variable.
 
213
        """
 
214
        self._deprecation_version = deprecation_version
 
215
        self._variable_name = variable_name
 
216
        self._advice = advice
 
217
        dict.__init__(self, initial_value)
 
218
 
 
219
    # This isn't every possible method but it should trap anyone using the
 
220
    # dict -- add more if desired
 
221
    __len__ = _dict_deprecation_wrapper(dict.__len__)
 
222
    __getitem__ = _dict_deprecation_wrapper(dict.__getitem__)
 
223
    __setitem__ = _dict_deprecation_wrapper(dict.__setitem__)
 
224
    __delitem__ = _dict_deprecation_wrapper(dict.__delitem__)
 
225
    keys = _dict_deprecation_wrapper(dict.keys)
 
226
    __contains__ = _dict_deprecation_wrapper(dict.__contains__)
 
227
 
 
228
 
 
229
def deprecated_list(deprecation_version, variable_name,
 
230
                    initial_value, extra=None):
 
231
    """Create a list that warns when modified
 
232
 
 
233
    :param deprecation_version: something like zero_nine
 
234
    :param initial_value: The contents of the list
 
235
    :param variable_name: This allows better warnings to be printed
 
236
    :param extra: Extra info to print when printing a warning
 
237
    """
 
238
 
 
239
    subst_text = 'Modifying %s' % (variable_name,)
 
240
    msg = deprecation_version % (subst_text,)
 
241
    if extra:
 
242
        msg += ' ' + extra
 
243
 
 
244
    class _DeprecatedList(list):
 
245
        __doc__ = list.__doc__ + msg
 
246
 
 
247
        is_deprecated = True
 
248
 
 
249
        def _warn_deprecated(self, func, *args, **kwargs):
 
250
            warn(msg, DeprecationWarning, stacklevel=3)
 
251
            return func(self, *args, **kwargs)
 
252
            
 
253
        def append(self, obj):
 
254
            """appending to %s is deprecated""" % (variable_name,)
 
255
            return self._warn_deprecated(list.append, obj)
 
256
 
 
257
        def insert(self, index, obj):
 
258
            """inserting to %s is deprecated""" % (variable_name,)
 
259
            return self._warn_deprecated(list.insert, index, obj)
 
260
 
 
261
        def extend(self, iterable):
 
262
            """extending %s is deprecated""" % (variable_name,)
 
263
            return self._warn_deprecated(list.extend, iterable)
 
264
 
 
265
        def remove(self, value):
 
266
            """removing from %s is deprecated""" % (variable_name,)
 
267
            return self._warn_deprecated(list.remove, value)
 
268
 
 
269
        def pop(self, index=None):
 
270
            """pop'ing from from %s is deprecated""" % (variable_name,)
 
271
            if index:
 
272
                return self._warn_deprecated(list.pop, index)
 
273
            else:
 
274
                # Can't pass None
 
275
                return self._warn_deprecated(list.pop)
 
276
 
 
277
    return _DeprecatedList(initial_value)