swig-py: Allow SubversionException to add attributes
A C API function can simultaneously return an error plus additional
non-error information. But in Python, returning an error (that is,
raising an exception) and returning (as a function return value) other
non-error information are somewhat mutually exclusive. With this
patch, we introduce a new set of typemaps to allow the bindings code
to attach any non-error information returned from an errorful C API
function as attributes to the raised exception so that callers can
access everything the C API returned.
[in subversion/bindings/swig/]
* include/svn_types.swg
(typemap(out) svn_error_t * SVN_ERR_WITH_ATTRS,
typemap(ret) svn_error_t * SVN_ERR_WITH_ATTRS):
New typemaps to all wrapper functions to use the attributes-on-
exception-objects feature. With these typemaps, the argout
typemaps can use the status code of the subversion error 'apr_err'
and the exception instance object 'exc_ob'.
* python/libsvn_swig_py/swigutil_py.h, python/libsvn_swig_py/swigutil_py.c
(svn_swig_py_build_svn_exception): New function, abstracted from...
(svn_swig_py_svn_exception): ...this, which now uses
svn_swig_py_build_svn_exception() to build the exception.
* svn_fs.i
(typemap(argout) (const char **conflict_p, svn_revnum_t *new_rev)):
Removed. This reverts the behavor that svn.fs.commit_txn()
always returns a 2-tuple of which the first item is always None.
(typemap(argout) (const char **conflict_p)):
New custom typemap to add the conflict path as an attibute to the
exception instance. This applies to svn_fs_merge(),
svn_fs_commit_txn() and svn_repos_fs_commit_txn().
(typemap(argout) (svn_revnum_t *new_rev)):
New custom typemap to add the created revision as an attibute to
the exception instance. This applies to svn_fs_commit_txn() and
svn_repos_fs_commit_txn().
(): Apply the 'SVN_ERR_WITH_ATTRS' typemap to svn_fs_merge()
and svn_fs_commit_txn().
* svn_repos.i
(): Apply the 'SVN_ERR_WITH_ATTRS' typemap to svn_repos_fs_merge().
Patch by: Yasuhito FUTATSUKI <futatuki at yf.bsdclub.org>
(Tweaked by me.)
git-svn-id: https://svn.apache.org/repos/asf/subversion/trunk@1880967 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/subversion/bindings/swig/include/svn_types.swg b/subversion/bindings/swig/include/svn_types.swg
index 7c933b1..30cd6b5 100644
--- a/subversion/bindings/swig/include/svn_types.swg
+++ b/subversion/bindings/swig/include/svn_types.swg
@@ -438,6 +438,55 @@
Py_INCREF(Py_None);
$result = Py_None;
}
+
+%typemap(out) svn_error_t * SVN_ERR_WITH_ATTRS (apr_status_t apr_err,
+ PyObject *exc_class,
+ PyObject *exc_ob) {
+ apr_err = 0;
+ exc_class = exc_ob = NULL;
+ if ($1 != NULL)
+ {
+ apr_err = $1->apr_err;
+ if (apr_err != SVN_ERR_SWIG_PY_EXCEPTION_SET)
+ {
+ svn_swig_py_build_svn_exception(&exc_class, &exc_ob, $1);
+ if (exc_ob == NULL)
+ {
+ /* We couldn't get an exception instance. */
+ if (exc_class != NULL)
+ {
+ /* Raise an exception without instance ... */
+ PyErr_SetNone(exc_class);
+ Py_DECREF(exc_class);
+ }
+ SWIG_fail;
+ }
+ /* We have an exeception instance, but we don't raise it.
+ Our caller will have to do that. */
+ }
+ else
+ {
+ svn_error_clear($1);
+ SWIG_fail;
+ }
+ }
+ else
+ {
+ Py_INCREF(Py_None);
+ $result = Py_None;
+ }
+}
+
+%typemap(ret) svn_error_t * SVN_ERR_WITH_ATTRS {
+ if (exc_ob != NULL)
+ {
+ PyErr_SetObject(exc_class, exc_ob);
+ Py_DECREF(exc_class);
+ Py_DECREF(exc_ob);
+ Py_XDECREF($result);
+ SWIG_fail;
+ }
+}
#endif
#ifdef SWIGPERL
diff --git a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
index 7f2cfd8..54629bc 100644
--- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
+++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.c
@@ -411,10 +411,12 @@
/*** Custom SubversionException stuffs. ***/
-void svn_swig_py_svn_exception(svn_error_t *error_chain)
+void svn_swig_py_build_svn_exception(PyObject **exc_class,
+ PyObject **exc_ob,
+ svn_error_t *error_chain)
{
PyObject *args_list, *args, *apr_err_ob, *message_ob, *file_ob, *line_ob;
- PyObject *svn_module, *exc_class, *exc_ob;
+ PyObject *svn_module;
svn_error_t *err;
if (error_chain == NULL)
@@ -422,7 +424,7 @@
/* Start with no references. */
args_list = args = apr_err_ob = message_ob = file_ob = line_ob = NULL;
- svn_module = exc_class = exc_ob = NULL;
+ svn_module = *exc_class = *exc_ob = NULL;
if ((args_list = PyList_New(0)) == NULL)
goto finished;
@@ -481,15 +483,12 @@
/* Create the exception object chain. */
if ((svn_module = PyImport_ImportModule((char *)"svn.core")) == NULL)
goto finished;
- if ((exc_class = PyObject_GetAttrString(svn_module,
- (char *)"SubversionException")) == NULL)
- goto finished;
- if ((exc_ob = PyObject_CallMethod(exc_class, (char *)"_new_from_err_list",
- (char *)"O", args_list)) == NULL)
- goto finished;
-
- /* Raise the exception. */
- PyErr_SetObject(exc_class, exc_ob);
+ if ((*exc_class = PyObject_GetAttrString(svn_module,
+ (char *)"SubversionException")) != NULL)
+ {
+ *exc_ob = PyObject_CallMethod(*exc_class, (char *)"_new_from_err_list",
+ (char *)"O", args_list);
+ }
finished:
/* Release any references. */
@@ -500,8 +499,30 @@
Py_XDECREF(file_ob);
Py_XDECREF(line_ob);
Py_XDECREF(svn_module);
- Py_XDECREF(exc_class);
- Py_XDECREF(exc_ob);
+}
+
+void svn_swig_py_svn_exception(svn_error_t *error_chain)
+{
+ PyObject *exc_class, *exc_ob;
+
+ /* First, we create the exception... */
+ svn_swig_py_build_svn_exception(&exc_class, &exc_ob, error_chain);
+
+ /* ...then, we raise it. If got only an exception class but no
+ instance, we'll raise the class without an instance. */
+ if (exc_class != NULL)
+ {
+ if (exc_ob != NULL)
+ {
+ PyErr_SetObject(exc_class, exc_ob);
+ Py_DECREF(exc_ob);
+ }
+ else
+ {
+ PyErr_SetNone(exc_class);
+ }
+ Py_DECREF(exc_class);
+ }
}
diff --git a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
index 7650cec..2998adf 100644
--- a/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
+++ b/subversion/bindings/swig/python/libsvn_swig_py/swigutil_py.h
@@ -98,11 +98,18 @@
/* Wrapper for SWIG_MustGetPtr */
void *svn_swig_py_must_get_ptr(void *input, swig_type_info *type, int argnum);
+
/*** Functions to expose a custom SubversionException ***/
-/* raise a subversion exception, created from a normal subversion
- error. consume the error. */
+/* Get a SubversionException class object and its instance built from
+ error_chain, but do not raise it immediately. Consume the
+ error_chain. */
+void svn_swig_py_build_svn_exception(
+ PyObject **exc_class, PyObject **exc_ob, svn_error_t *error_chain);
+
+/* Raise a SubversionException, created from a normal subversion
+ error. Consume the error. */
void svn_swig_py_svn_exception(svn_error_t *err);
diff --git a/subversion/bindings/swig/svn_fs.i b/subversion/bindings/swig/svn_fs.i
index ae1d847..6fda830 100644
--- a/subversion/bindings/swig/svn_fs.i
+++ b/subversion/bindings/swig/svn_fs.i
@@ -93,23 +93,68 @@
%apply apr_hash_t *MERGEINFO { apr_hash_t *mergeinhash };
/* -----------------------------------------------------------------------
- Fix the return value for svn_fs_commit_txn(). If the conflict result is
- NULL, then %append_output() is passed Py_None, but that goofs up
- because that is *also* the marker for "I haven't started assembling a
- multi-valued return yet" which means the second return value (new_rev)
- will not cause a 2-tuple to be manufactured.
-
- The answer is to explicitly create a 2-tuple return value.
-
- FIXME: Do the Perl and Ruby bindings need to do something similar?
+ Tweak a SubversionException instance when it is raised in
+ svn_fs_merge(), svn_fs_commit_txn() and svn_repos_fs_commit_txn().
+ Those APIs return conflicts (and revision number on svn_fs_commit_txn
+ and svn_repos_fs_commit_txn) related to the conflict error when it
+ is occured. As Python wrapper functions report errors by raising
+ exceptions and don't return values, we use attributes of the exception
+ to pass these values to the caller.
*/
+
#ifdef SWIGPYTHON
-%typemap(argout) (const char **conflict_p, svn_revnum_t *new_rev) {
- /* this is always Py_None */
- Py_DECREF($result);
- /* build the result tuple */
- $result = Py_BuildValue("zi", *$1, (long)*$2);
+%typemap(argout) (const char **conflict_p) (PyObject* conflict_ob) {
+ if (*$1 == NULL)
+ {
+ Py_INCREF(Py_None);
+ conflict_ob = Py_None;
+ }
+ else
+ {
+ /* Note: We can check if apr_err == SVN_ERR_FS_CONFLICT or not
+ before access to *$1 */
+ conflict_ob = PyBytes_FromString((const char *)*$1);
+ if (conflict_ob == NULL)
+ {
+ Py_XDECREF(exc_ob);
+ Py_XDECREF($result);
+ SWIG_fail;
+ }
+ }
+ if (exc_ob != NULL)
+ {
+ PyObject_SetAttrString(exc_ob, "$1_name", conflict_ob);
+ Py_DECREF(conflict_ob);
+ }
+ else
+ {
+ %append_output(conflict_ob);
+ }
}
+
+%typemap(argout) svn_revnum_t *new_rev (PyObject *rev_ob) {
+ rev_ob = PyInt_FromLong((long)*$1);
+ if (rev_ob == NULL)
+ {
+ Py_XDECREF(exc_ob);
+ Py_XDECREF($result);
+ SWIG_fail;
+ }
+ if (exc_ob != NULL)
+ {
+ PyObject_SetAttrString(exc_ob, "$1_name", rev_ob);
+ Py_DECREF(rev_ob);
+ }
+ else
+ {
+ %append_output(rev_ob);
+ }
+}
+
+%apply svn_error_t *SVN_ERR_WITH_ATTRS {
+ svn_error_t * svn_fs_commit_txn,
+ svn_error_t * svn_fs_merge
+};
#endif
/* Ruby fixups for functions not following the pool convention. */
diff --git a/subversion/bindings/swig/svn_repos.i b/subversion/bindings/swig/svn_repos.i
index bab95c8..2c4532f 100644
--- a/subversion/bindings/swig/svn_repos.i
+++ b/subversion/bindings/swig/svn_repos.i
@@ -109,6 +109,16 @@
#endif
/* -----------------------------------------------------------------------
+ Tweak a SubversionException instance (See svn_fs.i for detail).
+*/
+
+#ifdef SWIGPYTHON
+%apply svn_error_t *SVN_ERR_WITH_ATTRS {
+ svn_error_t * svn_repos_fs_commit_txn
+};
+#endif
+
+/* -----------------------------------------------------------------------
handle svn_repos_get_committed_info().
*/
#ifdef SWIGRUBY