Hi,

the gobject.option implementation of GOption currently doesn't handle
errors when for example an option gets the wrong argument (for example:
"--number=text"). 

To support exception in gobject.Option i implemented:

/**
 * Checks if a GError exception occurred in python and set the GError.
 * If the exception is not a GError or the GError exception has no
 * domain, code and message attributes the exception is printed.
 *
 * Ensures the GIL state.
 *
 * Returns: True if a python-exception was set and this exception was a 
 *          correct GError-Exception.
 */
gboolean
pyg_set_gerror_from_exception (GError **error_dest)

To use this function, the python-code must create a gobject.GError()
object and add domain, code and message attributes manually, for
example:

err = gobject.GError(message)
err.domain = gobject.OPTION_ERROR
err.code = gobject.OPTION_ERROR_BAD_VALUE
err.message = "Wrong option ..."
raise err

Or should i add a full GError-class? 

I didn't look, but are there other places in pygtk where a
GError-Exception isn't translated into GError?

The attached patch adds this function and the exception handling in
GOption.

best regards,
        Johannes Hölzl

PS: Are two patches, one with only pyg_set_gerror_from_exception
implementation and another with exception support in GOption preferred?

Index: ChangeLog
===================================================================
RCS file: /cvs/gnome/gnome-python/pygobject/ChangeLog,v
retrieving revision 1.43
diff -u -r1.43 ChangeLog
--- ChangeLog	20 May 2006 13:45:35 -0000	1.43
+++ ChangeLog	25 May 2006 18:25:52 -0000
@@ -1,3 +1,16 @@
+2006-05-24  Johannes Hölzl  <[EMAIL PROTECTED]>
+
+	* gobject/gobjectmodule.c (pyg_exception_to_gerror):
+	Added function to convert gobject.GError-exception into GError.
+	
+	* gobject/gobjectmodule.c (init_gobjectmodule):
+	* gobject/pygoptiongroup.c (arg_func): 
+	* gobject/option.py (OptionParser._parse_args, 
+	OptionGroup._to_goptiongroup):
+	* tests/test_option.py: 
+	Added conversion of Python exceptions
+	into GErrors and vice versa.
+
 2006-05-20  Yevgen Muntyan  <[EMAIL PROTECTED]>
 
 	reviewed by: Gustavo Carneiro
Index: gobject/gobjectmodule.c
===================================================================
RCS file: /cvs/gnome/gnome-python/pygobject/gobject/gobjectmodule.c,v
retrieving revision 1.219
diff -u -r1.219 gobjectmodule.c
--- gobject/gobjectmodule.c	7 May 2006 21:32:21 -0000	1.219
+++ gobject/gobjectmodule.c	25 May 2006 18:25:57 -0000
@@ -2799,6 +2799,66 @@
     return FALSE;
 }
 
+/**
+ * Checks if a GError exception occurred in python and set the GError.
+ * If the exception is not a GError or the GError exception has no
+ * domain, code and message attributes the exception is printed.
+ *
+ * Ensures the GIL state.
+ *
+ * Returns: True if a python-exception was set and this exception was a 
+ *          correct GError-Exception.
+ */
+gboolean
+pyg_set_gerror_from_exception (GError **error_dest)
+{
+    PyObject *exception_type, *exception_value, *exception_tb;
+    GQuark domain;
+    gint code;
+    PyObject *value = NULL;
+    PyGILState_STATE state = pyg_gil_state_ensure();
+
+    g_assert(error_dest != NULL);
+    g_assert(*error_dest == NULL);
+    
+    PyErr_Fetch(&exception_type, &exception_value, &exception_tb);
+    if (!exception_type) return FALSE;
+    if (!PyErr_GivenExceptionMatches(exception_type, gerror_exc)) goto failed;
+    if (!exception_value) goto failed;
+
+    if (!PyObject_HasAttrString(exception_value, "domain")) goto failed;
+    value = PyObject_GetAttrString(exception_value, "domain");
+    if (!PyString_Check(value)) goto failed;
+    domain = g_quark_from_string(PyString_AsString(value));
+    Py_CLEAR(value);
+    
+    if (!PyObject_HasAttrString(exception_value, "code")) goto failed;
+    value = PyObject_GetAttrString(exception_value, "code");
+    if (!PyInt_Check(value)) goto failed;
+    code = PyInt_AsLong(value);
+    Py_CLEAR(value);
+
+    if (!PyObject_HasAttrString(exception_value, "message")) goto failed;
+    value = PyObject_GetAttrString(exception_value, "message");
+    if (!PyString_Check(value)) goto failed;
+    *error_dest = g_error_new_literal(domain, code, PyString_AsString(value));
+    Py_CLEAR(value);
+
+    Py_XDECREF(exception_tb);
+    Py_XDECREF(exception_value);
+    Py_DECREF(exception_type);
+
+    pyg_gil_state_release(state);
+    return TRUE;        
+    
+failed:
+    Py_XDECREF(value);
+    
+    PyErr_Restore(exception_type, exception_value, exception_tb);
+    PyErr_Print();
+    pyg_gil_state_release(state);
+    return FALSE;
+}
 
 static PyObject *
 _pyg_strv_from_gvalue(const GValue *value)
