Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Doc/c-api/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ Printing and clearing
.. versionadded:: 3.12


.. c:function:: void PyErr_Display(PyObject *unused, PyObject *value, PyObject *tb)

Legacy variant of :c:func:`PyErr_DisplayException`.

Print the exception *value* with its traceback to :data:`sys.stderr`.
If *value* has no traceback set, *tb* is used as its traceback.
The first argument is ignored.

If :data:`sys.stderr` is ``None``, nothing is printed.
If :data:`sys.stderr` is not set, the exception is dumped to the
C ``stderr`` stream instead.

.. deprecated:: 3.12
Use :c:func:`PyErr_DisplayException` instead.

Raising exceptions
==================

Expand Down
9 changes: 4 additions & 5 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1235,9 +1235,9 @@ PyConfig

.. c:member:: wchar_t* base_executable

Python base executable: :data:`sys._base_executable`.
Python base executable: ``sys._base_executable``.

Set by the :envvar:`__PYVENV_LAUNCHER__` environment variable.
Set by the ``__PYVENV_LAUNCHER__`` environment variable.

Set from :c:member:`PyConfig.executable` if ``NULL``.

Expand Down Expand Up @@ -1748,7 +1748,7 @@ PyConfig

* On macOS, use :envvar:`PYTHONEXECUTABLE` environment variable if set.
* If the ``WITH_NEXT_FRAMEWORK`` macro is defined, use
:envvar:`__PYVENV_LAUNCHER__` environment variable if set.
``__PYVENV_LAUNCHER__`` environment variable if set.
* Use ``argv[0]`` of :c:member:`~PyConfig.argv` if available and
non-empty.
* Otherwise, use ``L"python"`` on Windows, or ``L"python3"`` on other
Expand Down Expand Up @@ -1984,8 +1984,7 @@ PyConfig

The :mod:`warnings` module adds :data:`sys.warnoptions` in the reverse
order: the last :c:member:`PyConfig.warnoptions` item becomes the first
item of :data:`warnings.filters` which is checked first (highest
priority).
item of ``warnings.filters`` which is checked first (highest priority).

The :option:`-W` command line options adds its value to
:c:member:`~PyConfig.warnoptions`, it can be used multiple times.
Expand Down
2 changes: 1 addition & 1 deletion Doc/c-api/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1163,7 +1163,7 @@ when defined by the compiler, will also implicitly enable :c:macro:`!Py_DEBUG`.
In addition to the reference count debugging described below, extra checks are
performed. See :ref:`Python Debug Build <debug-build>` for more details.

Defining :c:macro:`Py_TRACE_REFS` enables reference tracing
Defining ``Py_TRACE_REFS`` enables reference tracing
(see the :option:`configure --with-trace-refs option <--with-trace-refs>`).
When defined, a circular doubly linked list of active objects is maintained by adding two extra
fields to every :c:type:`PyObject`. Total allocations are tracked as well. Upon
Expand Down
3 changes: 0 additions & 3 deletions Doc/tools/.nitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
# as tested on the CI via check-warnings.py in reusable-docs.yml.
# Keep lines sorted lexicographically to help avoid merge conflicts.

Doc/c-api/init_config.rst
Doc/c-api/intro.rst
Doc/c-api/stable.rst
Doc/library/ast.rst
Doc/library/asyncio-extending.rst
Doc/library/email.charset.rst
Expand Down
2 changes: 1 addition & 1 deletion Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ def _format_args(self, action, default_metavar):
return result

def _expand_help(self, action):
help_string = self._get_help_string(action)
help_string = str(self._get_help_string(action))
if '%' not in help_string:
return self._apply_text_markup(help_string)
params = dict(vars(action), prog=self._prog)
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/libregrtest/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,7 @@ def clear_caches():
except KeyError:
pass
else:
for f in typing._cleanups:
f()
typing._clear_caches()

import inspect
abs_classes = filter(inspect.isabstract, typing.__dict__.values())
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -7898,6 +7898,41 @@ def test_backtick_markup_in_argument_help_color_disabled(self):
self.assertIn("set the `foo` value", help_text)
self.assertNotIn("\x1b[", help_text)

def test_argument_help_interpolation_accepts_string_like_proxy(self):
class LazyStr:
def __init__(self, message):
self._message = message

def __str__(self):
return self._message

def __getattr__(self, name):
return getattr(str(self), name)

def __contains__(self, item):
return item in self._message

def __mod__(self, other):
return self._message % other

parser = argparse.ArgumentParser(prog="PROG", color=True)
parser.add_argument(
"--foo",
default="bar",
help=LazyStr("foo (default: %(default)s)"),
)
parser.add_argument(
"--baz",
help=LazyStr("baz plain text"),
)

interp = self.theme.interpolated_value
reset = self.theme.reset

