Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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`.

.. versionchange:: 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 @@ -1451,4 +1455,7 @@ Debug-mode variables

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

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

.. versionadded:: 3.13
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 @@ -1396,7 +1396,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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Accept a function name in :option:`-X presite <-X>` command line option and
:envvar:`PYTHON_PRESITE` environment variable. Patch by Victor Stinner.
69 changes: 57 additions & 12 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -1228,22 +1228,67 @@ run_presite(PyThreadState *tstate)
return;
}

PyObject *presite_modname = PyUnicode_FromWideChar(
config->run_presite,
wcslen(config->run_presite)
);
if (presite_modname == NULL) {
fprintf(stderr, "Could not convert pre-site module name to unicode\n");
PyObject *presite = PyUnicode_FromWideChar(config->run_presite, -1);
if (presite == NULL) {
fprintf(stderr, "Could not convert pre-site command to Unicode\n");
_PyErr_Print(tstate);
return;
}

// Accept "mod_name" and "mod_name:func_name" entry point syntax
Py_ssize_t len = PyUnicode_GET_LENGTH(presite);
Py_ssize_t pos = PyUnicode_FindChar(presite, ':', 0, len, 1);
PyObject *mod_name = NULL;
PyObject *func_name = NULL;
PyObject *module = NULL;
if (pos > 0) {
mod_name = PyUnicode_Substring(presite, 0, pos);
if (mod_name == NULL) {
goto error;
}

func_name = PyUnicode_Substring(presite, pos + 1, len);
if (func_name == NULL) {
goto error;
}
}
else {
PyObject *presite = PyImport_Import(presite_modname);
if (presite == NULL) {
fprintf(stderr, "pre-site import failed:\n");
_PyErr_Print(tstate);
mod_name = Py_NewRef(presite);
}

module = PyImport_Import(mod_name);
if (module == NULL) {
goto error;
}

if (func_name != NULL) {
PyObject *func = PyObject_GetAttr(module, func_name);
if (func == NULL) {
goto error;
}

PyObject *res = PyObject_CallNoArgs(func);
Py_DECREF(func);
if (res == NULL) {
goto error;
}
Py_XDECREF(presite);
Py_DECREF(presite_modname);
Py_DECREF(res);
}

Py_DECREF(presite);
Py_DECREF(mod_name);
Py_XDECREF(func_name);
Py_DECREF(module);
return;

error:
fprintf(stderr, "pre-site failed:\n");
_PyErr_Print(tstate);

Py_DECREF(presite);
Py_XDECREF(mod_name);
Py_XDECREF(func_name);
Py_XDECREF(module);
}
#endif

Expand Down
Loading