Blender/Python API
Send the full python stack trace to the reporting api, added BPY_exception_buffer which temporarily overrides sys.stdout and sys.stderr to get the output (uses the io module in py3 StringIO in py2 to avoid writing into a real file), pity the Py/C api has no function to do this. fix for crash when showing menu's that have no items.
This commit is contained in:
@@ -150,7 +150,7 @@ void BPY_end_python( void )
|
||||
}
|
||||
|
||||
/* Can run a file or text block */
|
||||
int BPY_run_python_script( bContext *C, const char *fn, struct Text *text )
|
||||
int BPY_run_python_script( bContext *C, const char *fn, struct Text *text, struct ReportList *reports)
|
||||
{
|
||||
PyObject *py_dict, *py_result;
|
||||
PyGILState_STATE gilstate;
|
||||
@@ -178,7 +178,7 @@ int BPY_run_python_script( bContext *C, const char *fn, struct Text *text )
|
||||
MEM_freeN( buf );
|
||||
|
||||
if( PyErr_Occurred( ) ) {
|
||||
PyErr_Print(); PyErr_Clear();
|
||||
BPy_errors_to_report(reports);
|
||||
BPY_free_compiled_text( text );
|
||||
PyGILState_Release(gilstate);
|
||||
return 0;
|
||||
@@ -194,7 +194,7 @@ int BPY_run_python_script( bContext *C, const char *fn, struct Text *text )
|
||||
}
|
||||
|
||||
if (!py_result) {
|
||||
PyErr_Print(); PyErr_Clear();
|
||||
BPy_errors_to_report(reports);
|
||||
} else {
|
||||
Py_DECREF( py_result );
|
||||
}
|
||||
@@ -221,7 +221,7 @@ static void exit_pydraw( SpaceScript * sc, short err )
|
||||
script = sc->script;
|
||||
|
||||
if( err ) {
|
||||
PyErr_Print(); PyErr_Clear();
|
||||
BPy_errors_to_report(NULL); // TODO, reports
|
||||
script->flags = 0; /* mark script struct for deletion */
|
||||
SCRIPT_SET_NULL(script);
|
||||
script->scriptname[0] = '\0';
|
||||
@@ -250,7 +250,7 @@ static int bpy_run_script_init(bContext *C, SpaceScript * sc)
|
||||
return 0;
|
||||
|
||||
if (sc->script->py_draw==NULL && sc->script->scriptname[0] != '\0')
|
||||
BPY_run_python_script(C, sc->script->scriptname, NULL);
|
||||
BPY_run_python_script(C, sc->script->scriptname, NULL, NULL);
|
||||
|
||||
if (sc->script->py_draw==NULL)
|
||||
return 0;
|
||||
@@ -329,7 +329,7 @@ int BPY_run_python_script_space(const char *modulename, const char *func)
|
||||
}
|
||||
|
||||
if (!py_result) {
|
||||
PyErr_Print(); PyErr_Clear();
|
||||
BPy_errors_to_report(NULL); // TODO - reports
|
||||
} else
|
||||
Py_DECREF( py_result );
|
||||
|
||||
@@ -410,7 +410,7 @@ void BPY_run_ui_scripts(bContext *C, int reload)
|
||||
if(mod) {
|
||||
Py_DECREF(mod); /* could be NULL from reloading */
|
||||
} else {
|
||||
PyErr_Print(); PyErr_Clear();
|
||||
BPy_errors_to_report(NULL); // TODO - reports
|
||||
fprintf(stderr, "unable to import \"%s\" %s/%s\n", path, dirname, de->d_name);
|
||||
}
|
||||
}
|
||||
@@ -530,7 +530,7 @@ static float pydriver_error(ChannelDriver *driver)
|
||||
driver->flag |= DRIVER_FLAG_INVALID; /* py expression failed */
|
||||
fprintf(stderr, "\nError in Driver: The following Python expression failed:\n\t'%s'\n\n", driver->expression);
|
||||
|
||||
PyErr_Print(); PyErr_Clear();
|
||||
BPy_errors_to_report(NULL); // TODO - reports
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
@@ -589,7 +589,7 @@ float BPY_pydriver_eval (ChannelDriver *driver)
|
||||
}
|
||||
|
||||
fprintf(stderr, "\tBPY_pydriver_eval() - couldn't add variable '%s' to namespace \n", dtar->name);
|
||||
PyErr_Print(); PyErr_Clear();
|
||||
BPy_errors_to_report(NULL); // TODO - reports
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,54 +137,6 @@ static PyObject *pyop_dict_from_event(wmEvent *event)
|
||||
return dict;
|
||||
}
|
||||
|
||||
/* TODO - a whole traceback would be ideal */
|
||||
static void pyop_error_report(ReportList *reports)
|
||||
{
|
||||
const char *string;
|
||||
PyObject *exception, *v, *tb;
|
||||
PyErr_Fetch(&exception, &v, &tb);
|
||||
if (exception == NULL)
|
||||
return;
|
||||
|
||||
/* get the string from the exception */
|
||||
if(v==NULL) {
|
||||
string= "py exception not set";
|
||||
}
|
||||
else if(string = _PyUnicode_AsString(v)) {
|
||||
/* do nothing */
|
||||
}
|
||||
else { /* a valid PyObject but not a string, try get its string value */
|
||||
PyObject *repr;
|
||||
|
||||
Py_INCREF(v); /* incase clearing the error below somehow frees this */
|
||||
PyErr_Clear();
|
||||
|
||||
repr= PyObject_Repr(v);
|
||||
|
||||
if(repr==NULL) {
|
||||
PyErr_Clear();
|
||||
string= "py exception found but can't be converted";
|
||||
}
|
||||
else {
|
||||
string = _PyUnicode_AsString(repr);
|
||||
Py_DECREF(repr);
|
||||
|
||||
if(string==NULL) { /* unlikely to happen */
|
||||
PyErr_Clear();
|
||||
string= "py exception found but can't be converted";
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(v); /* finished dealing with v, PyErr_Clear isnt called anymore so can decref it */
|
||||
}
|
||||
/* done getting the string */
|
||||
|
||||
/* Now we know v != NULL too */
|
||||
BKE_report(reports, RPT_ERROR, string);
|
||||
|
||||
PyErr_Print();
|
||||
}
|
||||
|
||||
static struct BPY_flag_def pyop_ret_flags[] = {
|
||||
{"RUNNING_MODAL", OPERATOR_RUNNING_MODAL},
|
||||
{"CANCELLED", OPERATOR_CANCELLED},
|
||||
@@ -291,13 +243,13 @@ static int PYTHON_OT_generic(int mode, bContext *C, wmOperator *op, wmEvent *eve
|
||||
}
|
||||
|
||||
if (ret == NULL) { /* covers py_class_instance failing too */
|
||||
pyop_error_report(op->reports);
|
||||
BPy_errors_to_report(op->reports);
|
||||
}
|
||||
else {
|
||||
if (mode==PYOP_POLL) {
|
||||
if (PyBool_Check(ret) == 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "Python poll function return value ");
|
||||
pyop_error_report(op->reports);
|
||||
BPy_errors_to_report(op->reports);
|
||||
}
|
||||
else {
|
||||
ret_flag= ret==Py_True ? 1:0;
|
||||
@@ -305,7 +257,7 @@ static int PYTHON_OT_generic(int mode, bContext *C, wmOperator *op, wmEvent *eve
|
||||
|
||||
} else if (BPY_flag_from_seq(pyop_ret_flags, ret, &ret_flag) == -1) {
|
||||
/* the returned value could not be converted into a flag */
|
||||
pyop_error_report(op->reports);
|
||||
BPy_errors_to_report(op->reports);
|
||||
|
||||
}
|
||||
/* there is no need to copy the py keyword dict modified by
|
||||
|
||||
@@ -168,6 +168,12 @@ void PyObSpit(char *name, PyObject *var) {
|
||||
PyObject_Print(var, stderr, 0);
|
||||
fprintf(stderr, " ref:%d ", var->ob_refcnt);
|
||||
fprintf(stderr, " ptr:%ld", (long)var);
|
||||
|
||||
fprintf(stderr, " type:");
|
||||
if(Py_TYPE(var))
|
||||
fprintf(stderr, "%s", Py_TYPE(var)->tp_name);
|
||||
else
|
||||
fprintf(stderr, "<NIL>");
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
@@ -329,6 +335,72 @@ int BPY_class_validate(const char *class_type, PyObject *class, PyObject *base_c
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* returns the exception string as a new PyUnicode object, depends on external StringIO module */
|
||||
PyObject *BPY_exception_buffer(void)
|
||||
{
|
||||
PyObject *stdout_backup = PySys_GetObject("stdout"); /* borrowed */
|
||||
PyObject *stderr_backup = PySys_GetObject("stderr"); /* borrowed */
|
||||
PyObject *string_io = NULL;
|
||||
PyObject *string_io_buf = NULL;
|
||||
PyObject *string_io_mod;
|
||||
PyObject *string_io_getvalue;
|
||||
|
||||
PyObject *error_type, *error_value, *error_traceback;
|
||||
|
||||
if (!PyErr_Occurred())
|
||||
return NULL;
|
||||
|
||||
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
||||
|
||||
PyErr_Clear();
|
||||
|
||||
/* import StringIO / io
|
||||
* string_io = StringIO.StringIO()
|
||||
*/
|
||||
|
||||
#if PY_VERSION_HEX < 0x03000000
|
||||
if(! (string_io_mod= PyImport_ImportModule("StringIO")) ) {
|
||||
#else
|
||||
if(! (string_io_mod= PyImport_ImportModule("io")) ) {
|
||||
#endif
|
||||
return NULL;
|
||||
} else if (! (string_io = PyObject_CallMethod(string_io_mod, "StringIO", NULL))) {
|
||||
Py_DECREF(string_io_mod);
|
||||
return NULL;
|
||||
} else if (! (string_io_getvalue= PyObject_GetAttrString(string_io, "getvalue"))) {
|
||||
Py_DECREF(string_io_mod);
|
||||
Py_DECREF(string_io);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_INCREF(stdout_backup); // since these were borrowed we dont want them freed when replaced.
|
||||
Py_INCREF(stderr_backup);
|
||||
|
||||
PySys_SetObject("stdout", string_io); // both of these are free'd when restoring
|
||||
PySys_SetObject("stderr", string_io);
|
||||
|
||||
PyErr_Restore(error_type, error_value, error_traceback);
|
||||
PyErr_Print(); /* print the error */
|
||||
PyErr_Clear();
|
||||
|
||||
string_io_buf = PyObject_CallObject(string_io_getvalue, NULL);
|
||||
|
||||
PySys_SetObject("stdout", stdout_backup);
|
||||
PySys_SetObject("stderr", stderr_backup);
|
||||
|
||||
Py_DECREF(stdout_backup); /* now sys owns the ref again */
|
||||
Py_DECREF(stderr_backup);
|
||||
|
||||
Py_DECREF(string_io_mod);
|
||||
Py_DECREF(string_io_getvalue);
|
||||
Py_DECREF(string_io); /* free the original reference */
|
||||
|
||||
PyErr_Clear();
|
||||
return string_io_buf;
|
||||
}
|
||||
|
||||
char *BPy_enum_as_string(EnumPropertyItem *item)
|
||||
{
|
||||
DynStr *dynstr= BLI_dynstr_new();
|
||||
@@ -358,3 +430,33 @@ int BPy_reports_to_error(ReportList *reports)
|
||||
return (report_str != NULL);
|
||||
}
|
||||
|
||||
|
||||
int BPy_errors_to_report(ReportList *reports)
|
||||
{
|
||||
PyObject *pystring;
|
||||
char *cstring;
|
||||
|
||||
if (!PyErr_Occurred())
|
||||
return 1;
|
||||
|
||||
/* less hassle if we allow NULL */
|
||||
if(reports==NULL) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
return 1;
|
||||
}
|
||||
|
||||
pystring= BPY_exception_buffer();
|
||||
|
||||
if(pystring==NULL) {
|
||||
BKE_report(reports, RPT_ERROR, "unknown py-exception, could not convert");
|
||||
return 0;
|
||||
}
|
||||
|
||||
cstring= _PyUnicode_AsString(pystring);
|
||||
|
||||
BKE_report(reports, RPT_ERROR, cstring);
|
||||
fprintf(stderr, "%s\n", cstring); // not exactly needed. just for testing
|
||||
Py_DECREF(pystring);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ void PyObSpit(char *name, PyObject *var);
|
||||
void PyLineSpit(void);
|
||||
void BPY_getFileAndNum(char **filename, int *lineno);
|
||||
|
||||
PyObject *BPY_exception_buffer(void);
|
||||
|
||||
/* own python like utility function */
|
||||
PyObject *PyObject_GetAttrStringArgs(PyObject *o, Py_ssize_t n, ...);
|
||||
|
||||
@@ -73,6 +75,7 @@ char *BPy_enum_as_string(struct EnumPropertyItem *item);
|
||||
|
||||
/* error reporting */
|
||||
int BPy_reports_to_error(struct ReportList *reports);
|
||||
int BPy_errors_to_report(struct ReportList *reports);
|
||||
|
||||
/* TODO - find a better solution! */
|
||||
struct bContext *BPy_GetContext(void);
|
||||
|
||||
Reference in New Issue
Block a user