python plugin: catch and report exceptions, closes #1422

This commit is contained in:
Dane Springmeyer 2012-10-19 17:05:51 -07:00
parent 8c2604e69c
commit bc4a74f5b0
4 changed files with 128 additions and 69 deletions

View file

@ -15,6 +15,7 @@ plugin_sources = Split(
"""
%(PLUGIN_NAME)s_datasource.cpp
%(PLUGIN_NAME)s_featureset.cpp
%(PLUGIN_NAME)s_utils.cpp
""" % locals()
)

View file

@ -69,17 +69,17 @@ void python_datasource::bind() const
// if no factory callable is defined, bind is a nop
if (factory_.empty()) return;
try
{
// split factory at ':' to parse out module and callable
std::vector<std::string> factory_split;
split(factory_split, factory_, is_any_of(":"));
if ((factory_split.size() < 1) || (factory_split.size() > 2))
{
// FIMXE: is this appropriate error reporting?
std::cerr << "python: factory string must be of the form '[module:]callable' when parsing \""
<< factory_ << '"' << std::endl;
return;
throw mapnik::datasource_exception(
std::string("python: factory string must be of the form '[module:]callable' when parsing \"")
+ factory_ + '"');
}
// extract the module and the callable
str module_name("__main__"), callable_name;
if (factory_split.size() == 1)
@ -91,16 +91,12 @@ void python_datasource::bind() const
module_name = str(factory_split[0]);
callable_name = str(factory_split[1]);
}
{
ensure_gil lock;
// import the main module from Python (in case we're embedding the
// interpreter directly) and also import the callable.
object main_module = import("__main__");
object callable_module = import(module_name);
object callable = callable_module.attr(callable_name);
// prepare the arguments
dict kwargs;
typedef std::map<std::string, std::string>::value_type kv_type;
@ -112,6 +108,10 @@ void python_datasource::bind() const
// get our wrapped data source
datasource_ = callable(*boost::python::make_tuple(), **kwargs);
}
catch ( error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
is_bound_ = true;
}
@ -124,12 +124,19 @@ mapnik::datasource::datasource_t python_datasource::type() const
if (!is_bound_) bind();
try
{
ensure_gil lock;
object data_type = datasource_.attr("data_type");
long data_type_integer = extract<long>(data_type);
return mapnik::datasource::datasource_t(data_type_integer);
}
catch ( error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}
mapnik::box2d<double> python_datasource::envelope() const
{
@ -137,9 +144,16 @@ mapnik::box2d<double> python_datasource::envelope() const
if (!is_bound_) bind();
try
{
ensure_gil lock;
return extract<mapnik::box2d<double> >(datasource_.attr("envelope"));
}
catch ( error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}
boost::optional<mapnik::datasource::geometry_t> python_datasource::get_geometry_type() const
{
@ -149,21 +163,28 @@ boost::optional<mapnik::datasource::geometry_t> python_datasource::get_geometry_
if (!is_bound_) bind();
try
{
ensure_gil lock;
// if the datasource object has no geometry_type attribute, return a 'none' value
if (!PyObject_HasAttrString(datasource_.ptr(), "geometry_type"))
{
return return_type();
}
object py_geometry_type = datasource_.attr("geometry_type");
// if the attribute value is 'None', return a 'none' value
if (py_geometry_type.ptr() == object().ptr())
{
return return_type();
}
long geom_type_integer = extract<long>(py_geometry_type);
return mapnik::datasource::geometry_t(geom_type_integer);
}
catch ( error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}
mapnik::featureset_ptr python_datasource::features(mapnik::query const& q) const
{
@ -171,23 +192,28 @@ mapnik::featureset_ptr python_datasource::features(mapnik::query const& q) const
if (!is_bound_) bind();
try
{
// if the query box intersects our world extent then query for features
if (envelope().intersects(q.get_bbox()))
{
ensure_gil lock;
object features(datasource_.attr("features")(q));
// if 'None' was returned, return an empty feature set
if(features.ptr() == object().ptr())
{
return mapnik::featureset_ptr();
}
return boost::make_shared<python_featureset>(features);
}
// otherwise return an empty featureset pointer
return mapnik::featureset_ptr();
}
catch ( error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}
mapnik::featureset_ptr python_datasource::features_at_point(mapnik::coord2d const& pt, double tol) const
{
@ -195,14 +221,21 @@ mapnik::featureset_ptr python_datasource::features_at_point(mapnik::coord2d cons
if (!is_bound_) bind();
try
{
ensure_gil lock;
object features(datasource_.attr("features_at_point")(pt));
// if we returned none, return an empty set
if(features.ptr() == object().ptr())
{
return mapnik::featureset_ptr();
}
// otherwise, return a feature set which can iterate over the iterator
return boost::make_shared<python_featureset>(features);
}
catch ( error_already_set )
{
throw mapnik::datasource_exception(extractException());
}
}

View file

@ -0,0 +1,23 @@
#include "python_utils.hpp"
std::string extractException()
{
using namespace boost::python;
PyObject *exc,*val,*tb;
PyErr_Fetch(&exc,&val,&tb);
PyErr_NormalizeException(&exc,&val,&tb);
handle<> hexc(exc),hval(allow_null(val)),htb(allow_null(tb));
if(!hval)
{
return extract<std::string>(str(hexc));
}
else
{
object traceback(import("traceback"));
object format_exception(traceback.attr("format_exception"));
object formatted_list(format_exception(hexc,hval,htb));
object formatted(str("").join(formatted_list));
return extract<std::string>(formatted);
}
}

View file

@ -13,4 +13,6 @@ class ensure_gil
PyGILState_STATE gil_state_;
};
std::string extractException();
#endif // PYTHON_UTILS_HPP