From d044be4dc523f72da247cf16a0f0f0294cbfcbe0 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Tue, 15 Nov 2022 23:33:42 +0000 Subject: [PATCH 01/11] gh-86682: Adds sys._get_calling_module_name and replaces _getframe calls in collections, doctest, enum, and typing modules --- Lib/collections/__init__.py | 9 ++- Lib/doctest.py | 5 +- Lib/enum.py | 10 ++- Lib/test/test_sys.py | 8 +++ Lib/typing.py | 8 ++- ...2-11-15-23-30-39.gh-issue-86682.gK9i1N.rst | 2 + Python/clinic/sysmodule.c.h | 69 ++++++++++++++++++- Python/sysmodule.c | 33 +++++++++ 8 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index f07ee143a5aff1..f0a82b3b1e47aa 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -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 diff --git a/Lib/doctest.py b/Lib/doctest.py index b2ef2ce63672eb..7c427eb71c1430 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -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") diff --git a/Lib/enum.py b/Lib/enum.py index 1b683c702d59b4..d9d746fa62c0e5 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -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: diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 2403c7c815f2c0..d5e855d32044e4 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -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() diff --git a/Lib/typing.py b/Lib/typing.py index 233941598f76a3..64d1d814faa596 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -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. diff --git a/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst new file mode 100644 index 00000000000000..8b244286f37cbf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst @@ -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``. diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index 5678d0ac2a608b..f92cf2e036314a 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -1275,6 +1275,73 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored)) return sys_is_stack_trampoline_active_impl(module); } +PyDoc_STRVAR(sys__get_calling_module_name__doc__, +"_get_calling_module_name($module, /, depth=0)\n" +"--\n" +"\n" +"Return the name of the calling module.\n" +"\n" +"The default depth returns the module containing the call to this function.\n" +"A more typical use in a library will pass a depth of 1 to get the user\'s\n" +"module rather than the library module."); + +#define SYS__GET_CALLING_MODULE_NAME_METHODDEF \ + {"_get_calling_module_name", _PyCFunction_CAST(sys__get_calling_module_name), METH_FASTCALL|METH_KEYWORDS, sys__get_calling_module_name__doc__}, + +static PyObject * +sys__get_calling_module_name_impl(PyObject *module, int depth); + +static PyObject * +sys__get_calling_module_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(depth), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"depth", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_get_calling_module_name", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int depth = 0; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + depth = _PyLong_AsInt(args[0]); + if (depth == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_pos: + return_value = sys__get_calling_module_name_impl(module, depth); + +exit: + return return_value; +} + #ifndef SYS_GETWINDOWSVERSION_METHODDEF #define SYS_GETWINDOWSVERSION_METHODDEF #endif /* !defined(SYS_GETWINDOWSVERSION_METHODDEF) */ @@ -1318,4 +1385,4 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored)) #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=79228e569529129c input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e5518d74e7578a25 input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 6f0a126a62277b..6a5291202ce32e 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -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 @@ -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 From 3457acfaf8f958086032944ace482b4a3f93362b Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 16 Nov 2022 00:08:06 +0000 Subject: [PATCH 02/11] Update to match design for _getcaller --- Doc/library/sys.rst | 15 ++++++++++++ Lib/collections/__init__.py | 2 +- Lib/doctest.py | 4 +++- Lib/enum.py | 6 ++--- Lib/test/audit-tests.py | 11 +++++++++ Lib/test/test_audit.py | 12 ++++++++++ Lib/test/test_sys.py | 22 ++++++++++++----- Lib/typing.py | 4 ++-- ...2-11-15-23-30-39.gh-issue-86682.gK9i1N.rst | 2 +- Python/clinic/sysmodule.c.h | 24 +++++++++---------- Python/sysmodule.c | 18 ++++++++------ 11 files changed, 86 insertions(+), 34 deletions(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index d54ecd75a2628f..f6c368da5cddbf 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -787,6 +787,21 @@ always available. .. versionadded:: 3.2 +.. function:: _getcaller([depth]) + + Return a function object from the call stack. If optional integer *depth* is + given, return the frame object that many calls below the top of the stack. If + that is deeper than the call stack, :exc:`ValueError` is raised. The default + for *depth* is zero, returning the function at the top of the call stack. + + .. audit-event:: sys._getcaller depth sys._getcaller + + .. impl-detail:: + + This function should be used for internal and specialized purposes only. + It is not guaranteed to exist in all implementations of Python. + + .. function:: _getframe([depth]) Return a frame object from the call stack. If optional integer *depth* is diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index f0a82b3b1e47aa..eabcc572b3430f 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -507,7 +507,7 @@ def __getnewargs__(self): # specified a particular module. if module is None: try: - module = _sys._get_calling_module_name(1) or '__main__' + module = getattr(_sys._getcaller(1), '__module__', '__main__') except AttributeError: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') diff --git a/Lib/doctest.py b/Lib/doctest.py index 7c427eb71c1430..3fef534b8abd96 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -208,9 +208,11 @@ def _normalize_module(module, depth=2): return __import__(module, globals(), locals(), ["*"]) elif module is None: try: - return sys.modules[sys._get_calling_module_name(depth)] + caller = sys._getcaller(depth) except AttributeError: return sys.modules[sys._getframe(depth).f_globals['__name__']] + else: + return sys.modules[caller.__module__] else: raise TypeError("Expected a module, string, or None") diff --git a/Lib/enum.py b/Lib/enum.py index d9d746fa62c0e5..5b9c620d063e99 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -860,13 +860,11 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s member_name, member_value = item classdict[member_name] = member_value - # TODO: replace the frame hack if a blessed way to know the calling - # module is ever developed if module is None: try: - module = sys._get_calling_module_name(2) + module = getattr(sys._getcaller(2), '__module__', None) except AttributeError: - # Fall back on _getframe if _get_calling_module_name is missing + # Fall back on _getframe if _getcaller is missing try: module = sys._getframe(2).f_globals['__name__'] except (AttributeError, ValueError, KeyError): diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index a411072ba7d0fd..dd2b8d71515ec7 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -419,6 +419,17 @@ def hook(event, args): sys._getframe() +def test_sys_getcaller(): + import sys + + def hook(event, args): + if event.startswith("sys."): + print(event, *args) + + sys.addaudithook(hook) + sys._getcaller() + + def test_wmi_exec_query(): import _wmi diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index e8d560ad5ce023..92670a7829a04f 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -186,6 +186,18 @@ def test_sys_getframe(self): self.assertEqual(actual, expected) + def test_sys_getcaller(self): + returncode, events, stderr = self.run_python("test_sys_getcaller") + if returncode: + self.fail(stderr) + + if support.verbose: + print(*events, sep='\n') + actual = [(ev[0], ev[2]) for ev in events] + expected = [("sys._getcaller", "0")] + + self.assertEqual(actual, expected) + def test_wmi_exec_query(self): import_helper.import_module("_wmi") returncode, events, stderr = self.run_python("test_wmi_exec_query") diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index d5e855d32044e4..36347d6b75be23 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -399,13 +399,23 @@ def test_getframe(self): is sys._getframe().f_code ) - def test_get_calling_module_name(self): + def test_getcaller(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)) + func = type(self).test_getcaller + self.assertIs(func, sys._getcaller()) + + def get_1(d): + return sys._getcaller(d) + + def get_2(d): + return get_1(d) + + self.assertIs(get_1, get_1(0)) + self.assertIs(func, get_1(1)) + + self.assertIs(get_1, get_2(0)) + self.assertIs(get_2, get_2(1)) + self.assertIs(func, get_2(2)) # sys._current_frames() is a CPython-only gimmick. @threading_helper.reap_threads diff --git a/Lib/typing.py b/Lib/typing.py index 64d1d814faa596..883eb7450a44bf 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1943,8 +1943,8 @@ 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() + return getattr(sys._getcaller(depth + 1), '__module__', default) + except AttributeError: # For platforms without _getcaller() pass try: return sys._getframe(depth + 1).f_globals.get('__name__', default) diff --git a/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst index 8b244286f37cbf..22e10bca6c5599 100644 --- a/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst +++ b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst @@ -1,2 +1,2 @@ Changes internal function used to ensure runtime-created collections have -the correct module name from ``_getframe`` to ``_get_calling_module_name``. +the correct module name from :func:`sys._getframe` to :func:`sys._getcaller`. diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index f92cf2e036314a..c748a8ad28e610 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -1275,24 +1275,24 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored)) return sys_is_stack_trampoline_active_impl(module); } -PyDoc_STRVAR(sys__get_calling_module_name__doc__, -"_get_calling_module_name($module, /, depth=0)\n" +PyDoc_STRVAR(sys__getcaller__doc__, +"_getcaller($module, /, depth=0)\n" "--\n" "\n" -"Return the name of the calling module.\n" +"Return the calling function object.\n" "\n" -"The default depth returns the module containing the call to this function.\n" +"The default depth returns the function containing the call to this API.\n" "A more typical use in a library will pass a depth of 1 to get the user\'s\n" -"module rather than the library module."); +"function rather than the library module."); -#define SYS__GET_CALLING_MODULE_NAME_METHODDEF \ - {"_get_calling_module_name", _PyCFunction_CAST(sys__get_calling_module_name), METH_FASTCALL|METH_KEYWORDS, sys__get_calling_module_name__doc__}, +#define SYS__GETCALLER_METHODDEF \ + {"_getcaller", _PyCFunction_CAST(sys__getcaller), METH_FASTCALL|METH_KEYWORDS, sys__getcaller__doc__}, static PyObject * -sys__get_calling_module_name_impl(PyObject *module, int depth); +sys__getcaller_impl(PyObject *module, int depth); static PyObject * -sys__get_calling_module_name(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +sys__getcaller(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1316,7 +1316,7 @@ sys__get_calling_module_name(PyObject *module, PyObject *const *args, Py_ssize_t static const char * const _keywords[] = {"depth", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "_get_calling_module_name", + .fname = "_getcaller", .kwtuple = KWTUPLE, }; #undef KWTUPLE @@ -1336,7 +1336,7 @@ sys__get_calling_module_name(PyObject *module, PyObject *const *args, Py_ssize_t goto exit; } skip_optional_pos: - return_value = sys__get_calling_module_name_impl(module, depth); + return_value = sys__getcaller_impl(module, depth); exit: return return_value; @@ -1385,4 +1385,4 @@ sys__get_calling_module_name(PyObject *module, PyObject *const *args, Py_ssize_t #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=e5518d74e7578a25 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=6130a81ea4035753 input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 6a5291202ce32e..8e30175e340866 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2181,21 +2181,24 @@ sys_is_stack_trampoline_active_impl(PyObject *module) /*[clinic input] -sys._get_calling_module_name +sys._getcaller depth: int = 0 -Return the name of the calling module. +Return the calling function object. -The default depth returns the module containing the call to this function. +The default depth returns the function containing the call to this API. A more typical use in a library will pass a depth of 1 to get the user's -module rather than the library module. +function 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]*/ +sys__getcaller_impl(PyObject *module, int depth) +/*[clinic end generated code: output=250f47adb2372e4a input=1993a1558597f1ed]*/ { + if (PySys_Audit("sys._getcaller", "i", depth) < 0) { + return NULL; + } _PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame; while (f && (f->owner == FRAME_OWNED_BY_CSTACK || depth-- > 0)) { f = f->previous; @@ -2203,6 +2206,7 @@ sys__get_calling_module_name_impl(PyObject *module, int depth) if (f == NULL) { Py_RETURN_NONE; } + return Py_NewRef(f->f_funcobj); PyObject *r = PyDict_GetItemWithError(f->f_globals, &_Py_ID(__name__)); if (!r) { PyErr_Clear(); @@ -2229,7 +2233,6 @@ 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 @@ -2240,6 +2243,7 @@ static PyMethodDef sys_methods[] = { SYS_GETRECURSIONLIMIT_METHODDEF {"getsizeof", _PyCFunction_CAST(sys_getsizeof), METH_VARARGS | METH_KEYWORDS, getsizeof_doc}, + SYS__GETCALLER_METHODDEF SYS__GETFRAME_METHODDEF SYS_GETWINDOWSVERSION_METHODDEF SYS__ENABLELEGACYWINDOWSFSENCODING_METHODDEF From 4486fd0b357f3fa8494d266fc6d5268f1aa7d37f Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 16 Nov 2022 17:13:45 +0000 Subject: [PATCH 03/11] Remove dead code --- Python/sysmodule.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 8e30175e340866..f9485f8650fa35 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2207,12 +2207,6 @@ sys__getcaller_impl(PyObject *module, int depth) Py_RETURN_NONE; } return Py_NewRef(f->f_funcobj); - PyObject *r = PyDict_GetItemWithError(f->f_globals, &_Py_ID(__name__)); - if (!r) { - PyErr_Clear(); - r = Py_None; - } - return Py_NewRef(r); } From 3e3023ff0f76e19107bc8ca98eab61797246f9e8 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 17 Nov 2022 00:21:23 +0000 Subject: [PATCH 04/11] Switch back to _getcallingmodule and only return the name --- Doc/library/sys.rst | 13 +++++++------ Lib/collections/__init__.py | 2 +- Lib/doctest.py | 4 +--- Lib/enum.py | 4 ++-- Lib/test/audit-tests.py | 4 ++-- Lib/test/test_audit.py | 6 +++--- Lib/test/test_sys.py | 19 +++---------------- Lib/typing.py | 4 ++-- Python/clinic/sysmodule.c.h | 24 ++++++++++++------------ Python/sysmodule.c | 23 ++++++++++++++--------- 10 files changed, 47 insertions(+), 56 deletions(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index f6c368da5cddbf..d0d50ac840e2a6 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -787,14 +787,15 @@ always available. .. versionadded:: 3.2 -.. function:: _getcaller([depth]) +.. function:: _getcallingmodule([depth]) - Return a function object from the call stack. If optional integer *depth* is - given, return the frame object that many calls below the top of the stack. If - that is deeper than the call stack, :exc:`ValueError` is raised. The default - for *depth* is zero, returning the function at the top of the call stack. + Return the name of a module from the call stack. If optional integer *depth* + is given, return the module that many calls below the top of the stack. If + that is deeper than the call stack, or if the module is unidentifiable, + ``None`` is returned. The default for *depth* is zero, returning the + function at the top of the call stack. - .. audit-event:: sys._getcaller depth sys._getcaller + .. audit-event:: sys._getcallingmodule depth sys._getcallingmodule .. impl-detail:: diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index eabcc572b3430f..6d3d53fd000446 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -507,7 +507,7 @@ def __getnewargs__(self): # specified a particular module. if module is None: try: - module = getattr(_sys._getcaller(1), '__module__', '__main__') + module = _sys._getcallingmodule(1) or '__main__' except AttributeError: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') diff --git a/Lib/doctest.py b/Lib/doctest.py index 3fef534b8abd96..01e3e88972b5ac 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -208,11 +208,9 @@ def _normalize_module(module, depth=2): return __import__(module, globals(), locals(), ["*"]) elif module is None: try: - caller = sys._getcaller(depth) + return sys.modules[sys._getcallingmodule(depth)] except AttributeError: return sys.modules[sys._getframe(depth).f_globals['__name__']] - else: - return sys.modules[caller.__module__] else: raise TypeError("Expected a module, string, or None") diff --git a/Lib/enum.py b/Lib/enum.py index 5b9c620d063e99..dd9e38becc7181 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -862,9 +862,9 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s if module is None: try: - module = getattr(sys._getcaller(2), '__module__', None) + module = sys._getcallingmodule(2) except AttributeError: - # Fall back on _getframe if _getcaller is missing + # Fall back on _getframe if _getcallingmodule is missing try: module = sys._getframe(2).f_globals['__name__'] except (AttributeError, ValueError, KeyError): diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index 69df530e08505e..d45a036246a60f 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -419,7 +419,7 @@ def hook(event, args): sys._getframe() -def test_sys_getcaller(): +def test_sys_getcallingmodule(): import sys def hook(event, args): @@ -427,7 +427,7 @@ def hook(event, args): print(event, *args) sys.addaudithook(hook) - sys._getcaller() + sys._getcallingmodule() def test_threading(): diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index bd7436e8a0b08b..2bdea8a8c219f0 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -186,15 +186,15 @@ def test_sys_getframe(self): self.assertEqual(actual, expected) - def test_sys_getcaller(self): - returncode, events, stderr = self.run_python("test_sys_getcaller") + def test_sys_getcallingmodule(self): + returncode, events, stderr = self.run_python("test_sys_getcallingmodule") if returncode: self.fail(stderr) if support.verbose: print(*events, sep='\n') actual = [(ev[0], ev[2]) for ev in events] - expected = [("sys._getcaller", "0")] + expected = [("sys._getcallingmodule", "0")] self.assertEqual(actual, expected) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 36347d6b75be23..168d4b1b40e22b 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -399,23 +399,10 @@ def test_getframe(self): is sys._getframe().f_code ) - def test_getcaller(self): + def test_getcallingmodule(self): # Default depth gets ourselves - func = type(self).test_getcaller - self.assertIs(func, sys._getcaller()) - - def get_1(d): - return sys._getcaller(d) - - def get_2(d): - return get_1(d) - - self.assertIs(get_1, get_1(0)) - self.assertIs(func, get_1(1)) - - self.assertIs(get_1, get_2(0)) - self.assertIs(get_2, get_2(1)) - self.assertIs(func, get_2(2)) + self.assertEqual(__name__, sys._getcallingmodule()) + self.assertEqual("unittest.case", sys._getcallingmodule(1)) # sys._current_frames() is a CPython-only gimmick. @threading_helper.reap_threads diff --git a/Lib/typing.py b/Lib/typing.py index 883eb7450a44bf..86ce98cbed37d9 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1943,8 +1943,8 @@ def _no_init_or_replace_init(self, *args, **kwargs): def _caller(depth=1, default='__main__'): try: - return getattr(sys._getcaller(depth + 1), '__module__', default) - except AttributeError: # For platforms without _getcaller() + return sys._getcallingmodule(depth + 1) or default + except AttributeError: # For platforms without _getcallingmodule() pass try: return sys._getframe(depth + 1).f_globals.get('__name__', default) diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index c748a8ad28e610..5c6368c28f2aa7 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -1275,24 +1275,24 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored)) return sys_is_stack_trampoline_active_impl(module); } -PyDoc_STRVAR(sys__getcaller__doc__, -"_getcaller($module, /, depth=0)\n" +PyDoc_STRVAR(sys__getcallingmodule__doc__, +"_getcallingmodule($module, /, depth=0)\n" "--\n" "\n" -"Return the calling function object.\n" +"Return the name of the calling module, or None if not available.\n" "\n" -"The default depth returns the function containing the call to this API.\n" +"The default depth returns the module containing the call to this API.\n" "A more typical use in a library will pass a depth of 1 to get the user\'s\n" -"function rather than the library module."); +"module rather than the library module."); -#define SYS__GETCALLER_METHODDEF \ - {"_getcaller", _PyCFunction_CAST(sys__getcaller), METH_FASTCALL|METH_KEYWORDS, sys__getcaller__doc__}, +#define SYS__GETCALLINGMODULE_METHODDEF \ + {"_getcallingmodule", _PyCFunction_CAST(sys__getcallingmodule), METH_FASTCALL|METH_KEYWORDS, sys__getcallingmodule__doc__}, static PyObject * -sys__getcaller_impl(PyObject *module, int depth); +sys__getcallingmodule_impl(PyObject *module, int depth); static PyObject * -sys__getcaller(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +sys__getcallingmodule(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1316,7 +1316,7 @@ sys__getcaller(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje static const char * const _keywords[] = {"depth", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "_getcaller", + .fname = "_getcallingmodule", .kwtuple = KWTUPLE, }; #undef KWTUPLE @@ -1336,7 +1336,7 @@ sys__getcaller(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje goto exit; } skip_optional_pos: - return_value = sys__getcaller_impl(module, depth); + return_value = sys__getcallingmodule_impl(module, depth); exit: return return_value; @@ -1385,4 +1385,4 @@ sys__getcaller(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=6130a81ea4035753 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=20e7ec1ce1732596 input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index f9485f8650fa35..941508e75df12f 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2181,22 +2181,22 @@ sys_is_stack_trampoline_active_impl(PyObject *module) /*[clinic input] -sys._getcaller +sys._getcallingmodule depth: int = 0 -Return the calling function object. +Return the name of the calling module, or None if not available. -The default depth returns the function containing the call to this API. +The default depth returns the module containing the call to this API. A more typical use in a library will pass a depth of 1 to get the user's -function rather than the library module. +module rather than the library module. [clinic start generated code]*/ static PyObject * -sys__getcaller_impl(PyObject *module, int depth) -/*[clinic end generated code: output=250f47adb2372e4a input=1993a1558597f1ed]*/ +sys__getcallingmodule_impl(PyObject *module, int depth) +/*[clinic end generated code: output=2035dac7eecb38ae input=8138669512c41eec]*/ { - if (PySys_Audit("sys._getcaller", "i", depth) < 0) { + if (PySys_Audit("sys._getcallingmodule", "i", depth) < 0) { return NULL; } _PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame; @@ -2206,7 +2206,12 @@ sys__getcaller_impl(PyObject *module, int depth) if (f == NULL) { Py_RETURN_NONE; } - return Py_NewRef(f->f_funcobj); + PyObject *r = PyDict_GetItemWithError(f->f_globals, &_Py_ID(__name__)); + if (!r) { + PyErr_Clear(); + r = Py_None; + } + return Py_NewRef(r); } @@ -2237,7 +2242,7 @@ static PyMethodDef sys_methods[] = { SYS_GETRECURSIONLIMIT_METHODDEF {"getsizeof", _PyCFunction_CAST(sys_getsizeof), METH_VARARGS | METH_KEYWORDS, getsizeof_doc}, - SYS__GETCALLER_METHODDEF + SYS__GETCALLINGMODULE_METHODDEF SYS__GETFRAME_METHODDEF SYS_GETWINDOWSVERSION_METHODDEF SYS__ENABLELEGACYWINDOWSFSENCODING_METHODDEF From a11854858f0d0821b1bc16c3a9f4eacddb6ca209 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 21 Nov 2022 19:03:23 +0000 Subject: [PATCH 05/11] Update NEWS --- .../Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst index 22e10bca6c5599..fe11493a0376f3 100644 --- a/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst +++ b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst @@ -1,2 +1,2 @@ -Changes internal function used to ensure runtime-created collections have -the correct module name from :func:`sys._getframe` to :func:`sys._getcaller`. +Ensure runtime-created collections have the correct module name using +the newly added (internal) :func:`sys._getcallingmodulename`. From e2714d697aaa8af6a5a3cda7009a3ad22d2509f6 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Thu, 24 Nov 2022 23:41:55 +0000 Subject: [PATCH 06/11] Update Doc/library/sys.rst --- Doc/library/sys.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index d0d50ac840e2a6..83bfe1052c8d72 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -793,7 +793,7 @@ always available. is given, return the module that many calls below the top of the stack. If that is deeper than the call stack, or if the module is unidentifiable, ``None`` is returned. The default for *depth* is zero, returning the - function at the top of the call stack. + module at the top of the call stack. .. audit-event:: sys._getcallingmodule depth sys._getcallingmodule From 52a498b484108f6a064a744497b30b54694be279 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 28 Nov 2022 23:59:52 +0000 Subject: [PATCH 07/11] Silence key error --- Lib/doctest.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py index 01e3e88972b5ac..ef82198871a1be 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -208,9 +208,12 @@ def _normalize_module(module, depth=2): return __import__(module, globals(), locals(), ["*"]) elif module is None: try: - return sys.modules[sys._getcallingmodule(depth)] - except AttributeError: - return sys.modules[sys._getframe(depth).f_globals['__name__']] + try: + return sys.modules[sys._getcallingmodule(depth)] + except AttributeError: + return sys.modules[sys._getframe(depth).f_globals['__name__']] + except KeyError: + pass else: raise TypeError("Expected a module, string, or None") From 7e0bc814002b716cce02e6e4293ea030aad81c62 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Wed, 30 Nov 2022 01:01:46 +0000 Subject: [PATCH 08/11] Rename to sys._getframemodulename --- Doc/library/sys.rst | 26 +++++++++---------- Lib/collections/__init__.py | 2 +- Lib/doctest.py | 2 +- Lib/enum.py | 4 +-- Lib/test/audit-tests.py | 4 +-- Lib/test/test_audit.py | 6 ++--- Lib/test/test_sys.py | 6 ++--- Lib/typing.py | 4 +-- ...2-11-15-23-30-39.gh-issue-86682.gK9i1N.rst | 2 +- Python/clinic/sysmodule.c.h | 24 +++++++++-------- Python/sysmodule.c | 14 +++++----- 11 files changed, 49 insertions(+), 45 deletions(-) diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 83bfe1052c8d72..5e0b629faaf3ae 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -787,15 +787,14 @@ always available. .. versionadded:: 3.2 -.. function:: _getcallingmodule([depth]) +.. function:: _getframe([depth]) - Return the name of a module from the call stack. If optional integer *depth* - is given, return the module that many calls below the top of the stack. If - that is deeper than the call stack, or if the module is unidentifiable, - ``None`` is returned. The default for *depth* is zero, returning the - module at the top of the call stack. + Return a frame object from the call stack. If optional integer *depth* is + given, return the frame object that many calls below the top of the stack. If + that is deeper than the call stack, :exc:`ValueError` is raised. The default + for *depth* is zero, returning the frame at the top of the call stack. - .. audit-event:: sys._getcallingmodule depth sys._getcallingmodule + .. audit-event:: sys._getframe frame sys._getframe .. impl-detail:: @@ -803,14 +802,15 @@ always available. It is not guaranteed to exist in all implementations of Python. -.. function:: _getframe([depth]) +.. function:: _getframemodulename([depth]) - Return a frame object from the call stack. If optional integer *depth* is - given, return the frame object that many calls below the top of the stack. If - that is deeper than the call stack, :exc:`ValueError` is raised. The default - for *depth* is zero, returning the frame at the top of the call stack. + Return the name of a module from the call stack. If optional integer *depth* + is given, return the module that many calls below the top of the stack. If + that is deeper than the call stack, or if the module is unidentifiable, + ``None`` is returned. The default for *depth* is zero, returning the + module at the top of the call stack. - .. audit-event:: sys._getframe frame sys._getframe + .. audit-event:: sys._getframemodulename depth sys._getframemodulename .. impl-detail:: diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 6d3d53fd000446..b5e4d16e9dbcad 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -507,7 +507,7 @@ def __getnewargs__(self): # specified a particular module. if module is None: try: - module = _sys._getcallingmodule(1) or '__main__' + module = _sys._getframemodulename(1) or '__main__' except AttributeError: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') diff --git a/Lib/doctest.py b/Lib/doctest.py index ef82198871a1be..21c9c55caa13d3 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -209,7 +209,7 @@ def _normalize_module(module, depth=2): elif module is None: try: try: - return sys.modules[sys._getcallingmodule(depth)] + return sys.modules[sys._getframemodulename(depth)] except AttributeError: return sys.modules[sys._getframe(depth).f_globals['__name__']] except KeyError: diff --git a/Lib/enum.py b/Lib/enum.py index dd9e38becc7181..d937763ba15511 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -862,9 +862,9 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s if module is None: try: - module = sys._getcallingmodule(2) + module = sys._getframemodulename(2) except AttributeError: - # Fall back on _getframe if _getcallingmodule is missing + # Fall back on _getframe if _getframemodulename is missing try: module = sys._getframe(2).f_globals['__name__'] except (AttributeError, ValueError, KeyError): diff --git a/Lib/test/audit-tests.py b/Lib/test/audit-tests.py index d45a036246a60f..0edc9d9c472766 100644 --- a/Lib/test/audit-tests.py +++ b/Lib/test/audit-tests.py @@ -419,7 +419,7 @@ def hook(event, args): sys._getframe() -def test_sys_getcallingmodule(): +def test_sys_getframemodulename(): import sys def hook(event, args): @@ -427,7 +427,7 @@ def hook(event, args): print(event, *args) sys.addaudithook(hook) - sys._getcallingmodule() + sys._getframemodulename() def test_threading(): diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py index a6980347e800ce..0b69864751d83d 100644 --- a/Lib/test/test_audit.py +++ b/Lib/test/test_audit.py @@ -186,15 +186,15 @@ def test_sys_getframe(self): self.assertEqual(actual, expected) - def test_sys_getcallingmodule(self): - returncode, events, stderr = self.run_python("test_sys_getcallingmodule") + def test_sys_getframemodulename(self): + returncode, events, stderr = self.run_python("test_sys_getframemodulename") if returncode: self.fail(stderr) if support.verbose: print(*events, sep='\n') actual = [(ev[0], ev[2]) for ev in events] - expected = [("sys._getcallingmodule", "0")] + expected = [("sys._getframemodulename", "0")] self.assertEqual(actual, expected) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 168d4b1b40e22b..871664bad4eae5 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -399,10 +399,10 @@ def test_getframe(self): is sys._getframe().f_code ) - def test_getcallingmodule(self): + def test_getframemodulename(self): # Default depth gets ourselves - self.assertEqual(__name__, sys._getcallingmodule()) - self.assertEqual("unittest.case", sys._getcallingmodule(1)) + self.assertEqual(__name__, sys._getframemodulename()) + self.assertEqual("unittest.case", sys._getframemodulename(1)) # sys._current_frames() is a CPython-only gimmick. @threading_helper.reap_threads diff --git a/Lib/typing.py b/Lib/typing.py index 86ce98cbed37d9..81268e6d886e14 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1943,8 +1943,8 @@ def _no_init_or_replace_init(self, *args, **kwargs): def _caller(depth=1, default='__main__'): try: - return sys._getcallingmodule(depth + 1) or default - except AttributeError: # For platforms without _getcallingmodule() + return sys._getframemodulename(depth + 1) or default + except AttributeError: # For platforms without _getframemodulename() pass try: return sys._getframe(depth + 1).f_globals.get('__name__', default) diff --git a/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst index fe11493a0376f3..64ef42a9a1c0b2 100644 --- a/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst +++ b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst @@ -1,2 +1,2 @@ Ensure runtime-created collections have the correct module name using -the newly added (internal) :func:`sys._getcallingmodulename`. +the newly added (internal) :func:`sys._getframemodulename`. diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h index 5c6368c28f2aa7..1b5c1c9fc765ae 100644 --- a/Python/clinic/sysmodule.c.h +++ b/Python/clinic/sysmodule.c.h @@ -1275,24 +1275,26 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored)) return sys_is_stack_trampoline_active_impl(module); } -PyDoc_STRVAR(sys__getcallingmodule__doc__, -"_getcallingmodule($module, /, depth=0)\n" +PyDoc_STRVAR(sys__getframemodulename__doc__, +"_getframemodulename($module, /, depth=0)\n" "--\n" "\n" -"Return the name of the calling module, or None if not available.\n" +"Return the name of the module for a calling frame.\n" "\n" "The default depth returns the module containing the call to this API.\n" "A more typical use in a library will pass a depth of 1 to get the user\'s\n" -"module rather than the library module."); +"module rather than the library module.\n" +"\n" +"If no frame, module, or name can be found, returns None."); -#define SYS__GETCALLINGMODULE_METHODDEF \ - {"_getcallingmodule", _PyCFunction_CAST(sys__getcallingmodule), METH_FASTCALL|METH_KEYWORDS, sys__getcallingmodule__doc__}, +#define SYS__GETFRAMEMODULENAME_METHODDEF \ + {"_getframemodulename", _PyCFunction_CAST(sys__getframemodulename), METH_FASTCALL|METH_KEYWORDS, sys__getframemodulename__doc__}, static PyObject * -sys__getcallingmodule_impl(PyObject *module, int depth); +sys__getframemodulename_impl(PyObject *module, int depth); static PyObject * -sys__getcallingmodule(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +sys__getframemodulename(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -1316,7 +1318,7 @@ sys__getcallingmodule(PyObject *module, PyObject *const *args, Py_ssize_t nargs, static const char * const _keywords[] = {"depth", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "_getcallingmodule", + .fname = "_getframemodulename", .kwtuple = KWTUPLE, }; #undef KWTUPLE @@ -1336,7 +1338,7 @@ sys__getcallingmodule(PyObject *module, PyObject *const *args, Py_ssize_t nargs, goto exit; } skip_optional_pos: - return_value = sys__getcallingmodule_impl(module, depth); + return_value = sys__getframemodulename_impl(module, depth); exit: return return_value; @@ -1385,4 +1387,4 @@ sys__getcallingmodule(PyObject *module, PyObject *const *args, Py_ssize_t nargs, #ifndef SYS_GETANDROIDAPILEVEL_METHODDEF #define SYS_GETANDROIDAPILEVEL_METHODDEF #endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */ -/*[clinic end generated code: output=20e7ec1ce1732596 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e0e28827a328d601 input=a9049054013a1b77]*/ diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 941508e75df12f..90cc06432c18a7 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2181,22 +2181,24 @@ sys_is_stack_trampoline_active_impl(PyObject *module) /*[clinic input] -sys._getcallingmodule +sys._getframemodulename depth: int = 0 -Return the name of the calling module, or None if not available. +Return the name of the module for a calling frame. The default depth returns the module containing the call to this API. A more typical use in a library will pass a depth of 1 to get the user's module rather than the library module. + +If no frame, module, or name can be found, returns None. [clinic start generated code]*/ static PyObject * -sys__getcallingmodule_impl(PyObject *module, int depth) -/*[clinic end generated code: output=2035dac7eecb38ae input=8138669512c41eec]*/ +sys__getframemodulename_impl(PyObject *module, int depth) +/*[clinic end generated code: output=1d70ef691f09d2db input=d4f1a8ed43b8fb46]*/ { - if (PySys_Audit("sys._getcallingmodule", "i", depth) < 0) { + if (PySys_Audit("sys._getframemodulename", "i", depth) < 0) { return NULL; } _PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame; @@ -2242,8 +2244,8 @@ static PyMethodDef sys_methods[] = { SYS_GETRECURSIONLIMIT_METHODDEF {"getsizeof", _PyCFunction_CAST(sys_getsizeof), METH_VARARGS | METH_KEYWORDS, getsizeof_doc}, - SYS__GETCALLINGMODULE_METHODDEF SYS__GETFRAME_METHODDEF + SYS__GETFRAMEMODULENAME_METHODDEF SYS_GETWINDOWSVERSION_METHODDEF SYS__ENABLELEGACYWINDOWSFSENCODING_METHODDEF SYS_INTERN_METHODDEF From ab5a26e123346493d0151922d76bae1271d2e2a3 Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Fri, 2 Dec 2022 16:55:01 +0000 Subject: [PATCH 09/11] Check func.__module__ first, and match getframe() unwind semantics --- Lib/test/test_sys.py | 15 +++++++++++++++ Python/sysmodule.c | 12 ++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 871664bad4eae5..cba52ac634fc3a 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -403,6 +403,21 @@ def test_getframemodulename(self): # Default depth gets ourselves self.assertEqual(__name__, sys._getframemodulename()) self.assertEqual("unittest.case", sys._getframemodulename(1)) + i = 0 + f = sys._getframe(i) + while f: + self.assertEqual( + f.f_globals['__name__'], + sys._getframemodulename(i) or '__main__' + ) + i += 1 + f2 = f.f_back + try: + f = sys._getframe(i) + except ValueError: + break + self.assertIs(f, f2) + self.assertIsNone(sys._getframemodulename(i)) # sys._current_frames() is a CPython-only gimmick. @threading_helper.reap_threads diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 90cc06432c18a7..00c5f2f677878c 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2202,16 +2202,20 @@ sys__getframemodulename_impl(PyObject *module, int depth) return NULL; } _PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame; - while (f && (f->owner == FRAME_OWNED_BY_CSTACK || depth-- > 0)) { + while (f && (_PyFrame_IsIncomplete(f) || depth-- > 0)) { f = f->previous; } - if (f == NULL) { + if (f == NULL || f->f_funcobj == NULL) { Py_RETURN_NONE; } - PyObject *r = PyDict_GetItemWithError(f->f_globals, &_Py_ID(__name__)); + PyObject *r = PyFunction_GetModule(f->f_funcobj); if (!r) { PyErr_Clear(); - r = Py_None; + r = PyDict_GetItemWithError(f->f_globals, &_Py_ID(__name__)); + if (!r) { + PyErr_Clear(); + r = Py_None; + } } return Py_NewRef(r); } From 320e4d112fd9a3d87cd5ba01bcdc6c26597984ca Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 5 Dec 2022 17:20:14 +0000 Subject: [PATCH 10/11] Set __module__ on exec'd code and simplify getframemodulename --- Objects/funcobject.c | 5 ++++- Python/sysmodule.c | 6 +----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Objects/funcobject.c b/Objects/funcobject.c index cab80006589b48..20474c73cc0c4f 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -26,7 +26,10 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr) op->func_doc = Py_NewRef(Py_None); op->func_dict = NULL; op->func_weakreflist = NULL; - op->func_module = NULL; + op->func_module = Py_XNewRef(PyDict_GetItem(op->func_globals, &_Py_ID(__name__))); + if (!op->func_module) { + PyErr_Clear(); + } op->func_annotations = NULL; op->vectorcall = _PyFunction_Vectorcall; op->func_version = 0; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 00c5f2f677878c..9760a4d31f3450 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2211,11 +2211,7 @@ sys__getframemodulename_impl(PyObject *module, int depth) PyObject *r = PyFunction_GetModule(f->f_funcobj); if (!r) { PyErr_Clear(); - r = PyDict_GetItemWithError(f->f_globals, &_Py_ID(__name__)); - if (!r) { - PyErr_Clear(); - r = Py_None; - } + r = Py_None; } return Py_NewRef(r); } From 1a79876ded0e10ef483989187e49d41436589b7a Mon Sep 17 00:00:00 2001 From: Steve Dower Date: Mon, 5 Dec 2022 20:59:12 +0000 Subject: [PATCH 11/11] Override DecimalTuple module name --- Lib/_pydecimal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index f9d6c9901f1f31..2692f2fcba45bf 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -159,7 +159,7 @@ try: from collections import namedtuple as _namedtuple - DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent') + DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent', module='decimal') except ImportError: DecimalTuple = lambda *args: args