help_text = parser.format_help()
self.assertIn(f"foo (default: {interp}bar{reset})", help_text)
self.assertIn("baz plain text", help_text)

def test_help_with_format_specifiers(self):
# GH-142950: format specifiers like %x should work with color=True
parser = argparse.ArgumentParser(prog='PROG', color=True)
Expand Down
172 changes: 116 additions & 56 deletions Lib/test/test_codeop.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,64 @@
"""
import unittest
import warnings
from test.support import warnings_helper
from test.support import subTests, warnings_helper
from textwrap import dedent
import functools

from codeop import compile_command, PyCF_DONT_IMPLY_DEDENT
from codeop import compile_command, CommandCompiler, Compile
from codeop import PyCF_DONT_IMPLY_DEDENT, PyCF_ONLY_AST
import ast


WRAPPING_COMPILERS = [compile_command, CommandCompiler()]
RAW_COMPILERS = [Compile()]
COMPILERS = WRAPPING_COMPILERS + RAW_COMPILERS

class CodeopTests(unittest.TestCase):

def assertValid(self, str, symbol='single'):
class CodeopTests(unittest.TestCase):
def assertValid(self, str, symbol='single', *, compiler):
'''succeed iff str is a valid piece of code'''
expected = compile(str, "<input>", symbol, PyCF_DONT_IMPLY_DEDENT)
self.assertEqual(compile_command(str, "<input>", symbol), expected)
self.assertEqual(compiler(str, "<input>", symbol), expected)

def assertIncomplete(self, str, symbol='single'):
def assertIncomplete(self, str, symbol='single', *, compiler):
'''succeed iff str is the start of a valid piece of code'''
self.assertEqual(compile_command(str, symbol=symbol), None)

def assertInvalid(self, str, symbol='single', is_syntax=1):
if compiler in WRAPPING_COMPILERS:
self.assertEqual(compiler(str, "<input>", symbol=symbol), None)
else:
# Compile has should raise like built-in compile
with self.assertRaises(SyntaxError) as cm_original_error:
compile(str, "<input>", symbol, compiler.flags)
expected_error = cm_original_error.exception
with self.assertRaises(type(expected_error)) as cm_wrapped_error:
compiler(str, "<input>", symbol=symbol)
self.assertEqual(
expected_error.args,
cm_wrapped_error.exception.args
)

def assertInvalid(self, str, symbol='single', is_syntax=1, *, compiler):
'''succeed iff str is the start of an invalid piece of code'''
try:
compile_command(str,symbol=symbol)
compiler(str,"<input>", symbol=symbol)
self.fail("No exception raised for invalid code")
except SyntaxError:
self.assertTrue(is_syntax)
except OverflowError:
self.assertTrue(not is_syntax)

def test_valid(self):
av = self.assertValid

# special case
self.assertEqual(compile_command(""),
compile("pass", "<input>", 'single',
PyCF_DONT_IMPLY_DEDENT))
self.assertEqual(compile_command("\n"),
compile("pass", "<input>", 'single',
PyCF_DONT_IMPLY_DEDENT))

@subTests('compiler', WRAPPING_COMPILERS)
def test_empty(self, compiler):
self.assertEqual(
compiler("", "<input>", 'single'),
compile("pass", "<input>", 'single', PyCF_DONT_IMPLY_DEDENT))
self.assertEqual(
compiler("\n", "<input>", 'single'),
compile("pass", "<input>", 'single', PyCF_DONT_IMPLY_DEDENT))

@subTests('compiler', COMPILERS)
def test_valid(self, compiler):
av = functools.partial(self.assertValid, compiler=compiler)
av("a = 1")
av("\na = 1")
av("a = 1\n")
Expand Down Expand Up @@ -92,8 +113,9 @@ def test_valid(self):
av("def f():\n pass\n#foo\n")
av("@a.b.c\ndef f():\n pass\n")

def test_incomplete(self):
ai = self.assertIncomplete
@subTests('compiler', COMPILERS)
def test_incomplete(self, compiler):
ai = functools.partial(self.assertIncomplete, compiler=compiler)

ai("(a **")
ai("(a,b,")
Expand Down Expand Up @@ -226,8 +248,9 @@ def test_incomplete(self):
ai('a = f"""')
ai('a = \\')

def test_invalid(self):
ai = self.assertInvalid
@subTests('compiler', COMPILERS)
def test_invalid(self, compiler):
ai = functools.partial(self.assertInvalid, compiler=compiler)
ai("a b")

ai("a @")
Expand Down Expand Up @@ -263,67 +286,104 @@ def test_invalid(self):

ai("[i for i in range(10)] = (1, 2, 3)")

def test_invalid_exec(self):
ai = self.assertInvalid
@subTests('compiler', COMPILERS)
def test_invalid_exec(self, compiler):
ai = functools.partial(self.assertInvalid, compiler=compiler)
ai("raise = 4", symbol="exec")
ai('def a-b', symbol='exec')
ai('await?', symbol='exec')
ai('=!=', symbol='exec')
ai('a await raise b', symbol='exec')
ai('a await raise b?+1', symbol='exec')

