Skip to content

Commit d044be4

Browse files
committed
pythongh-86682: Adds sys._get_calling_module_name and replaces _getframe calls in collections, doctest, enum, and typing modules
1 parent 86a49e0 commit d044be4

File tree

8 files changed

+134
-10
lines changed

8 files changed

+134
-10
lines changed

Lib/collections/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,9 +507,12 @@ def __getnewargs__(self):
507507
# specified a particular module.
508508
if module is None:
509509
try:
510-
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
511-
except (AttributeError, ValueError):
512-
pass
510+
module = _sys._get_calling_module_name(1) or '__main__'
511+
except AttributeError:
512+
try:
513+
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
514+
except (AttributeError, ValueError):
515+
pass
513516
if module is not None:
514517
result.__module__ = module
515518

Lib/doctest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,10 @@ def _normalize_module(module, depth=2):
207207
elif isinstance(module, str):
208208
return __import__(module, globals(), locals(), ["*"])
209209
elif module is None:
210-
return sys.modules[sys._getframe(depth).f_globals['__name__']]
210+
try:
211+
return sys.modules[sys._get_calling_module_name(depth)]
212+
except AttributeError:
213+
return sys.modules[sys._getframe(depth).f_globals['__name__']]
211214
else:
212215
raise TypeError("Expected a module, string, or None")
213216

Lib/enum.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -864,9 +864,13 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s
864864
# module is ever developed
865865
if module is None:
866866
try:
867-
module = sys._getframe(2).f_globals['__name__']
868-
except (AttributeError, ValueError, KeyError):
869-
pass
867+
module = sys._get_calling_module_name(2)
868+
except AttributeError:
869+
# Fall back on _getframe if _get_calling_module_name is missing
870+
try:
871+
module = sys._getframe(2).f_globals['__name__']
872+
except (AttributeError, ValueError, KeyError):
873+
pass
870874
if module is None:
871875
_make_class_unpicklable(classdict)
872876
else:

Lib/test/test_sys.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,14 @@ def test_getframe(self):
399399
is sys._getframe().f_code
400400
)
401401

402+
def test_get_calling_module_name(self):
403+
# Default depth gets ourselves
404+
self.assertEqual("test.test_sys", sys._get_calling_module_name())
405+
# Get our caller
406+
self.assertEqual("unittest.case", sys._get_calling_module_name(1))
407+
# Get our caller's caller's caller's caller
408+
self.assertEqual("unittest.suite", sys._get_calling_module_name(4))
409+
402410
# sys._current_frames() is a CPython-only gimmick.
403411
@threading_helper.reap_threads
404412
@threading_helper.requires_working_threading()

Lib/typing.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,11 +1942,15 @@ def _no_init_or_replace_init(self, *args, **kwargs):
19421942

19431943

19441944
def _caller(depth=1, default='__main__'):
1945+
try:
1946+
return sys._get_calling_module_name(depth + 1) or default
1947+
except AttributeError: # For platforms without _get_calling_module_name()
1948+
pass
19451949
try:
19461950
return sys._getframe(depth + 1).f_globals.get('__name__', default)
19471951
except (AttributeError, ValueError): # For platforms without _getframe()
1948-
return None
1949-
1952+
pass
1953+
return None
19501954

19511955
def _allow_reckless_class_checks(depth=3):
19521956
"""Allow instance and class checks for special stdlib modules.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Changes internal function used to ensure runtime-created collections have
2+
the correct module name from ``_getframe`` to ``_get_calling_module_name``.

Python/clinic/sysmodule.c.h

Lines changed: 68 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/sysmodule.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,6 +2180,38 @@ sys_is_stack_trampoline_active_impl(PyObject *module)
21802180
}
21812181

21822182

2183+
/*[clinic input]
2184+
sys._get_calling_module_name
2185+
2186+
depth: int = 0
2187+
2188+
Return the name of the calling module.
2189+
2190+
The default depth returns the module containing the call to this function.
2191+
A more typical use in a library will pass a depth of 1 to get the user's
2192+
module rather than the library module.
2193+
[clinic start generated code]*/
2194+
2195+
static PyObject *
2196+
sys__get_calling_module_name_impl(PyObject *module, int depth)
2197+
/*[clinic end generated code: output=bd04c211226f8b84 input=dd268ae6a20311ab]*/
2198+
{
2199+
_PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame;
2200+
while (f && (f->owner == FRAME_OWNED_BY_CSTACK || depth-- > 0)) {
2201+
f = f->previous;
2202+
}
2203+
if (f == NULL) {
2204+
Py_RETURN_NONE;
2205+
}
2206+
PyObject *r = PyDict_GetItemWithError(f->f_globals, &_Py_ID(__name__));
2207+
if (!r) {
2208+
PyErr_Clear();
2209+
r = Py_None;
2210+
}
2211+
return Py_NewRef(r);
2212+
}
2213+
2214+
21832215
static PyMethodDef sys_methods[] = {
21842216
/* Might as well keep this in alphabetic order */
21852217
SYS_ADDAUDITHOOK_METHODDEF
@@ -2197,6 +2229,7 @@ static PyMethodDef sys_methods[] = {
21972229
SYS_GETDEFAULTENCODING_METHODDEF
21982230
SYS_GETDLOPENFLAGS_METHODDEF
21992231
SYS_GETALLOCATEDBLOCKS_METHODDEF
2232+
SYS__GET_CALLING_MODULE_NAME_METHODDEF
22002233
SYS_GETFILESYSTEMENCODING_METHODDEF
22012234
SYS_GETFILESYSTEMENCODEERRORS_METHODDEF
22022235
#ifdef Py_TRACE_REFS

0 commit comments

Comments
 (0)