[pypy-svn] r79728 - in pypy/trunk: lib_pypy/_ctypes pypy/module/test_lib_pypy/ctypes_tests
afa at codespeak.net
afa at codespeak.net
Thu Dec 2 00:45:41 CET 2010
Author: afa
Date: Thu Dec 2 00:45:35 2010
New Revision: 79728
Modified:
pypy/trunk/lib_pypy/_ctypes/array.py
pypy/trunk/lib_pypy/_ctypes/basics.py
pypy/trunk/lib_pypy/_ctypes/builtin.py
pypy/trunk/lib_pypy/_ctypes/function.py
pypy/trunk/lib_pypy/_ctypes/pointer.py
pypy/trunk/lib_pypy/_ctypes/primitive.py
pypy/trunk/lib_pypy/_ctypes/structure.py
pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_guess_argtypes.py
pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_keepalive.py
Log:
Merge the most important _ctypes fixes from the fast-forward branch
- better keepalives should avoid some crashes
- sub-classes of Struct should put the base fields first
- respect the "from_param" conversion method
... 10 less failures in the (skipped!) cpython test suite
Modified: pypy/trunk/lib_pypy/_ctypes/array.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/array.py (original)
+++ pypy/trunk/lib_pypy/_ctypes/array.py Thu Dec 2 00:45:35 2010
@@ -99,6 +99,9 @@
if len(value) > self._length_:
raise ValueError("Invalid length")
value = self(*value)
+ elif not isinstance(value, self):
+ raise TypeError("expected string or Unicode object, %s found"
+ % (value.__class__.__name__,))
else:
if isinstance(value, tuple):
if len(value) > self._length_:
@@ -107,22 +110,43 @@
return _CDataMeta.from_param(self, value)
def array_get_slice_params(self, index):
- if index.step is not None:
- raise TypeError("3 arg slices not supported (for no reason)")
- start = index.start or 0
- stop = index.stop or self._length_
- return start, stop
+ if hasattr(self, '_length_'):
+ start, stop, step = index.indices(self._length_)
+ else:
+ step = index.step
+ if step is None:
+ step = 1
+ start = index.start
+ stop = index.stop
+ if start is None:
+ if step > 0:
+ start = 0
+ else:
+ raise ValueError("slice start is required for step < 0")
+ if stop is None:
+ raise ValueError("slice stop is required")
+
+ return start, stop, step
def array_slice_setitem(self, index, value):
- start, stop = self._get_slice_params(index)
- if stop - start != len(value):
+ start, stop, step = self._get_slice_params(index)
+
+ if ((step < 0 and stop >= start) or
+ (step > 0 and start >= stop)):
+ slicelength = 0
+ elif step < 0:
+ slicelength = (stop - start + 1) / step + 1
+ else:
+ slicelength = (stop - start - 1) / step + 1;
+
+ if slicelength != len(value):
raise ValueError("Can only assign slices of the same length")
- for i in range(start, stop):
- self[i] = value[i - start]
+ for i, j in enumerate(range(start, stop, step)):
+ self[j] = value[i]
def array_slice_getitem(self, index):
- start, stop = self._get_slice_params(index)
- l = [self[i] for i in range(start, stop)]
+ start, stop, step = self._get_slice_params(index)
+ l = [self[i] for i in range(start, stop, step)]
letter = getattr(self._type_, '_type_', None)
if letter == 'c':
return "".join(l)
@@ -134,8 +158,12 @@
__metaclass__ = ArrayMeta
_ffiargshape = 'P'
- def __init__(self, *args):
+ def __new__(cls, *args):
+ self = _CData.__new__(cls, *args)
self._buffer = self._ffiarray(self._length_, autofree=True)
+ return self
+
+ def __init__(self, *args):
for i, arg in enumerate(args):
self[i] = arg
@@ -162,9 +190,10 @@
self._slice_setitem(index, value)
return
index = self._fix_index(index)
- if ensure_objects(value) is not None:
- store_reference(self, index, value._objects)
- arg = self._type_._CData_value(value)
+ cobj = self._type_.from_param(value)
+ if ensure_objects(cobj) is not None:
+ store_reference(self, index, cobj._objects)
+ arg = cobj._get_buffer_value()
if self._type_._fficompositesize is None:
self._buffer[index] = arg
# something more sophisticated, cannot set field directly
@@ -183,7 +212,7 @@
return self._length_
def _get_buffer_for_param(self):
- return CArgObject(self._buffer.byptr())
+ return CArgObject(self, self._buffer.byptr())
def _get_buffer_value(self):
return self._buffer.buffer
Modified: pypy/trunk/lib_pypy/_ctypes/basics.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/basics.py (original)
+++ pypy/trunk/lib_pypy/_ctypes/basics.py Thu Dec 2 00:45:35 2010
@@ -46,21 +46,6 @@
else:
return self.from_param(as_parameter)
- def _CData_input(self, value):
- """Used when data enters into ctypes from user code. 'value' is
- some user-specified Python object, which is converted into a _rawffi
- array of length 1 containing the same value according to the
- type 'self'.
- """
- cobj = self.from_param(value)
- return cobj, cobj._get_buffer_for_param()
-
- def _CData_value(self, value):
- cobj = self.from_param(value)
- # we don't care here if this stuff will live afterwards, as we're
- # interested only in value anyway
- return cobj._get_buffer_value()
-
def _CData_output(self, resbuffer, base=None, index=-1):
#assert isinstance(resbuffer, _rawffi.ArrayInstance)
"""Used when data exits ctypes and goes into user code.
@@ -92,13 +77,20 @@
""" simple wrapper around buffer, just for the case of freeing
it afterwards
"""
- def __init__(self, buffer):
+ def __init__(self, obj, buffer):
+ self._obj = obj
self._buffer = buffer
def __del__(self):
self._buffer.free()
self._buffer = None
+ def __repr__(self):
+ return repr(self._obj)
+
+ def __eq__(self, other):
+ return self._obj == other
+
class _CData(object):
""" The most basic object for all ctypes types
"""
@@ -128,7 +120,7 @@
return buffer(self._buffer)
def _get_b_base(self):
- return self._objects
+ return self._base
_b_base_ = property(_get_b_base)
_b_needsfree_ = False
Modified: pypy/trunk/lib_pypy/_ctypes/builtin.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/builtin.py (original)
+++ pypy/trunk/lib_pypy/_ctypes/builtin.py Thu Dec 2 00:45:35 2010
@@ -11,7 +11,8 @@
def _string_at(addr, lgt):
# address here can be almost anything
import ctypes
- arg = ctypes.c_void_p._CData_value(addr)
+ cobj = ctypes.c_void_p.from_param(addr)
+ arg = cobj._get_buffer_value()
return _rawffi.charp2rawstring(arg, lgt)
def set_conversion_mode(encoding, errors):
@@ -22,7 +23,8 @@
def _wstring_at(addr, lgt):
import ctypes
- arg = ctypes.c_void_p._CData_value(addr)
+ cobj = ctypes.c_void_p.from_param(addr)
+ arg = cobj._get_buffer_value()
# XXX purely applevel
if lgt == -1:
lgt = sys.maxint
Modified: pypy/trunk/lib_pypy/_ctypes/function.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/function.py (original)
+++ pypy/trunk/lib_pypy/_ctypes/function.py Thu Dec 2 00:45:35 2010
@@ -35,6 +35,7 @@
_argtypes_ = None
_restype_ = None
+ _errcheck_ = None
_flags_ = 0
_ffiargshape = 'P'
_ffishape = 'P'
@@ -53,7 +54,15 @@
return self._argtypes_
def _setargtypes(self, argtypes):
self._ptr = None
- self._argtypes_ = argtypes
+ if argtypes is None:
+ self._argtypes_ = None
+ else:
+ for i, argtype in enumerate(argtypes):
+ if not hasattr(argtype, 'from_param'):
+ raise TypeError(
+ "item %d in _argtypes_ has no from_param method" % (
+ i + 1,))
+ self._argtypes_ = argtypes
argtypes = property(_getargtypes, _setargtypes)
def _getrestype(self):
@@ -72,9 +81,24 @@
del self._restype_
restype = property(_getrestype, _setrestype, _delrestype)
+ def _geterrcheck(self):
+ return getattr(self, '_errcheck_', None)
+ def _seterrcheck(self, errcheck):
+ if not callable(errcheck):
+ raise TypeError("The errcheck attribute must be callable")
+ self._errcheck_ = errcheck
+ def _delerrcheck(self):
+ try:
+ del self._errcheck_
+ except AttributeError:
+ pass
+ errcheck = property(_geterrcheck, _seterrcheck, _delerrcheck)
+
def _ffishapes(self, args, restype):
argtypes = [arg._ffiargshape for arg in args]
if restype is not None:
+ if not isinstance(restype, _CDataMeta):
+ raise TypeError("invalid result type for callback function")
restype = restype._ffiargshape
else:
restype = 'O' # void
@@ -140,6 +164,7 @@
def __call__(self, *args):
if self.callable is not None:
+ args = args[:len(self._argtypes_)]
try:
res = self.callable(*args)
except:
@@ -162,13 +187,29 @@
thisarg = None
if argtypes is None:
- argtypes = self._guess_argtypes(args)
- argtypes, argsandobjs = self._wrap_args(argtypes, args)
+ argtypes = []
+ args = self._convert_args(argtypes, args)
+ argtypes = [type(arg) for arg in args]
restype = self._restype_
funcptr = self._getfuncptr(argtypes, restype, thisarg)
- resbuffer = funcptr(*[arg._buffer for _, arg in argsandobjs])
- return self._build_result(restype, resbuffer, argtypes, argsandobjs)
+ resbuffer = funcptr(*[arg._get_buffer_for_param()._buffer
+ for arg in args])
+ result = self._build_result(restype, resbuffer, argtypes, args)
+
+ # The 'errcheck' protocol
+ if self._errcheck_:
+ v = self._errcheck_(result, self, args)
+ # If the errcheck funtion failed, let it throw
+ # If the errcheck function returned callargs unchanged,
+ # continue normal processing.
+ # If the errcheck function returned something else,
+ # use that as result.
+ if v is not args:
+ result = v
+
+ return result
+
def _getfuncptr(self, argtypes, restype, thisarg=None):
if self._ptr is not None and argtypes is self._argtypes_:
@@ -212,31 +253,33 @@
raise
@staticmethod
- def _guess_argtypes(args):
+ def _conv_param(argtype, arg, index):
from ctypes import c_char_p, c_wchar_p, c_void_p, c_int
- res = []
- for arg in args:
- if hasattr(arg, '_as_parameter_'):
- arg = arg._as_parameter_
- if isinstance(arg, str):
- res.append(c_char_p)
- elif isinstance(arg, unicode):
- res.append(c_wchar_p)
- elif isinstance(arg, _CData):
- res.append(type(arg))
- elif arg is None:
- res.append(c_void_p)
- #elif arg == 0:
- # res.append(c_void_p)
- elif isinstance(arg, (int, long)):
- res.append(c_int)
- else:
- raise TypeError("Don't know how to handle %s" % (arg,))
- return res
+ if argtype is not None:
+ arg = argtype.from_param(arg)
+ if hasattr(arg, '_as_parameter_'):
+ arg = arg._as_parameter_
+
+ if isinstance(arg, _CData):
+ # The usual case when argtype is defined
+ cobj = arg
+ elif isinstance(arg, str):
+ cobj = c_char_p(arg)
+ elif isinstance(arg, unicode):
+ cobj = c_wchar_p(arg)
+ elif arg is None:
+ cobj = c_void_p()
+ elif isinstance(arg, (int, long)):
+ cobj = c_int(arg)
+ else:
+ raise TypeError("Don't know how to handle %s" % (arg,))
- def _wrap_args(self, argtypes, args):
+ return cobj
+
+ def _convert_args(self, argtypes, args):
wrapped_args = []
consumed = 0
+
for i, argtype in enumerate(argtypes):
defaultvalue = None
if i > 0 and self._paramflags is not None:
@@ -263,7 +306,7 @@
val = defaultvalue
if val is None:
val = 0
- wrapped = argtype._CData_input(val)
+ wrapped = self._conv_param(argtype, val, consumed)
wrapped_args.append(wrapped)
continue
else:
@@ -278,23 +321,22 @@
raise TypeError("Not enough arguments")
try:
- wrapped = argtype._CData_input(arg)
- except (UnicodeError, TypeError), e:
+ wrapped = self._conv_param(argtype, arg, consumed)
+ except (UnicodeError, TypeError, ValueError), e:
raise ArgumentError(str(e))
wrapped_args.append(wrapped)
consumed += 1
if len(wrapped_args) < len(args):
extra = args[len(wrapped_args):]
- extra_types = self._guess_argtypes(extra)
- for arg, argtype in zip(extra, extra_types):
+ argtypes = list(argtypes)
+ for i, arg in enumerate(extra):
try:
- wrapped = argtype._CData_input(arg)
- except (UnicodeError, TypeError), e:
+ wrapped = self._conv_param(None, arg, i)
+ except (UnicodeError, TypeError, ValueError), e:
raise ArgumentError(str(e))
wrapped_args.append(wrapped)
- argtypes = list(argtypes) + extra_types
- return argtypes, wrapped_args
+ return wrapped_args
def _build_result(self, restype, resbuffer, argtypes, argsandobjs):
"""Build the function result:
@@ -307,7 +349,7 @@
if self._com_index:
if resbuffer[0] & 0x80000000:
raise get_com_error(resbuffer[0],
- self._com_iid, argsandobjs[0][0])
+ self._com_iid, argsandobjs[0])
else:
retval = int(resbuffer[0])
elif restype is not None:
@@ -326,8 +368,8 @@
results = []
if self._paramflags:
- for argtype, (obj, _), paramflag in zip(argtypes[1:], argsandobjs[1:],
- self._paramflags):
+ for argtype, obj, paramflag in zip(argtypes[1:], argsandobjs[1:],
+ self._paramflags):
if len(paramflag) == 2:
idlflag, name = paramflag
elif len(paramflag) == 3:
Modified: pypy/trunk/lib_pypy/_ctypes/pointer.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/pointer.py (original)
+++ pypy/trunk/lib_pypy/_ctypes/pointer.py Thu Dec 2 00:45:35 2010
@@ -1,7 +1,8 @@
import _rawffi
from _ctypes.basics import _CData, _CDataMeta, cdata_from_address
-from _ctypes.basics import sizeof, byref, keepalive_key
+from _ctypes.basics import keepalive_key, store_reference, ensure_objects
+from _ctypes.basics import sizeof, byref
from _ctypes.array import Array, array_get_slice_params, array_slice_getitem,\
array_slice_setitem
@@ -99,7 +100,10 @@
return self._type_._CData_output(self._subarray(index), self, index)
def __setitem__(self, index, value):
- self._subarray(index)[0] = self._type_._CData_value(value)
+ cobj = self._type_.from_param(value)
+ if ensure_objects(cobj) is not None:
+ store_reference(self, index, cobj._objects)
+ self._subarray(index)[0] = cobj._get_buffer_value()
def __nonzero__(self):
return self._buffer[0] != 0
@@ -110,29 +114,32 @@
if not (isinstance(tp, _CDataMeta) and tp._is_pointer_like()):
raise TypeError("cast() argument 2 must be a pointer type, not %s"
% (tp,))
- if isinstance(obj, Array):
- ptr = tp.__new__(tp)
- ptr._buffer = tp._ffiarray(1, autofree=True)
- ptr._buffer[0] = obj._buffer
- return ptr
if isinstance(obj, (int, long)):
result = tp()
result._buffer[0] = obj
return result
- if obj is None:
+ elif obj is None:
result = tp()
return result
- if not (isinstance(obj, _CData) and type(obj)._is_pointer_like()):
+ elif isinstance(obj, Array):
+ ptr = tp.__new__(tp)
+ ptr._buffer = tp._ffiarray(1, autofree=True)
+ ptr._buffer[0] = obj._buffer
+ result = ptr
+ elif not (isinstance(obj, _CData) and type(obj)._is_pointer_like()):
raise TypeError("cast() argument 1 must be a pointer, not %s"
% (type(obj),))
- result = tp()
+ else:
+ result = tp()
+ result._buffer[0] = obj._buffer[0]
# The casted objects '_objects' member:
- # It must certainly contain the source objects one.
+ # From now on, both objects will use the same dictionary
+ # It must certainly contain the source objects
# It must contain the source object itself.
if obj._ensure_objects() is not None:
- result._objects = {keepalive_key(0): obj._objects,
- keepalive_key(1): obj}
+ result._objects = obj._objects
+ if isinstance(obj._objects, dict):
+ result._objects[id(obj)] = obj
- result._buffer[0] = obj._buffer[0]
return result
Modified: pypy/trunk/lib_pypy/_ctypes/primitive.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/primitive.py (original)
+++ pypy/trunk/lib_pypy/_ctypes/primitive.py Thu Dec 2 00:45:35 2010
@@ -132,8 +132,8 @@
ConvMode.errors)
#self._objects = value
array = _rawffi.Array('c')(len(value)+1, value)
+ self._objects = CArgObject(value, array)
value = array.buffer
- self._objects = {'0': CArgObject(array)}
elif value is None:
value = 0
self._buffer[0] = value
@@ -155,8 +155,8 @@
ConvMode.errors)
#self._objects = value
array = _rawffi.Array('u')(len(value)+1, value)
+ self._objects = CArgObject(value, array)
value = array.buffer
- self._objects = {'0': CArgObject(array)}
elif value is None:
value = 0
self._buffer[0] = value
@@ -174,8 +174,8 @@
def _setvalue(self, value):
if isinstance(value, str):
array = _rawffi.Array('c')(len(value)+1, value)
+ self._objects = CArgObject(value, array)
value = array.buffer
- self._objects = {'0': CArgObject(array)}
elif value is None:
value = 0
self._buffer[0] = value
@@ -271,7 +271,9 @@
def _CData_output(self, resbuffer, base=None, index=-1):
output = super(SimpleType, self)._CData_output(resbuffer, base, index)
- return output.value
+ if self.__bases__[0] is _SimpleCData:
+ return output.value
+ return output
def _sizeofinstances(self):
return _rawffi.sizeof(self._type_)
@@ -286,8 +288,12 @@
__metaclass__ = SimpleType
_type_ = 'i'
- def __init__(self, value=DEFAULT_VALUE):
+ def __new__(cls, *args, **kwds):
+ self = _CData.__new__(cls, *args, **kwds)
self._buffer = self._ffiarray(1, autofree=True)
+ return self
+
+ def __init__(self, value=DEFAULT_VALUE):
if value is not DEFAULT_VALUE:
self.value = value
@@ -312,7 +318,11 @@
return self.value
def __repr__(self):
- return "%s(%r)" % (type(self).__name__, self.value)
+ if type(self).__bases__[0] is _SimpleCData:
+ return "%s(%r)" % (type(self).__name__, self.value)
+ else:
+ return "<%s object at 0x%x>" % (type(self).__name__,
+ id(self))
def __nonzero__(self):
return self._buffer[0] not in (0, '\x00')
Modified: pypy/trunk/lib_pypy/_ctypes/structure.py
==============================================================================
--- pypy/trunk/lib_pypy/_ctypes/structure.py (original)
+++ pypy/trunk/lib_pypy/_ctypes/structure.py Thu Dec 2 00:45:35 2010
@@ -34,9 +34,11 @@
if not isinstance(tp, _CDataMeta):
raise TypeError("Expected CData subclass, got %s" % (tp,))
import ctypes
- all_fields = _fields_[:]
- for cls in inspect.getmro(superclass):
- all_fields += getattr(cls, '_fields_', [])
+ all_fields = []
+ for cls in reversed(inspect.getmro(superclass)):
+ # The first field comes from the most base class
+ all_fields.extend(getattr(cls, '_fields_', []))
+ all_fields.extend(_fields_)
names = [name for name, ctype in all_fields]
rawfields = [(name, ctype._ffishape)
for name, ctype in all_fields]
@@ -168,7 +170,7 @@
def __init__(self, *args, **kwds):
if len(args) > len(self._names):
- raise TypeError("too many arguments")
+ raise TypeError("too many initializers")
for name, arg in zip(self._names, args):
if name in kwds:
raise TypeError("duplicate value for argument %r" % (
Modified: pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_guess_argtypes.py
==============================================================================
--- pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_guess_argtypes.py (original)
+++ pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_guess_argtypes.py Thu Dec 2 00:45:35 2010
@@ -11,21 +11,23 @@
py.test.skip("pypy white-box test")
from _ctypes.function import CFuncPtr
- guess = CFuncPtr._guess_argtypes
+ def guess(value):
+ cobj = CFuncPtr._conv_param(None, value, 0)
+ return type(cobj)
- assert guess([13]) == [c_int]
- assert guess([0]) == [c_int]
- assert guess(['xca']) == [c_char_p]
- assert guess([None]) == [c_void_p]
- assert guess([c_int(3)]) == [c_int]
- assert guess([u'xca']) == [c_wchar_p]
+ assert guess(13) == c_int
+ assert guess(0) == c_int
+ assert guess('xca') == c_char_p
+ assert guess(None) == c_void_p
+ assert guess(c_int(3)) == c_int
+ assert guess(u'xca') == c_wchar_p
class Stuff:
pass
s = Stuff()
s._as_parameter_ = None
- assert guess([s]) == [c_void_p]
+ assert guess(s) == c_void_p
def test_guess_unicode():
if not hasattr(sys, 'pypy_translation_info') and sys.platform != 'win32':
Modified: pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_keepalive.py
==============================================================================
--- pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_keepalive.py (original)
+++ pypy/trunk/pypy/module/test_lib_pypy/ctypes_tests/test_keepalive.py Thu Dec 2 00:45:35 2010
@@ -99,7 +99,7 @@
def test_primitive(self):
if not hasattr(sys, 'pypy_translation_info'):
py.test.skip("pypy white-box test")
- assert c_char_p("abc")._objects['0']._buffer[0] == "a"
+ assert c_char_p("abc")._objects._buffer[0] == "a"
assert c_int(3)._objects is None
def test_pointer_to_pointer(self):
@@ -123,7 +123,7 @@
pass
cf = CFUNCTYPE(c_int, c_int)(f)
p1 = cast(cf, c_void_p)
- assert p1._objects == {'1': cf, '0': {'0': cf}}
+ assert p1._objects == {id(cf): cf, '0': cf}
def test_array_of_struct_with_pointer(self):
class S(Structure):
@@ -221,7 +221,7 @@
import gc; gc.collect()
print 'x =', repr(x)
assert x.value == 'hellohello'
- assert x._objects.keys() == ['0']
+ assert x._objects == 'hellohello'
#
class datum(Structure):
_fields_ = [
More information about the Pypy-commit
mailing list