@@ -3106,6 +3166,7 @@
   pyg_constant_strip_prefix,
 
   pyg_error_check,
+  pyg_set_gerror_from_exception,
 
   pyg_set_thread_block_funcs,
   (PyGThreadBlockFunc)0, /* block_threads */
@@ -3322,6 +3383,8 @@
 #undef addint
   
     PyModule_AddStringConstant(m, "OPTION_REMAINING", G_OPTION_REMAINING);
+
+    PyModule_AddStringConstant(m, "OPTION_ERROR", (char*) g_quark_to_string(G_OPTION_ERROR));
 
     PyModule_AddIntConstant(m, "SPAWN_LEAVE_DESCRIPTORS_OPEN",
 			    G_SPAWN_LEAVE_DESCRIPTORS_OPEN);
Index: gobject/option.py
===================================================================
RCS file: /cvs/gnome/gnome-python/pygobject/gobject/option.py,v
retrieving revision 1.1
diff -u -r1.1 option.py
--- gobject/option.py	29 Apr 2006 18:58:20 -0000	1.1
+++ gobject/option.py	25 May 2006 18:25:57 -0000
@@ -31,11 +31,16 @@
 
 import sys
 import optparse
-from optparse import OptionError
+from optparse import OptParseError, OptionError, OptionValueError, \
+                     BadOptionError, OptionConflictError
 import _gobject as gobject
 
 __all__ = [
+    "OptParseError",
     "OptionError",
+    "OptionValueError",
+    "BadOptionError",
+    "OptionConflictError"
     "Option",
     "OptionGroup",
     "OptionParser",
@@ -166,11 +171,18 @@
         
     def _to_goptiongroup(self, parser):
         def callback(option_name, option_value, group):
-            if option_name.startswith('--'):
-                opt = self._long_opt[option_name]
-            else:            
-                opt = self._short_opt[option_name]
-            opt.process(option_name, option_value, self.values, parser)
+	    if option_name.startswith('--'):
+	        opt = self._long_opt[option_name]
+	    else:            
+	        opt = self._short_opt[option_name]
+            try:
+	        opt.process(option_name, option_value, self.values, parser)
+	    except OptionValueError, error:
+	    	gerror = gobject.GError(str(error))
+	    	gerror.domain = gobject.OPTION_ERROR
+	    	gerror.code = gobject.OPTION_ERROR_BAD_VALUE
+	    	gerror.message = str(error)
+	    	raise gerror
                 
         group = gobject.OptionGroup(self.name, self.description, 
                                     self.help_description, callback)
@@ -294,7 +306,21 @@
     
     def _process_args(self, largs, rargs, values):
         context = self._to_goptioncontext(values)
-        largs.extend(context.parse([sys.argv[0]] + rargs))
+	largs.extend(context.parse([sys.argv[0]] + rargs))
 
+    def parse_args(self, args=None, values=None):
+        try:
+            return optparse.OptionParser.parse_args(self, args, values)
+	except gobject.GError, error:
+	    if error.domain != gobject.OPTION_ERROR:
+	    	raise
+	    if error.code == gobject.OPTION_ERROR_BAD_VALUE:
+	        raise OptionValueError(error.message)
+	    elif error.code == gobject.OPTION_ERROR_UNKNOWN_OPTION:
+	    	raise BadOptionError(error.message)
+	    elif error.code == gobject.OPTION_ERROR_FAILED:
+	        raise OptParseError(error.message)
+	    else:
+	        raise
 
 make_option = Option
Index: gobject/pygobject-private.h
===================================================================
RCS file: /cvs/gnome/gnome-python/pygobject/gobject/pygobject-private.h,v
retrieving revision 1.47
diff -u -r1.47 pygobject-private.h
--- gobject/pygobject-private.h	29 Apr 2006 18:58:20 -0000	1.47
+++ gobject/pygobject-private.h	25 May 2006 18:25:57 -0000
@@ -56,6 +56,7 @@
 void     pyg_destroy_notify   (gpointer     user_data);
 gboolean pyg_handler_marshal  (gpointer     user_data);
 gboolean pyg_error_check      (GError     **error);
+gboolean pyg_set_gerror_from_exception (GError **error_dest);
 int      pygobject_constructv (PyGObject   *self,
                                guint        n_parameters,
                                GParameter  *parameters);
Index: gobject/pygobject.h
===================================================================
RCS file: /cvs/gnome/gnome-python/pygobject/gobject/pygobject.h,v
retrieving revision 1.59
diff -u -r1.59 pygobject.h
--- gobject/pygobject.h	7 May 2006 21:32:21 -0000	1.59
+++ gobject/pygobject.h	25 May 2006 18:25:58 -0000
@@ -118,6 +118,7 @@
 				     const gchar *strip_prefix);
 
     gboolean (* error_check)(GError **error);
