def injector(*args,**kw): """ The Ladon inner injection function is called from the dispatcher. It does run-time type checking against the types registered with the service method. If everything is OK the user method is called. The result of the userspace-method is then checked before it is passed back to the dispatcher. """ # Get the LadonMethodInfo object generated at parsetime which is stored as a member # on the function object lmi = injector._ladon_method_info # Reference the incomming arguments in callargs (filter out the function reference) callargs = args[1:] for argidx in range(len(callargs)): # Check the type of each argument against the type which the method has been # registered to take in the order-wise corresponding argument if not validate_type(lmi._arg_types[argidx],callargs[argidx]): # Raise ArgTypeMismatch raise ArgTypeMismatch( lmi.sinfo, lmi._func_name, lmi._arg_names[argidx], lmi._arg_types[argidx], type(callargs[argidx])) # Call the userspace service method (**kw will be used to transport Ladon info # and tools all the way to userspace of the service method. I.e. the TypeConverter # is passed with the keyword "LADON_METHOD_TC") res = f(*args,**kw) # Check the return type if not validate_type(lmi._rtype,res): # Raise Arg-type mismatch raise ReturnTypeMismatch(lmi.sinfo,lmi._func_name,lmi._rtype,type(res)) # Return the result to the dispatcher return res
def injector(*args, **kw): """ The Ladon inner injection function is called from the dispatcher. It does run-time type checking against the types registered with the service method. If everything is OK the user method is called. The result of the userspace-method is then checked before it is passed back to the dispatcher. """ # Get the LadonMethodInfo object generated at parsetime which is stored as a member # on the function object lmi = injector._ladon_method_info # Reference the incomming arguments in callargs (filter out the function reference) callargs = args[1:] for argidx in range(len(callargs)): # Check the type of each argument against the type which the method has been # registered to take in the order-wise corresponding argument if not validate_type(lmi._arg_types[argidx], callargs[argidx]): # Raise ArgTypeMismatch raise ArgTypeMismatch(lmi.sinfo, lmi._func_name, lmi._arg_names[argidx], lmi._arg_types[argidx], type(callargs[argidx])) # Call the userspace service method (**kw will be used to transport Ladon info # and tools all the way to userspace of the service method. I.e. the TypeConverter # is passed with the keyword "LADON_METHOD_TC") res = f(*args, **kw) # Check the return type if not validate_type(lmi._rtype, res): # Raise Arg-type mismatch raise ReturnTypeMismatch(lmi.sinfo, lmi._func_name, lmi._rtype, type(res)) # Return the result to the dispatcher return res
def __init__(self, sinfo, f, *def_args, **def_kw): global rx_doc_params, rx_doc_rtype, rx_doc_param_lines # Inspect the methods's parameters argspecs = inspect.getargspec(f) # Get the method's doc-string doc = inspect.getdoc(f) if doc: if sys.version_info[0] == 2: # Convert the documentation to unicode doc = unicode(doc, sinfo.src_encoding) # Associate the method with it's parent service-info object self.sinfo = sinfo # _args: Store for arguments (name, type, documentation and default value) self._args = {} # _has_keywords: Check if the method takes keyword arguments (**kw). The # dispatcher will use this information to decide whether to push ladon's # helper tools to the method. Helper tools are sent via keyword arguments # therefore keyword arguments are required in the method in order to recieve # the helper tools self._has_keywords = argspecs.keywords != None # _arg_names,_defaults: Store method parameter names and defalut values here. # These will be used later in the parsing process. self._arg_names = argspecs.args[1:] self._defaults = argspecs.defaults # Store the raw doc-string in _doc self._doc = f.__doc__ # Extract the method name self._func_name = get_function_name(f) # Store the parameter types delivered by the user via the ladonize decorator self._arg_types = def_args # _encoding: is kind of a contract between the method and Ladon's dispatcher. # It tells Ladon that it must deliver raw strings to the service method in that # encoding thus converting if nessecary. You can think of it as the method's # internal encoding. The method itself also commit's itself to the contract by # promising to deliver raw strings back to the dispatcher in the same encoding. # Raw strings are defined as: str in Python2 and bytes in Python3 self._encoding = 'UTF-8' # _allow_unsafe_conversion: is a user-option that tells Ladon whether it may do # unsafe convertions or not. An example of an unsafe convertion is float to int. # unsafe convertions are disabled by default self._allow_unsafe_conversion = False # _rtype: The return type of the method. try: self._rtype = def_kw['rtype'] except KeyError as ke: # return type must be specified. raise ReturnTypeUndefined(sinfo, self._func_name) # _rtype_doc: Initialize the return type doc-string container self._rtype_doc = [] # overwrite the default values of user options if they are passed on via the # ladonize decorator. if 'encoding' in def_kw: self._encoding = def_kw['encoding'] if 'allow_unsafe_conversion' in def_kw: self._allow_unsafe_conversion = def_kw['allow_unsafe_conversion'] # Let the parent service's TypeManager object parse, analyze and store the return type self._multipart_response_required = sinfo.typemanager.analyze_param( self._rtype) self._multipart_request_required = False if self._defaults == None: self._defaults = tuple() # Identify which arguments are mandatory and which are optional self._mandatory_args = self._arg_names[:-len(self._defaults)] self._optional_args = self._arg_names[-len(self._defaults):] # Check that the number of type defines (via the ladonize decorator) matches then # number of method arguments if len(self._arg_names) != len(def_args): raise ArgDefCountMismatch(sinfo, self._func_name, self._arg_types, self._arg_names) alen = len(self._arg_names) dlen = len(self._defaults) for argidx in range(len(self._arg_names)): # Let the parent service's TypeManager object parse, analyze and store the argument self._multipart_request_required |= sinfo.typemanager.analyze_param( def_args[argidx]) has_default = False # find the index of this parameter's default value in _defaults. If index is below # 0 it means it has no default value, thus a mandatory parameter. defidx = dlen - (alen - argidx) if defidx > -1: # Optional parameter, check and store it's default value has_default = True def_val = self._defaults[defidx] def_type = type(self._defaults[defidx]) # Check that the default value matches the user defined type if not validate_type(def_args[argidx], def_val): # type mismatch raise DefaultArgTypeMismatch(sinfo, self._func_name, self._arg_names[argidx], self._arg_types[argidx], def_type) # Store the info about this parameter self._args[self._arg_names[argidx]] = { 'name': self._arg_names[argidx], 'type': def_args[argidx], 'optional': has_default, 'doc': [] } if has_default: self._args[self._arg_names[argidx]]['default'] = def_val # Parse the methods documentation self._method_doc = [] if doc: # Make sure all newlines in doc are Unix style, then split it into lines doc = doc.replace('\r\n', '\n') doclines = doc.split('\n') cur_argname = None for dline in doclines: # Iterate through doc lines and do regular expression matches to # find method, argument and return documentation # Does the current doc line match the beginning of a parameters documentation param_match = rx_doc_params.match(dline) if param_match: # Set the parameter as current cur_argname = param_match.groups()[0] # if not an actual parameter cancel param documentation if not cur_argname in self._args: cur_argname = None continue # Add the first doc line to the parameter doc lines docline1 = param_match.groups()[1].strip() if docline1: self._args[cur_argname]['doc'] += [docline1] continue # Does the current doc line match the beginning of the return documentation rtype_match = rx_doc_rtype.match(dline) if rtype_match: # Add the first doc line to the return doc lines cur_argname = '_rtype' docline1 = rtype_match.groups()[0].strip() if docline1: self._rtype_doc += [docline1] continue # does the doc line look like a param or return line param_line_match = rx_doc_param_lines.match(dline) # Add it to the current param doc that is being processed if param_line_match and cur_argname: if cur_argname == '_rtype': self._rtype_doc += [dline.strip()] else: self._args[cur_argname]['doc'] += [ param_line_match.groups()[0].strip() ] continue # If this doc line cannot be mapped to an argument or the return documentation, then # add it to the methods documentation if not len(dline.strip()) and cur_argname: if cur_argname == '_rtype': self._rtype_doc += [dline.strip()] else: self._args[cur_argname]['doc'] += [dline.strip()] continue # If no match found, reset the current param if one is in focus cur_argname = None self._method_doc += [dline] self.__doc__ = '\n'.join(self._method_doc)
def __init__(self,sinfo,f,*def_args,**def_kw): global rx_doc_params,rx_doc_rtype,rx_doc_param_lines # Inspect the methods's parameters argspecs = inspect.getargspec(f) # Get the method's doc-string doc = inspect.getdoc(f) if doc: if sys.version_info[0]==2: # Convert the documentation to unicode doc = unicode(doc,sinfo.src_encoding) # Associate the method with it's parent service-info object self.sinfo = sinfo # _args: Store for arguments (name, type, documentation and default value) self._args = {} # _has_keywords: Check if the method takes keyword arguments (**kw). The # dispatcher will use this information to decide whether to push ladon's # helper tools to the method. Helper tools are sent via keyword arguments # therefore keyword arguments are required in the method in order to recieve # the helper tools self._has_keywords = argspecs.keywords!=None # _arg_names,_defaults: Store method parameter names and defalut values here. # These will be used later in the parsing process. self._arg_names = argspecs.args[1:] self._defaults = argspecs.defaults # Store the raw doc-string in _doc self._doc = f.__doc__ # Extract the method name self._func_name = get_function_name(f) # Store the parameter types delivered by the user via the ladonize decorator self._arg_types = def_args # _encoding: is kind of a contract between the method and Ladon's dispatcher. # It tells Ladon that it must deliver raw strings to the service method in that # encoding thus converting if nessecary. You can think of it as the method's # internal encoding. The method itself also commit's itself to the contract by # promising to deliver raw strings back to the dispatcher in the same encoding. # Raw strings are defined as: str in Python2 and bytes in Python3 self._encoding = 'UTF-8' # _allow_unsafe_conversion: is a user-option that tells Ladon whether it may do # unsafe convertions or not. An example of an unsafe convertion is float to int. # unsafe convertions are disabled by default self._allow_unsafe_conversion = False # _rtype: The return type of the method. try: self._rtype = def_kw['rtype'] except KeyError as ke: # return type must be specified. raise ReturnTypeUndefined(sinfo,self._func_name) # _rtype_doc: Initialize the return type doc-string container self._rtype_doc = [] # overwrite the default values of user options if they are passed on via the # ladonize decorator. if 'encoding' in def_kw: self._encoding = def_kw['encoding'] if 'allow_unsafe_conversion' in def_kw: self._allow_unsafe_conversion = def_kw['allow_unsafe_conversion'] # Let the parent service's TypeManager object parse, analyze and store the return type self._multipart_response_required = sinfo.typemanager.analyze_param(self._rtype) self._multipart_request_required = False if self._defaults==None: self._defaults = tuple() # Identify which arguments are mandatory and which are optional self._mandatory_args = self._arg_names[:-len(self._defaults)] self._optional_args = self._arg_names[-len(self._defaults):] # Check that the number of type defines (via the ladonize decorator) matches then # number of method arguments if len(self._arg_names)!=len(def_args): raise ArgDefCountMismatch(sinfo,self._func_name,self._arg_types,self._arg_names) alen = len(self._arg_names) dlen = len(self._defaults) for argidx in range(len(self._arg_names)): # Let the parent service's TypeManager object parse, analyze and store the argument self._multipart_request_required |= sinfo.typemanager.analyze_param(def_args[argidx]) has_default = False # find the index of this parameter's default value in _defaults. If index is below # 0 it means it has no default value, thus a mandatory parameter. defidx = dlen - (alen - argidx) if defidx>-1: # Optional parameter, check and store it's default value has_default = True def_val = self._defaults[defidx] def_type = type(self._defaults[defidx]) # Check that the default value matches the user defined type if not validate_type(def_args[argidx],def_val): # type mismatch raise DefaultArgTypeMismatch( sinfo, self._func_name, self._arg_names[argidx], self._arg_types[argidx], def_type) # Store the info about this parameter self._args[self._arg_names[argidx]] = { 'name': self._arg_names[argidx], 'type': def_args[argidx], 'optional': has_default, 'doc': []} if has_default: self._args[self._arg_names[argidx]]['default'] = def_val # Parse the methods documentation self._method_doc = [] if doc: # Make sure all newlines in doc are Unix style, then split it into lines doc = doc.replace('\r\n','\n') doclines = doc.split('\n') cur_argname = None for dline in doclines: # Iterate through doc lines and do regular expression matches to # find method, argument and return documentation # Does the current doc line match the beginning of a parameters documentation param_match = rx_doc_params.match(dline) if param_match: # Set the parameter as current cur_argname = param_match.groups()[0] # if not an actual parameter cancel param documentation if not cur_argname in self._args: cur_argname = None continue # Add the first doc line to the parameter doc lines docline1 = param_match.groups()[1].strip() if docline1: self._args[cur_argname]['doc'] += [docline1] continue # Does the current doc line match the beginning of the return documentation rtype_match = rx_doc_rtype.match(dline) if rtype_match: # Add the first doc line to the return doc lines cur_argname = '_rtype' docline1 = rtype_match.groups()[0].strip() if docline1: self._rtype_doc += [docline1] continue # does the doc line look like a param or return line param_line_match = rx_doc_param_lines.match(dline) # Add it to the current param doc that is being processed if param_line_match and cur_argname: if cur_argname=='_rtype': self._rtype_doc += [ dline.strip() ] else: self._args[cur_argname]['doc'] += [param_line_match.groups()[0].strip()] continue # If this doc line cannot be mapped to an argument or the return documentation, then # add it to the methods documentation if not len(dline.strip()) and cur_argname: if cur_argname=='_rtype': self._rtype_doc += [ dline.strip() ] else: self._args[cur_argname]['doc'] += [dline.strip()] continue # If no match found, reset the current param if one is in focus cur_argname = None self._method_doc += [ dline ] self.__doc__ = '\n'.join(self._method_doc)
def injector(*args, **kw): """ The Ladon inner injection function is called from the dispatcher. It does run-time type checking against the types registered with the service method. If everything is OK the user method is called. The result of the userspace-method is then checked before it is passed back to the dispatcher. """ if def_kw.get('tasktype', False) == True: return f(*args, **kw) # Get the LadonMethodInfo object generated at parsetime which is stored as a member # on the function object lmi = injector._ladon_method_info # Reference the incomming arguments in callargs (filter out the function reference) callargs = args[1:] for argidx in range(len(callargs)): # Check the type of each argument against the type which the method has been # registered to take in the order-wise corresponding argument if not validate_type(lmi._arg_types[argidx], callargs[argidx]): # Raise ArgTypeMismatch raise ArgTypeMismatch(lmi.sinfo, lmi._func_name, lmi._arg_names[argidx], lmi._arg_types[argidx], type(callargs[argidx])) # Call the userspace service method (**kw will be used to transport Ladon info # and tools all the way to userspace of the service method. I.e. the TypeConverter # is passed with the keyword "LADON_METHOD_TC") debug_mode = log.get_loglevel() >= 6 if debug_mode: # Build a request_dict for debugging req_dict = {} for argidx in range(len(callargs)): req_dict[lmi._arg_names[argidx]] = expand_value( callargs[argidx], lmi._arg_types[argidx], service_name=lmi.sinfo.servicename, method_name=lmi._func_name) log_line = [ 'Method:%s.%s' % (lmi.sinfo.servicename, lmi._func_name), 'RequestArgs:%s' % str(req_dict), 'RequestKeywordArgs:%s' % str(kw) ] if debug_mode: try: start = time.time() res = f(*args, **kw) log_line.insert( 0, 'ExecutionTime:%s' % str(time.time() - start)) except Exception as e: log_line += ['Exception:%s' % str((log.get_traceback(), ))] log.debug('\t%s' % ('\t'.join(log_line))) raise e log_line += ['ReturnValue:%s' % str(result_to_dict(lmi, res))] log.debug('\t%s' % ('\t'.join(log_line))) else: res = f(*args, **kw) # Check the return type if not validate_type(lmi._rtype, res): # Raise Arg-type mismatch raise ReturnTypeMismatch(lmi.sinfo, lmi._func_name, lmi._rtype, type(res)) # Return the result to the dispatcher return res