pamda.pamda_curry
1import types, threading, ctypes 2from functools import update_wrapper 3import type_enforced 4 5 6class curry_obj: 7 def __init__( 8 self, 9 __fn__, 10 *__args__, 11 __flips__=[], 12 __fnExecute__=None, 13 __isThunk__=False, 14 __isTypeEnforced__=False, 15 **__kwargs__, 16 ): 17 update_wrapper(self, __fn__) 18 self.__fn__ = __fn__ 19 self.__fnExecute__ = ( 20 __fnExecute__ if __fnExecute__ is not None else __fn__ 21 ) 22 self.__args__ = __args__ 23 self.__kwargs__ = __kwargs__ 24 self.__isCurried__ = True 25 self.__isThunk__ = __isThunk__ 26 self.__isTypeEnforced__ = __isTypeEnforced__ 27 self.__flips__ = __flips__ 28 self.__fnArity__ = self.__getFnArity__() 29 self.__arity__ = self.__getArity__(__args__, __kwargs__) 30 self.__thread__ = None 31 self.__thread_results__ = None 32 self.__get_fn_arg_default_keys__() 33 34 def __call__(self, *args, **kwargs): 35 new_args = self.__args__ + args 36 new_kwargs = dict(**self.__kwargs__, **kwargs) 37 # Create a comprehensive set of assigned variable names to determine arity 38 assigned_vars = set( 39 self.__fn_arg_default_keys__ 40 + self.__fn_arg_keys__[: len(new_args)] 41 + list(new_kwargs.keys()) 42 ) 43 arity = self.__fnArity__ - len(assigned_vars) 44 if arity < 0: 45 self.__exception__("Too many arguments were supplied") 46 elif arity == 0: 47 if len(self.__flips__) > 0: 48 new_args = self.__unflipArgs__(new_args) 49 if (not self.__isThunk__) or (len(args) + len(kwargs) == 0): 50 results = self.__fnExecute__(*new_args, **new_kwargs) 51 if self.__thread__ != None: 52 self.__thread_results__ = results 53 return results 54 return curry_obj( 55 self.__fn__, 56 *new_args, 57 __flips__=self.__flips__, 58 __isThunk__=self.__isThunk__, 59 __isTypeEnforced__=self.__isTypeEnforced__, 60 __fnExecute__=self.__fnExecute__, 61 **new_kwargs, 62 ) 63 64 def __get__(self, instance, owner): 65 def bind(*args, **kwargs): 66 if instance is not None and self.__arity__ == self.__fnArity__: 67 return self.__call__(instance, *args, **kwargs) 68 else: 69 return self.__call__(*args, **kwargs) 70 71 return bind 72 73 def __repr__(self): 74 return f"<curried {self.__fn__.__module__}.{self.__fn__.__qualname__} object at {hex(id(self))}>" 75 76 def __getArity__(self, args, kwargs): 77 return self.__fnArity__ - (len(args) + len(kwargs)) 78 79 def __getFnArity__(self): 80 if not isinstance(self.__fn__, (types.MethodType, types.FunctionType)): 81 self.__exception__( 82 "A non function was passed as a function and does not have any arity. See the stack trace above for more information." 83 ) 84 extra_method_input_count = ( 85 1 if isinstance(self.__fn__, (types.MethodType)) else 0 86 ) 87 return self.__fn__.__code__.co_argcount - extra_method_input_count 88 89 def __get_fn_arg_default_keys__(self): 90 """ 91 Get the default values of the passed function or method and store them in `self.__fn_defaults__`. 92 """ 93 self.__fn_var_keys__ = list(self.__fn__.__code__.co_varnames) 94 self.__fn_arg_keys__ = self.__fn_var_keys__[ 95 : self.__fn__.__code__.co_argcount 96 ] 97 if self.__fn__.__defaults__ is not None: 98 self.__fn_arg_default_keys__ = self.__fn_arg_keys__[ 99 -len(self.__fn__.__defaults__) : 100 ] 101 else: 102 self.__fn_arg_default_keys__ = [] 103 if self.__fn__.__kwdefaults__ is not None: 104 self.__fn_arg_default_keys__.extend( 105 list(self.__fn__.__kwdefaults__.keys()) 106 ) 107 108 def thunkify(self): 109 self.__isThunk__ = True 110 return self 111 112 def flip(self): 113 if self.__arity__ < 2: 114 self.__exception__( 115 "To `flip` a function, it must have an arity of at least 2 (take two or more inputs)" 116 ) 117 self.__flips__ = [len(self.__args__)] + self.__flips__ 118 return self 119 120 def __unflipArgs__(self, args): 121 args = list(args) 122 for flip in self.__flips__: 123 arg = args.pop(flip + 1) 124 args.insert(flip, arg) 125 return tuple(args) 126 127 def __exception__(self, message): 128 pre_message = ( 129 f"({self.__fn__.__module__}.{self.__fn__.__qualname__}_curried): " 130 ) 131 raise Exception(pre_message + message) 132 133 def typeEnforce(self): 134 if not self.__isTypeEnforced__: 135 self.__fnExecute__ = type_enforced.Enforcer(self.__fnExecute__) 136 self.__isTypeEnforced__ = True 137 return self 138 139 def asyncRun(self, daemon=False): 140 if not self.__isThunk__ and self.__arity__ == 0: 141 self.__exception__( 142 f"To `asyncRun` a Function, it must be a thunk with arity 0" 143 ) 144 if self.__thread__ is not None: 145 self.__exception__( 146 "`asyncRun` has already been executed on this thunk" 147 ) 148 self.__thread_completed__ = False 149 self.__thread__ = threading.Thread(target=self) 150 self.__thread__.setDaemon(daemon) 151 self.__thread__.start() 152 return self 153 154 def asyncWait(self): 155 if self.__thread__ == None: 156 self.__exception__( 157 f"To `asyncWait` a Function, it must be `asyncRun` first" 158 ) 159 if not self.__thread_completed__: 160 self.__thread__.join() 161 self.__thread_completed__ = True 162 return self.__thread_results__ 163 164 def asyncKill(self): 165 if self.__thread__ == None: 166 self.__exception__( 167 f"To `asyncKill` a Function, it must be `asyncRun` first" 168 ) 169 if self.__thread_completed__: 170 return self.__thread_results__ 171 172 thread_id = self.__thread__.ident 173 if thread_id is None: 174 self.__exception__( 175 f"Cannot `asyncKill` a Function that does not have a thread id" 176 ) 177 178 # Use ctypes to set the async exception 179 try: 180 res = ctypes.pythonapi.PyThreadState_SetAsyncExc( 181 ctypes.c_long(thread_id), ctypes.py_object(SystemExit) 182 ) 183 except: 184 self.__exception__( 185 f"Failed to `asyncKill`: Something failed when seting the Exit state for thread id ({thread_id})" 186 ) 187 if res == 0: 188 self.__exception__( 189 f"Failed to `asyncKill`: Thread id not found ({thread_id})" 190 ) 191 elif res == 1: 192 # Success, thread killed join the thread to clean up resources 193 self.__thread_completed__ = True 194 self.__thread__.join() 195 return self.__thread_results__ 196 elif res > 1: 197 # Something is wrong, set it back to 0 198 ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) 199 self.__exception__( 200 f"Failed to `asyncKill`: ctypes returned multiple results for thread id ({thread_id}). This should not happen. Returned thread state back to normal." 201 )
class
curry_obj:
7class curry_obj: 8 def __init__( 9 self, 10 __fn__, 11 *__args__, 12 __flips__=[], 13 __fnExecute__=None, 14 __isThunk__=False, 15 __isTypeEnforced__=False, 16 **__kwargs__, 17 ): 18 update_wrapper(self, __fn__) 19 self.__fn__ = __fn__ 20 self.__fnExecute__ = ( 21 __fnExecute__ if __fnExecute__ is not None else __fn__ 22 ) 23 self.__args__ = __args__ 24 self.__kwargs__ = __kwargs__ 25 self.__isCurried__ = True 26 self.__isThunk__ = __isThunk__ 27 self.__isTypeEnforced__ = __isTypeEnforced__ 28 self.__flips__ = __flips__ 29 self.__fnArity__ = self.__getFnArity__() 30 self.__arity__ = self.__getArity__(__args__, __kwargs__) 31 self.__thread__ = None 32 self.__thread_results__ = None 33 self.__get_fn_arg_default_keys__() 34 35 def __call__(self, *args, **kwargs): 36 new_args = self.__args__ + args 37 new_kwargs = dict(**self.__kwargs__, **kwargs) 38 # Create a comprehensive set of assigned variable names to determine arity 39 assigned_vars = set( 40 self.__fn_arg_default_keys__ 41 + self.__fn_arg_keys__[: len(new_args)] 42 + list(new_kwargs.keys()) 43 ) 44 arity = self.__fnArity__ - len(assigned_vars) 45 if arity < 0: 46 self.__exception__("Too many arguments were supplied") 47 elif arity == 0: 48 if len(self.__flips__) > 0: 49 new_args = self.__unflipArgs__(new_args) 50 if (not self.__isThunk__) or (len(args) + len(kwargs) == 0): 51 results = self.__fnExecute__(*new_args, **new_kwargs) 52 if self.__thread__ != None: 53 self.__thread_results__ = results 54 return results 55 return curry_obj( 56 self.__fn__, 57 *new_args, 58 __flips__=self.__flips__, 59 __isThunk__=self.__isThunk__, 60 __isTypeEnforced__=self.__isTypeEnforced__, 61 __fnExecute__=self.__fnExecute__, 62 **new_kwargs, 63 ) 64 65 def __get__(self, instance, owner): 66 def bind(*args, **kwargs): 67 if instance is not None and self.__arity__ == self.__fnArity__: 68 return self.__call__(instance, *args, **kwargs) 69 else: 70 return self.__call__(*args, **kwargs) 71 72 return bind 73 74 def __repr__(self): 75 return f"<curried {self.__fn__.__module__}.{self.__fn__.__qualname__} object at {hex(id(self))}>" 76 77 def __getArity__(self, args, kwargs): 78 return self.__fnArity__ - (len(args) + len(kwargs)) 79 80 def __getFnArity__(self): 81 if not isinstance(self.__fn__, (types.MethodType, types.FunctionType)): 82 self.__exception__( 83 "A non function was passed as a function and does not have any arity. See the stack trace above for more information." 84 ) 85 extra_method_input_count = ( 86 1 if isinstance(self.__fn__, (types.MethodType)) else 0 87 ) 88 return self.__fn__.__code__.co_argcount - extra_method_input_count 89 90 def __get_fn_arg_default_keys__(self): 91 """ 92 Get the default values of the passed function or method and store them in `self.__fn_defaults__`. 93 """ 94 self.__fn_var_keys__ = list(self.__fn__.__code__.co_varnames) 95 self.__fn_arg_keys__ = self.__fn_var_keys__[ 96 : self.__fn__.__code__.co_argcount 97 ] 98 if self.__fn__.__defaults__ is not None: 99 self.__fn_arg_default_keys__ = self.__fn_arg_keys__[ 100 -len(self.__fn__.__defaults__) : 101 ] 102 else: 103 self.__fn_arg_default_keys__ = [] 104 if self.__fn__.__kwdefaults__ is not None: 105 self.__fn_arg_default_keys__.extend( 106 list(self.__fn__.__kwdefaults__.keys()) 107 ) 108 109 def thunkify(self): 110 self.__isThunk__ = True 111 return self 112 113 def flip(self): 114 if self.__arity__ < 2: 115 self.__exception__( 116 "To `flip` a function, it must have an arity of at least 2 (take two or more inputs)" 117 ) 118 self.__flips__ = [len(self.__args__)] + self.__flips__ 119 return self 120 121 def __unflipArgs__(self, args): 122 args = list(args) 123 for flip in self.__flips__: 124 arg = args.pop(flip + 1) 125 args.insert(flip, arg) 126 return tuple(args) 127 128 def __exception__(self, message): 129 pre_message = ( 130 f"({self.__fn__.__module__}.{self.__fn__.__qualname__}_curried): " 131 ) 132 raise Exception(pre_message + message) 133 134 def typeEnforce(self): 135 if not self.__isTypeEnforced__: 136 self.__fnExecute__ = type_enforced.Enforcer(self.__fnExecute__) 137 self.__isTypeEnforced__ = True 138 return self 139 140 def asyncRun(self, daemon=False): 141 if not self.__isThunk__ and self.__arity__ == 0: 142 self.__exception__( 143 f"To `asyncRun` a Function, it must be a thunk with arity 0" 144 ) 145 if self.__thread__ is not None: 146 self.__exception__( 147 "`asyncRun` has already been executed on this thunk" 148 ) 149 self.__thread_completed__ = False 150 self.__thread__ = threading.Thread(target=self) 151 self.__thread__.setDaemon(daemon) 152 self.__thread__.start() 153 return self 154 155 def asyncWait(self): 156 if self.__thread__ == None: 157 self.__exception__( 158 f"To `asyncWait` a Function, it must be `asyncRun` first" 159 ) 160 if not self.__thread_completed__: 161 self.__thread__.join() 162 self.__thread_completed__ = True 163 return self.__thread_results__ 164 165 def asyncKill(self): 166 if self.__thread__ == None: 167 self.__exception__( 168 f"To `asyncKill` a Function, it must be `asyncRun` first" 169 ) 170 if self.__thread_completed__: 171 return self.__thread_results__ 172 173 thread_id = self.__thread__.ident 174 if thread_id is None: 175 self.__exception__( 176 f"Cannot `asyncKill` a Function that does not have a thread id" 177 ) 178 179 # Use ctypes to set the async exception 180 try: 181 res = ctypes.pythonapi.PyThreadState_SetAsyncExc( 182 ctypes.c_long(thread_id), ctypes.py_object(SystemExit) 183 ) 184 except: 185 self.__exception__( 186 f"Failed to `asyncKill`: Something failed when seting the Exit state for thread id ({thread_id})" 187 ) 188 if res == 0: 189 self.__exception__( 190 f"Failed to `asyncKill`: Thread id not found ({thread_id})" 191 ) 192 elif res == 1: 193 # Success, thread killed join the thread to clean up resources 194 self.__thread_completed__ = True 195 self.__thread__.join() 196 return self.__thread_results__ 197 elif res > 1: 198 # Something is wrong, set it back to 0 199 ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) 200 self.__exception__( 201 f"Failed to `asyncKill`: ctypes returned multiple results for thread id ({thread_id}). This should not happen. Returned thread state back to normal." 202 )
curry_obj( __fn__, *__args__, __flips__=[], __fnExecute__=None, __isThunk__=False, __isTypeEnforced__=False, **__kwargs__)
8 def __init__( 9 self, 10 __fn__, 11 *__args__, 12 __flips__=[], 13 __fnExecute__=None, 14 __isThunk__=False, 15 __isTypeEnforced__=False, 16 **__kwargs__, 17 ): 18 update_wrapper(self, __fn__) 19 self.__fn__ = __fn__ 20 self.__fnExecute__ = ( 21 __fnExecute__ if __fnExecute__ is not None else __fn__ 22 ) 23 self.__args__ = __args__ 24 self.__kwargs__ = __kwargs__ 25 self.__isCurried__ = True 26 self.__isThunk__ = __isThunk__ 27 self.__isTypeEnforced__ = __isTypeEnforced__ 28 self.__flips__ = __flips__ 29 self.__fnArity__ = self.__getFnArity__() 30 self.__arity__ = self.__getArity__(__args__, __kwargs__) 31 self.__thread__ = None 32 self.__thread_results__ = None 33 self.__get_fn_arg_default_keys__()
def
asyncRun(self, daemon=False):
140 def asyncRun(self, daemon=False): 141 if not self.__isThunk__ and self.__arity__ == 0: 142 self.__exception__( 143 f"To `asyncRun` a Function, it must be a thunk with arity 0" 144 ) 145 if self.__thread__ is not None: 146 self.__exception__( 147 "`asyncRun` has already been executed on this thunk" 148 ) 149 self.__thread_completed__ = False 150 self.__thread__ = threading.Thread(target=self) 151 self.__thread__.setDaemon(daemon) 152 self.__thread__.start() 153 return self
def
asyncKill(self):
165 def asyncKill(self): 166 if self.__thread__ == None: 167 self.__exception__( 168 f"To `asyncKill` a Function, it must be `asyncRun` first" 169 ) 170 if self.__thread_completed__: 171 return self.__thread_results__ 172 173 thread_id = self.__thread__.ident 174 if thread_id is None: 175 self.__exception__( 176 f"Cannot `asyncKill` a Function that does not have a thread id" 177 ) 178 179 # Use ctypes to set the async exception 180 try: 181 res = ctypes.pythonapi.PyThreadState_SetAsyncExc( 182 ctypes.c_long(thread_id), ctypes.py_object(SystemExit) 183 ) 184 except: 185 self.__exception__( 186 f"Failed to `asyncKill`: Something failed when seting the Exit state for thread id ({thread_id})" 187 ) 188 if res == 0: 189 self.__exception__( 190 f"Failed to `asyncKill`: Thread id not found ({thread_id})" 191 ) 192 elif res == 1: 193 # Success, thread killed join the thread to clean up resources 194 self.__thread_completed__ = True 195 self.__thread__.join() 196 return self.__thread_results__ 197 elif res > 1: 198 # Something is wrong, set it back to 0 199 ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) 200 self.__exception__( 201 f"Failed to `asyncKill`: ctypes returned multiple results for thread id ({thread_id}). This should not happen. Returned thread state back to normal." 202 )