Skip to content

gh-105922: Add PyImport_AddModuleRef() function #105923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 20, 2023
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
37 changes: 25 additions & 12 deletions Doc/c-api/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,27 +98,40 @@ 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.

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.
The module name *name* is decoded from UTF-8.

.. 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.

.. versionadded:: 3.13


.. c:function:: PyObject* PyImport_AddModuleObject(PyObject *name)

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.
Similar to :c:func:`PyImport_AddModuleRef`, but return a :term:`borrowed
reference` and *name* is a Python :class:`str` object.

.. versionadded:: 3.3


.. 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`.


.. c:function:: PyObject* PyImport_ExecCodeModule(const char *name, PyObject *co)
Expand Down
3 changes: 3 additions & 0 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
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.

5 changes: 5 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
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`: similar to
:c:func:`PyImport_AddModule`, but return a :term:`strong reference` instead
of a :term:`borrowed reference`.
(Contributed by Victor Stinner in :gh:`105922`.)


Porting to Python 3.13
----------------------
Expand Down
5 changes: 5 additions & 0 deletions Include/import.h
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`PyImport_AddModuleRef`: similar to :c:func:`PyImport_AddModule`,
but return a :term:`strong reference` instead of a :term:`borrowed reference`.
Patch by Victor Stinner.
2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.

39 changes: 29 additions & 10 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -350,20 +350,38 @@ 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)
{
PyThreadState *tstate = _PyThreadState_GET();
PyObject *mod = import_add_module(tstate, name);
if (mod) {
PyObject *ref = PyWeakref_NewRef(mod, NULL);
Py_DECREF(mod);
if (ref == NULL) {
return NULL;
}
mod = PyWeakref_GetObject(ref);
Py_DECREF(ref);
if (!mod) {
return NULL;
}

// gh-86160: PyImport_AddModuleObject() returns a borrowed reference
PyObject *ref = PyWeakref_NewRef(mod, NULL);
Py_DECREF(mod);
if (ref == NULL) {
return NULL;
}

mod = PyWeakref_GetObject(ref);
Py_DECREF(ref);
return mod; /* borrowed reference */
}

Expand Down Expand Up @@ -2240,11 +2258,12 @@ init_importlib(PyThreadState *tstate, PyObject *sysmod)
if (PyImport_ImportFrozenModule("_frozen_importlib") <= 0) {
return -1;
}
PyObject *importlib = PyImport_AddModule("_frozen_importlib"); // borrowed

PyObject *importlib = PyImport_AddModuleRef("_frozen_importlib");
if (importlib == NULL) {
return -1;
}
IMPORTLIB(interp) = Py_NewRef(importlib);
IMPORTLIB(interp) = importlib;

// Import the _imp module
if (verbose) {
Expand Down
4 changes: 2 additions & 2 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit,
{
int ret = -1;

PyObject *main_module = Py_XNewRef(PyImport_AddModule("__main__"));
PyObject *main_module = PyImport_AddModuleRef("__main__");
if (main_module == NULL)
return -1;
PyObject *dict = PyModule_GetDict(main_module); // borrowed ref
Expand Down Expand Up @@ -502,7 +502,7 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
int
PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
{
PyObject *main_module = Py_XNewRef(PyImport_AddModule("__main__"));
PyObject *main_module = PyImport_AddModuleRef("__main__");
if (main_module == NULL) {
return -1;
}
Expand Down