~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/symbol_versioning.py

  • Committer: John Arbash Meinel
  • Date: 2007-03-15 22:35:35 UTC
  • mto: This revision was merged to the branch mainline in revision 2363.
  • Revision ID: john@arbash-meinel.com-20070315223535-d3d4964oe1hc8zhg
Add an overzealous test, for Unicode support of _iter_changes.
For both knowns and unknowns.
And include a basic, if suboptimal, fix.
I would rather defer the decoding until we've determined that we are going to return the tuple.
There is still something broken with added files, but I'll get to that.

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