[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