Skip to content

Commit

Permalink
pythongh-105922: Add PyImport_AddModuleRef() function
Browse files Browse the repository at this point in the history
* Add tests on PyImport_AddModuleRef(), PyImport_AddModule() and
  PyImport_AddModuleObject().
* PyRun_InteractiveOneObjectEx(), _PyRun_SimpleFileObject()
  and PyRun_SimpleStringFlags() now use PyImport_AddModuleRef()
  and keep the module strong reference alive longer than before.
  • Loading branch information
vstinner committed Jun 19, 2023
1 parent 7fdfe5e commit fa7d2d6
Show file tree
Hide file tree
Showing 14 changed files with 177 additions and 37 deletions.
43 changes: 31 additions & 12 deletions Doc/c-api/import.rst
Expand Up @@ -98,27 +98,46 @@ Importing Modules
an exception set on failure (the module still exists in this case).
.. c:function:: PyObject* PyImport_AddModuleObject(PyObject *name)
.. c:function:: PyObject* PyImport_AddModuleRef(const char *name)
Return the module object corresponding to a module name.
The *name* argument may be of the form ``package.module``. First check the
modules dictionary if there's one there, and if not, create a new one and
insert it in the modules dictionary.
Return a :term:`strong reference` to the module on success. Return ``NULL``
with an exception set on failure.
The module name *name* is decoded from UTF-8.
This function does not load or import the module; if the module wasn't
already loaded, you will get an empty module object. Use
:c:func:`PyImport_ImportModule` or one of its variants to import a module.
Package structures implied by a dotted name for *name* are not created if
not already present.
Return the module object corresponding to a module name. The *name* argument
may be of the form ``package.module``. First check the modules dictionary if
there's one there, and if not, create a new one and insert it in the modules
dictionary. Return ``NULL`` with an exception set on failure.
.. versionadded:: 3.13
.. note::
This function does not load or import the module; if the module wasn't already
loaded, you will get an empty module object. Use :c:func:`PyImport_ImportModule`
or one of its variants to import a module. Package structures implied by a
dotted name for *name* are not created if not already present.
.. c:function:: PyObject* PyImport_AddModuleObject(PyObject *name)
Similar to :c:func:`PyImport_AddModuleRef`, but return a :term:`borrowed
reference` and *name* is a Python :class:`str` object.
.. versionadded:: 3.3
.. deprecated-removed:: 3.13 3.15
Use :c:func:`PyImport_AddModuleRef` instead.
.. c:function:: PyObject* PyImport_AddModule(const char *name)
Similar to :c:func:`PyImport_AddModuleObject`, but the name is a UTF-8
encoded string instead of a Unicode object.
Similar to :c:func:`PyImport_AddModuleRef`, but return a :term:`borrowed
reference`.
.. deprecated-removed:: 3.13 3.15
Use :c:func:`PyImport_AddModuleRef` instead.
.. c:function:: PyObject* PyImport_ExecCodeModule(const char *name, PyObject *co)
Expand Down
3 changes: 3 additions & 0 deletions Doc/data/refcounts.dat
Expand Up @@ -974,6 +974,9 @@ PyCoro_New:PyFrameObject*:frame:0:
PyCoro_New:PyObject*:name:0:
PyCoro_New:PyObject*:qualname:0:

PyImport_AddModuleRef:PyObject*::+1:
PyImport_AddModuleRef:const char*:name::

PyImport_AddModule:PyObject*::0:reference borrowed from sys.modules
PyImport_AddModule:const char*:name::

Expand Down
1 change: 1 addition & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions Doc/extending/extending.rst
Expand Up @@ -989,9 +989,11 @@ is less clear, here, however, since a few common routines are exceptions:
:c:func:`PyDict_GetItemString` all return references that you borrow from the
tuple, list or dictionary.

The function :c:func:`PyImport_AddModule` also returns a borrowed reference, even
though it may actually create the object it returns: this is possible because an
owned reference to the object is stored in ``sys.modules``.
The deprecated function :c:func:`PyImport_AddModule` also returns a
:term:`borrowed reference`, even though it may actually create the object it
returns: this is possible because an owned reference to the object is stored in
``sys.modules``. The new function :c:func:`PyImport_AddModuleRef` is
recommended since it returns a :term:`strong reference`.

When you pass an object reference into another function, in general, the
function borrows the reference from you --- if it needs to store it, it will use
Expand Down
11 changes: 11 additions & 0 deletions Doc/whatsnew/3.13.rst
Expand Up @@ -426,6 +426,11 @@ New Features
APIs accepting the format codes always use ``Py_ssize_t`` for ``#`` formats.
(Contributed by Inada Naoki in :gh:`104922`.)

