bpo-33375: Get filename for warnings from frame.f_code.co_filename (GH-6622)

More consistent with how other parts of Python find the filename (e.g. tracebacks and pdb).
This commit is contained in:
Thomas Kluyver 2018-06-08 21:28:37 +02:00 committed by Brett Cannon
parent 3b0b90c8c3
commit 11a896652e
4 changed files with 19 additions and 155 deletions

View File

@ -441,78 +441,14 @@ class WarnTests(BaseTest):
self.assertEqual(len(w), 1)
self.assertEqual(w[0].filename, __file__)
def test_missing_filename_not_main(self):
# If __file__ is not specified and __main__ is not the module name,
# then __file__ should be set to the module name.
filename = warning_tests.__file__
try:
del warning_tests.__file__
with warnings_state(self.module):
with original_warnings.catch_warnings(record=True,
module=self.module) as w:
warning_tests.inner("spam8", stacklevel=1)
self.assertEqual(w[-1].filename, warning_tests.__name__)
finally:
warning_tests.__file__ = filename
@unittest.skipUnless(hasattr(sys, 'argv'), 'test needs sys.argv')
def test_missing_filename_main_with_argv(self):
# If __file__ is not specified and the caller is __main__ and sys.argv
# exists, then use sys.argv[0] as the file.
filename = warning_tests.__file__
module_name = warning_tests.__name__
try:
del warning_tests.__file__
warning_tests.__name__ = '__main__'
with warnings_state(self.module):
with original_warnings.catch_warnings(record=True,
module=self.module) as w:
warning_tests.inner('spam9', stacklevel=1)
self.assertEqual(w[-1].filename, sys.argv[0])
finally:
warning_tests.__file__ = filename
warning_tests.__name__ = module_name
def test_missing_filename_main_without_argv(self):
# If __file__ is not specified, the caller is __main__, and sys.argv
# is not set, then '__main__' is the file name.
filename = warning_tests.__file__
module_name = warning_tests.__name__
argv = sys.argv
try:
del warning_tests.__file__
warning_tests.__name__ = '__main__'
del sys.argv
with warnings_state(self.module):
with original_warnings.catch_warnings(record=True,
module=self.module) as w:
warning_tests.inner('spam10', stacklevel=1)
self.assertEqual(w[-1].filename, '__main__')
finally:
warning_tests.__file__ = filename
warning_tests.__name__ = module_name
sys.argv = argv
def test_missing_filename_main_with_argv_empty_string(self):
# If __file__ is not specified, the caller is __main__, and sys.argv[0]
# is the empty string, then '__main__ is the file name.
# Tests issue 2743.
file_name = warning_tests.__file__
module_name = warning_tests.__name__
argv = sys.argv
try:
del warning_tests.__file__
warning_tests.__name__ = '__main__'
sys.argv = ['']
with warnings_state(self.module):
with original_warnings.catch_warnings(record=True,
module=self.module) as w:
warning_tests.inner('spam11', stacklevel=1)
self.assertEqual(w[-1].filename, '__main__')
finally:
warning_tests.__file__ = file_name
warning_tests.__name__ = module_name
sys.argv = argv
def test_exec_filename(self):
filename = "<warnings-test>"
codeobj = compile(("import warnings\n"
"warnings.warn('hello', UserWarning)"),
filename, "exec")
with original_warnings.catch_warnings(record=True) as w:
exec(codeobj)
self.assertEqual(w[0].filename, filename)
def test_warn_explicit_non_ascii_filename(self):
with original_warnings.catch_warnings(record=True,
@ -1245,9 +1181,7 @@ class A:
a=A()
"""
rc, out, err = assert_python_ok("-c", code)
# note: "__main__" filename is not correct, it should be the name
# of the script
self.assertEqual(err.decode(), '__main__:7: UserWarning: test')
self.assertEqual(err.decode(), '<string>:7: UserWarning: test')
def test_late_resource_warning(self):
# Issue #21925: Emitting a ResourceWarning late during the Python

View File

@ -303,28 +303,16 @@ def warn(message, category=None, stacklevel=1, source=None):
raise ValueError
except ValueError:
globals = sys.__dict__
filename = "sys"
lineno = 1
else:
globals = frame.f_globals
filename = frame.f_code.co_filename
lineno = frame.f_lineno
if '__name__' in globals:
module = globals['__name__']
else:
module = "<string>"
filename = globals.get('__file__')
if filename:
fnl = filename.lower()
if fnl.endswith(".pyc"):
filename = filename[:-1]
else:
if module == "__main__":
try:
filename = sys.argv[0]
except AttributeError:
# embedded interpreters don't have sys.argv, see bug #839151
filename = '__main__'
if not filename:
filename = module
registry = globals.setdefault("__warningregistry__", {})
warn_explicit(message, category, filename, lineno, module, registry,
globals, source)

View File

@ -0,0 +1,4 @@
The warnings module now finds the Python file associated with a warning from
the code object, rather than the frame's global namespace. This is
consistent with how tracebacks and pdb find filenames, and should work
better for dynamically executed code.

View File

@ -671,7 +671,7 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno,
{
PyObject *globals;
/* Setup globals and lineno. */
/* Setup globals, filename and lineno. */
PyFrameObject *f = PyThreadState_GET()->frame;
// Stack level comparisons to Python code is off by one as there is no
// warnings-related stack level to avoid.
@ -688,10 +688,13 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno,
if (f == NULL) {
globals = PyThreadState_Get()->interp->sysdict;
*filename = PyUnicode_FromString("sys");
*lineno = 1;
}
else {
globals = f->f_globals;
*filename = f->f_code->co_filename;
Py_INCREF(*filename);
*lineno = PyFrame_GetLineNumber(f);
}
@ -726,71 +729,6 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno,
goto handle_error;
}
/* Setup filename. */
*filename = PyDict_GetItemString(globals, "__file__");
if (*filename != NULL && PyUnicode_Check(*filename)) {
Py_ssize_t len;
int kind;
void *data;
if (PyUnicode_READY(*filename))
goto handle_error;
len = PyUnicode_GetLength(*filename);
kind = PyUnicode_KIND(*filename);
data = PyUnicode_DATA(*filename);
#define ascii_lower(c) ((c <= 127) ? Py_TOLOWER(c) : 0)
/* if filename.lower().endswith(".pyc"): */
if (len >= 4 &&
PyUnicode_READ(kind, data, len-4) == '.' &&
ascii_lower(PyUnicode_READ(kind, data, len-3)) == 'p' &&
ascii_lower(PyUnicode_READ(kind, data, len-2)) == 'y' &&
ascii_lower(PyUnicode_READ(kind, data, len-1)) == 'c')
{
*filename = PyUnicode_Substring(*filename, 0,
PyUnicode_GET_LENGTH(*filename)-1);
if (*filename == NULL)
goto handle_error;
}
else
Py_INCREF(*filename);
}
else {
*filename = NULL;
if (*module != Py_None && _PyUnicode_EqualToASCIIString(*module, "__main__")) {
PyObject *argv = _PySys_GetObjectId(&PyId_argv);
/* PyList_Check() is needed because sys.argv is set to None during
Python finalization */
if (argv != NULL && PyList_Check(argv) && PyList_Size(argv) > 0) {
int is_true;
*filename = PyList_GetItem(argv, 0);
Py_INCREF(*filename);
/* If sys.argv[0] is false, then use '__main__'. */
is_true = PyObject_IsTrue(*filename);
if (is_true < 0) {
Py_DECREF(*filename);
goto handle_error;
}
else if (!is_true) {
Py_SETREF(*filename, PyUnicode_FromString("__main__"));
if (*filename == NULL)
goto handle_error;
}
}
else {
/* embedded interpreters don't have sys.argv, see bug #839151 */
*filename = PyUnicode_FromString("__main__");
if (*filename == NULL)
goto handle_error;
}
}
if (*filename == NULL) {
*filename = *module;
Py_INCREF(*filename);
}
}
return 1;
handle_error: