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
6 changes: 3 additions & 3 deletions Doc/c-api/init_config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1807,10 +1807,10 @@ PyConfig

.. c:member:: wchar_t* run_presite

``package.module`` path to module that should be imported before
``site.py`` is run.
``module`` or ``module:func`` entry point that should be executed before
the :mod:`site` module is imported.

Set by the :option:`-X presite=package.module <-X>` command-line
Set by the :option:`-X presite=module:func <-X>` command-line
option and the :envvar:`PYTHON_PRESITE` environment variable.
The command-line option takes precedence.

Expand Down
11 changes: 9 additions & 2 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -654,13 +654,17 @@ Miscellaneous options

.. versionadded:: 3.13

* :samp:`-X presite={package.module}` specifies a module that should be
imported before the :mod:`site` module is executed and before the
* :samp:`-X presite={module}` or :samp:`-X presite={module:func}` specifies
an entry point that should be executed before the :mod:`site` module is
executed and before the
:mod:`__main__` module exists. Therefore, the imported module isn't
:mod:`__main__`. This can be used to execute code early during Python
initialization. Python needs to be :ref:`built in debug mode <debug-build>`
for this option to exist. See also :envvar:`PYTHON_PRESITE`.

.. versionchanged:: next
Accept also ``module:func`` entry point format.

.. versionadded:: 3.13

* :samp:`-X gil={0,1}` forces the GIL to be disabled or enabled,
Expand Down Expand Up @@ -1458,4 +1462,7 @@ Debug-mode variables

Needs Python configured with the :option:`--with-pydebug` build option.

.. versionchanged:: next
Accept also ``module:func`` entry point format.

.. versionadded:: 3.13
1 change: 1 addition & 0 deletions Lib/_pyrepl/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .types import CharBuffer, CharWidths
from .trace import trace


