Skip to content

gh-106915: Add PyImport_ImportOrAddModule() function #111559

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

Closed
wants to merge 6 commits into from
Closed
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
31 changes: 18 additions & 13 deletions Doc/c-api/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,40 +98,45 @@ Importing Modules
an exception set on failure (the module still exists in this case).


.. c:function:: PyObject* PyImport_AddModuleRef(const char *name)
.. c:function:: int PyImport_ImportOrAddModule(const char *name, PyObject **module)

Return the module object corresponding to a module name.
Get an already imported module, or create a new empty one.

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.
- If the module name is already present in :data:`sys.modules`,
set *\*module* to a :term:`strong reference` to the existing module, and
return 1.
- If the module does not exist in :data:`sys.modules`, create a new empty
module, store it in :data:`sys.modules`, set *\*module* to a :term:`strong
reference` to the module, and return 0.
- On error, raise an exception, set *\*module* to NULL, and return -1.

Return a :term:`strong reference` to the module on success. Return ``NULL``
with an exception set on failure.
The *name* argument may be of the form ``package.module``. Package
structures implied by a dotted name for *name* are not created if not
already present.

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.

.. versionadded:: 3.13


.. 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.
Similar to :c:func:`PyImport_ImportOrAddModule`, but return a :term:`borrowed
reference`, *name* is a Python :class:`str` object, and don't provide the
information if the module was created or was already imported.

.. versionadded:: 3.3


.. c:function:: PyObject* PyImport_AddModule(const char *name)

Similar to :c:func:`PyImport_AddModuleRef`, but return a :term:`borrowed
reference`.
Similar to :c:func:`PyImport_ImportOrAddModule`, but return a :term:`borrowed
reference`, and don't provide the information if the module was already
imported or was created.


.. c:function:: PyObject* PyImport_ExecCodeModule(const char *name, PyObject *co)
Expand Down
3 changes: 0 additions & 3 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
Expand Up @@ -974,9 +974,6 @@ 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
2 changes: 1 addition & 1 deletion Doc/data/stable_abi.dat

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

