Skip to content

Commit

Permalink
pythongh-86682: Adds sys._get_calling_module_name and replaces _getfr…
Browse files Browse the repository at this point in the history
…ame calls in collections, doctest, enum, and typing modules
  • Loading branch information
zooba committed Nov 15, 2022
1 parent 86a49e0 commit d044be4
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 10 deletions.
9 changes: 6 additions & 3 deletions Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,9 +507,12 @@ def __getnewargs__(self):
# specified a particular module.
if module is None:
try:
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
module = _sys._get_calling_module_name(1) or '__main__'
except AttributeError:
try:
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
if module is not None:
result.__module__ = module

Expand Down
5 changes: 4 additions & 1 deletion Lib/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,10 @@ def _normalize_module(module, depth=2):
elif isinstance(module, str):
return __import__(module, globals(), locals(), ["*"])
elif module is None:
return sys.modules[sys._getframe(depth).f_globals['__name__']]
try:
return sys.modules[sys._get_calling_module_name(depth)]
except AttributeError:
return sys.modules[sys._getframe(depth).f_globals['__name__']]
else:
raise TypeError("Expected a module, string, or None")

Expand Down
10 changes: 7 additions & 3 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,9 +864,13 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s
# module is ever developed
if module is None:
try:
module = sys._getframe(2).f_globals['__name__']
except (AttributeError, ValueError, KeyError):
pass
module = sys._get_calling_module_name(2)
except AttributeError:
# Fall back on _getframe if _get_calling_module_name is missing
try:
module = sys._getframe(2).f_globals['__name__']
except (AttributeError, ValueError, KeyError):
pass
if module is None:
_make_class_unpicklable(classdict)
else:
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,14 @@ def test_getframe(self):
is sys._getframe().f_code
)

def test_get_calling_module_name(self):
# Default depth gets ourselves
self.assertEqual("test.test_sys", sys._get_calling_module_name())
# Get our caller
self.assertEqual("unittest.case", sys._get_calling_module_name(1))
# Get our caller's caller's caller's caller
self.assertEqual("unittest.suite", sys._get_calling_module_name(4))

# sys._current_frames() is a CPython-only gimmick.
@threading_helper.reap_threads
@threading_helper.requires_working_threading()
Expand Down
8 changes: 6 additions & 2 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1942,11 +1942,15 @@ def _no_init_or_replace_init(self, *args, **kwargs):


def _caller(depth=1, default='__main__'):
try:
return sys._get_calling_module_name(depth + 1) or default
except AttributeError: # For platforms without _get_calling_module_name()
pass
try:
return sys._getframe(depth + 1).f_globals.get('__name__', default)
except (AttributeError, ValueError): # For platforms without _getframe()
return None

pass
return None

def _allow_reckless_class_checks(depth=3):
"""Allow instance and class checks for special stdlib modules.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Changes internal function used to ensure runtime-created collections have
the correct module name from ``_getframe`` to ``_get_calling_module_name``.
69 changes: 68 additions & 1 deletion Python/clinic/sysmodule.c.h

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

33 changes: 33 additions & 0 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2180,6 +2180,38 @@ sys_is_stack_trampoline_active_impl(PyObject *module)
}


/*[clinic input]
sys._get_calling_module_name
depth: int = 0
Return the name of the calling module.
The default depth returns the module containing the call to this function.
A more typical use in a library will pass a depth of 1 to get the user's
module rather than the library module.
[clinic start generated code]*/

static PyObject *
sys__get_calling_module_name_impl(PyObject *module, int depth)
/*[clinic end generated code: output=bd04c211226f8b84 input=dd268ae6a20311ab]*/
{
_PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame;
while (f && (f->owner == FRAME_OWNED_BY_CSTACK || depth-- > 0)) {
f = f->previous;
}
if (f == NULL) {
Py_RETURN_NONE;
}
PyObject *r = PyDict_GetItemWithError(f->f_globals, &_Py_ID(__name__));
if (!r) {
PyErr_Clear();
r = Py_None;
}
return Py_NewRef(r);
}


static PyMethodDef sys_methods[] = {
/* Might as well keep this in alphabetic order */
SYS_ADDAUDITHOOK_METHODDEF
Expand All @@ -2197,6 +2229,7 @@ static PyMethodDef sys_methods[] = {
SYS_GETDEFAULTENCODING_METHODDEF
SYS_GETDLOPENFLAGS_METHODDEF
SYS_GETALLOCATEDBLOCKS_METHODDEF
SYS__GET_CALLING_MODULE_NAME_METHODDEF
SYS_GETFILESYSTEMENCODING_METHODDEF
SYS_GETFILESYSTEMENCODEERRORS_METHODDEF
#ifdef Py_TRACE_REFS
Expand Down

0 comments on commit d044be4

Please sign in to comment.