I am trying to wrap to python3 a very simple Object Oriented C-API. This basically follow the same pattern as:
So basically my issue is as follow, I have the following input API:
$ cat api.h
/* ns::a */
typedef struct ns_a ns_a;
ns_a* ns_a_create();
void ns_a_delete(ns_a* a);
bool ns_a_execute(ns_a* a);
/* ns::b */
typedef struct ns_b ns_b;
ns_b* ns_b_create();
void ns_b_delete(ns_b* b);
bool ns_b_run(ns_b* b);
Which I then wrap using the following steps:
class _TypeSafeHandle:
def __init__(self, ptr):
self._as_parameter_ = ptr
@classmethod
def from_param(cls, obj):
if obj.__class__ != cls:
raise ValueError(f"Not a {obj.__class__.__name__} reference")
if not obj._as_parameter_:
raise ValueError("Passing undefined instance")
return obj
@final
class _A(_TypeSafeHandle):
""" for library author, not intended to be exposed publicly """
def __init__(self, ptr):
super().__init__(ptr)
@final
class _B(_TypeSafeHandle):
def __init__(self, ptr):
super().__init__(ptr)
Given the type definition I can now expose the C-API as:
# a
ns_a_create = _func('ns_a_create', c_void_p, None, _check_ns_a)
ns_a_delete = _func('ns_a_delete', None, [_A])
ns_a_execute = _func('ns_a_execute', c_bool, [_A])
# b
ns_b_create = _func('ns_b_create', c_void_p, None, _check_ns_b)
ns_b_delete = _func('ns_b_delete', None, [_B])
ns_b_run = _func('ns_b_run', c_bool, [_B])
Using:
def _func(name, restype, argtypes, errcheck):
func = getattr(_lib, name)
func.restype = restype
func.argtypes = argtypes
func.errcheck = errcheck
return func
and:
def _check_ns_a(result, _func, _args):
if result is None:
raise MemoryError("internal memory allocation failure")
if type(result) != int:
raise TypeError("Expecting int value for pointer")
return = _A(c_void_p(result))
and
def _check_ns_b(result, _func, _args):
if result is None:
raise MemoryError("internal memory allocation failure")
if type(result) != int:
raise TypeError("Expecting int value for pointer")
return = _B(c_void_p(result))
At this step, I can now expose the class A
to my user:
class A:
def __init__(self):
self._a = ns_a_create()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
ns_a_delete(self._a)
def execute(self):
ret = ns_a_execute(self._a)
if not ret:
raise ValueError("Could not execute")
I'd like to keep the intermediate _A
and _B
class as implementation detail for the library author (avoid shooting in the foot passing wrong instance to wrong delete
function). I find it more readable than declaring a forward declared class A:
class A: # forward declaration
pass
# _func declaration with type `A` instead of `_A`
class A:
# actual implementation detail
So my question is: is there a way to reduce code redundancy in between _A
and _B
? Is there a way to reduce code redundancy in between _check_ns_a
and _check_ns_b
?