From 8ab7fbe79563895b38bcfc50625e19fa2b38f843 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Sun, 14 Jun 2009 12:53:47 +0000 Subject: 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. --- .../blender/editors/interface/interface_regions.c | 3 + source/blender/editors/space_script/script_edit.c | 10 +- source/blender/editors/space_text/text_ops.c | 2 +- source/blender/python/BPY_extern.h | 2 +- source/blender/python/intern/bpy_interface.c | 18 ++-- source/blender/python/intern/bpy_operator_wrap.c | 54 +---------- source/blender/python/intern/bpy_util.c | 102 +++++++++++++++++++++ source/blender/python/intern/bpy_util.h | 3 + source/creator/creator.c | 4 +- 9 files changed, 130 insertions(+), 68 deletions(-) (limited to 'source') diff --git a/source/blender/editors/interface/interface_regions.c b/source/blender/editors/interface/interface_regions.c index 7ccb6c5163b..32bcae77e6b 100644 --- a/source/blender/editors/interface/interface_regions.c +++ b/source/blender/editors/interface/interface_regions.c @@ -1948,6 +1948,9 @@ uiBlock *ui_block_func_PUPMENU(bContext *C, uiPopupBlockHandle *handle, void *ar md= decompose_menu_string(info->instr); rows= md->nitems; + if(rows<1) + rows= 1; + columns= 1; /* size and location, title slightly bigger for bold */ diff --git a/source/blender/editors/space_script/script_edit.c b/source/blender/editors/space_script/script_edit.c index 797302a8652..88b8dccc6c9 100644 --- a/source/blender/editors/space_script/script_edit.c +++ b/source/blender/editors/space_script/script_edit.c @@ -62,15 +62,17 @@ static int run_pyfile_exec(bContext *C, wmOperator *op) { ARegion *ar= CTX_wm_region(C); + char filename[512]; RNA_string_get(op->ptr, "filename", filename); #ifndef DISABLE_PYTHON - BPY_run_python_script(C, filename, NULL); + if(BPY_run_python_script(C, filename, NULL, op->reports)) { + ED_region_tag_redraw(ar); + return OPERATOR_FINISHED; + } #endif - ED_region_tag_redraw(ar); - - return OPERATOR_FINISHED; + return OPERATOR_CANCELLED; /* FAIL */ } void SCRIPT_OT_python_file_run(wmOperatorType *ot) diff --git a/source/blender/editors/space_text/text_ops.c b/source/blender/editors/space_text/text_ops.c index ebb42aa2098..f43888b08da 100644 --- a/source/blender/editors/space_text/text_ops.c +++ b/source/blender/editors/space_text/text_ops.c @@ -521,7 +521,7 @@ static int run_script_exec(bContext *C, wmOperator *op) #else Text *text= CTX_data_edit_text(C); - if (BPY_run_python_script( C, NULL, text )) + if (BPY_run_python_script(C, NULL, text, op->reports)) return OPERATOR_FINISHED; /* Dont report error messages while live editing */ diff --git a/source/blender/python/BPY_extern.h b/source/blender/python/BPY_extern.h index ff3e89a6e25..855fdde50c5 100644 --- a/source/blender/python/BPY_extern.h +++ b/source/blender/python/BPY_extern.h @@ -97,7 +97,7 @@ extern "C" { int BPY_menu_invoke( struct BPyMenu *pym, short menutype ); /* 2.5 UI Scripts */ - int BPY_run_python_script( struct bContext *C, const char *filename, struct Text *text ); // 2.5 working + int BPY_run_python_script( struct bContext *C, const char *filename, struct Text *text, struct ReportList *reports ); // 2.5 working int BPY_run_script_space_draw(struct bContext *C, struct SpaceScript * sc); // 2.5 working void BPY_run_ui_scripts(struct bContext *C, int reload); // int BPY_run_script_space_listener(struct bContext *C, struct SpaceScript * sc, struct ARegion *ar, struct wmNotifier *wmn); // 2.5 working diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 7b3a67ebff5..22336bd4f71 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -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 } } diff --git a/source/blender/python/intern/bpy_operator_wrap.c b/source/blender/python/intern/bpy_operator_wrap.c index 8cd1bc64f11..9b7893a949b 100644 --- a/source/blender/python/intern/bpy_operator_wrap.c +++ b/source/blender/python/intern/bpy_operator_wrap.c @@ -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 diff --git a/source/blender/python/intern/bpy_util.c b/source/blender/python/intern/bpy_util.c index c447e7de982..d5b131583dc 100644 --- a/source/blender/python/intern/bpy_util.c +++ b/source/blender/python/intern/bpy_util.c @@ -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, ""); } 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; +} diff --git a/source/blender/python/intern/bpy_util.h b/source/blender/python/intern/bpy_util.h index 49f48802249..6429af67eb0 100644 --- a/source/blender/python/intern/bpy_util.h +++ b/source/blender/python/intern/bpy_util.h @@ -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); diff --git a/source/creator/creator.c b/source/creator/creator.c index 5617435049d..a19e5d0718c 100644 --- a/source/creator/creator.c +++ b/source/creator/creator.c @@ -710,7 +710,7 @@ int main(int argc, char **argv) //XXX // FOR TESTING ONLY a++; - BPY_run_python_script(C, argv[a], NULL); + BPY_run_python_script(C, argv[a], NULL, NULL); // use reports? #if 0 a++; if (a < argc) { @@ -719,7 +719,7 @@ int main(int argc, char **argv) main_init_screen(); scr_init = 1; } - BPY_run_python_script(C, argv[a], NULL); + BPY_run_python_script(C, argv[a], NULL, NULL); // use reports? } else printf("\nError: you must specify a Python script after '-P '.\n"); #endif -- cgit v1.2.3