Completed
Pull Request — master (#519)
by
unknown
01:16
created

finalize()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
c 3
b 0
f 0
dl 0
loc 9
rs 9.6666
1
import warnings
2
import functools
3
4
5
class MetpyDeprecationWarning(UserWarning):
6
    """
7
    A class for issuing deprecation warnings for MetPy users.
8
9
    In light of the fact that Python builtin DeprecationWarnings are ignored
10
    by default as of Python 2.7 (see link below), this class was put in to
11
    allow for the signaling of deprecation, but via UserWarnings which are not
12
    ignored by default. Borrowed with love from matplotlib.
13
14
    https://docs.python.org/dev/whatsnew/2.7.html#the-future-for-python-2-x
15
    """
16
    pass
17
18
19
metpyDeprecation = MetpyDeprecationWarning
20
21
22
def _generate_deprecation_message(since, message='', name='',
23
                                  alternative='', pending=False,
24
                                  obj_type='attribute',
25
                                  addendum=''):
26
27
    if not message:
28
29
        if pending:
30
            message = (
31
                'The %(name)s %(obj_type)s will be deprecated in a '
32
                'future version.')
33
        else:
34
            message = (
35
                'The %(name)s %(obj_type)s was deprecated in version '
36
                '%(since)s.')
37
38
    altmessage = ''
39
    if alternative:
40
        altmessage = ' Use %s instead.' % alternative
41
42
    message = ((message % {
43
        'func': name,
44
        'name': name,
45
        'alternative': alternative,
46
        'obj_type': obj_type,
47
        'since': since}) +
48
        altmessage)
49
50
    if addendum:
51
        message += addendum
52
53
    return message
54
55
56
def warn_deprecated(
57
        since, message='', name='', alternative='', pending=False,
58
        obj_type='attribute', addendum=''):
59
    """
60
    Used to display deprecation warning in a standard way.
61
62
    Parameters
63
    ----------
64
    since : str
65
        The release at which this API became deprecated.
66
67
    message : str, optional
68
        Override the default deprecation message.  The format
69
        specifier `%(name)s` may be used for the name of the function,
70
        and `%(alternative)s` may be used in the deprecation message
71
        to insert the name of an alternative to the deprecated
72
        function.  `%(obj_type)s` may be used to insert a friendly name
73
        for the type of object being deprecated.
74
75
    name : str, optional
76
        The name of the deprecated object.
77
78
    alternative : str, optional
79
        An alternative function that the user may use in place of the
80
        deprecated function.  The deprecation warning will tell the user
81
        about this alternative if provided.
82
83
    pending : bool, optional
84
        If True, uses a PendingDeprecationWarning instead of a
85
        DeprecationWarning.
86
87
    obj_type : str, optional
88
        The object type being deprecated.
89
90
    addendum : str, optional
91
        Additional text appended directly to the final message.
92
93
    Examples
94
    --------
95
96
        Basic example::
97
98
            # To warn of the deprecation of "metpy.name_of_module"
99
            warn_deprecated('0.6.0', name='metpy.name_of_module',
100
                            obj_type='module')
101
102
    """
103
    message = _generate_deprecation_message(
104
                since, message, name, alternative, pending, obj_type)
105
106
    warnings.warn(message, metpyDeprecation, stacklevel=1)
107
108
109
def deprecated(since, message='', name='', alternative='', pending=False,
110
               obj_type=None, addendum=''):
111
    """
112
    Decorator to mark a function or a class as deprecated.
113
114
    Parameters
115
    ----------
116
    since : str
117
        The release at which this API became deprecated.  This is
118
        required.
119
120
    message : str, optional
121
        Override the default deprecation message.  The format
122
        specifier `%(name)s` may be used for the name of the object,
123
        and `%(alternative)s` may be used in the deprecation message
124
        to insert the name of an alternative to the deprecated
125
        object.  `%(obj_type)s` may be used to insert a friendly name
126
        for the type of object being deprecated.
127
128
    name : str, optional
129
        The name of the deprecated object; if not provided the name
130
        is automatically determined from the passed in object,
131
        though this is useful in the case of renamed functions, where
132
        the new function is just assigned to the name of the
133
        deprecated function.  For example::
134
135
            def new_function():
136
                ...
137
            oldFunction = new_function
138
139
    alternative : str, optional
140
        An alternative object that the user may use in place of the
141
        deprecated object.  The deprecation warning will tell the user
142
        about this alternative if provided.
143
144
    pending : bool, optional
145
        If True, uses a PendingDeprecationWarning instead of a
146
        DeprecationWarning.
147
148
    addendum : str, optional
149
        Additional text appended directly to the final message.
150
151
    Examples
152
    --------
153
154
        Basic example::
155
156
            @deprecated('1.4.0')
157
            def the_function_to_deprecate():
158
                pass
159
160
    """
161
162
    def deprecate(obj, message=message, name=name, alternative=alternative,
163
                  pending=pending, addendum=addendum):
164
        import textwrap
165
166
        if not name:
167
            name = obj.__name__
168
169
        if isinstance(obj, type):
170
            obj_type = "class"
171
            old_doc = obj.__doc__
172
            func = obj.__init__
173
174
            def finalize(wrapper, new_doc):
175
                try:
176
                    obj.__doc__ = new_doc
177
                except (AttributeError, TypeError):
178
                    # cls.__doc__ is not writeable on Py2.
179
                    # TypeError occurs on PyPy
180
                    pass
181
                obj.__init__ = wrapper
182
                return obj
183
        else:
184
            obj_type = "function"
185
            if isinstance(obj, classmethod):
186
                func = obj.__func__
187
                old_doc = func.__doc__
188
189
                def finalize(wrapper, new_doc):
190
                    wrapper = functools.wraps(func)(wrapper)
191
                    wrapper.__doc__ = new_doc
192
                    return classmethod(wrapper)
193
            else:
194
                func = obj
195
                old_doc = func.__doc__
196
197
                def finalize(wrapper, new_doc):
198
                    wrapper = functools.wraps(func)(wrapper)
199
                    wrapper.__doc__ = new_doc
200
                    return wrapper
201
202
        message = _generate_deprecation_message(
203
                    since, message, name, alternative, pending,
204
                    obj_type, addendum)
205
206
        def wrapper(*args, **kwargs):
207
            warnings.warn(message, metpyDeprecation, stacklevel=2)
208
            return func(*args, **kwargs)
209
210
        old_doc = textwrap.dedent(old_doc or '').strip('\n')
211
        message = message.strip()
212
        new_doc = (('\n.. deprecated:: %(since)s'
213
                    '\n    %(message)s\n\n' %
214
                    {'since': since, 'message': message}) + old_doc)
215
        if not old_doc:
216
            # This is to prevent a spurious 'unexected unindent' warning from
217
            # docutils when the original docstring was blank.
218
            new_doc += r'\ '
219
220
        return finalize(wrapper, new_doc)
221
222
    return deprecate
223