File: white_paper.rst

package info (click to toggle)
python-deprecated 1.2.18-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,308 kB
  • sloc: python: 1,458; makefile: 32
file content (394 lines) | stat: -rw-r--r-- 15,400 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
.. _white_paper:

White Paper
===========

This white paper shows some examples of how function deprecation is implemented in the Python Standard Library and Famous Open Source libraries.

You will see which kind of deprecation you can find in such libraries, and how it is documented in the user manuel.

.. _Python Standard Library:

The Python Standard Library
---------------------------

:Library: Python_
:GitHub: `python/cpython <https://github.com/python/cpython>`_.
:Version: v3.8.dev

An example of function deprecation can be found in the :mod:`urllib` module (:file:`Lib/urllib/parse.py`):

.. code-block:: python

    def to_bytes(url):
        warnings.warn("urllib.parse.to_bytes() is deprecated as of 3.8",
                      DeprecationWarning, stacklevel=2)
        return _to_bytes(url)

In the Python library, a warning is emitted in the function body using the function :func:`warnings.warn`.
This implementation is straightforward, it uses the category :exc:`DeprecationWarning` for warning filtering.

Another example is the deprecation of the *collections* ABC, which are now moved in the :mod:`collections.abc` module.
This example is available in the :mod:`collections` module (:file:`Lib/collections/__init__.py`):

.. code-block:: python

    def __getattr__(name):
        if name in _collections_abc.__all__:
            obj = getattr(_collections_abc, name)
            import warnings
            warnings.warn("Using or importing the ABCs from 'collections' instead "
                          "of from 'collections.abc' is deprecated, "
                          "and in 3.8 it will stop working",
                          DeprecationWarning, stacklevel=2)
            globals()[name] = obj
            return obj
        raise AttributeError(f'module {__name__!r} has no attribute {name!r}')

The warning is only emitted when an ABC is accessed from the :mod:`collections` instead of :mod:`collections.abc` module.

We can also see an example of keyword argument deprecation in the :class:`~collections.UserDict` class:

.. code-block:: python

    def __init__(*args, **kwargs):
        if not args:
            raise TypeError("descriptor '__init__' of 'UserDict' object "
                            "needs an argument")
        self, *args = args
        if len(args) > 1:
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
        if args:
            dict = args[0]
        elif 'dict' in kwargs:
            dict = kwargs.pop('dict')
            import warnings
            warnings.warn("Passing 'dict' as keyword argument is deprecated",
                          DeprecationWarning, stacklevel=2)
        else:
            dict = None
        self.data = {}
        if dict is not None:
            self.update(dict)
        if len(kwargs):
            self.update(kwargs)

Again, this implementation is straightforward: if the *dict* keyword argument is used, a warning is emitted.

Python make also use of the category :exc:`PendingDeprecationWarning` for instance in the :mod:`asyncio.tasks` module
(:file:`Lib/asyncio/tasks.py`):

.. code-block:: python

    @classmethod
    def current_task(cls, loop=None):
        warnings.warn("Task.current_task() is deprecated, "
                      "use asyncio.current_task() instead",
                      PendingDeprecationWarning,
                      stacklevel=2)
        if loop is None:
            loop = events.get_event_loop()
        return current_task(loop)

The category :exc:`FutureWarning` is also used to emit a warning when the functions is broken and will be
fixed in a "future" release. We can see for instance the method :meth:`~xml.etree.ElementTree.ElementTree.find`
of the class :class:`~xml.etree.ElementTree.ElementTree` (:file:`Lib/xml/etree/ElementTree.py`):

.. code-block:: python

    def find(self, path, namespaces=None):
        if path[:1] == "/":
            path = "." + path
            warnings.warn(
                "This search is broken in 1.3 and earlier, and will be "
                "fixed in a future version.  If you rely on the current "
                "behaviour, change it to %r" % path,
                FutureWarning, stacklevel=2
                )
        return self._root.find(path, namespaces)

As a conclusion:

-   Python library uses :func:`warnings.warn` to emit a deprecation warning in the body of functions.
-   3 categories are used: :exc:`DeprecationWarning`, :exc:`PendingDeprecationWarning` and :exc:`FutureWarning`.
-   The docstring doesn't show anything about deprecation.
-   The documentation warns about some, but not all, deprecated usages.

.. _Python: https://docs.python.org/fr/3/

.. _Flask Library:

The Flask Library
-----------------

:Library: Flask_
:GitHub: `pallets/flask <https://github.com/pallets/flask>`_.
:Version: v1.1.dev

In the source code of Flask, we find only few deprecations: in the :mod:`~flask.app` (:file:`flask/app.py`)
and in the :mod:`~flask.helpers` (:file:`flask/helpers.py`) modules.