+    gboolean (* pyg_set_gerror_from_exception)(GError **error_dest);
 
     /* hooks to register handlers for getting GDK threads to cooperate
      * with python threading */
@@ -215,6 +216,7 @@
 #define pyg_flags_add_constants     (_PyGObject_API->flags_add_constants)
 #define pyg_constant_strip_prefix   (_PyGObject_API->constant_strip_prefix)
 #define pyg_error_check             (_PyGObject_API->error_check)
+#define pyg_set_gerror_from_exception (_PyGObject_API->pyg_set_gerror_from_exception)
 #define pyg_set_thread_block_funcs  (_PyGObject_API->set_thread_block_funcs)
 #define PyGParamSpec_Type           (*_PyGObject_API->paramspec_type)
 #define pyg_param_spec_new          (_PyGObject_API->paramspec_new)
Index: gobject/pygoptiongroup.c
===================================================================
RCS file: /cvs/gnome/gnome-python/pygobject/gobject/pygoptiongroup.c,v
retrieving revision 1.1
diff -u -r1.1 pygoptiongroup.c
--- gobject/pygoptiongroup.c	29 Apr 2006 18:58:20 -0000	1.1
+++ gobject/pygoptiongroup.c	25 May 2006 18:25:58 -0000
@@ -38,8 +38,8 @@
     return FALSE;
 }
 
-
-static void destroy_g_group(PyGOptionGroup *self)
+static void
+destroy_g_group(PyGOptionGroup *self)
 {
     PyGILState_STATE state;
     state = pyg_gil_state_ensure();
@@ -100,7 +100,7 @@
 arg_func(const gchar *option_name,
          const gchar *value,
          PyGOptionGroup *self,
-         GError *error)
+         GError **error)
 {
     PyObject *ret;
     PyGILState_STATE state;
@@ -114,15 +114,16 @@
         ret = PyObject_CallFunction(self->callback, "ssO", 
                                     option_name, value, self);
     
-    if (ret == NULL)
-        PyErr_Print();
-
-    pyg_gil_state_release(state);
 
     if (ret == NULL)
-        return FALSE;
+    {
+	pyg_gil_state_release(state);
+	return !pyg_set_gerror_from_exception(error);
+    }
 
     Py_DECREF(ret);
+    pyg_gil_state_release(state);
+
     return TRUE;      
 }
 
Index: tests/test_option.py
===================================================================
RCS file: /cvs/gnome/gnome-python/pygobject/tests/test_option.py,v
retrieving revision 1.1
diff -u -r1.1 test_option.py
--- tests/test_option.py	29 Apr 2006 18:58:20 -0000	1.1
+++ tests/test_option.py	25 May 2006 18:25:58 -0000
@@ -1,13 +1,19 @@
 #!/usr/bin/env python
 
 import unittest
+import sys
 
+from StringIO import StringIO
 from common import gobject
 from gobject import option
 
 class TestOption(unittest.TestCase):
+    EXCEPTION_MESSAGE = "This callback fails"
 
     def setup_group(self):
+        def option_callback(option, opt, value, parser):
+            raise StandardError(self.EXCEPTION_MESSAGE)
+        
         group = option.OptionGroup(
             "unittest", "Unit test options", "Show all unittest options",
             option_list = [
@@ -15,6 +21,15 @@
                                    type="filename",
                                    dest="unit_file",
                                    help="Unit test option"),
+                option.make_option("--test-integer",
+                                   type="int",
+                                   dest="test_integer",
+                                   help="Unit integer option"),
+                option.make_option("--callback-failure-test",
+                                   action="callback",
+                                   callback=option_callback,
+                                   dest="test_integer",
+                                   help="Unit integer option"),
             ])
         group.add_option("-t", "--test", 
                          action="store_false", 
@@ -24,7 +39,8 @@
         return group
 
     def setup_parser(self):
-        parser = option.OptionParser("NAMES...", description="Option unit test")
+        parser = option.OptionParser("NAMES...", 
+                                     description="Option unit test")
         parser.add_option("-t", "--test", help="Unit test option",
                           action="store_false", dest="test", default=True)
         return parser
@@ -38,3 +54,28 @@
         assert group.values.test
         assert not parser.values.test
         assert group.values.unit_file == "test"
+        
+        try:
+            parser.parse_args(["test_option.py", "--test-integer=text"])
+	except option.OptionValueError:
+	    pass
+	else:
+	    assert False
+	
+	sio = StringIO()
+	old_stderr = sys.stderr
+	sys.stderr = sio
+	try:
+	    parser.parse_args(["test_option.py", "--callback-failure-test"])
+	finally:
+	    sys.stderr = old_stderr
+	assert (sio.getvalue().split('\n')[-2] == 
+	        "StandardError: " + self.EXCEPTION_MESSAGE)
+
+	try:
+	    parser.parse_args(["test_option.py", "--unknwon-option"])
+	except option.BadOptionError:
+	    pass
+	else:
+	    assert False
+	
_______________________________________________
pygtk mailing list   pygtk@daa.com.au
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/

Reply via email to