├── README.md ├── setup.py ├── gui_fun └── __init__.py └── notebooks └── tutorial.ipynb /README.md: -------------------------------------------------------------------------------- 1 | # gui_fun 2 | Instantly create a gui for any Python function in a Jupyter notebook. 3 | 4 | The basic idea is that if we create a function, say 5 | 6 |
 7 |   def add(a,b):
 8 |     return a+b
 9 | 
10 | 11 | We can create a gui to collect the input (values for "a" and "b") and 12 | call the function to produce output by simply calling: 13 |
14 |   gui_fun(add)
15 | 
16 | 17 | For details, see the tutorial notebook. 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='gui_fun', 5 | version='1.0.8', 6 | description='Create a basic gui for a function', 7 | long_description=''' 8 | Create a basic gui for a function. 9 | Calling gui_fun(func) will generate a sequence of input boxes 10 | for each argument in func, plus a run button to execute the function. 11 | The output of gui_fun() is an asyncio.Future that can be queried for 12 | the result. 13 | ''', 14 | url='http://cct.lsu.edu/~sbrandt/', 15 | author='Steven R. Brandt', 16 | author_email='steven@stevenrbrandt.com', 17 | license='LGPL', 18 | packages=['gui_fun'] 19 | ) 20 | -------------------------------------------------------------------------------- /gui_fun/__init__.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import pickle 3 | import stat 4 | import os 5 | import re 6 | import html 7 | from ipywidgets import * 8 | 9 | class ResultStream: 10 | def __init__(self): 11 | self.results = [] 12 | self.listeners = [] 13 | def add_listener(self, listener): 14 | self.listeners += [listener] 15 | def has_result(self): 16 | return len(self.results) > 0 17 | def pop_result(self): 18 | val = self.results[0] 19 | self.results = self.results[1:] 20 | return val 21 | def add_result(self,result): 22 | new_listeners = [] 23 | for listener in self.listeners: 24 | try: 25 | listener(result) 26 | new_listeners += [listener] 27 | except Exception as e: 28 | print(e) 29 | self.listeners = new_listeners 30 | self.results += [result] 31 | 32 | display(HTML(""" 33 | 52 | """)) 53 | 54 | gui_settings = {} 55 | 56 | def settings(f, s): 57 | global gui_settings 58 | fn = f.__name__ 59 | mo = str(f.__module__) 60 | if mo not in gui_settings: 61 | gui_settings[mo] = {} 62 | gui_settings[mo][fn] = s 63 | 64 | 65 | def load_property(f, tag): 66 | if tag == "defaults": 67 | return {} 68 | fn = f.__name__ 69 | mo = str(f.__module__) 70 | path = os.path.join(os.environ["HOME"],"tmp",mo,fn,tag,"data.txt") 71 | if os.path.exists(path): 72 | with open(path,"rb") as fd: 73 | return pickle.loads(fd.read()) 74 | return {} 75 | 76 | def store_property(f,r,tag): 77 | fn = f.__name__ 78 | mo = str(f.__module__) 79 | dirp = os.path.join(os.environ["HOME"],"tmp",mo,fn,tag) 80 | os.makedirs(dirp, exist_ok=True) 81 | path = os.path.join(dirp, "data.txt") 82 | with open(path, "wb") as fd: 83 | os.chmod(path, stat.S_IREAD | stat.S_IWRITE) 84 | fd.write(pickle.dumps(r)) 85 | 86 | # A length function that returns 0 for None 87 | def zlen(z): 88 | if z is None: 89 | return 0 90 | else: 91 | return len(z) 92 | 93 | # Convert a string from the input. If the string 94 | # begins with [ or {, it is a list or an array 95 | # and eval should be called. 96 | def unstr(s): 97 | if type(s) != str: 98 | return s 99 | 100 | if s == "None": 101 | return None 102 | elif re.match(r'[\[{"]',s): 103 | return eval(s) 104 | else: 105 | return s 106 | 107 | def BLabel(value,style): 108 | label = Label(value=value) 109 | label.add_class(style) 110 | return label 111 | 112 | def gui_fun(f, tag="", defaults=None, settings = None): 113 | global gui_settings 114 | fn = f.__name__ 115 | mo = str(f.__module__) 116 | if settings is None: 117 | if mo in gui_settings: 118 | settings = gui_settings[mo].get(fn, None) 119 | if settings is None: 120 | settings = {} 121 | 122 | fargs = inspect.getfullargspec(f) 123 | # widgets to collect data 124 | disp = [] 125 | # labels for the widgets, using the description 126 | # field in the widget seems not to work right. 127 | # If the description it gets too long and no 128 | # setting of Layout fixes it. 129 | desc = [] 130 | disp += [BLabel(value=f.__name__,style='gui_label_style')] 131 | desc += [Label()] 132 | index = 1 133 | for i in range(len(fargs.args)): 134 | arg = fargs.args[i] 135 | if arg == "self" and i == 0: 136 | index = 0 137 | continue 138 | a = fargs.annotations.get(arg, None) 139 | if type(settings.get(arg,"")) == list: 140 | options = settings.get(arg) 141 | if type(options[0]) == tuple: 142 | value = options[0][1] 143 | else: 144 | value = options[0] 145 | t = Dropdown(options=options, value=value) 146 | elif a == list: 147 | options = a 148 | if type(options[0]) == tuple: 149 | value = options[0][0] 150 | else: 151 | value = options[0] 152 | t = Dropdown(options=options, value=value) 153 | elif a == int: 154 | t = IntText() 155 | elif a == float: 156 | t = FloatText() 157 | elif settings.get(arg,"") == "password" or a == "password": 158 | t = Password() 159 | elif settings.get(arg,"") == "textarea" or a == "textarea": 160 | t = Textarea() 161 | else: 162 | t = Text() 163 | if i % 2 == 0: 164 | d = BLabel(value=arg,style='off_label_style_1') 165 | else: 166 | d = BLabel(value=arg,style='off_label_style_0') 167 | desc += [d] 168 | disp += [t] 169 | 170 | # Create an array of blank strings to use 171 | # for default display values 172 | empty = object() 173 | r = [empty for i in range(len(fargs.args))] 174 | 175 | # Load the default values from the 176 | # function definitions. 177 | off = zlen(fargs.args) - zlen(fargs.defaults) 178 | for i in range(zlen(fargs.defaults)): 179 | r[i+off] = fargs.defaults[i] 180 | 181 | # Load the values stored from the 182 | # last time this function was invoked. 183 | if defaults is not None: 184 | pairs = load_property(f, defaults) 185 | for i in range(len(fargs.args)): 186 | r[i] = pairs.get(fargs.args[i], r[i]) 187 | pairs = load_property(f, tag) 188 | for i in range(len(fargs.args)): 189 | r[i] = pairs.get(fargs.args[i], r[i]) 190 | for i in range(len(fargs.args)): 191 | # Don't prompt for the self field of 192 | # member functions. That is already 193 | # filled in if gui() is called properly. 194 | if r[i] == empty: 195 | pass 196 | elif fargs.args[i] == "self": 197 | pass 198 | elif type(disp[i+index]) in [Password, Text, Textarea]: 199 | disp[i+index].value = str(r[i]) 200 | elif type(disp[i+index]) == IntText: 201 | if r[i] == "": 202 | disp[i+index].value = 0 203 | else: 204 | disp[i+index].value = int(r[i]) 205 | elif type(disp[i+index]) == FloatText: 206 | if r[i] == "": 207 | disp[i+index].value = 0 208 | else: 209 | disp[i+index].value = float(r[i]) 210 | elif type(disp[i+index]) == Dropdown: 211 | disp[i+index].value = r[i] 212 | run = Button(description="Run") 213 | retval = ResultStream() 214 | def gui_run(_): 215 | vals = [] 216 | pairs = {} 217 | for i in range(len(fargs.args)): 218 | if i == 0 and fargs.args[0] == "self": 219 | pass 220 | else: 221 | # load the values... 222 | d = disp[i+index] 223 | # load the argument names... 224 | l = desc[i+index] 225 | vals += [unstr(d.value)] 226 | # store them. 227 | pairs[l.value] = d.value 228 | retval.add_result( f(*vals) ) 229 | # Because we store name value pairs, 230 | # stored values will still load properly 231 | # if we change the arguments around. 232 | store_property(f, pairs, tag) 233 | run.on_click(gui_run) 234 | disp += [run] 235 | desc += [Label()] 236 | grid = [] 237 | for i in range(len(disp)): 238 | grid += [desc[i], disp[i]] 239 | display(GridBox(children=grid, layout=Layout(grid_template_columns='30% 50%'))) 240 | return retval 241 | 242 | def ctype(x): 243 | t = type(x) 244 | if t == int: 245 | return "itype" 246 | elif t == float: 247 | return "ftype" 248 | elif t == list: 249 | return "ltype" 250 | elif t == dict: 251 | return "dtype" 252 | elif t == str: 253 | return "stype" 254 | elif t == tuple: 255 | return "ttype" 256 | else: 257 | return "otype" 258 | 259 | def ddata(x): 260 | if type(x) in [list, set, tuple]: 261 | out = "" 262 | for i,k in enumerate(x): 263 | out += "" % (i,ctype(k),ddata(k)) 264 | return out + "
%d%s
" 265 | elif type(x) == dict: 266 | out = "" 267 | for i in x: 268 | out += "" % (ddata(i),ctype(x[i]),ddata(x[i])) 269 | return out + "
%s%s
" 270 | else: 271 | return html.escape(str(x)) 272 | 273 | def gui_show(x): 274 | display(HTML(""" 275 | 308 | """)) 309 | display(HTML(ddata(x))) 310 | -------------------------------------------------------------------------------- /notebooks/tutorial.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "data": { 10 | "application/vnd.jupyter.widget-view+json": { 11 | "model_id": "2d76e6689f3c4010a255eb1684d60a9b", 12 | "version_major": 2, 13 | "version_minor": 0 14 | }, 15 | "text/plain": [ 16 | "HTML(value='\\n