7 changes: 4 additions & 3 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1018,9 +1018,10 @@ 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`.
* Add :c:func:`PyImport_ImportOrAddModule`: similar to
:c:func:`PyImport_AddModule`, but get a :term:`strong reference` instead
of a :term:`borrowed reference` and return 0 if the module was already
imported.
(Contributed by Victor Stinner in :gh:`105922`.)

* Add :c:func:`PyWeakref_GetRef` function: similar to
Expand Down
15 changes: 13 additions & 2 deletions Include/import.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,22 @@ 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 */
// Get an already imported module, or create a new empty one.
//
// - If the module name is already present in sys.modules, set '*module' to
// a strong reference to the existing module, and return 0.
// - If the module does not exist in sys.modules, create a new empty module,
// store it in sys.modules, set '*module' to a strong reference to the
// module, and return 1.
// - On error, raise an exception, set '*module' to NULL, and return -1.
PyAPI_FUNC(int) PyImport_ImportOrAddModule(
const char *name, // UTF-8 encoded string
PyObject **module
);
#endif

PyAPI_FUNC(PyObject *) PyImport_ImportModule(
const char *name /* UTF-8 encoded string */
);
Expand Down
11 changes: 6 additions & 5 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2669,24 +2669,25 @@ def test_basic_multiple_interpreters_reset_each(self):
@cpython_only
class CAPITests(unittest.TestCase):
def test_pyimport_addmodule(self):
# gh-105922: Test PyImport_AddModuleRef(), PyImport_AddModule()
# and PyImport_AddModuleObject()
# gh-105922: Test PyImport_ImportOrAddModule(), PyImport_AddModule()
# and PyImport_AddModuleObject(): module already exists.
import _testcapi
for name in (
'sys', # frozen module
'test', # package
__name__, # package.module
):
_testcapi.check_pyimport_addmodule(name)
self.assertIn(name, sys.modules)
_testcapi.check_pyimport_addmodule(name, True)

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

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


Expand Down
2 changes: 1 addition & 1 deletion 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,2 @@
Add :c:func:`PyImport_ImportOrAddModule` function and remove
:c:func:`PyImport_AddModuleRef` function. Patch by Victor Stinner.
2 changes: 1 addition & 1 deletion Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2428,7 +2428,7 @@
added = '3.12'
[const.Py_TPFLAGS_ITEMS_AT_END]
added = '3.12'
[function.PyImport_AddModuleRef]
[function.PyImport_ImportOrAddModule]
added = '3.13'
[function.PyWeakref_GetRef]
added = '3.13'
Expand Down
11 changes: 7 additions & 4 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3036,15 +3036,18 @@ static PyObject *
check_pyimport_addmodule(PyObject *self, PyObject *args)
{
const char *name;
if (!PyArg_ParseTuple(args, "s", &name)) {
int expected;
if (!PyArg_ParseTuple(args, "si", &name, &expected)) {
return NULL;
}

// test PyImport_AddModuleRef()
PyObject *module = PyImport_AddModuleRef(name);
if (module == NULL) {
// test PyImport_ImportOrAddModule()
PyObject *module = UNINITIALIZED_PTR;
int res = PyImport_ImportOrAddModule(name, &module);
if (res < 0) {
return NULL;
}
assert(res == expected);
assert(PyModule_Check(module));
// module is a strong reference

Expand Down
2 changes: 1 addition & 1 deletion PC/python3dll.c

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

55 changes: 39 additions & 16 deletions Python/import.c
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,8 @@ PyImport_GetModule(PyObject *name)
/* Get the module object corresponding to a module name.
First check the modules dictionary if there's one there,
if not, create a new one and insert it in the modules dictionary. */

static PyObject *
import_add_module(PyThreadState *tstate, PyObject *name)
import_import_or_add_module(PyThreadState *tstate, PyObject *name, int *exists)
{
PyObject *modules = MODULES(tstate->interp);
if (modules == NULL) {
Expand All @@ -305,31 +304,55 @@ import_add_module(PyThreadState *tstate, PyObject *name)
return NULL;
}
if (m != NULL && PyModule_Check(m)) {
if (exists) {
*exists = 1;
}
return m;
}
Py_XDECREF(m);

m = PyModule_NewObject(name);
if (m == NULL)
if (m == NULL) {
return NULL;
}
if (PyObject_SetItem(modules, name, m) != 0) {
Py_DECREF(m);
return NULL;
}

if (exists) {
*exists = 0;
}
return m;
}

PyObject *
PyImport_AddModuleRef(const char *name)

static PyObject *
import_add_module(PyThreadState *tstate, PyObject *name)
{
return import_import_or_add_module(tstate, name, NULL);
}


int
PyImport_ImportOrAddModule(const char *name, PyObject **pmodule)
{
PyObject *name_obj = PyUnicode_FromString(name);
if (name_obj == NULL) {
return NULL;
*pmodule = NULL;
return -1;
}
PyThreadState *tstate = _PyThreadState_GET();
PyObject *module = import_add_module(tstate, name_obj);
int exists;
PyObject *module = import_import_or_add_module(tstate, name_obj, &exists);
Py_DECREF(name_obj);
return module;

*pmodule = module;
if (module == NULL) {
assert(PyErr_Occurred());
return -1;
}
return exists;
}


Expand All @@ -351,7 +374,7 @@ PyImport_AddModuleObject(PyObject *name)
// unknown. With weakref we can be sure that we get either a reference to
// live object or NULL.
//
// Use PyImport_AddModuleRef() to avoid these issues.
// Use PyImport_ImportOrAddModule() to avoid these issues.
PyObject *ref = PyWeakref_NewRef(mod, NULL);
Py_DECREF(mod);
if (ref == NULL) {
Expand Down Expand Up @@ -1669,14 +1692,14 @@ PyImport_ExecCodeModuleWithPathnames(const char *name, PyObject *co,
static PyObject *
module_dict_for_exec(PyThreadState *tstate, PyObject *name)
{
PyObject *m, *d;

m = import_add_module(tstate, name);
if (m == NULL)
PyObject *m = import_add_module(tstate, name);
if (m == NULL) {
return NULL;
}

/* If the module is being reloaded, we get the old module back
and re-use its dict to exec the new code. */
d = PyModule_GetDict(m);
PyObject *d = PyModule_GetDict(m);
int r = PyDict_Contains(d, &_Py_ID(__builtins__));
if (r == 0) {
r = PyDict_SetItem(d, &_Py_ID(__builtins__), PyEval_GetBuiltins());
Expand Down Expand Up @@ -2252,8 +2275,8 @@ init_importlib(PyThreadState *tstate, PyObject *sysmod)
return -1;
}

PyObject *importlib = PyImport_AddModuleRef("_frozen_importlib");
if (importlib == NULL) {
PyObject *importlib;
if (PyImport_ImportOrAddModule("_frozen_importlib", &importlib) < 0) {
return -1;
}
IMPORTLIB(interp) = importlib;
Expand Down
13 changes: 7 additions & 6 deletions Python/pythonrun.c
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,8 @@ PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
return parse_res;
}

PyObject *main_module = PyImport_AddModuleRef("__main__");
if (main_module == NULL) {
PyObject *main_module;
if (PyImport_ImportOrAddModule("__main__", &main_module) < 0) {
_PyArena_Free(arena);
return -1;
}
Expand Down Expand Up @@ -407,9 +407,10 @@ _PyRun_SimpleFileObject(FILE *fp, PyObject *filename, int closeit,
{
int ret = -1;

PyObject *main_module = PyImport_AddModuleRef("__main__");
if (main_module == NULL)
PyObject *main_module;
if (PyImport_ImportOrAddModule("__main__", &main_module) < 0) {
return -1;
}
PyObject *dict = PyModule_GetDict(main_module); // borrowed ref

int set_file_name = 0;
Expand Down Expand Up @@ -503,8 +504,8 @@ PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,

int
_PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags) {
PyObject *main_module = PyImport_AddModuleRef("__main__");
if (main_module == NULL) {
PyObject *main_module;
if (PyImport_ImportOrAddModule("__main__", &main_module) < 0) {
return -1;
}
PyObject *dict = PyModule_GetDict(main_module); // borrowed ref
Expand Down