Skip to content

Commit feee573

Browse files
authored
gh-148014: Accept a function name in -X presite option (#148015)
1 parent e65987d commit feee573

File tree

10 files changed

+166
-36
lines changed

10 files changed

+166
-36
lines changed

Doc/c-api/init_config.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,10 +1807,10 @@ PyConfig
18071807
18081808
.. c:member:: wchar_t* run_presite
18091809
1810-
``package.module`` path to module that should be imported before
1811-
``site.py`` is run.
1810+
``module`` or ``module:func`` entry point that should be executed before
1811+
the :mod:`site` module is imported.
18121812
1813-
Set by the :option:`-X presite=package.module <-X>` command-line
1813+
Set by the :option:`-X presite=module:func <-X>` command-line
18141814
option and the :envvar:`PYTHON_PRESITE` environment variable.
18151815
The command-line option takes precedence.
18161816

Doc/using/cmdline.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,13 +654,17 @@ Miscellaneous options
654654

655655
.. versionadded:: 3.13
656656

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

665+
.. versionchanged:: next
666+
Accept also ``module:func`` entry point format.
667+
664668
.. versionadded:: 3.13
665669

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

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

1465+
.. versionchanged:: next
1466+
Accept also ``module:func`` entry point format.
1467+
14611468
.. versionadded:: 3.13

Lib/test/_test_embed_structseq.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,21 @@ def test_sys_funcs(self):
4747
self.check_structseq(type(obj))
4848

4949

50-
try:
51-
unittest.main(
52-
module=(
53-
'__main__'
54-
if __name__ == '__main__'
55-
# Avoiding a circular import:
56-
else sys.modules['test._test_embed_structseq']
50+
def main():
51+
try:
52+
unittest.main(
53+
module=(
54+
'__main__'
55+
if __name__ == '__main__'
56+
# Avoiding a circular import:
57+
else sys.modules['test._test_embed_structseq']
58+
)
5759
)
58-
)
59-
except SystemExit as exc:
60-
if exc.args[0] != 0:
61-
raise
62-
print("Tests passed")
60+
except SystemExit as exc:
61+
if exc.args[0] != 0:
62+
raise
63+
print("Tests passed")
64+
65+
66+
if __name__ == "__main__":
67+
main()

Lib/test/cov.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
"""A minimal hook for gathering line coverage of the standard library.
22
3-
Designed to be used with -Xpresite= which means:
4-
* it installs itself on import
5-
* it's not imported as `__main__` so can't use the ifmain idiom
3+
Designed to be used with -Xpresite=test.cov:enable which means:
4+
65
* it can't import anything besides `sys` to avoid tainting gathered coverage
76
* filenames are not normalized
87
@@ -45,4 +44,5 @@ def disable():
4544
mon.free_tool_id(mon.COVERAGE_ID)
4645

4746

48-
enable()
47+
if __name__ == "__main__":
48+
enable()

Lib/test/libregrtest/runtests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def create_python_cmd(self) -> list[str]:
159159
if '-u' not in python_opts:
160160
cmd.append('-u') # Unbuffered stdout and stderr
161161
if self.coverage:
162-
cmd.append("-Xpresite=test.cov")
162+
cmd.append("-Xpresite=test.cov:enable")
163163
return cmd
164164

165165
def bisect_cmd_args(self) -> list[str]:

Lib/test/support/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1397,7 +1397,7 @@ def trace_wrapper(*args, **kwargs):
13971397
sys.settrace(original_trace)
13981398

13991399
coverage_wrapper = trace_wrapper
1400-
if 'test.cov' in sys.modules: # -Xpresite=test.cov used
1400+
if 'test.cov' in sys.modules: # -Xpresite=test.cov:enable used
14011401
cov = sys.monitoring.COVERAGE_ID
14021402
@functools.wraps(func)
14031403
def coverage_wrapper(*args, **kwargs):

Lib/test/test_cmd_line.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ def _kill_python_and_exit_code(p):
3232
return data, returncode
3333

3434

35+
def presite_func():
36+
print("presite func")
37+
38+
class Namespace:
39+
pass
40+
41+
presite = Namespace()
42+
presite.attr = Namespace()
43+
presite.attr.func = presite_func
44+
45+
3546
class CmdLineTest(unittest.TestCase):
3647
def test_directories(self):
3748
assert_python_failure('.')
@@ -1266,6 +1277,17 @@ def test_invalid_thread_local_bytecode(self):
12661277
rc, out, err = assert_python_failure(PYTHON_TLBC="2")
12671278
self.assertIn(b"PYTHON_TLBC=N: N is missing or invalid", err)
12681279

1280+
@unittest.skipUnless(support.Py_DEBUG,
1281+
'-X presite requires a Python debug build')
1282+
def test_presite(self):
1283+
entrypoint = "test.test_cmd_line:presite_func"
1284+
proc = assert_python_ok("-X", f"presite={entrypoint}", "-c", "pass")
1285+
self.assertEqual(proc.out.rstrip(), b"presite func")
1286+
1287+
entrypoint = "test.test_cmd_line:presite.attr.func"
1288+
proc = assert_python_ok("-X", f"presite={entrypoint}", "-c", "pass")
1289+
self.assertEqual(proc.out.rstrip(), b"presite func")
1290+
12691291

12701292
@unittest.skipIf(interpreter_requires_environment(),
12711293
'Cannot run -I tests when PYTHON env vars are required.')

Lib/test/test_embed.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2051,7 +2051,7 @@ def test_no_memleak(self):
20512051
def test_presite(self):
20522052
cmd = [
20532053
sys.executable,
2054-
"-I", "-X", "presite=test._test_embed_structseq",
2054+
"-I", "-X", "presite=test._test_embed_structseq:main",
20552055
"-c", "print('unique-python-message')",
20562056
]
20572057
proc = subprocess.run(
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Accept a function name in :option:`-X presite <-X>` command line option and
2+
:envvar:`PYTHON_PRESITE` environment variable. Patch by Victor Stinner.

Python/pylifecycle.c

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,54 @@ pyinit_main_reconfigure(PyThreadState *tstate)
12181218

12191219

12201220
#ifdef Py_DEBUG
1221+
// Equivalent to the Python code:
1222+
//
1223+
// for part in attr.split('.'):
1224+
// obj = getattr(obj, part)
1225+
static PyObject*
1226+
presite_resolve_name(PyObject *obj, PyObject *attr)
1227+
{
1228+
obj = Py_NewRef(obj);
1229+
attr = Py_NewRef(attr);
1230+
PyObject *res;
1231+
1232+
while (1) {
1233+
Py_ssize_t len = PyUnicode_GET_LENGTH(attr);
1234+
Py_ssize_t pos = PyUnicode_FindChar(attr, '.', 0, len, 1);
1235+
if (pos < 0) {
1236+
break;
1237+
}
1238+
1239+
PyObject *name = PyUnicode_Substring(attr, 0, pos);
1240+
if (name == NULL) {
1241+
goto error;
1242+
}
1243+
res = PyObject_GetAttr(obj, name);
1244+
Py_DECREF(name);
1245+
if (res == NULL) {
1246+
goto error;
1247+
}
1248+
Py_SETREF(obj, res);
1249+
1250+
PyObject *suffix = PyUnicode_Substring(attr, pos + 1, len);
1251+
if (suffix == NULL) {
1252+
goto error;
1253+
}
1254+
Py_SETREF(attr, suffix);
1255+
}
1256+
1257+
res = PyObject_GetAttr(obj, attr);
1258+
Py_DECREF(obj);
1259+
Py_DECREF(attr);
1260+
return res;
1261+
1262+
error:
1263+
Py_DECREF(obj);
1264+
Py_DECREF(attr);
1265+
return NULL;
1266+
}
1267+
1268+
12211269
static void
12221270
run_presite(PyThreadState *tstate)
12231271
{
@@ -1228,22 +1276,68 @@ run_presite(PyThreadState *tstate)
12281276
return;
12291277
}
12301278

1231-
PyObject *presite_modname = PyUnicode_FromWideChar(
1232-
config->run_presite,
1233-
wcslen(config->run_presite)
1234-
);
1235-
if (presite_modname == NULL) {
1236-
fprintf(stderr, "Could not convert pre-site module name to unicode\n");
1279+
PyObject *presite = PyUnicode_FromWideChar(config->run_presite, -1);
1280+
if (presite == NULL) {
1281+
fprintf(stderr, "Could not convert pre-site command to Unicode\n");
1282+
_PyErr_Print(tstate);
1283+
return;
1284+
}
1285+
1286+
// Accept "mod_name" and "mod_name:func_name" entry point syntax
1287+
Py_ssize_t len = PyUnicode_GET_LENGTH(presite);
1288+
Py_ssize_t pos = PyUnicode_FindChar(presite, ':', 0, len, 1);
1289+
PyObject *mod_name = NULL;
1290+
PyObject *func_name = NULL;
1291+
PyObject *module = NULL;
1292+
if (pos > 0) {
1293+
mod_name = PyUnicode_Substring(presite, 0, pos);
1294+
if (mod_name == NULL) {
1295+
goto error;
1296+
}
1297+
1298+
func_name = PyUnicode_Substring(presite, pos + 1, len);
1299+
if (func_name == NULL) {
1300+
goto error;
1301+
}
12371302
}
12381303
else {
1239-
PyObject *presite = PyImport_Import(presite_modname);
1240-
if (presite == NULL) {
1241-
fprintf(stderr, "pre-site import failed:\n");
1242-
_PyErr_Print(tstate);
1304+
mod_name = Py_NewRef(presite);
1305+
}
1306+
1307+
// mod_name can contain dots (ex: "math.integer")
1308+
module = PyImport_Import(mod_name);
1309+
if (module == NULL) {
1310+
goto error;
1311+
}
1312+
1313+
if (func_name != NULL) {
1314+
PyObject *func = presite_resolve_name(module, func_name);
1315+
if (func == NULL) {
1316+
goto error;
1317+
}
1318+
1319+
PyObject *res = PyObject_CallNoArgs(func);
1320+
Py_DECREF(func);
1321+
if (res == NULL) {
1322+
goto error;
12431323
}
1244-
Py_XDECREF(presite);
1245-
Py_DECREF(presite_modname);
1324+
Py_DECREF(res);
12461325
}
1326+
1327+
Py_DECREF(presite);
1328+
Py_DECREF(mod_name);
1329+
Py_XDECREF(func_name);
1330+
Py_DECREF(module);
1331+
return;
1332+
1333+
error:
1334+
fprintf(stderr, "pre-site failed:\n");
1335+
_PyErr_Print(tstate);
1336+
1337+
Py_DECREF(presite);
1338+
Py_XDECREF(mod_name);
1339+
Py_XDECREF(func_name);
1340+
Py_XDECREF(module);
12471341
}
12481342
#endif
12491343

0 commit comments

Comments
 (0)