summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2023-01-13 11:31:06 (GMT)
committerGitHub <noreply@github.com>2023-01-13 11:31:06 (GMT)
commitb5d4347950399800c6703736d716f08761b29245 (patch)
treebb838154108d6740e062cd5b3d0756fd1a4f2d89
parent94fc7706b7bc3d57cdd6d15bf8e8c4499ae53a69 (diff)
downloadcpython-b5d4347950399800c6703736d716f08761b29245.zip
cpython-b5d4347950399800c6703736d716f08761b29245.tar.gz
cpython-b5d4347950399800c6703736d716f08761b29245.tar.bz2
gh-86682: Adds sys._getframemodulename as an alternative to using _getframe (GH-99520)
Also updates calls in collections, doctest, enum, and typing modules to use _getframemodulename first when available.
-rw-r--r--Doc/library/sys.rst16
-rw-r--r--Lib/_pydecimal.py2
-rw-r--r--Lib/collections/__init__.py9
-rw-r--r--Lib/doctest.py8
-rw-r--r--Lib/enum.py12
-rw-r--r--Lib/test/audit-tests.py11
-rw-r--r--Lib/test/test_audit.py12
-rw-r--r--Lib/test/test_sys.py20
-rw-r--r--Lib/typing.py8
-rw-r--r--Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst2
-rw-r--r--Objects/funcobject.c5
-rw-r--r--Python/clinic/sysmodule.c.h71
-rw-r--r--Python/sysmodule.c38
13 files changed, 200 insertions, 14 deletions
diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 28adca1..605e2c9 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -808,6 +808,22 @@ always available.
It is not guaranteed to exist in all implementations of Python.
+.. function:: _getframemodulename([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.
+
+ .. audit-event:: sys._getframemodulename depth sys._getframemodulename
+
+ .. 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:: getprofile()
.. index::
diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py
index f9d6c99..2692f2f 100644
--- a/Lib/_pydecimal.py
+++ b/Lib/_pydecimal.py
@@ -159,7 +159,7 @@ import sys
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
diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py
index f07ee14..b5e4d16 100644
--- a/Lib/collections/__init__.py
+++ b/Lib/collections/__init__.py
@@ -507,9 +507,12 @@ def namedtuple(typename, field_names, *, rename=False, defaults=None, module=Non
# specified a particular module.
if module is None:
try:
- module = _sys._getframe(1).f_globals.get('__name__', '__main__')
- except (AttributeError, ValueError):
- pass
+ module = _sys._getframemodulename(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 dafad50..2776d74 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -207,7 +207,13 @@ 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:
+ try:
+ return sys.modules[sys._getframemodulename(depth)]
+ except AttributeError:
+ return sys.modules[sys._getframe(depth).f_globals['__name__']]
+ except KeyError:
+ pass
else:
raise TypeError("Expected a module, string, or None")
diff --git a/Lib/enum.py b/Lib/enum.py
index 21f6388..4658393 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -862,13 +862,15 @@ class EnumType(type):
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._getframe(2).f_globals['__name__']
- except (AttributeError, ValueError, KeyError):
- pass
+ module = sys._getframemodulename(2)
+ except AttributeError:
+ # Fall back on _getframe if _getframemodulename 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/audit-tests.py b/Lib/test/audit-tests.py
index bf56cea..0edc9d9 100644
--- a/Lib/test/audit-tests.py
+++ b/Lib/test/audit-tests.py
@@ -419,6 +419,17 @@ def test_sys_getframe():
sys._getframe()
+def test_sys_getframemodulename():
+ import sys
+
+ def hook(event, args):
+ if event.startswith("sys."):
+ print(event, *args)
+
+ sys.addaudithook(hook)
+ sys._getframemodulename()
+
+
def test_threading():
import _thread
diff --git a/Lib/test/test_audit.py b/Lib/test/test_audit.py
index 70f8a77..0b69864 100644
--- a/Lib/test/test_audit.py
+++ b/Lib/test/test_audit.py
@@ -186,6 +186,18 @@ class AuditTest(unittest.TestCase):
self.assertEqual(actual, expected)
+ 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._getframemodulename", "0")]
+
+ self.assertEqual(actual, expected)
+
def test_threading(self):
returncode, events, stderr = self.run_python("test_threading")
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 232b799..ab1a065 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -399,6 +399,26 @@ class SysModuleTest(unittest.TestCase):
is sys._getframe().f_code
)
+ 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
@threading_helper.requires_working_threading()
diff --git a/Lib/typing.py b/Lib/typing.py
index 8bc38f9..4675af1 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1940,10 +1940,14 @@ def _no_init_or_replace_init(self, *args, **kwargs):
def _caller(depth=1, default='__main__'):
try:
+ 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)
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 0000000..64ef42a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-11-15-23-30-39.gh-issue-86682.gK9i1N.rst
@@ -0,0 +1,2 @@
+Ensure runtime-created collections have the correct module name using
+the newly added (internal) :func:`sys._getframemodulename`.
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index d5cf5b9..baa3603 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -93,7 +93,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/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h
index 03eeda8..46252dd 100644
--- a/Python/clinic/sysmodule.c.h
+++ b/Python/clinic/sysmodule.c.h
@@ -1275,6 +1275,75 @@ sys_is_stack_trampoline_active(PyObject *module, PyObject *Py_UNUSED(ignored))
return sys_is_stack_trampoline_active_impl(module);
}
+PyDoc_STRVAR(sys__getframemodulename__doc__,
+"_getframemodulename($module, /, depth=0)\n"
+"--\n"
+"\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.\n"
+"\n"
+"If no frame, module, or name can be found, returns None.");
+
+#define SYS__GETFRAMEMODULENAME_METHODDEF \
+ {"_getframemodulename", _PyCFunction_CAST(sys__getframemodulename), METH_FASTCALL|METH_KEYWORDS, sys__getframemodulename__doc__},
+
+static PyObject *
+sys__getframemodulename_impl(PyObject *module, int depth);
+
+static PyObject *
+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)
+
+ #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 = "_getframemodulename",
+ .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__getframemodulename_impl(module, depth);
+
+exit:
+ return return_value;
+}
+
#ifndef SYS_GETWINDOWSVERSION_METHODDEF
#define SYS_GETWINDOWSVERSION_METHODDEF
#endif /* !defined(SYS_GETWINDOWSVERSION_METHODDEF) */
@@ -1318,4 +1387,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=b32b444538dfd354 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=5c761f14326ced54 input=a9049054013a1b77]*/
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index acee794..f9f766a 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2172,6 +2172,43 @@ sys_is_stack_trampoline_active_impl(PyObject *module)
}
+/*[clinic input]
+sys._getframemodulename
+
+ depth: int = 0
+
+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__getframemodulename_impl(PyObject *module, int depth)
+/*[clinic end generated code: output=1d70ef691f09d2db input=d4f1a8ed43b8fb46]*/
+{
+ if (PySys_Audit("sys._getframemodulename", "i", depth) < 0) {
+ return NULL;
+ }
+ _PyInterpreterFrame *f = _PyThreadState_GET()->cframe->current_frame;
+ while (f && (_PyFrame_IsIncomplete(f) || depth-- > 0)) {
+ f = f->previous;
+ }
+ if (f == NULL || f->f_funcobj == NULL) {
+ Py_RETURN_NONE;
+ }
+ PyObject *r = PyFunction_GetModule(f->f_funcobj);
+ 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
@@ -2200,6 +2237,7 @@ static PyMethodDef sys_methods[] = {
{"getsizeof", _PyCFunction_CAST(sys_getsizeof),
METH_VARARGS | METH_KEYWORDS, getsizeof_doc},
SYS__GETFRAME_METHODDEF
+ SYS__GETFRAMEMODULENAME_METHODDEF
SYS_GETWINDOWSVERSION_METHODDEF
SYS__ENABLELEGACYWINDOWSFSENCODING_METHODDEF
SYS_INTERN_METHODDEF