* Add :c:func:`PyImport_AddModuleRef` function to replace the deprecated
:c:func:`PyImport_AddModule` function: return a :term:`strong reference` to
the module, instead of a :term:`borrowed reference`.
(Contributed by Victor Stinner in :gh:`105922`.)


Porting to Python 3.13
----------------------
Expand Down Expand Up @@ -457,6 +462,12 @@ Deprecated
Scheduled for removal in Python 3.15.
(Contributed by Victor Stinner in :gh:`105396`.)

* Deprecate :c:func:`PyImport_AddModule` and :c:func:`PyImport_AddModuleObject`
functions which return a :term:`borrowed reference`. Replace them with the
new :c:func:`PyImport_AddModuleRef` function which returns a :term:`strong
reference`.
(Contributed by Victor Stinner in :gh:`105922`.)

Removed
-------

Expand Down
5 changes: 5 additions & 0 deletions Include/import.h
Expand Up @@ -43,6 +43,11 @@ PyAPI_FUNC(PyObject *) PyImport_AddModuleObject(
PyAPI_FUNC(PyObject *) PyImport_AddModule(
const char *name /* UTF-8 encoded string */
);
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
PyAPI_FUNC(PyObject *) PyImport_AddModuleRef(
const char *name /* UTF-8 encoded string */
);
#endif
PyAPI_FUNC(PyObject *) PyImport_ImportModule(
const char *name /* UTF-8 encoded string */
);
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_import/__init__.py
Expand Up @@ -2621,6 +2621,30 @@ def test_basic_multiple_interpreters_reset_each(self):
# * module's global state was initialized, not reset


@cpython_only
class CAPITests(unittest.TestCase):
def test_pyimport_addmodule(self):
# gh-105922: Test PyImport_AddModuleRef(), PyImport_AddModule()
# and PyImport_AddModuleObject()
import _testcapi
for name in (
'sys', # frozen module
'test', # package
__name__, # package.module
):
_testcapi.check_pyimport_addmodule(name)

def test_pyimport_addmodule_create(self):
# gh-105922: Test PyImport_AddModuleRef(), create a new module
import _testcapi
name = 'dontexist'
self.assertNotIn(name, sys.modules)
self.addCleanup(unload, name)

mod = _testcapi.check_pyimport_addmodule(name)
self.assertIs(mod, sys.modules[name])


if __name__ == '__main__':
# Test needs to be a package, so we can do relative imports.
unittest.main()
1 change: 1 addition & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
Expand Up @@ -2428,3 +2428,5 @@
added = '3.12'
[const.Py_TPFLAGS_ITEMS_AT_END]
added = '3.12'
[function.PyImport_AddModuleRef]
added = '3.13'
48 changes: 48 additions & 0 deletions Modules/_testcapimodule.c
Expand Up @@ -3325,6 +3325,53 @@ test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
}


static PyObject *
check_pyimport_addmodule(PyObject *self, PyObject *args)
{
const char *name;
if (!PyArg_ParseTuple(args, "s", &name)) {
return NULL;
}

// test PyImport_AddModuleRef()
PyObject *module = PyImport_AddModuleRef(name);
if (module == NULL) {
return NULL;
}
assert(PyModule_Check(module));
// module is a strong reference

// test PyImport_AddModule()
PyObject *module2 = PyImport_AddModule(name);
if (module2 == NULL) {
goto error;
}
assert(PyModule_Check(module2));
assert(module2 == module);
// module2 is a borrowed ref

// test PyImport_AddModuleObject()
PyObject *name_obj = PyUnicode_FromString(name);
if (name_obj == NULL) {
goto error;
}
PyObject *module3 = PyImport_AddModuleObject(name_obj);
Py_DECREF(name_obj);
if (module3 == NULL) {
goto error;
}
assert(PyModule_Check(module3));
assert(module3 == module);
// module3 is a borrowed ref

return module;

error:
Py_DECREF(module);
return NULL;
}


static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
{"test_config", test_config, METH_NOARGS},
Expand Down Expand Up @@ -3468,6 +3515,7 @@ static PyMethodDef TestMethods[] = {
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
{"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL},
{"test_atexit", test_atexit, METH_NOARGS},
{"check_pyimport_addmodule", check_pyimport_addmodule, METH_VARARGS},
{NULL, NULL} /* sentinel */
};

Expand Down
1 change: 1 addition & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions Python/import.c
Expand Up @@ -350,6 +350,20 @@ import_add_module(PyThreadState *tstate, PyObject *name)
return m;
}

PyObject *
PyImport_AddModuleRef(const char *name)
{
PyObject *name_obj = PyUnicode_FromString(name);
if (name_obj == NULL) {
return NULL;
}
PyThreadState *tstate = _PyThreadState_GET();
PyObject *module = import_add_module(tstate, name_obj);
Py_DECREF(name_obj);
return module;
}


