How can I return a boolean value from Python to CLIPS?

Some users have noticed that, while many "basic" types (mostly numbers and strings) are automatically converted to their CLIPS counterpart when calling an external Python function, the 'bool' type is not. There is a reason for this behaviour, that is the absence of a real boolean type in the base classes of CLIPS. CLIPS uses the "special" SYMBOLs 'TRUE' and 'FALSE' as if they were booleans, but they remain SYMBOLs.

The issue is partially covered by letting the value clips.Symbol('FALSE') to actually evaluate to a False boolean. In this way, whenever a CLIPS function called from Python returns that particular SYMBOL, it is seen as False only in tests, while not loosing its SYMBOL nature.

However it seemed too arbitrary to convert a Python 'bool' to one of the two discussed SYMBOLs. In fact, the documentation states that it should not been taken for granted that PyCLIPS does all the conversion job, and that especially Python functions that return values back to CLIPS must be carefully written, and possibly return values wrapped in CLIPS types.

For functions that return booleans, this is even more needed: Python has a peculiar way to treat many things as True or False, depending on their values: an empty string is False, a non-empty one is True, for instance.

A solution to this issue could be to write a wrapper function in order to let test functions return the correct SYMBOL value to CLIPS:

def clips_test_wrapper(f):
    def wf(*a, **kwa):
        if f(*a, **kwa):
            return clips.Symbol("TRUE")
        else:
            return clips.Symbol("FALSE")
    wf.__name__ = f.__name__    # explanation is below
    return wf

The reason why we rewrite the __name__ attribute of the returned function is that in this way the function registration utility will register the wrapper function with the original name. An example follows:

>>> def py_true():
        return True
>>> clips.RegisterPythonFunction(clips_test_wrapper(py_true))
>>> clips.Eval("(python-call py_true)")
<Symbol 'TRUE'>
>>>

To unregister the function we only need to supply either the name of the function as it is known to CLIPS or any function whose __name__ attribute corresponds to this, such as the wrapped function or the original one.