Skip to content

bpo-33375: Get filename for warnings from frame.f_code.co_filename #6622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 9 additions & 75 deletions Lib/test/test_warnings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,78 +420,14 @@ def test_stacklevel_import(self):
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,
Expand Down Expand Up @@ -1224,9 +1160,7 @@ def __del__(self):
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
Expand Down
16 changes: 2 additions & 14 deletions Lib/warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
70 changes: 4 additions & 66 deletions Python/_warnings.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
}

Expand Down Expand Up @@ -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:
Expand Down