Skip to content

Commit 3d5bbe6

Browse files
Merge python#5
5: Add 2.x related warnings r=ltratt a=nanjekyejoannah I have broken away the warning bit from the [flag](python#3 ) and the [port ](python#4 )PR. Well, the way function calls are done between C and Python is confusing, nothing scary anyway, review maybe a bit annoying. Review this PR before python#4 Co-authored-by: Joannah Nanjekye <[email protected]>
2 parents b286d72 + cbd66f6 commit 3d5bbe6

File tree

16 files changed

+845
-8
lines changed

16 files changed

+845
-8
lines changed

Doc/c-api/exceptions.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,14 @@ an error value).
339339
340340
.. versionadded:: 3.4
341341
342+
.. c:function:: int PyErr_WarnExplicitWithFixObject(PyObject *category, PyObject *message, PyObject *fix, PyObject *filename, int lineno, PyObject *module, PyObject *registry)
343+
344+
Issue a warning message and fix with explicit control over all warning attributes. This
345+
is a straightforward wrapper around the Python function
346+
:func:`warnings.warnings_warn_explicit_with_fix`; see there for more information. The *module*
347+
and *registry* arguments may be set to ``NULL`` to get the default effect
348+
described there.
349+
342350
343351
.. c:function:: int PyErr_WarnExplicit(PyObject *category, const char *message, const char *filename, int lineno, const char *module, PyObject *registry)
344352
@@ -347,6 +355,11 @@ an error value).
347355
:term:`filesystem encoding and error handler`.
348356
349357
358+
.. c:function:: int PyErr_WarnExplicit_WithFix(PyObject *category, const char *message, const char *fix, const char *filename, int lineno, const char *module, PyObject *registry)
359+
360+
Similar to :c:func:`PyErr_WarnExplicit` with an additional *fix* parameter.
361+
362+
350363
.. c:function:: int PyErr_WarnFormat(PyObject *category, Py_ssize_t stack_level, const char *format, ...)
351364
352365
Function similar to :c:func:`PyErr_WarnEx`, but use
@@ -363,6 +376,11 @@ an error value).
363376
364377
.. versionadded:: 3.6
365378
379+
.. c:function:: int PyErr_WarnPy2x(char *message, char *fix, int stacklevel)
380+
381+
Issue a :exc:`DeprecationWarning` with the given *message*, *fix* and *stacklevel*
382+
if the :c:data:`Py_Py2xWarningFlag` flag is enabled.
383+
366384
367385
Querying the error indicator
368386
============================

Doc/library/test.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,18 @@ The :mod:`test.support` module defines the following functions:
526526
Return a list of command line arguments reproducing the current
527527
optimization settings in ``sys.flags``.
528528

529+
.. function:: check_py2x_warnings(*filters, quiet=False)
530+
531+
Similar to :func:`check_warnings`, but for Python 3 compatibility warnings.
532+
If ``sys.py3xwarning == 1``, it checks if the warning is effectively raised.
533+
If ``sys.py3xwarning == 0``, it checks that no warning is raised. It
534+
accepts 2-tuples of the form ``("message regexp", WarningCategory)`` as
535+
positional arguments. When the optional keyword argument *quiet* is
536+
:const:`True`, it does not fail if a filter catches nothing. Without
537+
arguments, it defaults to::
538+
539+
check_py2x_warnings(("", DeprecationWarning), quiet=False)
540+
529541

530542
.. function:: captured_stdin()
531543
captured_stdout()

Doc/library/warnings.rst

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,6 @@ Available Functions
419419
.. versionchanged:: 3.6
420420
Added *source* parameter.
421421

422-
423422
.. function:: warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, source=None)
424423

425424
This is a low-level interface to the functionality of :func:`warn`, passing in
@@ -439,6 +438,25 @@ Available Functions
439438
*source*, if supplied, is the destroyed object which emitted a
440439
:exc:`ResourceWarning`.
441440

441+
.. function:: warn_explicit_with_fix(message, fix, category, filename, lineno, module=None, registry=None, module_globals=None, source=None)
442+
443+
This is a low-level interface to the functionality of :func:`warn`, passing in
444+
explicitly the message, fix, category, filename and line number, and optionally the
445+
module name and the registry (which should be the ``__warningregistry__``
446+
dictionary of the module). The module name defaults to the filename with
447+
``.py`` stripped; if no registry is passed, the warning is never suppressed.
448+
*message* must be a string and *category* a subclass of :exc:`Warning` or
449+
*message* may be a :exc:`Warning` instance, in which case *category* will be
450+
ignored.
451+
452+
*module_globals*, if supplied, should be the global namespace in use by the code
453+
for which the warning is issued. (This argument is used to support displaying
454+
source for modules found in zipfiles or other non-filesystem import
455+
sources).
456+
457+
*source*, if supplied, is the destroyed object which emitted a
458+
:exc:`ResourceWarning`.
459+
442460
.. versionchanged:: 3.6
443461
Add the *source* parameter.
444462

