231 lines
9.9 KiB
ReStructuredText
231 lines
9.9 KiB
ReStructuredText
|
PEP: 565
|
|||
|
Title: Show DeprecationWarning in __main__
|
|||
|
Author: Nick Coghlan <ncoghlan@gmail.com>
|
|||
|
Status: Draft
|
|||
|
Type: Standards Track
|
|||
|
Content-Type: text/x-rst
|
|||
|
Created: 12-Nov-2017
|
|||
|
Python-Version: 3.7
|
|||
|
Post-History: 12-Nov-2017
|
|||
|
|
|||
|
|
|||
|
Abstract
|
|||
|
========
|
|||
|
|
|||
|
In Python 2.7 and Python 3.2, the default warning filters were updated to hide
|
|||
|
DeprecationWarning by default, such that deprecation warnings in development
|
|||
|
tools that were themselves written in Python (e.g. linters, static analysers,
|
|||
|
test runners, code generators) wouldn't be visible to their users unless they
|
|||
|
explicitly opted in to seeing them.
|
|||
|
|
|||
|
However, this change has had the unfortunate side effect of making
|
|||
|
DeprecationWarning markedly less effective at its primary intended purpose:
|
|||
|
providing advance notice of breaking changes in APIs (whether in CPython, the
|
|||
|
standard library, or in third party libraries) to users of those APIs.
|
|||
|
|
|||
|
To improve this situation, this PEP proposes a single adjustment to the
|
|||
|
default warnings filter: displaying deprecation warnings attributed to the main
|
|||
|
module by default.
|
|||
|
|
|||
|
This change will mean that code entered at the interactive prompt and code in
|
|||
|
single file scripts will revert to reporting these warnings by default, while
|
|||
|
they will continue to be silenced by default for packaged code distributed as
|
|||
|
part of an importable module.
|
|||
|
|
|||
|
The PEP also proposes a number of small adjustments to the reference
|
|||
|
interpreter and standard library documentation to help make the warnings
|
|||
|
subsystem more approachable for new Python developers.
|
|||
|
|
|||
|
|
|||
|
Specification
|
|||
|
=============
|
|||
|
|
|||
|
The current set of default warnings filters consists of::
|
|||
|
|
|||
|
ignore::DeprecationWarning
|
|||
|
ignore::PendingDeprecationWarning
|
|||
|
ignore::ImportWarning
|
|||
|
ignore::BytesWarning
|
|||
|
ignore::ResourceWarning
|
|||
|
|
|||
|
The default ``unittest`` test runner then uses ``warnings.catch_warnings()``
|
|||
|
``warnings.simplefilter('default')`` to override the default filters while
|
|||
|
running test cases.
|
|||
|
|
|||
|
The change proposed in this PEP is to update the default warning filter list
|
|||
|
to be::
|
|||
|
|
|||
|
default::DeprecationWarning:__main__
|
|||
|
ignore::DeprecationWarning
|
|||
|
ignore::PendingDeprecationWarning
|
|||
|
ignore::ImportWarning
|
|||
|
ignore::BytesWarning
|
|||
|
ignore::ResourceWarning
|
|||
|
|
|||
|
This means that in cases where the nominal location of the warning (as
|
|||
|
determined by the ``stacklevel`` parameter to ``warnings.warn``) is in the
|
|||
|
``__main__`` module, the first occurrence of each DeprecationWarning will once
|
|||
|
again be reported.
|
|||
|
|
|||
|
This change will lead to DeprecationWarning being displayed by default for:
|
|||
|
|
|||
|
* code executed directly at the interactive prompt
|
|||
|
* code executed directly as part of a single-file script
|
|||
|
|
|||
|
While continuing to be hidden by default for:
|
|||
|
|
|||
|
* code imported from another module in a ``zipapp`` archive's ``__main__.py``
|
|||
|
file
|
|||
|
* code imported from another module in an executable package's ``__main__``
|
|||
|
submodule
|
|||
|
* code imported from an executable script wrapper generated at installation time
|
|||
|
based on a ``console_scripts`` or ``gui_scripts`` entry point definition
|
|||
|
|
|||
|
As a result, API deprecation warnings encountered by development tools written
|
|||
|
in Python should continue to be hidden by default for users of those tools
|
|||
|
|
|||
|
While not its originally intended purpose, the standard library documentation
|
|||
|
will also be updated to explicitly recommend the use of ``FutureWarning`` (rather
|
|||
|
than ``DeprecationWarning``) for backwards compatibility warnings that are
|
|||
|
intended to be seen by *users* of an application.
|
|||
|
|
|||
|
This will give the following three distinct categories of backwards
|
|||
|
compatibility warning, with three different intended audiences:
|
|||
|
|
|||
|
* ``PendingDeprecationWarning``: reported by default only in test runners that
|
|||
|
override the default set of warning filters. The intended audience is Python
|
|||
|
developers that take an active interest in ensuring the future compatibility
|
|||
|
of their software (e.g. professional Python application developers with
|
|||
|
specific support obligations).
|
|||
|
* ``DeprecationWarning``: reported by default for code that runs directly in
|
|||
|
the ``__main__`` module (as such code is considered relatively unlikely to
|
|||
|
have a dedicated test suite), but relies on test suite based reporting for
|
|||
|
code in other modules. The intended audience is Python developers that are at
|
|||
|
risk of upgrades to their dependencies (including upgrades to Python itself)
|
|||
|
breaking their software (e.g. developers using Python to script environments
|
|||
|
where someone else is in control of the timing of dependency upgrades).
|
|||
|
* ``FutureWarning``: always reported by default. The intended audience is users
|
|||
|
of applications written in Python, rather than other Python developers
|
|||
|
(e.g. warning about use of a deprecated setting in a configuration file
|
|||
|
format).
|
|||
|
|
|||
|
Given its presence in the standard library since Python 2.3, ``FutureWarning``
|
|||
|
would then also have a secondary use case for libraries and frameworks that
|
|||
|
support multiple Python versions: as a more reliably visible alternative to
|
|||
|
``DeprecationWarning`` in Python 2.7 and versions of Python 3.x prior to 3.7.
|
|||
|
|
|||
|
|
|||
|
Motivation
|
|||
|
==========
|
|||
|
|
|||
|
As discussed in [1_] and mentioned in [2_], Python 2.7 and Python 3.2 changed
|
|||
|
the default handling of ``DeprecationWarning`` such that:
|
|||
|
|
|||
|
* the warning was hidden by default during normal code execution
|
|||
|
* the `unittest`` test runner was updated to re-enable it when running tests
|
|||
|
|
|||
|
The intent was to avoid cases of tooling output like the following::
|
|||
|
|
|||
|
$ devtool mycode/
|
|||
|
/usr/lib/python3.6/site-packages/devtool/cli.py:1: DeprecationWarning: 'async' and 'await' will become reserved keywords in Python 3.7
|
|||
|
async = True
|
|||
|
... actual tool output ...
|
|||
|
|
|||
|
Even when `devtool` is a tool specifically for Python programmers, this is not
|
|||
|
a particularly useful warning, as it will be shown on every invocation, even
|
|||
|
though the main helpful step an end user can take is to report a bug to the
|
|||
|
developers of ``devtool``. The warning is even less helpful for general purpose
|
|||
|
developer tools that are used across more languages than just Python.
|
|||
|
|
|||
|
However, this change proved to have unintended consequences for the following
|
|||
|
audiences:
|
|||
|
|
|||
|
* anyone using a test runner other than the default one built into ``unittest``
|
|||
|
(since the request for third party test runners to change their default
|
|||
|
warnings filters was never made explicitly)
|
|||
|
* anyone using the default ``unittest`` test runner to test their Python code
|
|||
|
in a subprocess (since even ``unittest`` only adjusts the warnings settings
|
|||
|
in the current process)
|
|||
|
* anyone writing Python code at the interactive prompt or as part of a directly
|
|||
|
executed script that didn't have a Python level test suite at all
|
|||
|
|
|||
|
In these cases, ``DeprecationWarning`` ended up become almost entirely
|
|||
|
equivalent to ``PendingDeprecationWarning``: it was simply never seen at all.
|
|||
|
|
|||
|
|
|||
|
Limitations on PEP Scope
|
|||
|
========================
|
|||
|
|
|||
|
This PEP exists specifically to explain both the proposed addition to the
|
|||
|
default warnings filter for 3.7, *and* to more clearly articulate the rationale
|
|||
|
for the original change to the handling of DeprecationWarning back in Python 2.7
|
|||
|
and 3.2.
|
|||
|
|
|||
|
This PEP does not solve all known problems with the current approach to handling
|
|||
|
deprecation warnings. Most notably:
|
|||
|
|
|||
|
* the default ``unittest`` test runner does not currently report deprecation
|
|||
|
warnings emitted at module import time, as the warnings filter override is only
|
|||
|
put in place during test execution, not during test discovery and loading.
|
|||
|
* the default ``unittest`` test runner does not currently report deprecation
|
|||
|
warnings in subprocesses, as the warnings filter override is applied directly
|
|||
|
to the loaded ``warnings`` module, not to the ``PYTHONWARNINGS`` environment
|
|||
|
variable.
|
|||
|
* the standard library doesn't provide a straightforward way to opt-in to seeing
|
|||
|
all warnings emitted *by* a particular dependency prior to upgrading it
|
|||
|
(the third-party ``warn`` module [3_] does provide this, but enabling it
|
|||
|
involves monkeypatching the standard library's ``warnings`` module).
|
|||
|
* re-enabling deprecation warnings by default in __main__ doesn't help in
|
|||
|
handling cases where software has been factored out into support modules, but
|
|||
|
those modules still have little or no automated test coverage. Near term, the
|
|||
|
best currently available answer is to run such applications with
|
|||
|
``PYTHONWARNINGS=default::DeprecationWarning`` or
|
|||
|
``python -W default::DeprecationWarning`` and pay attention to their
|
|||
|
``stderr`` output. Longer term, this is really a question for researchers
|
|||
|
working on static analysis of Python code: how to reliably find usage of
|
|||
|
deprecated APIs, and how to infer that an API or parameter is deprecated
|
|||
|
based on ``warnings.warn`` calls, without actually running either the code
|
|||
|
providing the API or the code accessing it
|
|||
|
|
|||
|
While these are real problems with the status quo, they're excluded from
|
|||
|
consideration in this PEP because they're going to require more complex
|
|||
|
solutions than a single additional entry in the default warnings filter,
|
|||
|
and resolving them at least potentially won't require going through the PEP
|
|||
|
process.
|
|||
|
|
|||
|
For anyone interested in pursuing them further, the first two would be
|
|||
|
``unittest`` module enhancement requests, the third would be a ``warnings``
|
|||
|
module enhancement request, while the last would only require a PEP if
|
|||
|
inferring API deprecations from their contents was deemed to be an intractable
|
|||
|
code analysis problem, and an explicit function and parameter marker syntax in
|
|||
|
annotations was proposed instead.
|
|||
|
|
|||
|
|
|||
|
References
|
|||
|
==========
|
|||
|
|
|||
|
.. [1] stdlib-sig thread proposing the original default filter change
|
|||
|
(https://mail.python.org/pipermail/stdlib-sig/2009-November/000789.html)
|
|||
|
|
|||
|
.. [2] Python 2.7 notification of the default warnings filter change
|
|||
|
(https://docs.python.org/3/whatsnew/2.7.html#changes-to-the-handling-of-deprecation-warnings)
|
|||
|
|
|||
|
.. [3] Emitting warnings based on the location of the warning itself
|
|||
|
(https://pypi.org/project/warn/)
|
|||
|
|
|||
|
Copyright
|
|||
|
=========
|
|||
|
|
|||
|
This document has been placed in the public domain.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
..
|
|||
|
Local Variables:
|
|||
|
mode: indented-text
|
|||
|
indent-tabs-mode: nil
|
|||
|
sentence-end-double-space: t
|
|||
|
fill-column: 70
|
|||
|
coding: utf-8
|
|||
|
End:
|