In the Flask Library, like in the `Python Standard Library`_, deprecation warnings are emitted during function calls.
The implementation make use of the category :exc:`DeprecationWarning`.

Unlike the `Python Standard Library`_, the docstring documents explicitly the deprecation.
Flask uses Sphinx_’s `deprecated directive`_:

The bellow example shows the deprecation of the :meth:`~flask.Flask.open_session` method:

.. code-block:: python

    def open_session(self, request):
        """Creates or opens a new session.  Default implementation stores all
        session data in a signed cookie.  This requires that the
        :attr:`secret_key` is set.  Instead of overriding this method
        we recommend replacing the :class:`session_interface`.

        .. deprecated: 1.0
            Will be removed in 1.1. Use ``session_interface.open_session``
            instead.

        :param request: an instance of :attr:`request_class`.
        """

        warnings.warn(DeprecationWarning(
            '"open_session" is deprecated and will be removed in 1.1. Use'
            ' "session_interface.open_session" instead.'
        ))
        return self.session_interface.open_session(self, request)

.. _deprecated directive: https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-deprecated
.. _Sphinx: http://www.sphinx-doc.org/en/stable/index.html

.. hint::

    When the function :func:`warnings.warn` is called with a :exc:`DeprecationWarning` instance,
    the instance class is used like a warning category.

The documentation also mention a :exc:`flask.exthook.ExtDeprecationWarning` (which is not found in Flask’s source code):

.. code-block:: rst

    Extension imports
    `````````````````

    Extension imports of the form ``flask.ext.foo`` are deprecated, you should use
    ``flask_foo``.

    The old form still works, but Flask will issue a
    ``flask.exthook.ExtDeprecationWarning`` for each extension you import the old
    way. We also provide a migration utility called `flask-ext-migrate
    <https://github.com/pallets/flask-ext-migrate>`_ that is supposed to
    automatically rewrite your imports for this.

As a conclusion:

-   Flask library uses :func:`warnings.warn` to emit a deprecation warning in the body of functions.
-   Only one category is used: :exc:`DeprecationWarning`.
-   The docstring use `Sphinx`_’s `deprecated directive`_.
-   The API documentation contains the deprecated usages.

.. _Flask: http://flask.pocoo.org/docs/

.. _Django Library:

The Django Library
------------------

:Library: Django
:GitHub: `django/django <https://github.com/django/django>`_.
:Version: v3.0.dev

The `Django`_ Library defines several categories for deprecation in the module :mod:`django.utils.deprecation`:

-   The category :exc:`~django.utils.deprecation.RemovedInDjango31Warning` which inherits
    from :exc:`DeprecationWarning`.
-   The category :exc:`~django.utils.deprecation.RemovedInDjango40Warning` which inherits
    from :exc:`PendingDeprecationWarning`.
-   The category :exc:`~django.utils.deprecation.RemovedInNextVersionWarning` which is an alias
    of :exc:`~django.utils.deprecation.RemovedInDjango40Warning`.

The `Django`_ Library don't use :exc:`DeprecationWarning` or :exc:`PendingDeprecationWarning` directly,
but always use one of this 2 classes. The category :exc:`~django.utils.deprecation.RemovedInNextVersionWarning`
is only used in unit tests.

There are a lot of class deprecation examples. The deprecation warning is emitted during the call
of the ``__init__`` method. For instance in the class :class:`~django.contrib.postgres.forms.ranges.FloatRangeField`
(:file:`django/contrib/staticfiles/storage.py`):

.. code-block:: python

    class FloatRangeField(DecimalRangeField):
        base_field = forms.FloatField

        def __init__(self, **kwargs):
            warnings.warn(
                'FloatRangeField is deprecated in favor of DecimalRangeField.',
                RemovedInDjango31Warning, stacklevel=2,
            )
            super().__init__(**kwargs)

The implementation in the Django Library is similar to the one done in the `Python Standard Library`_:
deprecation warnings are emitted during function calls.
The implementation use the category :exc:`~django.utils.deprecation.RemovedInDjango31Warning`.

In the Django Library, we also find an example of property deprecation:
The property :meth:`~django.conf.LazySettings.FILE_CHARSET` of the class :class:`django.conf.LazySettings`.
The implementation of this property is:

.. code-block:: python

    @property
    def FILE_CHARSET(self):
        stack = traceback.extract_stack()
        # Show a warning if the setting is used outside of Django.
        # Stack index: -1 this line, -2 the caller.
        filename, _line_number, _function_name, _text = stack[-2]
        if not filename.startswith(os.path.dirname(django.__file__)):
            warnings.warn(
                FILE_CHARSET_DEPRECATED_MSG,
                RemovedInDjango31Warning,
                stacklevel=2,
            )
        return self.__getattr__('FILE_CHARSET')