@@ -454,6 +472,17 @@ Available Functions
454472
try to read the line specified by *filename* and *lineno*.
455473

456474

475+
.. function:: showwarningwithfix(message, fix, category, filename, lineno, file=None, line=None)
476+
477+
Write a warning to a file. The default implementation calls
478+
``formatwarningwithfix(message, fix, category, filename, lineno, line)`` and writes the
479+
resulting string to *file*, which defaults to :data:`sys.stderr`. You may replace
480+
this function with any callable by assigning to ``warnings.showwarning``.
481+
*line* is a line of source code to be included in the warning
482+
message; if *line* is not supplied, :func:`showwarning` will
483+
try to read the line specified by *filename* and *lineno*.
484+
485+
457486
.. function:: formatwarning(message, category, filename, lineno, line=None)
458487

459488
Format a warning the standard way. This returns a string which may contain
@@ -463,6 +492,15 @@ Available Functions
463492
*lineno*.
464493

465494

495+
.. function:: formatwarningwithfix(message, fix, category, filename, lineno, line=None)
496+
497+
Format a warning the standard way. This returns a string which may contain
498+
embedded newlines and ends in a newline. *line* is a line of source code to
499+
be included in the warning message; if *line* is not supplied,
500+
:func:`formatwarning` will try to read the line specified by *filename* and
501+
*lineno*.
502+
503+
466504
.. function:: filterwarnings(action, message='', category=Warning, module='', lineno=0, append=False)
467505

