Skip to content

Commit

Permalink
Merge python#5
Browse files Browse the repository at this point in the history
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 <jnanjekye@python.org>
  • Loading branch information
bors[bot] and nanjekyejoannah authored Jun 16, 2022
2 parents b286d72 + cbd66f6 commit 3d5bbe6
Show file tree
Hide file tree
Showing 16 changed files with 845 additions and 8 deletions.
18 changes: 18 additions & 0 deletions Doc/c-api/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,14 @@ an error value).
.. versionadded:: 3.4
.. c:function:: int PyErr_WarnExplicitWithFixObject(PyObject *category, PyObject *message, PyObject *fix, PyObject *filename, int lineno, PyObject *module, PyObject *registry)
Issue a warning message and fix with explicit control over all warning attributes. This
is a straightforward wrapper around the Python function
:func:`warnings.warnings_warn_explicit_with_fix`; see there for more information. The *module*
and *registry* arguments may be set to ``NULL`` to get the default effect
described there.
.. c:function:: int PyErr_WarnExplicit(PyObject *category, const char *message, const char *filename, int lineno, const char *module, PyObject *registry)
Expand All @@ -347,6 +355,11 @@ an error value).
:term:`filesystem encoding and error handler`.
.. c:function:: int PyErr_WarnExplicit_WithFix(PyObject *category, const char *message, const char *fix, const char *filename, int lineno, const char *module, PyObject *registry)
Similar to :c:func:`PyErr_WarnExplicit` with an additional *fix* parameter.
.. c:function:: int PyErr_WarnFormat(PyObject *category, Py_ssize_t stack_level, const char *format, ...)
Function similar to :c:func:`PyErr_WarnEx`, but use
Expand All @@ -363,6 +376,11 @@ an error value).
.. versionadded:: 3.6
.. c:function:: int PyErr_WarnPy2x(char *message, char *fix, int stacklevel)
Issue a :exc:`DeprecationWarning` with the given *message*, *fix* and *stacklevel*
if the :c:data:`Py_Py2xWarningFlag` flag is enabled.
Querying the error indicator
============================
Expand Down
12 changes: 12 additions & 0 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,18 @@ The :mod:`test.support` module defines the following functions:
Return a list of command line arguments reproducing the current
optimization settings in ``sys.flags``.

.. function:: check_py2x_warnings(*filters, quiet=False)

Similar to :func:`check_warnings`, but for Python 3 compatibility warnings.
If ``sys.py3xwarning == 1``, it checks if the warning is effectively raised.
If ``sys.py3xwarning == 0``, it checks that no warning is raised. It
accepts 2-tuples of the form ``("message regexp", WarningCategory)`` as
positional arguments. When the optional keyword argument *quiet* is
:const:`True`, it does not fail if a filter catches nothing. Without
arguments, it defaults to::

check_py2x_warnings(("", DeprecationWarning), quiet=False)


.. function:: captured_stdin()
captured_stdout()
Expand Down
40 changes: 39 additions & 1 deletion Doc/library/warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,6 @@ Available Functions
.. versionchanged:: 3.6
Added *source* parameter.


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

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

.. function:: warn_explicit_with_fix(message, fix, category, filename, lineno, module=None, registry=None, module_globals=None, source=None)

This is a low-level interface to the functionality of :func:`warn`, passing in
explicitly the message, fix, category, filename and line number, and optionally the
module name and the registry (which should be the ``__warningregistry__``
dictionary of the module). The module name defaults to the filename with
``.py`` stripped; if no registry is passed, the warning is never suppressed.
*message* must be a string and *category* a subclass of :exc:`Warning` or
*message* may be a :exc:`Warning` instance, in which case *category* will be
ignored.

*module_globals*, if supplied, should be the global namespace in use by the code
for which the warning is issued. (This argument is used to support displaying
source for modules found in zipfiles or other non-filesystem import
sources).

*source*, if supplied, is the destroyed object which emitted a
:exc:`ResourceWarning`.

.. versionchanged:: 3.6
Add the *source* parameter.

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


.. function:: showwarningwithfix(message, fix, category, filename, lineno, file=None, line=None)

Write a warning to a file. The default implementation calls
``formatwarningwithfix(message, fix, category, filename, lineno, line)`` and writes the
resulting string to *file*, which defaults to :data:`sys.stderr`. You may replace
this function with any callable by assigning to ``warnings.showwarning``.
*line* is a line of source code to be included in the warning
message; if *line* is not supplied, :func:`showwarning` will
try to read the line specified by *filename* and *lineno*.


