functions.py
1 """ 2 Functions module 3 """ 4 5 from types import FunctionType, MethodType 6 7 8 class Functions: 9 """ 10 Resolves function configuration to function references. 11 """ 12 13 def __init__(self, embeddings): 14 """ 15 Creates a new function resolver. 16 17 Args: 18 embeddings: embeddings instance 19 """ 20 21 self.embeddings = embeddings 22 23 # Handle to all reference objects 24 self.references = None 25 26 def __call__(self, config): 27 """ 28 Resolves a list of functions to function references. 29 30 Args: 31 config: configuration 32 33 Returns: 34 list of function references 35 """ 36 37 # Initialize stored references array 38 self.references = [] 39 40 # Resolve callable functions 41 functions = [] 42 for fn in config["functions"]: 43 if isinstance(fn, dict): 44 fn = fn.copy() 45 fn["function"] = self.function(fn["function"]) 46 else: 47 fn = self.function(fn) 48 functions.append(fn) 49 50 return functions 51 52 def reset(self): 53 """ 54 Clears all resolved references. 55 """ 56 57 if self.references: 58 for reference in self.references: 59 reference.reset() 60 61 def function(self, function): 62 """ 63 Resolves function configuration. If function is a string, it's split on '.' and each part 64 is separately resolved to an object, attribute or function. Each part is resolved upon the 65 first invocation of the function. Otherwise, the input is returned. 66 67 Args: 68 function: function configuration 69 70 Returns: 71 function reference 72 """ 73 74 if isinstance(function, str): 75 parts = function.split(".") 76 77 if hasattr(self.embeddings, parts[0]): 78 m = Reference(self.embeddings, parts[0]) 79 self.references.append(m) 80 else: 81 module = ".".join(parts[:-1]) 82 m = __import__(module) 83 84 for comp in parts[1:]: 85 m = Reference(m, comp) 86 self.references.append(m) 87 88 return m 89 90 return function 91 92 93 class Reference: 94 """ 95 Stores a reference to an object attribute. This attribute is resolved by invoking the __call__ method. 96 This allows for functions to be independent of the initialization order of an embeddings instance. 97 """ 98 99 def __init__(self, obj, attribute): 100 """ 101 Create a new reference. 102 103 Args: 104 obj: object handle 105 attribute: attribute name 106 """ 107 108 # Object handle and attribute 109 self.obj = obj 110 self.attribute = attribute 111 112 # Keep a handle to the original inputs 113 self.inputs = (obj, attribute) 114 115 # True if the object and attribute have been resolved 116 self.resolved = False 117 118 # True if the attribute is a function 119 self.function = None 120 121 def __call__(self, *args): 122 """ 123 Resolves an object attribute reference. If the attribute is a function, the function is executed. 124 Otherwise, the object attribute value is returned. 125 126 Args: 127 args: list of function arguments to the object attribute, when attribute is a function 128 129 Returns: 130 object attribute function result or object attribute value 131 """ 132 133 # Resolve nested function arguments, if necessary 134 if not self.resolved: 135 self.obj = self.obj() if isinstance(self.obj, Reference) else self.obj 136 self.attribute = self.attribute() if isinstance(self.attribute, Reference) else self.attribute 137 self.resolved = True 138 139 # Lookup attribute 140 attribute = getattr(self.obj, self.attribute) 141 142 # Determine if attribute is a function 143 if self.function is None: 144 self.function = isinstance(attribute, (FunctionType, MethodType)) or (hasattr(attribute, "__call__") and args) 145 146 # If attribute is a function, execute and return, otherwise return attribute 147 return attribute(*args) if self.function else attribute 148 149 def reset(self): 150 """ 151 Clears resolved references. 152 """ 153 154 self.obj, self.attribute = self.inputs 155 self.resolved = False