468506
Insert an entry into the list of :ref:`warnings filter specifications

Include/cpython/warnings.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ PyAPI_FUNC(int) PyErr_WarnExplicitObject(
1010
PyObject *module,
1111
PyObject *registry);
1212

13+
PyAPI_FUNC(int) PyErr_WarnExplicitWithFixObject(
14+
PyObject *category,
15+
PyObject *message,
16+
PyObject *fix,
17+
PyObject *filename,
18+
int lineno,
19+
PyObject *module,
20+
PyObject *registry);
21+
1322
PyAPI_FUNC(int) PyErr_WarnExplicitFormat(
1423
PyObject *category,
1524
const char *filename, int lineno,

Include/internal/pycore_global_strings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ struct _Py_global_strings {
5757
STRUCT_FOR_ID(TextIOWrapper)
5858
STRUCT_FOR_ID(True)
5959
STRUCT_FOR_ID(WarningMessage)
60+
STRUCT_FOR_ID(WarningMessageAndFix)
6061
STRUCT_FOR_ID(_)
6162
STRUCT_FOR_ID(__IOBase_closed)
6263
STRUCT_FOR_ID(__abc_tpflags__)
@@ -221,6 +222,7 @@ struct _Py_global_strings {
221222
STRUCT_FOR_ID(_is_text_encoding)
222223
STRUCT_FOR_ID(_lock_unlock_module)
223224
STRUCT_FOR_ID(_showwarnmsg)
225+
STRUCT_FOR_ID(_showwarnmsgwithfix)
224226
STRUCT_FOR_ID(_shutdown)
225227
STRUCT_FOR_ID(_slotnames)
226228
STRUCT_FOR_ID(_strptime_time)

Include/internal/pycore_runtime_init.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ extern "C" {
679679
INIT_ID(TextIOWrapper), \
680680
INIT_ID(True), \
681681
INIT_ID(WarningMessage), \
682+
INIT_ID(WarningMessageAndFix), \
682683
INIT_ID(_), \
683684
INIT_ID(__IOBase_closed), \
684685
INIT_ID(__abc_tpflags__), \
@@ -843,6 +844,7 @@ extern "C" {
843844
INIT_ID(_is_text_encoding), \
844845
INIT_ID(_lock_unlock_module), \
845846
INIT_ID(_showwarnmsg), \
847+
INIT_ID(_showwarnmsgwithfix), \
846848
INIT_ID(_shutdown), \
847849
INIT_ID(_slotnames), \
848850
INIT_ID(_strptime_time), \

Include/warnings.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ PyAPI_FUNC(int) PyErr_WarnEx(
99
const char *message, /* UTF-8 encoded string */
1010
Py_ssize_t stack_level);
1111

12+
PyAPI_FUNC(int) PyErr_WarnEx_WithFix(
13+
PyObject *category,
14+
const char *message, /* UTF-8 encoded string */
15+
const char *fix, /* UTF-8 encoded string */
16+
Py_ssize_t stack_level);
17+
1218
PyAPI_FUNC(int) PyErr_WarnFormat(
1319
PyObject *category,
1420
Py_ssize_t stack_level,
@@ -32,6 +38,18 @@ PyAPI_FUNC(int) PyErr_WarnExplicit(
3238
const char *module, /* UTF-8 encoded string */
3339
PyObject *registry);
3440

41+
PyAPI_FUNC(int) PyErr_WarnExplicit_WithFix(
42+
PyObject *category,
43+
const char *message, /* UTF-8 encoded string */
44+
const char *fix, /* UTF-8 encoded string */
45+
const char *filename, /* decoded from the filesystem encoding */
46+
int lineno,
47+
const char *module, /* UTF-8 encoded string */
48+
PyObject *registry);
49+
50+
#define PyErr_WarnPy2x(msg, fix, stacklevel) \
51+
(Py_Py2xWarningFlag ? PyErr_WarnEx_WithFix(PyExc_DeprecationWarning, msg, fix, stacklevel) : 0)
52+
3553
#ifndef Py_LIMITED_API
3654
# define Py_CPYTHON_WARNINGS_H
3755
# include "cpython/warnings.h"

Lib/logging/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2256,6 +2256,26 @@ def _showwarning(message, category, filename, lineno, file=None, line=None):
22562256
# since some log aggregation tools group logs by the msg arg
22572257
logger.warning(str(s))
22582258

2259+
def _showwarningwithfix(message, fix, category, filename, lineno, file=None, line=None):
2260+
"""
2261+
Implementation of showwarnings which redirects to logging, which will first
2262+
check to see if the file parameter is None. If a file is specified, it will
2263+
delegate to the original warnings implementation of showwarning. Otherwise,
2264+
it will call warnings.formatwarningwithfix and will log the resulting string to a
2265+
warnings logger named "py.warnings" with level logging.WARNING.
2266+
"""
2267+
if file is not None:
2268+
if _warnings_showwarningwithfix is not None:
2269+
_warnings_showwarningwithfix(message, fix, category, filename, lineno, file, line)
2270+
else:
2271+
s = warnings.formatwarningwithfix(message, fix, category, filename, lineno, line)
2272+
logger = getLogger("py.warnings")
2273+
if not logger.handlers:
2274+
logger.addHandler(NullHandler())
2275+
# bpo-46557: Log str(s) as msg instead of logger.warning("%s", s)
2276+
# since some log aggregation tools group logs by the msg arg
2277+
logger.warning(str(s))
2278+
22592279
def captureWarnings(capture):
22602280
"""
22612281
If capture is true, redirect all warnings to the logging package.

Lib/test/support/warnings_helper.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,11 @@ def __getattr__(self, attr):
7272
return getattr(self._warnings[-1], attr)
7373
elif attr in warnings.WarningMessage._WARNING_DETAILS:
7474
return None
75+
elif attr in warnings.WarningMessageAndFix._WARNING_DETAILS:
76+
return None
7577
raise AttributeError("%r has no attribute %r" % (self, attr))
7678

79+
7780
@property
7881
def warnings(self):
7982
return self._warnings[self._last:]
@@ -106,6 +109,29 @@ def check_warnings(*filters, **kwargs):
106109
return _filterwarnings(filters, quiet)
107110

108111

112+
@contextlib.contextmanager
113+
def check_py2x_warnings(*filters, **kwargs):
114+
"""Context manager to silence py2x warnings.
115+
116+
Accept 2-tuples as positional arguments:
117+
("message regexp", WarningCategory)
118+
119+
Optional argument:
120+
- if 'quiet' is True, it does not fail if a filter catches nothing
121+
(default False)
122+
123+
Without argument, it defaults to:
124+
check_py2x_warnings(("", DeprecationWarning), quiet=False)
125+
"""
126+
if sys.py2x_warning:
127+
if not filters:
128+
filters = (("", DeprecationWarning),)
129+
else:
130+
# It should not raise any py2x warning
131+
filters = ()
132+
return _filterwarnings(filters, kwargs.get('quiet'))
133+
134+
109135
@contextlib.contextmanager
110136
def check_no_warnings(testcase, message='', category=Warning, force_gc=False):
111137
"""Context manager to check that no warnings are emitted.

Lib/test/test_py2xwarn.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import unittest
2+
import sys
3+
from test.support.warnings_helper import check_py2x_warnings
4+
import warnings
5+
6+
if not sys.py2x_warning:
7+
raise unittest.SkipTest('%s must be run with the -2 flag' % __name__)
8+
9+
class TestPy2xWarnings(unittest.TestCase):
10+
11+
def assertWarning(self, _, warning, expected_message):
12+
self.assertEqual(str(warning.message), expected_message)
13+
14+
def assertNoWarning(self, _, recorder):
15+
self.assertEqual(len(recorder.warnings), 0)

0 commit comments

Comments
 (0)