Argument checking

Checking the arguments that a function receives or returns can be useful when it is executed in a specific context. For example, if a function is to be called through XML-RPC, Python will not be able to directly provide its full signature like it does in statically-typed languages. This feature is needed to provide introspection capabilities when the XML-RPC client asks for the function signatures.

The XML-RPC protocol
The XML-RPC protocol is a lightweight   Remote Procedure Call  protocol that uses XML over HTTP to encode its calls. It is often used instead of SOAP for simple client-server exchanges. Unlike SOAP, which provides a page that lists all callable functions (WSDL), XML-RPC does not have a directory of available functions. An extension of the protocol that allows the discovery of the server API was proposed, and Python's   xmlrpc  module implements it (refer to   https://docs.python.org/3/library/xmlrpc.server.html) .

A custom decorator can provide this type of signature. It can also makes sure that what goes in and comes out respects the defined signature parameters:

rpc_info = {} 
 
 
def xmlrpc(in_=(), out=(type(None),)): 
    def _xmlrpc(function): 
        # registering the signature 
        func_name = function.__name__ 
        rpc_info[func_name] = (in_, out) 
        def _check_types(elements, types): 
            """Subfunction that checks the types.""" 
            if len(elements) != len(types): 
                raise TypeError('argument count is wrong') 
            typed = enumerate(zip(elements, types)) 
            for index, couple in typed: 
               arg, of_the_right_type = couple 
                if isinstance(arg, of_the_right_type): 
                    continue 
                raise TypeError( 
                    'arg #%d should be %s' % (index, 
of_the_right_type)) # wrapped function def __xmlrpc(*args): # no keywords allowed # checking what goes in checkable_args = args[1:] # removing self _check_types(checkable_args, in_) # running the function res = function(*args) # checking what goes out if not type(res) in (tuple, list): checkable_res = (res,) else: checkable_res = res _check_types(checkable_res, out) # the function and the type # checking succeeded return res return __xmlrpc return _xmlrpc

The decorator registers the function into a global dictionary, and keeps a list of the types for its arguments and for the returned values. Note that this example was highly simplified, just to demonstrate the idea of argument-checking decorators.

A usage example is as follws:

class RPCView: 
    @xmlrpc((int, int))  # two int -> None 
    def accept_integers(self, int1, int2): 
        print('received %d and %d' % (int1, int2)) 
 
    @xmlrpc((str,), (int,))  # string -> int 
    def accept_phrase(self, phrase): 
        print('received %s' % phrase) 
        return 12 

When it is read, this class definition populates the rpc_infos dictionary and can be used in a specific environment, where the argument types are checked:

>>> rpc_info
{'meth2': ((<class 'str'>,), (<class 'int'>,)), 'meth1': ((<class 
'int'>, <class 'int'>), (<class 'NoneType'>,))}
>>> my = RPCView() >>> my.accept_integers(1, 2) received 1 and 2 >>> my.accept_phrase(2) Traceback (most recent call last): File "<input>", line 1, in <module> File "<input>", line 26, in __xmlrpc File "<input>", line 20, in _check_types TypeError: arg #0 should be <class 'str'>