├── 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 += "| %d | %s |
" % (i,ctype(k),ddata(k))
264 | return out + "
"
265 | elif type(x) == dict:
266 | out = ""
267 | for i in x:
268 | out += "| %s | %s |
" % (ddata(i),ctype(x[i]),ddata(x[i]))
269 | return out + "
"
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