.. function:: formatwarning(message, category, filename, lineno, line=None)

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


.. function:: formatwarningwithfix(message, fix, category, filename, lineno, line=None)

Format a warning the standard way. This returns a string which may contain
embedded newlines and ends in a newline. *line* is a line of source code to
be included in the warning message; if *line* is not supplied,
:func:`formatwarning` will try to read the line specified by *filename* and
*lineno*.


.. function:: filterwarnings(action, message='', category=Warning, module='', lineno=0, append=False)

Insert an entry into the list of :ref:`warnings filter specifications
Expand Down
9 changes: 9 additions & 0 deletions Include/cpython/warnings.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ PyAPI_FUNC(int) PyErr_WarnExplicitObject(
PyObject *module,
PyObject *registry);

PyAPI_FUNC(int) PyErr_WarnExplicitWithFixObject(
PyObject *category,
PyObject *message,
PyObject *fix,
PyObject *filename,
int lineno,
PyObject *module,
PyObject *registry);

PyAPI_FUNC(int) PyErr_WarnExplicitFormat(
PyObject *category,
const char *filename, int lineno,
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(TextIOWrapper)
STRUCT_FOR_ID(True)
STRUCT_FOR_ID(WarningMessage)
STRUCT_FOR_ID(WarningMessageAndFix)
STRUCT_FOR_ID(_)
STRUCT_FOR_ID(__IOBase_closed)
STRUCT_FOR_ID(__abc_tpflags__)
Expand Down Expand Up @@ -221,6 +222,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_is_text_encoding)
STRUCT_FOR_ID(_lock_unlock_module)
STRUCT_FOR_ID(_showwarnmsg)
STRUCT_FOR_ID(_showwarnmsgwithfix)
STRUCT_FOR_ID(_shutdown)
STRUCT_FOR_ID(_slotnames)
STRUCT_FOR_ID(_strptime_time)
Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_runtime_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ extern "C" {
INIT_ID(TextIOWrapper), \
INIT_ID(True), \
INIT_ID(WarningMessage), \
INIT_ID(WarningMessageAndFix), \
INIT_ID(_), \
INIT_ID(__IOBase_closed), \
INIT_ID(__abc_tpflags__), \
Expand Down Expand Up @@ -843,6 +844,7 @@ extern "C" {
INIT_ID(_is_text_encoding), \
INIT_ID(_lock_unlock_module), \
INIT_ID(_showwarnmsg), \
INIT_ID(_showwarnmsgwithfix), \
INIT_ID(_shutdown), \
INIT_ID(_slotnames), \
INIT_ID(_strptime_time), \
Expand Down
18 changes: 18 additions & 0 deletions Include/warnings.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ PyAPI_FUNC(int) PyErr_WarnEx(
const char *message, /* UTF-8 encoded string */
Py_ssize_t stack_level);

PyAPI_FUNC(int) PyErr_WarnEx_WithFix(
PyObject *category,
const char *message, /* UTF-8 encoded string */
const char *fix, /* UTF-8 encoded string */
Py_ssize_t stack_level);

PyAPI_FUNC(int) PyErr_WarnFormat(
PyObject *category,
Py_ssize_t stack_level,
Expand All @@ -32,6 +38,18 @@ PyAPI_FUNC(int) PyErr_WarnExplicit(
const char *module, /* UTF-8 encoded string */
PyObject *registry);

PyAPI_FUNC(int) PyErr_WarnExplicit_WithFix(
PyObject *category,
const char *message, /* UTF-8 encoded string */
const char *fix, /* UTF-8 encoded string */
const char *filename, /* decoded from the filesystem encoding */
int lineno,
const char *module, /* UTF-8 encoded string */
PyObject *registry);

#define PyErr_WarnPy2x(msg, fix, stacklevel) \
(Py_Py2xWarningFlag ? PyErr_WarnEx_WithFix(PyExc_DeprecationWarning, msg, fix, stacklevel) : 0)

#ifndef Py_LIMITED_API
# define Py_CPYTHON_WARNINGS_H
# include "cpython/warnings.h"
Expand Down
20 changes: 20 additions & 0 deletions Lib/logging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2256,6 +2256,26 @@ def _showwarning(message, category, filename, lineno, file=None, line=None):
# since some log aggregation tools group logs by the msg arg
logger.warning(str(s))

def _showwarningwithfix(message, fix, category, filename, lineno, file=None, line=None):
"""
Implementation of showwarnings which redirects to logging, which will first
check to see if the file parameter is None. If a file is specified, it will
delegate to the original warnings implementation of showwarning. Otherwise,
it will call warnings.formatwarningwithfix and will log the resulting string to a
warnings logger named "py.warnings" with level logging.WARNING.
"""
if file is not None:
if _warnings_showwarningwithfix is not None:
_warnings_showwarningwithfix(message, fix, category, filename, lineno, file, line)
else:
s = warnings.formatwarningwithfix(message, fix, category, filename, lineno, line)
logger = getLogger("py.warnings")
if not logger.handlers:
logger.addHandler(NullHandler())
# bpo-46557: Log str(s) as msg instead of logger.warning("%s", s)
# since some log aggregation tools group logs by the msg arg
logger.warning(str(s))

def captureWarnings(capture):
"""
If capture is true, redirect all warnings to the logging package.
Expand Down
26 changes: 26 additions & 0 deletions Lib/test/support/warnings_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ def __getattr__(self, attr):
return getattr(self._warnings[-1], attr)
elif attr in warnings.WarningMessage._WARNING_DETAILS:
return None
elif attr in warnings.WarningMessageAndFix._WARNING_DETAILS:
return None
raise AttributeError("%r has no attribute %r" % (self, attr))


@property
def warnings(self):
return self._warnings[self._last:]
Expand Down Expand Up @@ -106,6 +109,29 @@ def check_warnings(*filters, **kwargs):
return _filterwarnings(filters, quiet)


@contextlib.contextmanager
def check_py2x_warnings(*filters, **kwargs):
"""Context manager to silence py2x warnings.
Accept 2-tuples as positional arguments:
("message regexp", WarningCategory)
Optional argument:
- if 'quiet' is True, it does not fail if a filter catches nothing
(default False)
Without argument, it defaults to:
check_py2x_warnings(("", DeprecationWarning), quiet=False)
"""
if sys.py2x_warning:
if not filters:
filters = (("", DeprecationWarning),)
else:
# It should not raise any py2x warning
filters = ()
return _filterwarnings(filters, kwargs.get('quiet'))


@contextlib.contextmanager
def check_no_warnings(testcase, message='', category=Warning, force_gc=False):
"""Context manager to check that no warnings are emitted.
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_py2xwarn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import unittest
import sys
from test.support.warnings_helper import check_py2x_warnings
import warnings

if not sys.py2x_warning:
raise unittest.SkipTest('%s must be run with the -2 flag' % __name__)

class TestPy2xWarnings(unittest.TestCase):

def assertWarning(self, _, warning, expected_message):
self.assertEqual(str(warning.message), expected_message)

def assertNoWarning(self, _, recorder):
self.assertEqual(len(recorder.warnings), 0)
23 changes: 21 additions & 2 deletions Lib/test/test_warnings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ class PublicAPITests(BaseTest):

def test_module_all_attribute(self):
self.assertTrue(hasattr(self.module, '__all__'))
target_api = ["warn", "warn_explicit", "showwarning",
"formatwarning", "filterwarnings", "simplefilter",
target_api = ["warn", "warn_explicit", "warn_explicit_with_fix", "showwarning", "showwarningwithfix",
"formatwarning", "formatwarningwithfix", "filterwarnings", "simplefilter",
"resetwarnings", "catch_warnings"]
self.assertSetEqual(set(self.module.__all__),
set(target_api))
Expand Down Expand Up @@ -904,6 +904,25 @@ def test_formatwarning(self):
self.assertEqual(expect, self.module.formatwarning(message,
category, file_name, line_num, file_line))

def test_formatwarningwithfix(self):
message = "msg"
fix = "fix"
category = Warning
file_name = os.path.splitext(warning_tests.__file__)[0] + '.py'
line_num = 3
file_line = linecache.getline(file_name, line_num).strip()
format = "%s:%s: %s: %s: %s\n %s\n"
expect = format % (file_name, line_num, category.__name__, message, fix,
file_line)
self.assertEqual(expect, self.module.formatwarningwithfix(message, fix,
category, file_name, line_num))
# Test the 'line' argument.
file_line += " for the win!"
expect = format % (file_name, line_num, category.__name__, message, fix,
file_line)
self.assertEqual(expect, self.module.formatwarningwithfix(message, fix,
category, file_name, line_num, file_line))

def test_showwarning(self):
file_name = os.path.splitext(warning_tests.__file__)[0] + '.py'
line_num = 3
Expand Down
Loading

0 comments on commit 3d5bbe6

Please sign in to comment.