Skip to content

Commit

Permalink
Merge branch 'maint'
Browse files Browse the repository at this point in the history
  • Loading branch information
sgillies committed Mar 16, 2015
2 parents 749297b + c3e4fb6 commit 187453d
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 14 deletions.
2 changes: 1 addition & 1 deletion shapely/ctypes_declarations.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class allocated_c_char_p(c_char_p):
'''char pointer return type'''
pass

EXCEPTION_HANDLER_FUNCTYPE = CFUNCTYPE(None, c_char_p, c_char_p)
EXCEPTION_HANDLER_FUNCTYPE = CFUNCTYPE(None, c_char_p, c_void_p)


def prototype(lgeos, geos_version):
Expand Down
43 changes: 30 additions & 13 deletions shapely/geos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
Proxies for libgeos, GEOS-specific exceptions, and utilities
"""

import re
import sys
import atexit
import logging
import threading
from ctypes import pointer, c_void_p, c_size_t, c_char_p, string_at
from ctypes import CDLL, cdll, pointer, string_at, cast, POINTER
from ctypes import c_void_p, c_size_t, c_char_p, c_int, c_float
from ctypes.util import find_library

from .ctypes_declarations import prototype, EXCEPTION_HANDLER_FUNCTYPE
from .libgeos import lgeos as _lgeos, geos_version
Expand Down Expand Up @@ -80,19 +83,33 @@ class TopologicalError(Exception):
class PredicateError(Exception):
pass


def error_handler(fmt, *args):
if sys.version_info[0] >= 3:
# While this function can take any number of positional arguments when
# called from Python and GEOS expects its error handler to accept any
# number of arguments (like printf), I'm unable to get ctypes to make
# a callback object from this function that will accept any number of
# arguments.
#
# At the moment, functions in the GEOS C API only pass 0 or 1 arguments
# to the error handler. We can deal with this, but when if that changes,
# Shapely may break.

def handler(level):
def callback(fmt, *args):
fmt = fmt.decode('ascii')
args = [arg.decode('ascii') for arg in args]
LOG.error(fmt, *args)


def notice_handler(fmt, args):
if sys.version_info[0] >= 3:
fmt = fmt.decode('ascii')
args = args.decode('ascii')
LOG.warning(fmt, args)
conversions = re.findall(r'%.', fmt)
log_vals = []
for spec, arg in zip(conversions, args):
if spec == '%s' and arg is not None:
log_vals.append(string_at(arg).decode('ascii'))
else:
LOG.error("An error occurred, but the format string "
"'%s' could not be converted.", fmt)
return
getattr(LOG, level)(fmt, *log_vals)
return callback

error_handler = handler('error')
notice_handler = handler('warning')

error_h = EXCEPTION_HANDLER_FUNCTYPE(error_handler)
notice_h = EXCEPTION_HANDLER_FUNCTYPE(notice_handler)
Expand Down
33 changes: 33 additions & 0 deletions tests/test_geos_err_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import logging

import pytest

from shapely.geometry import LineString
from shapely.geos import ReadingError
from shapely.wkt import loads


def test_error_handler(tmpdir):
logger = logging.getLogger('shapely.geos')
logger.setLevel(logging.DEBUG)

logfile = str(tmpdir.join('test_error.log'))
fh = logging.FileHandler(logfile)
logger.addHandler(fh)

# This operation calls error_handler with a format string that
# has *no* conversion specifiers.
LineString([(0, 0), (2, 2)]).project(LineString([(1, 1), (1.5, 1.5)]))

# This calls error_handler with a format string of "%s" and one
# value.
with pytest.raises(ReadingError):
loads('POINT (LOLWUT)')

g = loads('MULTIPOLYGON (((10 20, 10 120, 60 70, 30 70, 30 40, 60 40, 60 70, 90 20, 10 20)))')
assert g.is_valid == False

log = open(logfile).read()
assert "third argument of GEOSProject_r must be Point*" in log
assert "Expected number but encountered word: 'LOLWUT'" in log
assert "Ring Self-intersection at or near point 60 70" in log

0 comments on commit 187453d

Please sign in to comment.