PyObject *
PyImport_AddModuleObject(PyObject *name)
{
Expand All @@ -359,6 +373,7 @@ PyImport_AddModuleObject(PyObject *name)
return NULL;
}

// PyImport_AddModuleObject() returns a borrowed reference
// https://bugs.python.org/issue41994
PyObject *ref = PyWeakref_NewRef(mod, NULL);
Py_DECREF(mod);
Expand Down
13 changes: 7 additions & 6 deletions Python/pylifecycle.c
Expand Up @@ -2172,13 +2172,14 @@ _Py_IsInterpreterFinalizing(PyInterpreterState *interp)
static PyStatus
add_main_module(PyInterpreterState *interp)
{
PyObject *m, *d, *loader, *ann_dict;
m = PyImport_AddModule("__main__");
if (m == NULL)
PyObject *module = PyImport_AddModuleRef("__main__");
if (module == NULL) {
return _PyStatus_ERR("can't create __main__ module");
}
PyObject *d = PyModule_GetDict(module);
Py_DECREF(module);

d = PyModule_GetDict(m);
ann_dict = PyDict_New();
PyObject *ann_dict = PyDict_New();
if ((ann_dict == NULL) ||
(PyDict_SetItemString(d, "__annotations__", ann_dict) < 0)) {
return _PyStatus_ERR("Failed to initialize __main__.__annotations__");
Expand All @@ -2204,7 +2205,7 @@ add_main_module(PyInterpreterState *interp)
* will be set if __main__ gets further initialized later in the startup
* process.
*/
loader = _PyDict_GetItemStringWithError(d, "__loader__");
PyObject *loader = _PyDict_GetItemStringWithError(d, "__loader__");
if (loader == NULL || loader == Py_None) {
if (PyErr_Occurred()) {
return _PyStatus_ERR("Failed to test __main__.__loader__");
Expand Down
39 changes: 23 additions & 16 deletions Python/pythonrun.c
Expand Up @@ -185,7 +185,7 @@ static int
PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
PyCompilerFlags *flags)
{
PyObject *m, *d, *v, *w, *oenc = NULL;
PyObject *v, *w, *oenc = NULL;
mod_ty mod;
PyArena *arena;
const char *ps1 = "", *ps2 = "", *enc = NULL;
Expand Down Expand Up @@ -251,17 +251,21 @@ PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
}
return -1;
}
m = PyImport_AddModuleObject(&_Py_ID(__main__));
if (m == NULL) {

PyObject *module = PyImport_AddModuleRef("main");
if (module == NULL) {
_PyArena_Free(arena);
return -1;
}
d = PyModule_GetDict(m);
PyObject *d = PyModule_GetDict(module);

v = run_mod(mod, filename, d, d, flags, arena);
_PyArena_Free(arena);
Py_DECREF(module);
if (v == NULL) {
return -1;
}

Py_DECREF(v);
flush_io();
return 0;
Expand Down Expand Up @@ -376,14 +380,15 @@ int
_PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit,
PyCompilerFlags *flags)
{
PyObject *m, *d, *v;
PyObject *d, *v;
int set_file_name = 0, ret = -1;

m = PyImport_AddModule("__main__");
if (m == NULL)
PyObject *module = PyImport_AddModuleRef("__main__");
if (module == NULL) {
return -1;
Py_INCREF(m);
d = PyModule_GetDict(m);
}
d = PyModule_GetDict(module);

if (_PyDict_GetItemStringWithError(d, "__file__") == NULL) {
if (PyErr_Occurred()) {
goto done;
Expand Down Expand Up @@ -435,7 +440,7 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit,
}
flush_io();
if (v == NULL) {
Py_CLEAR(m);
Py_CLEAR(module);
PyErr_Print();
goto done;
}
Expand All @@ -450,7 +455,7 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit,
PyErr_Clear();
}
}
Py_XDECREF(m);
Py_DECREF(module);
return ret;
}

Expand All @@ -472,12 +477,14 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
int
PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
{
PyObject *m, *d, *v;
m = PyImport_AddModule("__main__");
if (m == NULL)
PyObject *module = PyImport_AddModuleRef("__main__");
if (module == NULL) {
return -1;
d = PyModule_GetDict(m);
v = PyRun_StringFlags(command, Py_file_input, d, d, flags);
}
PyObject *d = PyModule_GetDict(module);
Py_DECREF(module);

PyObject *v = PyRun_StringFlags(command, Py_file_input, d, d, flags);
if (v == NULL) {
PyErr_Print();
return -1;
Expand Down

0 comments on commit fa7d2d6

Please sign in to comment.