__init__.py

Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 
00003 ##
00004 # 
00005 # PyDIM is a Python interface to DIM.
00006 # 
00007 # PyDIM could be used to create DIM clients and servers, using an API very similar
00008 # to the one that is used for C.
00009 # 
00010 
00011 import random
00012 import string
00013 import time
00014 import types
00015 import threading
00016 import logging
00017 
00018 from dimc import *
00019 from dimcpp import *
00020 from debug import *
00021 
00022 _version = '1.3.4'
00023 
00024 # ###########################################################################
00025 # The DIM RPC proxy class.
00026 # ###########################################################################
00027 ##
00028 # 
00029 # Class that facilitates publishing of random python functions using a single 
00030 # DIM RPC. At creation it receives a lost of callable objects wo which the calls 
00031 # will be passed. It creates a single string DIM RPC and accepts a string with 
00032 # the format:
00033 #   'function_name/par1_name=par1_value1,par1_value2/par2_name=par2_value/.../'
00034 # or
00035 #   'function_name/par1_value,par2_value/.../' for possitional arguments
00036 # The appropiate function is called with the appropiate parameters and the 
00037 # result is returned in the same format.
00038 # The special characters (',', '=' and '/') must be excaped.
00039 # Identifiers must contain alfanumeric characters plus the '_' and '-' chars.
00040 # It makes the assumption that the python functions will return always a tuple
00041 # in the format (STATUSCODE, RESULTS). All the return parameters are converted 
00042 # to a string and are returned to the client. 
00043 #     
00044 class PyDimRpcProxy (DimRpc):
00045     def __init__(self, funcs, rpcName='testRPC'):
00046         DimRpc.__init__(self, rpcName, 'C', 'C')
00047         self.lastValue = None
00048         self.funcs = {}
00049         self.name = rpcName
00050         if not hasattr(funcs, '__iter__'):
00051             funcs = [funcs]
00052         for f in funcs:
00053             # creating a dictionary of function names and callable objects
00054             if hasattr(f, '__call__'):
00055                 self.funcs[f.func_name] = f
00056             else:
00057                 ERROR ('Object %s is not callable' %f)
00058                 DEBUG(dir(f))
00059 
00060     def convert(self, args):
00061         ret = ""
00062         if not hasattr(args, '__iter__'):
00063             args = (args,)
00064         for x in args:
00065             add = str(x)
00066             add = add.replace('/', '\/')
00067             add = add.replace('=', '\=')
00068             add = add.replace(',', '\,')
00069             ret += add + ','
00070         return ret[:-1]
00071 
00072     def split(self, s, sep, exc='\\'):
00073         args = []
00074         poz = 0
00075         while s:
00076             poz = s.find(sep, poz)
00077             if poz == 0:
00078                 s = s[1:]
00079             elif poz == -1:
00080                 args.append(s)
00081                 s = ""
00082             elif s[poz-1] != exc:
00083                 args.append(s[:poz])
00084                 s = s[poz+1:]
00085                 poz = 0
00086             else:
00087                 poz += 1
00088         if len(args) > 1 or len(args) == 0:
00089             return args
00090         else:
00091             return args[0]
00092  
00093     def parse(self, s):
00094         pozArgs = []
00095         keyArgs = {}
00096         args = self.split(s, '/')
00097         if type(args) is str: args = (args,)
00098         DEBUG(args)
00099         for arg in args:
00100             #DEBUG(arg)
00101             poz = arg.find('=')
00102             if poz > 0 and arg[poz-1] != '\\':
00103                 # we have a named argument
00104                 keyArgs[arg[:poz]] = self.split(arg[poz+1:], ',')
00105             else:
00106                 pozArgs.append(self.split(arg, ','))
00107         return pozArgs, keyArgs
00108 
00109     def rpcHandler(self):
00110         s = self.getString()
00111         DEBUG("Received: ", s)
00112         # figuring out which method to call and parsing arguments
00113         if s.find('/') > -1:
00114             funcName = s[:s.find('/')]
00115             s = s[s.find('/'):]
00116         elif s:
00117             # we have 0 arguments
00118             funcName = s
00119             s = ""
00120         else:
00121             self.setData('ERROR: function %s is not registered' %s)
00122             return
00123         try:
00124             funcObj = self.funcs[funcName]
00125         except KeyError, e:
00126             ERROR(e)
00127             self.setData('status=FAIL/error=Could not find function '+str(e))
00128             return
00129         #DEBUG(funcName, s)
00130         pozArgs, keyArgs = self.parse(s)
00131         #DEBUG(pozArgs, keyArgs)
00132         ret = funcName + '/'
00133 
00134         try:
00135            res = funcObj(*pozArgs, **keyArgs)
00136         except Exception, e:
00137             # catch all convert it to string and return
00138             print type(funcObj)
00139             ret += 'status=FAIL/error='+str(e)
00140             DEBUG('Returning exception message: %s' %ret)
00141         else:
00142             '''
00143             We have an result. Converting to a string and returning it.
00144             There can be two types of returned results: iterables and 
00145             dictionaries. Both results can contain basic types or other 
00146             iterables.
00147             Iterables: They will be  converted in the equivalent of positional
00148             arguments for calling a function.
00149             Dictionaries: Will be converted in the equivalent of named 
00150             arguments.
00151             '''
00152             if res[0] == 0:
00153                 ret += 'status=ERROR/'
00154                 try:
00155                     ret = '%serror=%s/' %(ret, res[1])
00156                 except:
00157                     pass
00158             else:
00159                 ret += 'status=SUCCESS/'
00160                 named = False
00161                 try:
00162                     res = res[1]
00163                 except:
00164                     pass
00165                 else:
00166                     if isinstance(res, dict):
00167                         for x in res:
00168                             ret = "%s%s=%s/" %(ret, 
00169                                                self.convert(x), 
00170                                                self.convert(res[x])
00171                                                )
00172                     elif hasattr(res, '__iter__'):
00173                         for x in res:
00174                             ret = "%s%s/" %(ret, self.convert(x))
00175                     else:
00176                         ret = "%s%s/" %(ret, self.convert(res))
00177       # this is particularly usefull if on the other side are C/C++ clients
00178         ret += '\0'
00179         # setting result
00180         DEBUG('Sending result: %s' %ret)
00181         self.setData(ret)
00182         
00183 
00184 
00185 # ###########################################################################
00186 # The DIM DNS abstraction class
00187 # ###########################################################################
00188 class Dns:
00189     def __init__(self):
00190         self.dns_srv_sub = dim.Subscription(name = "DIS_DNS/SERVER_LIST",
00191                                             tag = self, schema = "S",
00192                                             handler = self.servers_update)
00193         self.servers = None
00194     def services_update(self, svcstr):
00195         self.services = []
00196         for s in string.split(svcstr[0], '\n'):
00197             if s == '': continue
00198             i = string.rfind(s, '|')
00199             j = string.rfind(s, '|', 0, i)
00200             print s, i, j
00201             if (i == len(s) - 1):
00202                 type = "SVC"
00203             else:
00204                 type = s[i + 1:(len(s))]
00205             self.services.append([ s[0:j], s[j+1:i], type]) 
00206         self.service_update = True
00207  
00208     def servers_update(self, srv):
00209         self.servers = []; self.server_host = []
00210         for s in string.split(srv[0], '|'):
00211             [server, host ] = string.split(s, '@')
00212             self.servers.append(server)
00213             self.server_host.append([server, host])
00214 
00215     def get_services(self, server):
00216         if (not server in self.servers): return None;
00217         self.service_update = False
00218         srv = dim.Subscription(name=server+"/SERVICE_LIST", schema = "S",
00219                                handler = self.services_update)
00220         i = 0
00221         while (self.service_update == False and i < 50):
00222             i += 1
00223             time.sleep(0.1)
00224         return self.services
00225     def get_servers(self):
00226         while (self.servers == None):
00227             time.sleep(0.1)
00228         return self.servers
00229 
00230 ##
00231 # 
00232 #     A decorator function that makes easier to use normal functions as callbacks
00233 #     for DIM services.
00234 #     
00235 #     'fn' is a function (in general, a callable object) that doesn't 
00236 #     receive arguments, and returns a function that is suited for DIM callbacks.
00237 #     
00238 def dim_service(fn):
00239     def svc(tag):
00240         # Tag argument is declared but not used
00241         rtn = fn()
00242         # The returned value must be tuple, so we try to convert simple values
00243         if not rtn in (types.ListType, types.TupleType):
00244             rtn = (rtn,)
00245         return rtn
00246 
00247     return svc
00248 
00249 ##
00250 # 
00251 #     A decorator function that makes easier to use function with an argument as
00252 #     callbacks for DIM services.
00253 #     
00254 #     'fn' is a function (in general, a callable object) that receives one 
00255 #     argument, and returns a function that is suited for DIM callbacks. The 
00256 #     argument used is the tag of the service tag registerd with DIM.
00257 #     
00258 def dim_service_tag(fn):
00259     def svc(tag):
00260         rtn = fn(tag)
00261         if not rtn in (types.ListType, types.TupleType):
00262             rtn = (rtn,)
00263         return rtn
00264     
00265     return svc
00266 
00267 
00268 ##
00269 # 
00270 #     A synchronous call for getting the value of a service.
00271 #     
00272 #     This function is equivalent to `dic_info_service`, but it waits until the
00273 #     value is retrieved and returns the value of the service. For this reason a
00274 #     callback function is not needed.
00275 #     
00276 #     Arguments are:
00277 # 
00278 #        *name* Service name. same name used by server when declaring the service.
00279 #     
00280 #        *description* The description string of the service.
00281 #     
00282 #        *timeout* The number of seconds after which the service is considered to
00283 #         have failed. Optional, by default there is no timeout.  
00284 #     
00285 #        *default_value* The value that will be returned in case the service 
00286 #         doesn't succeed. Optional, default is `None`.
00287 #     
00288 def dic_sync_info_service(name, description, timeout=None, default_value=None):
00289     executed = threading.Event()
00290     state = dict(value=None)
00291 
00292     def create_callback(st):
00293         def _callback(*args):
00294             st['value'] = args
00295             executed.set()
00296     
00297         return _callback
00298     
00299     callback = create_callback(state)
00300     dim_timeout = 0 
00301     tag = random.randint(0,100000)
00302     sid = dic_info_service(name, description, callback, ONCE_ONLY, dim_timeout, tag, default_value)
00303     
00304     executed.wait(timeout)
00305 
00306     dic_release_service(sid)
00307 
00308     return state['value']
00309 
00310 ##
00311 # 
00312 #     A synchronous call for executing a command.
00313 #     
00314 #     This function works in the same way as `dic_cmnd_service`, but it waits
00315 #     until the command is executed.
00316 #     
00317 #     Arguments are:
00318 #         
00319 #        *name* Command name, same name used by server when declaring the command
00320 #        service. 
00321 #     
00322 #        *arguments* A tuple with the values that are sent to the command as 
00323 #        arguments.
00324 #     
00325 #        *description* The description string of the command.
00326 #        
00327 #        *timeout* The number of seconds after which the command is considered 
00328 #        to have failed. Optional, by default there is no timeout.  
00329 # 
00330 #     It returns an integer which indicates if the command was executed 
00331 #     successfully; 1 if it was correctly sent to the server, 0 otherwise.   
00332 # 
00333 #     
00334 def dic_sync_cmnd_service(name, arguments, description, timeout=None):
00335     executed = threading.Event()
00336     state = dict(retcode=None)
00337 
00338     def create_callback(st):
00339         def _callback(tag, retcode):
00340             state['retcode'] = retcode
00341             executed.set()
00342     
00343         return _callback
00344     
00345     callback = create_callback(state)
00346     tag = 0
00347     dic_cmnd_callback(name, arguments, description, callback, tag)
00348     
00349     executed.wait(timeout)
00350 
00351     return state['retcode']
00352 
00353 
00354 if __name__ == "__main__":
00355     # in case the file is executed directly
00356     dns = Dns()
00357     print dns.get_servers()
00358     pass    
00359     
00360 
00361 

Generated on 5 Feb 2014 for PyDIM by  doxygen 1.4.7