def test_filename(self):
self.assertEqual(compile_command("a = 1\n", "abc").co_filename,
compile("a = 1\n", "abc", 'single').co_filename)
self.assertNotEqual(compile_command("a = 1\n", "abc").co_filename,
compile("a = 1\n", "def", 'single').co_filename)

def test_warning(self):
@subTests('compiler', COMPILERS)
def test_filename(self, compiler):
self.assertEqual(
compiler("a = 1\n", "abc", "single").co_filename,
compile("a = 1\n", "abc", 'single').co_filename
)
self.assertNotEqual(
compiler("a = 1\n", "abc", "single").co_filename,
compile("a = 1\n", "def", 'single').co_filename
)

def assertReturnsModule(self, code, compiler):
retval = compiler(code, "<input>", 'exec', PyCF_ONLY_AST)
self.assertIsInstance(retval, ast.Module)

@subTests('compiler', RAW_COMPILERS)
def test_ast_return_value(self, compiler):
validate_ast = self.assertReturnsModule
validate_ast("x = 5", compiler)
validate_ast("\nx = 5", compiler)
validate_ast("x = 5\n", compiler)
validate_ast("x = 5\n\n", compiler)
validate_ast("\n\nx = 5\n\n", compiler)

@subTests('compiler', COMPILERS)
def test_warning(self, compiler):
# Test that the warning is only returned once.
with warnings_helper.check_warnings(
('"is" with \'str\' literal', SyntaxWarning),
('"\\\\e" is an invalid escape sequence', SyntaxWarning),
) as w:
compile_command(r"'\e' is 0")
self.assertEqual(len(w.warnings), 2)
compiler(r"'\e' is 0", "<input>", "single")
self.assertEqual(len(w.warnings), 2)

# bpo-41520: check SyntaxWarning treated as an SyntaxError
with warnings.catch_warnings(), self.assertRaises(SyntaxError):
warnings.simplefilter('error', SyntaxWarning)
compile_command('1 is 1', symbol='exec')
compiler('1 is 1', "<input>", 'exec')

# Check SyntaxWarning treated as an SyntaxError
with warnings.catch_warnings(), self.assertRaises(SyntaxError):
warnings.simplefilter('error', SyntaxWarning)
compile_command(r"'\e'", symbol='exec')
compiler(r"'\e'", "<input>", 'exec')

def test_incomplete_warning(self):
@subTests('compiler', WRAPPING_COMPILERS)
def test_incomplete_warning(self, compiler):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
self.assertIncomplete("'\\e' + (")
compiler("'\\e' + (")
self.assertEqual(w, [])

def test_invalid_warning(self):
@subTests('compiler', RAW_COMPILERS)
def test_raw_raises_error(self, compiler):
warnings_cm = warnings_helper.check_warnings(
('"\\\\e" is an invalid esceape sequence', SyntaxWarning)
)
with self.assertRaises(SyntaxError), warnings_cm as w:
compiler("'\\e' + (", "<input>", 'single')
self.assertEqual(len(w.warnings), 1)

@subTests('compiler', COMPILERS)
def test_invalid_warning(self, compiler):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
self.assertInvalid("'\\e' 1")
self.assertInvalid("'\\e' 1", compiler=compiler)
self.assertEqual(len(w), 1)
self.assertEqual(w[0].category, SyntaxWarning)
self.assertRegex(str(w[0].message), 'invalid escape sequence')
self.assertEqual(w[0].filename, '<input>')

def assertSyntaxErrorMatches(self, code, message):
with self.subTest(code):
with self.assertRaisesRegex(SyntaxError, message):
compile_command(code, symbol='exec')

def test_syntax_errors(self):
self.assertSyntaxErrorMatches(
dedent("""\
for warning in w:
self.assertEqual(warning.category, SyntaxWarning)
self.assertRegex(str(warning), 'invalid escape sequence')
self.assertEqual(warning.filename, '<input>')

@subTests('compiler', COMPILERS)
def test_syntax_errors(self, compiler):
code = dedent("""\
def foo(x,x):
pass
"""), "duplicate parameter 'x' in function definition")

""")
message = "duplicate parameter 'x' in function definition"
with self.assertRaisesRegex(SyntaxError, message):
compiler(code, "<input>", 'exec')

@subTests('compiler', RAW_COMPILERS)
def test_future_imports(self, compiler):
original_flags = compiler.flags
compiler('from __future__ import annotations', "<input>", 'single')
self.assertGreater(compiler.flags, original_flags)
# reset flags to ensure test has no side-effects
compiler.flags = original_flags


if __name__ == "__main__":
Expand Down
Loading
Loading