We also find function deprecations, mainly with the category :exc:`~django.utils.deprecation.RemovedInDjango40Warning`.
For instance, the function :func:`~django.utils.encoding.smart_text` emits a deprecation warning as follow:

.. code-block:: python

    def smart_text(s, encoding='utf-8', strings_only=False, errors='strict'):
        warnings.warn(
            'smart_text() is deprecated in favor of smart_str().',
            RemovedInDjango40Warning, stacklevel=2,
        )
        return smart_str(s, encoding, strings_only, errors)

The Django Library also define a decorator :class:`~django.utils.deprecation.warn_about_renamed_method`
which is used internally in the metaclass :class:`~django.utils.deprecation.RenameMethodsBase`.
This metaclass is only used in unit tests to check renamed methods.

As a conclusion:

-   The Django library uses :func:`warnings.warn` to emit a deprecation warning in the body of functions.
-   It uses two categories which inherits the standard categories :exc:`DeprecationWarning`
    and :exc:`PendingDeprecationWarning`.
-   The source code of the Django Library doesn't contains much docstring.
    The deprecation never appears in the docstring anyway.
-   The release notes contain information about deprecated features.

.. _Django: https://docs.djangoproject.com/

The lxml Library
----------------

:Library: lxml_
:GitHub: `lxml/lxml <https://github.com/lxml/lxml>`_.
:Version: v4.3.2.dev

The lxml_ Library is developed in Cython, not Python. But, it is a similar language.
This library mainly use comments or docstring to mark function as deprecated.

For instance, in the class :class:`lxml.xpath._XPathEvaluatorBase`(:file:`src/lxml/xpath.pxi`),
the ``evaluate`` method is deprecated as follow:

.. code-block:: python

    def evaluate(self, _eval_arg, **_variables):
        u"""evaluate(self, _eval_arg, **_variables)

        Evaluate an XPath expression.

        Instead of calling this method, you can also call the evaluator object
        itself.

        Variables may be provided as keyword arguments.  Note that namespaces
        are currently not supported for variables.

        :deprecated: call the object, not its method.
        """
        return self(_eval_arg, **_variables)

There is only one example of usage of the function :func:`warnings.warn`:
in the :class:`~lxml.etree._ElementTree` class (:file:`src/lxml/etree.pyx`):

.. code-block:: python

    if docstring is not None and doctype is None:
        import warnings
        warnings.warn(
            "The 'docstring' option is deprecated. Use 'doctype' instead.",
            DeprecationWarning)
        doctype = docstring


As a conclusion:

-   Except in one example, the lxml library doesn't use :func:`warnings.warn` to emit a deprecation warnings.
-   The deprecations are described in the function docstrings.
-   The release notes contain information about deprecated features.

.. _lxml: https://lxml.de

The openpyxl Library
--------------------

:Library: openpyxl
:Bitbucket: `openpyxl/openpyxl <https://bitbucket.org/openpyxl/openpyxl>`_.
:Version: v2.6.1.dev

openpyxl_ is a Python library to read/write Excel 2010 xlsx/xlsm/xltx/xltm files.
Tu warn about deprecation, this library uses a home-made ``@deprecated`` decorator.

The implementation of this decorator is an adapted copy of the first version of Tantale’s ``@deprecated`` decorator.
It has the enhancement to update the docstring of the decorated function.
So, this is similar to the function :func:`deprecated.sphinx.deprecated`.

.. code-block:: python

    string_types = (type(b''), type(u''))
    def deprecated(reason):

        if isinstance(reason, string_types):

            def decorator(func1):

                if inspect.isclass(func1):
                    fmt1 = "Call to deprecated class {name} ({reason})."
                else:
                    fmt1 = "Call to deprecated function {name} ({reason})."

                @wraps(func1)
                def new_func1(*args, **kwargs):
                    #warnings.simplefilter('default', DeprecationWarning)
                    warnings.warn(
                        fmt1.format(name=func1.__name__, reason=reason),
                        category=DeprecationWarning,
                        stacklevel=2
                    )
                    return func1(*args, **kwargs)

                # Enhance docstring with a deprecation note
                deprecationNote = "\n\n.. note::\n    Deprecated: " + reason
                if new_func1.__doc__:
                    new_func1.__doc__ += deprecationNote
                else:
                    new_func1.__doc__ = deprecationNote
                return new_func1

            return decorator

        elif inspect.isclass(reason) or inspect.isfunction(reason):
            raise TypeError("Reason for deprecation must be supplied")

        else:
            raise TypeError(repr(type(reason)))

As a conclusion:

-   The openpyxl library uses a decorator to deprecate functions.
-   It uses the category :exc:`DeprecationWarning`.
-   The decorator update the docstring and add a ``.. note::`` directive,
    which is visible in the documentation.

.. _openpyxl: https://openpyxl.readthedocs.io/