[PATCH evemu 15/19] py: Add base class LibraryWrapper
Peter Hutterer
peter.hutterer at who-t.net
Mon Jan 6 15:46:18 PST 2014
On Mon, Jan 06, 2014 at 06:38:15PM +0100, Daniel Martin wrote:
> LibraryWrapper will be a base class for others wrapping a shared
> library with ctypes.
>
> And add various functions (i.e. expect_ge_zero()), which will be used as
> callback functions to check the return value of an API call.
>
> Signed-off-by: Daniel Martin <consume.noise at gmail.com>
> ---
> python/evemu/base.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 128 insertions(+)
>
> diff --git a/python/evemu/base.py b/python/evemu/base.py
> index 713aec1..3a9de8d 100644
> --- a/python/evemu/base.py
> +++ b/python/evemu/base.py
> @@ -1,11 +1,139 @@
> +"""
> +The base module provides classes wrapping shared libraries.
> +"""
> import ctypes
> import ctypes.util
> import os
>
> +# Import types directly, so they don't have to be prefixed with "ctypes.".
> +from ctypes import c_char_p, c_int, c_uint, c_void_p
> +
> import evemu.const
> import evemu.exception
>
>
> +def expected_or_error(result, func, args, is_expected):
> + """
> + Raise an ExecutionError for an unexpected result (is_expected == True).
this commment seems off - the error is raised if is_expected == False.
how about calling this "raise_error_if" and reordering the parameters? that
way the caller code would be
def expect_eq_zero(result, func, args):
""" Expect 'result' being equal to zero. """
return raise_error_if(result != 0, result, func, args)
I think that's more self-explanatory than the current approach, and you can
rename "is_expected" to "raise_error". and the code becomes
if raise_error:
...
else:
return args
btw: returning "args" seems a bit odd, is that intended?
> +
> + The exception message includes the API call (name) plus arguments, the
> + unexpected result and, if errno is not zero, text describing the
> + error number.
> + """
> + def get_call_str():
> + """ Returns a str 'function_name(argument_values...)'. """
> + strargs = []
> + for (num, arg) in enumerate(func.argtypes):
> + # convert args to str for readable output
> + if arg == c_char_p:
> + strargs.append('"%s"' % args[num].decode(evemu.const.ENCODING))
> + elif arg == c_void_p:
> + strargs.append(hex(int(args[num])))
> + else:
> + strargs.append(str(args[num]))
> + return "%s(%s)" % (func.__name__, ", ".join(strargs))
> +
> + def get_retval_str():
> + """ Returns a str with the unexpected return value. """
> + return ", Unexpected return value: %s" % result
> +
> + def get_errno_str():
> + """ Returns a str describing the error number or an empty string. """
> + errno = ctypes.get_errno()
> + if errno != 0:
> + return ", errno[%d]: %s" % (errno, os.strerror(errno))
> + else:
> + return ""
> +
> + if is_expected:
> + return args
> + else:
> + msg = "%s%s%s" % (get_call_str(), get_retval_str(), get_errno_str())
> + raise evemu.exception.ExecutionError(msg)
> +
> +
> +def expect_eq_zero(result, func, args):
> + """ Expect 'result' being equal to zero. """
> + return expected_or_error(result, func, args, result == 0)
> +
not sure how the passing of objects works exactly but you may be able to
have expect_eq, expect_ne, expect_gt and the actual expected value as
attribute on the function somewhere.
e.g. in the declaration call something like
"evemu_new": {
"argtypes": (c_char_p,),
"restype": c_void_p,
"comparison": expect_ne,
"retval": None
}
and in the loading call something like
api_call.errcheck = attrs["comparison"]
api_call.retval = attrs["retval"]
and then use this here (provided you still have that object, I didn't try)
having said all this, I like this wrapper approach a lot more than the
current call() interface, thanks.
Cheers,
Peter
> +
> +def expect_ge_zero(result, func, args):
> + """ Expect 'result' being greater or equal to zero. """
> + return expected_or_error(result, func, args, result >= 0)
> +
> +
> +def expect_gt_zero(result, func, args):
> + """ Expect 'result' being greater then zero. """
> + return expected_or_error(result, func, args, result > 0)
> +
> +
> +def expect_not_none(result, func, args):
> + """ Expect 'result' being not None. """
> + return expected_or_error(result, func, args, result is not None)
> +
> +
> +class LibraryWrapper(object):
> + """
> + Base class for wrapping a shared library.
> + """
> + _loaded_lib = None
> + # Class variable containing the instance returned by CDLL(), which
> + # represents the shared library.
> + # Initialized once, shared between all instances of this class.
> +
> + def __init__(self):
> + super(LibraryWrapper, self).__init__()
> + self._load()
> +
> + # Prototypes for the API calls to wrap. Needs to be overwritten by sub
> + # classes.
> + _api_prototypes = {
> + #"API_CALL_NAME": {
> + # "argtypes": sequence of ARGUMENT TYPES,
> + # "restype": RETURN TYPE,
> + # "errcheck": callback for return value checking, optional
> + # },
> + }
> +
> + @classmethod
> + def _load(cls):
> + """
> + Returns an instance of the wrapped shared library.
> +
> + If not already initialized: set argument and return types on API
> + calls and optionally a callback function for return value checking.
> + Add the API call as attribute to the class at the end.
> + """
> + if cls._loaded_lib is not None:
> + # Already initialized, just return it.
> + return cls._loaded_lib
> +
> + # Get an instance of the wrapped shared library.
> + cls._loaded_lib = cls._cdll()
> +
> + # Iterate the API call prototypes.
> + for (name, attrs) in cls._api_prototypes.items():
> + # Get the API call.
> + api_call = getattr(cls._loaded_lib, name)
> + # Add argument and return types.
> + api_call.argtypes = attrs["argtypes"]
> + api_call.restype = attrs["restype"]
> + # Optionally, add a callback for return value checking.
> + if "errcheck" in attrs:
> + api_call.errcheck = attrs["errcheck"]
> + # Add the API call as attribute to the class.
> + setattr(cls, name, api_call)
> +
> + return cls._loaded_lib
> +
> + @staticmethod
> + # @abc.abstractmethod - Would be nice here, but it can't be mixed with
> + # @staticmethod until Python 3.3.
> + def _cdll():
> + """ Returns a new instance of the wrapped shared library. """
> + raise NotImplementedError
> +
> +
> class EvEmuBase(object):
> """
> A base wrapper class for the evemu functions, accessed via ctypes.
> --
> 1.8.5.2
>
> _______________________________________________
> Input-tools mailing list
> Input-tools at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/input-tools
More information about the Input-tools
mailing list