-
Notifications
You must be signed in to change notification settings - Fork 7
CFFI Wrapper
The CFFI Wrapper Layer is the middle wrapper of PyMEOS, and it is generated by the build_pymeos_functions.py
on top of the CFFI Layer.
It is located inside the pymeos_cffi
package, and the files used to build it are the following:
-
builder/meos.h
C header file that is going to be wrapped by CFFI. It is a slightly modified version of themeos.h
file provided by the MEOS Library, and contains some extra type/functions definitions needed in PyMEOS. More information about this file can be found in the CFFI Layer Wiki Page -
builder/build_pymeos_functions.py
Parses the custom header file in search of functions to wrap and creates the python scriptfunctions.py
containing all the wrappers that will be exported by the library. -
builder/build_pymeos_functions_modifiers.py
Contains some functions used bybuild_pymeos_functions.py
to edit functions that need extra customization that cannot (or should not) be generalized. -
builder/objects.py
Contains information about some C types and their relationship with the corresponding Python types.
To check if the wrapping works properly before packaging for distribution, you can generate it by running build_pymeos_functions.py
from the pymeos_cffi
directory:
foo@bar:~/PyMEOS/pymeos_cffi$ python3 ./pymeos_cffi/builder/build_pymeos_functions.py
For easier copying:
python3 ./pymeos_cffi/builder/build_pymeos_functions.py
If it was successful, the file pymeos_cffi/functions.py
should have a wrapper for every function exported in the header file. You can check the difference with the previous version to see which wrappers have been modified (or added/removed) to easily check if the changes that should have happened have happened.
There are some cases in which a function exported by MEOS is not so easily wrapped. These cases include, but may not be limited to:
- Function with output parameters that in Python should be transformed to having multiple return values
- Function with optional (nullable) parameters that will cause a crash when being cast to their supposed C type
- Function with pointer parameters that are actually arrays and have to be cast differently
- Function with an output parameter and boolean/int/void return type indicating (when bool/int) whether the operation was successful that should be transformed to return that output parameter or raise an exception on error
For these cases, some methods to ease wrapping are provided that will require developer intervention (the first time they occur):
Nullable parameters need to be checked before casting to their C type, since the cast will crash if they are None
:
# Will crash if duration is None
duration_converted = _ffi.cast('const Interval *', duration)
# Fix: Assign NULL if duration is None
duration_converted = _ffi.cast('const Interval *', duration) if duration is not None else _ffi.NULL
To register a function parameter as nullable, add an entry in the nullable_parameters
dictionary in build_pymeos_functions.py
with the format (function name, parameter name)
.
Output parameters need to be removed from the wrapper function parameter list and instead moved to the return type:
# Normal wrapper generated
def periodset_timestamps(ps: 'const PeriodSet *', count: int) -> 'TimestampTz *':
ps_converted = _ffi.cast('const PeriodSet *', ps)
count = _ffi.cast('int *', count)
result = _lib.periodset_timestamps(ps_converted, count)
return result if result != _ffi.NULL else None
# Wrapper with output parameter
def periodset_timestamps(ps: 'const PeriodSet *') -> "Tuple['TimestampTz *', 'int']":
ps_converted = _ffi.cast('const PeriodSet *', ps)
count = _ffi.new('int *')
result = _lib.periodset_timestamps(ps_converted, count)
return result if result != _ffi.NULL else None, count[0]
To register a function parameter as output, add an entry in the output_parameters
dictionary in build_pymeos_functions.py
with the format (function name, parameter name)
.
⚠️ All parameters that end with_out
or that are pointer parameters namedcount
are considered output parameters.
If at some point this behaviour is no longer desired, the following should be done:
- Modify the
is_output_parameter
function inbuild_pymeos_functions.py
(leaving only the return statement that checks the dictionary).- Add all output parameters that satisfied these conditions in the
output_parameters
dictionary.- Remove this message
⚠️ When having input/output parameters, you'll have to add a custom modification (see below)
Result parameters are similar to output parameters, but they differ in that they replace the actual returned object of the functions, instead of just being added to it:
# Normal wrapper generated
def tfloat_value_at_timestamp(temp: 'const Temporal *', t: int, strict: bool, result: float) -> 'bool':
temp_converted = _ffi.cast('const Temporal *', temp)
t_converted = _ffi.cast('TimestampTz', t)
result_converted = _ffi.cast('double *', result)
result = _lib.tfloat_value_at_timestamp(temp_converted, t_converted, strict, result_converted)
return result if result != _ffi.NULL else None
# Wrapper with result parameter
def tfloat_value_at_timestamp(temp: 'const Temporal *', t: int, strict: bool) -> 'double':
temp_converted = _ffi.cast('const Temporal *', temp)
t_converted = _ffi.cast('TimestampTz', t)
out_result = _ffi.new('double *')
result = _lib.tfloat_value_at_timestamp(temp_converted, t_converted, strict, out_result)
if result:
return out_result[0] if out_result[0] != _ffi.NULL else None
raise Exception(f'C call went wrong: {result}')
To register a function parameter as result, add an entry in the result_parameters
dictionary in build_pymeos_functions.py
with the format (function name, parameter name)
.
⚠️ All parameters namedresult
are considered result parameters.
If at some point this behaviour is no longer desired, the following should be done:
- Modify the
is_result_parameter
function inbuild_pymeos_functions.py
(leaving only the return statement that checks the dictionary).- Add all result parameters that satisfied these conditions in the
result_parameters
dictionary.- Remove this message
There are some cases when the previous modifications are not what is needed and further customization is needed to properly wrap a function. For example, a parameter that is supposed to be an array but it's declared as a pointer (parameters with double pointer types **
are cast as arrays properly) and input/output parameters.
For these cases, you can define a custom function to perform the necessary changes and add an entry in the function_modifiers
dictionary with the format (function name, modifier function)
.
For sake of organization and clarity, these modifier functions should be declared in the file build_pymeos_functions_modifiers.py
, and should be named after the function they modify appending _modifier
at the end (e.g. the modifier of the function timestampset_make
should be named timestampset_make_modifier
).
When called, the function will be passed a string containing the wrapper function generated, and it must return a string with the modified wrapper. The returned string will be written instead of the generated wrapper in the functions.py
file.
If necessary, TODO notes can be added to a wrapper function by adding an entry in the function_notes
dictionary with the format (function name, message)
(the message will be prepended with TODO:
).