diff --git a/baulab/__init__.py b/baulab/__init__.py
new file mode 100644
index 0000000..06f9eee
--- /dev/null
+++ b/baulab/__init__.py
@@ -0,0 +1,14 @@
+from .labwidget import Model, Widget, Trigger, Property, Event
+from .labwidget import Button, Label, Textbox, Numberbox, Range, ColorPicker
+from .labwidget import Choice, Menu, Datalist, Div, ClickDiv, Image
+from .paintwidget import PaintWidget
+from .plotwidget import PlotWidget
+from . import pbar
+from .nethook import Trace, TraceDict, set_requires_grad
+from .nethook import subsequence, get_module, get_parameter, replace_module
+from .runningstats import Stat, Mean, Variance, Covariance, Bincount, CrossCovariance
+from .runningstats import IoU, CrossIoU, Quantile, TopK, History, CombinedStat
+from .runningstats import tally
+from . import show
+from .workerpool import WorkerBase, WorkerPool
+from .pidfile import reserve_dir
diff --git a/baulab/labwidget.py b/baulab/labwidget.py
index 5af0732..03620b9 100644
--- a/baulab/labwidget.py
+++ b/baulab/labwidget.py
@@ -49,6 +49,7 @@ import json
import html
import re
from inspect import signature
+from . import show
class Model(object):
@@ -180,7 +181,7 @@ class Widget(Model):
for the top-level widget element.
'''
- def __init__(self, style=None, data=None, className=None):
+ def __init__(self, style=None):
# In the jupyter case, there can be some delay between js injection
# and comm creation, so we need to queue some initial messages.
if WIDGET_ENV == 'jupyter':
@@ -197,8 +198,6 @@ class Widget(Model):
# The style and data properties come standard, and are used to
# control the style and data attributes on the toplevel element.
self.style = Property(style)
- self.className = Property(className)
- self.data = Property(data)
# Each widget has a "write" event that is used to insert
# html before the widget.
self.write = Trigger()
@@ -217,7 +216,8 @@ class Widget(Model):
Override to define the initial HTML view of the widget. Should
define an element with id given by view_id().
'''
- return f'
'
+ with show.enter_tag() as t:
+ return t.begin() + t.end()
def view_id(self):
'''
@@ -228,13 +228,9 @@ class Widget(Model):
def std_attrs(self):
'''
- Returns id and (if applicable) style attributes, escaped and
- formatted for use within the top-level element of widget HTML.
+ Returns id attribute, and possibly other standard attrs.
'''
- return (f'id="{self.view_id()}"' +
- style_attr(self.style) +
- class_attr(self.className) +
- data_attrs(self.data))
+ return show.attr(id=self.view_id())
def _repr_html_(self):
'''
@@ -557,9 +553,8 @@ class Button(Widget):
''')
def widget_html(self):
- return f''''''
-
+ return show.emit_tag('input', self.std_attrs(),
+ type='button', value=self.label)
class Label(Widget):
def __init__(self, value='', **kwargs):
@@ -578,17 +573,18 @@ class Label(Widget):
''')
def widget_html(self):
- return f''''''
+ out = []
+ with show.enter_tag('label', self.std_attrs(), out=out):
+ out.append(html.escape(str(self.value)))
+ return ''.join(out)
class Textbox(Widget):
- def __init__(self, value='', size=20, style=None, desc=None, **kwargs):
+ def __init__(self, value='', size=20, style=None, **kwargs):
super().__init__(style=defaulted(style, display='inline-block'), **kwargs)
# databinding is defined using Property objects.
self.value = Property(value)
self.size = Property(size)
- self.desc = Property(desc)
def widget_js(self):
# Both "model" and "element" objects are defined within the scope
@@ -614,21 +610,16 @@ class Textbox(Widget):
''')
def widget_html(self):
-
- html_str = f''''''
- if self.desc is not None:
- html_str = f"""{self.desc}{html_str}"""
- return html_str
+ return show.emit_tag('input', self.std_attrs(),
+ type='text', value=self.value, size=self.size)
class Numberbox(Widget):
- def __init__(self, value='', size=20, style=None, desc=None, **kwargs):
+ def __init__(self, value='', size=20, style=None, **kwargs):
super().__init__(style=defaulted(style, display='inline-block'), **kwargs)
# databinding is defined using Property objects.
self.value = Property(value)
self.size = Property(size)
- self.desc = Property(desc)
def widget_js(self):
# Both "model" and "element" objects are defined within the scope
@@ -654,12 +645,8 @@ class Numberbox(Widget):
''')
def widget_html(self):
-
- html_str = f''''''
- if self.desc is not None:
- html_str = f"""{self.desc}{html_str}"""
- return html_str
+ return show.emit_tag('input', self.std_attrs(),
+ type='numeric', value=self.value, size=self.size)
class Range(Widget):
def __init__(self, value=50, min=0, max=100, step=1, **kwargs):
@@ -688,8 +675,9 @@ class Range(Widget):
''')
def widget_html(self):
- return f''''''
+ return show.emit_tag('input', self.std_attrs(),
+ type='range', value=self.value,
+ min=self.min, max=self.max, step=self.step)
class ColorPicker(Widget):
@@ -714,11 +702,8 @@ class ColorPicker(Widget):
''')
def widget_html(self):
- html_str = f''''''
- if self.desc is not None:
- html_str = f"""{self.desc}{html_str}"""
- return html_str
+ return show.emit_tag('input', self.std_attrs(),
+ type='color', value=self.value)
class Choice(Widget):
@@ -726,13 +711,12 @@ class Choice(Widget):
A set of radio button choices.
"""
- def __init__(self, choices=None, selection=None, horizontal=False,
+ def __init__(self, choices=None, selection=None,
**kwargs):
super().__init__(**kwargs)
if choices is None:
choices = []
self.choices = Property(choices)
- self.horizontal = Property(horizontal)
self.selection = Property(selection)
def widget_js(self):
@@ -748,7 +732,7 @@ class Choice(Widget):
return ''
});
- element.innerHTML = lines.join(model.get('horizontal')?' ':' ');
+ element.innerHTML = lines.join();
}
model.on('choices horizontal', render);
model.on('selection', (ev) => {
@@ -762,14 +746,14 @@ class Choice(Widget):
''')
def widget_html(self):
- radios = [
- f""""""
- for value in self.choices]
- sep = " " if self.horizontal else " "
- return f''
-
+ out = []
+ with show.enter_tag('form', self.std_attrs(), out=out):
+ for value in self.choices:
+ with show.enter_tag('label', out=out):
+ show.emit_tag('input',
+ (show.attrs(checked=None) if value == self.selection else None),
+ name='choice', type='radio', value=value, out=out)
+ return ''.join(out)
class Menu(Widget):
"""
@@ -798,7 +782,6 @@ class Menu(Widget):
});
element.menu.innerHTML = lines.join('\\n');
}
- model.on('choices horizontal', render);
model.on('selection', (ev) => {
[...element.querySelectorAll('option')].forEach((e) => {
e.selected = (e.value == ev.value);
@@ -810,14 +793,15 @@ class Menu(Widget):
''')
def widget_html(self):
- options = [
- f""""""
- for value in self.choices]
- sep = "\n"
- return f''''''
+ out = []
+ with show.enter_tag('form', self.std_attrs(), out=out):
+ with show.enter_tag(show.Tag('select', name='menu'), out=out):
+ for value in self.choices:
+ with show.enter_tag(show.Tag('option',
+ (show.attrs(selected=None) if value == self.selection else None),
+ value=value, out=out)):
+ out.append(html.escape(str(value)))
+ return ''.join(out)
class Datalist(Widget):
@@ -877,15 +861,14 @@ class Datalist(Widget):
''')
def widget_html(self):
- options = [
- f"""