ANSI_ESCAPE_SEQUENCE = re.compile(r"\x1b\[[ -@]*[A-~]")
ZERO_WIDTH_BRACKET = re.compile(r"\x01.*?\x02")
ZERO_WIDTH_TRANS = str.maketrans({"\x01": "", "\x02": ""})
Expand Down
8 changes: 6 additions & 2 deletions Lib/configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,15 +613,19 @@ class RawConfigParser(MutableMapping):
\] # ]
"""
_OPT_TMPL = r"""
(?P<option>.*?) # very permissive!
(?P<option> # very permissive!
(?:(?!{delim})\S)* # non-delimiter non-whitespace
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
\s*(?P<vi>{delim})\s* # any number of space/tab,
# followed by any of the
# allowed delimiters,
# followed by any space/tab
(?P<value>.*)$ # everything up to eol
"""
_OPT_NV_TMPL = r"""
(?P<option>.*?) # very permissive!
(?P<option> # very permissive!
(?:(?!{delim})\S)* # non-delimiter non-whitespace
(?:\s+(?:(?!{delim})\S)+)*) # optionally more words
\s*(?: # any number of space/tab,
(?P<vi>{delim})\s* # optionally followed by
# any of the allowed
Expand Down
3 changes: 2 additions & 1 deletion Lib/ctypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import os as _os
import sys as _sys
import sysconfig as _sysconfig
import types as _types

lazy import sysconfig as _sysconfig

from _ctypes import Union, Structure, Array
from _ctypes import _Pointer
from _ctypes import CFuncPtr as _CFuncPtr
Expand Down
3 changes: 2 additions & 1 deletion Lib/ctypes/_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
"""

import sys
import warnings

from _ctypes import CField, buffer_info
import ctypes

lazy import warnings

def round_down(n, multiple):
assert n >= 0
assert multiple > 0
Expand Down
5 changes: 3 additions & 2 deletions Lib/ctypes/util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
import shutil
import subprocess
import sys

lazy import shutil
lazy import subprocess

# find_library(name) returns the pathname of a library, or None.
if os.name == "nt":

Expand Down
29 changes: 17 additions & 12 deletions Lib/test/_test_embed_structseq.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,21 @@ def test_sys_funcs(self):
self.check_structseq(type(obj))


try:
unittest.main(
module=(
'__main__'
if __name__ == '__main__'
# Avoiding a circular import:
else sys.modules['test._test_embed_structseq']
def main():
try:
unittest.main(
module=(
'__main__'
if __name__ == '__main__'
# Avoiding a circular import:
else sys.modules['test._test_embed_structseq']
)
)
)
except SystemExit as exc:
if exc.args[0] != 0:
raise
print("Tests passed")
except SystemExit as exc:
if exc.args[0] != 0:
raise
print("Tests passed")


if __name__ == "__main__":
main()
8 changes: 4 additions & 4 deletions Lib/test/cov.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"""A minimal hook for gathering line coverage of the standard library.

Designed to be used with -Xpresite= which means:
* it installs itself on import
* it's not imported as `__main__` so can't use the ifmain idiom
Designed to be used with -Xpresite=test.cov:enable which means:

* it can't import anything besides `sys` to avoid tainting gathered coverage
* filenames are not normalized

Expand Down Expand Up @@ -45,4 +44,5 @@ def disable():
mon.free_tool_id(mon.COVERAGE_ID)


enable()
if __name__ == "__main__":
enable()
2 changes: 1 addition & 1 deletion Lib/test/libregrtest/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def create_python_cmd(self) -> list[str]:
if '-u' not in python_opts:
cmd.append('-u') # Unbuffered stdout and stderr
if self.coverage:
cmd.append("-Xpresite=test.cov")
cmd.append("-Xpresite=test.cov:enable")
return cmd

def bisect_cmd_args(self) -> list[str]:
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1397,7 +1397,7 @@ def trace_wrapper(*args, **kwargs):
sys.settrace(original_trace)

coverage_wrapper = trace_wrapper
if 'test.cov' in sys.modules: # -Xpresite=test.cov used
if 'test.cov' in sys.modules: # -Xpresite=test.cov:enable used
cov = sys.monitoring.COVERAGE_ID
@functools.wraps(func)
def coverage_wrapper(*args, **kwargs):
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ def _kill_python_and_exit_code(p):
return data, returncode


def presite_func():
print("presite func")

class Namespace:
pass

presite = Namespace()
presite.attr = Namespace()
presite.attr.func = presite_func


class CmdLineTest(unittest.TestCase):
def test_directories(self):
assert_python_failure('.')
Expand Down Expand Up @@ -1266,6 +1277,17 @@ def test_invalid_thread_local_bytecode(self):
rc, out, err = assert_python_failure(PYTHON_TLBC="2")
self.assertIn(b"PYTHON_TLBC=N: N is missing or invalid", err)

@unittest.skipUnless(support.Py_DEBUG,
'-X presite requires a Python debug build')
def test_presite(self):
entrypoint = "test.test_cmd_line:presite_func"
proc = assert_python_ok("-X", f"presite={entrypoint}", "-c", "pass")
self.assertEqual(proc.out.rstrip(), b"presite func")

entrypoint = "test.test_cmd_line:presite.attr.func"
proc = assert_python_ok("-X", f"presite={entrypoint}", "-c", "pass")
self.assertEqual(proc.out.rstrip(), b"presite func")


@unittest.skipIf(interpreter_requires_environment(),
'Cannot run -I tests when PYTHON env vars are required.')
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_configparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2270,6 +2270,26 @@ def test_section_bracket_in_key(self):
output.close()


class ReDoSTestCase(unittest.TestCase):
"""Regression tests for quadratic regex backtracking (gh-146333)."""

def test_option_regex_does_not_backtrack(self):
# A line with many spaces between non-delimiter characters
# should be parsed in linear time, not quadratic.
parser = configparser.RawConfigParser()
content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
# This should complete almost instantly. Before the fix,
# it would take over a minute due to catastrophic backtracking.
with self.assertRaises(configparser.ParsingError):
parser.read_string(content)

def test_option_regex_no_value_does_not_backtrack(self):
parser = configparser.RawConfigParser(allow_no_value=True)
content = "[section]\n" + "x" + " " * 40000 + "y" + "\n"
parser.read_string(content)
self.assertTrue(parser.has_option("section", "x" + " " * 40000 + "y"))


class MiscTestCase(unittest.TestCase):
def test__all__(self):
support.check__all__(self, configparser, not_exported={"Error"})
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -2051,7 +2051,7 @@ def test_no_memleak(self):
def test_presite(self):
cmd = [
sys.executable,
"-I", "-X", "presite=test._test_embed_structseq",
"-I", "-X", "presite=test._test_embed_structseq:main",
"-c", "print('unique-python-message')",
]
proc = subprocess.run(
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_opcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2047,5 +2047,25 @@ def load_module_attr_missing():
sys.modules.pop("test_module_with_getattr", None)


@cpython_only
@requires_specialization
def test_load_attr_enum(self):
import enum

class Color(enum.IntEnum):
RED = 1
GREEN = 2
BLUE = 3

def load_enum_member():
for _ in range(_testinternalcapi.SPECIALIZATION_THRESHOLD):
x = Color.RED
assert x == 1

load_enum_member()
self.assert_specialized(load_enum_member,
"LOAD_ATTR_CLASS_WITH_METACLASS_CHECK")


if __name__ == "__main__":
unittest.main()
87 changes: 87 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1790,6 +1790,7 @@ def f():
]
self.assertEqual(result_lines, expected)


class TestKeywordTypoSuggestions(unittest.TestCase):
TYPO_CASES = [
("with block ad something:\n pass", "and"),
Expand Down Expand Up @@ -5414,6 +5415,92 @@ def expected(t, m, fn, l, f, E, e, z, n):
]
self.assertEqual(actual, expected(**colors))

def test_colorized_traceback_unicode(self):
try:
啊哈=1; 啊哈/0####
except Exception as e:
exc = traceback.TracebackException.from_exception(e)

actual = "".join(exc.format(colorize=True)).splitlines()
def expected(t, m, fn, l, f, E, e, z, n):
return [
f" 啊哈=1; {e}啊哈{z}{E}/{z}{e}0{z}####",
f" {e}~~~~{z}{E}^{z}{e}~{z}",
]
self.assertEqual(actual[2:4], expected(**colors))

try:
ééééé/0
except Exception as e:
exc = traceback.TracebackException.from_exception(e)

actual = "".join(exc.format(colorize=True)).splitlines()
def expected(t, m, fn, l, f, E, e, z, n):
return [
f" {E}ééééé{z}/0",
f" {E}^^^^^{z}",
]
self.assertEqual(actual[2:4], expected(**colors))

def test_colorized_syntax_error_ascii_display_width(self):
"""Caret alignment for ASCII edge cases handled by _wlen.

The old ASCII fast track in _display_width returned the raw character
offset for ASCII strings, which is wrong for CTRL-Z (display width 2)
and ANSI escape sequences (display width 0).
"""
E = colors["E"]
z = colors["z"]
t = colors["t"]
m = colors["m"]
fn = colors["fn"]
l = colors["l"]

def _make_syntax_error(text, offset, end_offset):
err = SyntaxError("invalid syntax")
err.filename = "<string>"
err.lineno = 1
err.end_lineno = 1
err.text = text
err.offset = offset
err.end_offset = end_offset
return err

# CTRL-Z (\x1a) is ASCII but displayed as ^Z (2 columns).
# Verify caret aligns when CTRL-Z precedes the error.
err = _make_syntax_error("a\x1a$\n", offset=3, end_offset=4)
exc = traceback.TracebackException.from_exception(err)
actual = "".join(exc.format(colorize=True))
# 'a' (1 col) + '\x1a' (2 cols) = 3 cols before '$'
self.assertIn(
f' File {fn}"<string>"{z}, line {l}1{z}\n'
f' a\x1a{E}${z}\n'
f' {" " * 3}{E}^{z}\n'
f'{t}SyntaxError{z}: {m}invalid syntax{z}\n',
actual,
)

# CTRL-Z in the highlighted (error) region counts as 2 columns.
err = _make_syntax_error("$\x1a\n", offset=1, end_offset=3)
exc = traceback.TracebackException.from_exception(err)
actual = "".join(exc.format(colorize=True))
# '$' (1 col) + '\x1a' (2 cols) = 3 columns of carets
self.assertIn(
f' {E}$\x1a{z}\n'
f' {E}{"^" * 3}{z}\n',
actual,
)

# ANSI escape sequences are ASCII but take 0 display columns.
err = _make_syntax_error("a\x1b[1mb$\n", offset=7, end_offset=8)
exc = traceback.TracebackException.from_exception(err)
actual = "".join(exc.format(colorize=True))
# 'a' (1 col) + '\x1b[1m' (0 cols) + 'b' (1 col) = 2 before '$'
self.assertIn(
f' a\x1b[1mb{E}${z}\n'
f' {" " * 2}{E}^{z}\n',
actual,
)

class TestLazyImportSuggestions(unittest.TestCase):
"""Test that lazy imports are not reified when computing AttributeError suggestions."""
Expand Down
Loading
Loading