Blender/Python API
authorCampbell Barton <ideasman42@gmail.com>
Sun, 14 Jun 2009 12:53:47 +0000 (12:53 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Sun, 14 Jun 2009 12:53:47 +0000 (12:53 +0000)
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.

source/blender/editors/interface/interface_regions.c
source/blender/editors/space_script/script_edit.c
source/blender/editors/space_text/text_ops.c
source/blender/python/BPY_extern.h
source/blender/python/intern/bpy_interface.c
source/blender/python/intern/bpy_operator_wrap.c
source/blender/python/intern/bpy_util.c
source/blender/python/intern/bpy_util.h
source/creator/creator.c

index 7ccb6c5163b8cd516ea4575b7e5ad0ed949d44ad..32bcae77e6b1d5219714bcbe36409e5f7ad1c080 100644 (file)
@@ -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 */
index 797302a8652eadcc81fd504df5c51b1951598ea1..88b8dccc6c9f096e36efbf97ee82f2916a3a2ab2 100644 (file)
 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)
index ebb42aa2098e4fe75375efddcf4676c5a48a8569..f43888b08da5afeaf45b7cdd1deb024e6ad9d216 100644 (file)
@@ -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 */
index ff3e89a6e25ef937a2ad3f71b174361efc1ec722..855fdde50c5b881b1763cd163a271a3218053cd1 100644 (file)
@@ -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
index 7b3a67ebff5a570b410b4ed4071ee2ab74d18c4f..22336bd4f711a3abd24b770c5892fd9407d41afd 100644 (file)
@@ -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
                }
        }
        
index 8cd1bc64f1104372f8d5cd28e7e04968a4c8d2b3..9b7893a949b82af3c949f80ffcb0d48b73c789c5 100644 (file)
@@ -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
index c447e7de9823fb904f4651a5aaceb214036b2d5c..d5b131583dce7f8fab4be0bb1061158633e88631 100644 (file)
@@ -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;
+}
index 49f488022497155176e014a16a06237dc502101b..6429af67eb0d46ae66d18cd50fbf85e271861d86 100644 (file)
@@ -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);
index 5617435049dcaf492491dc9e42ce46a35492c59c..a19e5d0718cee6eb725c57ebffd3b9736263b5b1 100644 (file)
@@ -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