Bridge widget helpers

Basis of the bridge: bundling, blocking operations, and AnyWidget.

This module provides low-level infrastructure for Bridget: - JS Bundling: Concatenate and transform JavaScript sources - Blocking Operations: Block Python execution while allowing UI interaction - AnyWidget Base: BridgeWidget class for bidirectional messaging

VFile System

Hacking around to get anywidget vfile: working in Script and Style. Unfortunately, it’s not a public API. If useful, we could write a similar util for Bridget.

NOTE: %load_ext anywidget must have been previously run for this to work


source

read_vfile

 read_vfile (cts:str)

vfile: Components

FastHTML xtend Script and Style with vfile: support.


source

StyleV

 StyleV (*c, id=None, cls=None, title=None, style=None, attrmap=None,
         valmap=None, ft_cls=None, **kwargs)

A Style w/ code or vfile: contents that doesn’t escape its code

Type Default Details
c VAR_POSITIONAL
id NoneType None
cls NoneType None
title NoneType None
style NoneType None
attrmap NoneType None
valmap NoneType None
ft_cls NoneType None
kwargs VAR_KEYWORD
Returns FT type: ignore

source

ScriptV

 ScriptV (code:str='', id=None, cls=None, title=None, style=None,
          attrmap=None, valmap=None, ft_cls=None, **kwargs)

A Script w/ code or vfile: contents that doesn’t escape its code

Type Default Details
code str
id NoneType None
cls NoneType None
title NoneType None
style NoneType None
attrmap NoneType None
valmap NoneType None
ft_cls NoneType None
kwargs VAR_KEYWORD
Returns FT type: ignore

anysource


source

anysource

 anysource (*args:str|pathlib.Path|__main__.SourceProvider)

Read and join text from files, vfiles or strings


source

SourceProvider

 SourceProvider (*args, **kwargs)

Object with a .source attribute (string or callable returning string)

class Src:
    source = 'c'
test_eq(anysource('a', '', 'b', Src()), 'a\nb\nc')

Bundling utilities

Very basic JS bundler. Only concat and basic import transformation for now.

Two variants: async for use with await, sync for direct calls.
The sync version uses from_thread to handle async operations.

async def bundled(*sources, debugger=False, command:str|None=None, **kwargs):
    "Concat javascript `sources`. Optionally, first run command."
    if command:
        try:
            _, stderr = await arun_command(command, **kwargs)
        except Exception as e:
            stderr = str(e)
        if stderr: raise RuntimeError(stderr)
    if debugger: return anysource('debugger;', *sources)
    return anysource(*sources)
test_eq(await bundled('a'), 'a')
with test_raises(FileNotFoundError):
    await bundled(Path('not_found.js'))
with test_raises(RuntimeError):
    await bundled(command='ls', cwd=Path('/not/found'))
test_eq(await bundled(command='ls', debugger=True), 'debugger;')
with tempfile.NamedTemporaryFile('w') as fp:
    fp.write('a')
    fp.seek(0)
    test_eq(await bundled(Path(fp.name)), 'a')
def bundled(*sources, debugger=False, command:str|None=None, **kwargs):
    "Concat javascript `sources`. Run `command` first if not None."
    async def _run(): return await arun_command(command, **kwargs)  # type: ignore
    if command:
        with from_thread.start_blocking_portal() as portal: 
            try: _, stderr = portal.call(_run)
            except Exception as e: stderr = str(e)
            if stderr: raise RuntimeError(stderr)
    if debugger: return anysource('debugger;', *sources)
    return anysource(*sources)
test_eq(bundled('a'), 'a')
with test_raises(FileNotFoundError):
    bundled(Path('not_found.js'))
with test_raises(RuntimeError):
    bundled(command='ls', cwd=Path('/not/found'))
test_eq(bundled(command='ls', debugger=True), 'debugger;')
with tempfile.NamedTemporaryFile('w') as fp:
    fp.write('a')
    fp.seek(0)
    test_eq(bundled(Path(fp.name)), 'a')
#  ./node_modules/.bin/esbuild --bundle --format=esm --outdir=bridget/js nbs/js/logger.js

source

bundled

 bundled (*sources:str|Path, bundle:bool=True, bundler:str='copy',
          outdir:str|Path|None=None, command:str|None=None, **kwargs)
Type Default Details
sources str | Path type: ignore
bundle bool True
bundler str copy
outdir str | Path | None None
command str | None None
kwargs VAR_KEYWORD

source

Bundle

 Bundle (*sources:str|pathlib.Path, bundle:bool=True, bundler:str='copy',
         outdir:str|pathlib.Path|None=None, command:str|None=None,
         **cmd_kw)

Basic JS bundler class.

Type Default Details
sources str | pathlib.Path javascript source
bundle bool True Bundle using bundler with sources as entry points
bundler str copy Bundler type to use, one of ‘esbuild’, ‘copy’
outdir str | pathlib.Path | None None Output directory for bundled files
command str | None None Run command first if not None
cmd_kw VAR_KEYWORD
(f1 := Path('./js/b.js')).write_text('B');
bnd1 = bundled('a', f1, 'c')
test_eq(bnd1.sources, ('a', f1, 'c'))
test_eq(bnd1.bundled_sources, ('a', bnd1.outdir/ f1.name, 'c'))
test_eq(bnd1.source, 'a\nB\nc')
test_eq(bnd1(), 'a\nB\nc')
f2 = Path(bnd1.bundled_sources[1])

src = 'js/test.js'
bnd2 = bundled(Path(src))
test_eq(bnd2.bundled_sources[0], bundle_path('bridget')/src)
test_eq(bnd2.bundled_sources[0].read_text(), Path(src).read_text())  # type: ignore
f3 = Path(bnd2.bundled_sources[0])
test_eq(bnd2.bundle_path_of(src), f3)

prj_root = Path().resolve().parent
Path(src).resolve().relative_to(prj_root), f3.relative_to(prj_root)
(Path('nbs/js/test.js'), Path('bridget/js/test.js'))

Bundle(…), or bundled(…), works lazily, it simply create the bundle instance. It won’t move, transform, or aggregate files until you explicitly call it (or retrieve source or bundled_sources):

bundled(..., bundle=True,bundler='copy').bundled_sources, the default, will just copy the sources to the outdir

with tempfile.NamedTemporaryFile('w', suffix='.js', dir='./js') as fp:
    (p := Path(fp.name)).write_text('asdfgh')
    bndl = bundled(p, bundler='esbuild')
    test_is('asdfgh' in bndl(), True)
    bndl.bundled_sources[0].unlink()  # type: ignore

bnd3 = bundled(f1, Path(src), bundler='esbuild')
test_eq(bnd3.bundled_sources, (bnd3.outdir/ f1.name, bundle_path('bridget')/src))
test_is(r'stripAnsi("\x1B[4mBridget\x1B[0m")' in bnd3(), True)
f3 = Path(bnd3.bundled_sources[1])

Path(src).resolve().relative_to(prj_root), f3.relative_to(prj_root)
(Path('nbs/js/test.js'), Path('bridget/js/test.js'))

bundled(..., bundle=True,bundler='esbuild').bundled_sources will bundle each Path in sources as entry point.

with test_raises(ValueError):
    bundled(Path('not_found.js'), bundler='esbuild')()
with test_raises(FileNotFoundError):
    bundled(bundler='esbuild', command='ls', cwd=Path('/not/found'))()
bnd4 = bundled('import {a,b,c} from "./js/d.js"')
test_eq(bnd4.source, 'import {a,b,c} from "./js/d.js"')

test_eq(bnd4(), 'const {a, b, c} = await brdimport("./js/d.js");')

Note that source and __call__ are not the same.

source will simply concatenate all the bundled sources.

__call__ can bundled additional sources (string or another Bundle) and will tranform the final source. This is what you should use as the _esm of AnyWidgets.

test_eq(bundled(command='ls')(debugger=True), 'debugger;')

src = bundled()(debugger=True, ts=True)
print(src)
test_is('debugger;  // ' in src, True)

src = bundled('import a from "b"')(debugger=True)
test_eq(src, 'debugger;\nconst {default: a} = await brdimport("b");')
print(src)

src = bnd3('// a;', debugger=True)
cprint(src)
debugger;  // 1764771018.388513
debugger;
const {default: a} = await brdimport("b");
debugger;
// a;
// nbs/js/b.js
B;

// nbs/js/test.js
debugger;
var stripAnsi = require_strip_ansi();
function test() {
  console.log("test() called");
  console.log(stripAnsi("\x1B[4mBridget\x1B[0m"));
}
var test_default = test;
export {
  test_default as default
};

for f in (f1, f2, f3): f.unlink(True)

Resolve a JavaScript module specifier relative to a base path.

Given a string containing a module specifier like the ones used in JS import declarations or dynamic import(), returns an absolute URL or a local Path to an existing file.

the HTML spec
MDN

show(DetailsJSON(bridge_cfg.as_dict(), summary='Bridget Config'))
Bridget Config
  • auto_show: False
  • auto_mount: False
  • auto_id: False
  • bundle_cfg: {'out_dir': [Path('/Users/vic/dev/repo/project/bridget/nbs/js'), Path('/Users/vic/dev/repo/project/bridget/nbs'), Path('/Users/vic/dev/repo/…
  • bootstrap: False
  • current_did: None
cprint(bridge_cfg.bundle_cfg.out_dir)
if BUNDLE_PATH.resolve() not in (cfg := bridge_cfg.bundle_cfg).out_dir:
    cfg.update(out_dir=[BUNDLE_PATH, *cfg.out_dir])
cprint(bridge_cfg.bundle_cfg.out_dir)

By default, Bridget looks for JS files in: - the python bundle, if it can be determined (use bridge_cfg.for_module(...)) - nbdev.config.get_config().lib_path / ‘js’ and nbdev.config.get_config().lib_path / ‘js’, if found - Path.cwd() and Path.cwd()/‘js’ at time of call

More locations can be added by modifying the out_dir of the bundle_cfg object.


source

resolve_ESM

 resolve_ESM (spec:str, base:str|pathlib.Path|None=None)

Resolve a JavaScript module specifier relative to base or bundle_cfg.out_dir.

test_eq(resolve_ESM('./non-existent.js'), None)
test_eq(resolve_ESM('/non-existent.js'), None)
test_eq(resolve_ESM('./bridge.js'), Path('./js/bridge.js').resolve())

Test relative specifiers (these will depend on actual files existing)

test_eq(resolve_ESM('asdfretgs'), None)
test_eq(resolve_ESM('asdfretgs:wrwteryeyt'), None)

result = cast(ParseResult, resolve_ESM('https://unpkg.com/htmx.org@next/dist/htmx.js'))
test_eq(result.scheme, 'https')
test_eq(result.netloc, 'unpkg.com')

result = cast(ParseResult, resolve_ESM('data:text/javascript,export default 42;'))
test_eq(result.scheme, 'data')

test_eq(resolve_ESM('file:///path/to/x.js'), None)
test_eq(resolve_ESM('FILE:///observer.js', Path('./js')), Path('js/observer.js'))
# result = resolve_ESM('data:application/json,{"foo":42}', { type="json" })
# test_eq(result.scheme, 'data')

Absolute specifiers (URLs)

test_eq(resolve_ESM('lodash'), None)
test_eq(resolve_ESM('observer.js', Path('./js')), None)

Test bare specifiers (TBD, not yet supported)

Look up for JS files in well-known locations and return the contents


source

get_ESM

 get_ESM (module_spec)

Contents of a JS module

test_eq(get_ESM('lodash'), None)
test_eq(get_ESM('./test.js'), Path('./js/test.js').read_text())
test_eq(get_ESM('https://a'), 'https://a')
with tempfile.NamedTemporaryFile('w', dir='./js') as fp:
    p = Path(fp.name)
    p.write_text('import {a} from "b"')
    n = f"/{p.relative_to(Path('./js').resolve())}"
    test_eq(get_ESM(n), 'const {a} = await brdimport("b");')

BlockingWidget

JS -> Python

class Cons(anywidget.AnyWidget):
    _esm = '''
export default {
  async initialize({ model, experimental }) {
    model.on("msg:custom", async (cmd) => {
      const { kind, msg, timeout } = cmd;
      if (kind === 'brd-command') {
        let [res, buffers] = await experimental.invoke("_upper", msg, {signal: AbortSignal.timeout(timeout)});
        console.log(res);
      } else console.log(cmd);
    }); 
  },
};
'''
    @anywidget.experimental.command  # type: ignore
    def _upper(self, msg, buffers):
        # print(f'{msg=} {buffers=}')
        return msg.upper(), buffers
    
    def to_console(self, msg):
        self.send({'kind': 'brd-command', 'msg': msg, 'timeout': 5e3})
from bridget.helpers import find_active_widgets, get_kernel_widgets_comms, get_active_widgets_comms
ww = find_active_widgets()
ww
[]
w = Cons()
w.to_console('Hello, worlds!')

The (still) experimental invoke feature of AnyWidget allows for blocking operations frontend-backend.
We’ll also need the other way around, but that’s is surprisingly more involved.

Python -> JS

from matplotlib import pyplot as plt
import numpy as np
base = 1.5
fig = plt.figure()#figsize=(4, 2.67))
plt.plot(base + base**np.arange(10));


source

exp_backoff

 exp_backoff (base:float=1.552, max_value:float=10.0)

Exponential backoff generator of values until cumulative value is max_value, then yields 0 and stops.

list(itertools.takewhile(lambda t: t, exp_backoff()))
[1.552, 2.408704, 3.7383086080000005, 2.3009873919999997]
list(itertools.takewhile(lambda t: t, exp_backoff(max_value=60)))
[1.552,
 2.408704,
 3.7383086080000005,
 5.8018549596160005,
 9.004478897324033,
 13.9749512486469,
 21.68912433789999,
 1.8305779485130742]
list(itertools.takewhile(lambda t:t, exp_backoff(0.4, 3)))
[0.4, 0.8, 1.2000000000000002, 0.5999999999999996]
list(itertools.takewhile(lambda t:t, exp_backoff(1*0.0776, 1)))
[0.0776, 0.1552, 0.2328, 0.3104, 0.22399999999999998]
boff = iter(exp_backoff())
t, sum = 1, 0
while t:
    sum += (t := next(boff))
    print(f"t: {t:0.4f} sum: {sum:0.4f}")
t: 1.5520 sum: 1.5520
t: 2.4087 sum: 3.9607
t: 3.7383 sum: 7.6990
t: 2.3010 sum: 10.0000
t: 0.0000 sum: 10.0000
tm = 5.0
boff = iter(exp_backoff(tm*0.1, tm))
t, sum = 1, 0
while t:
    sum += (t := next(boff))
    print(f"t: {t:0.4f} sum: {sum:0.4f}")
t: 0.5000 sum: 0.5000
t: 1.0000 sum: 1.5000
t: 1.5000 sum: 3.0000
t: 2.0000 sum: 5.0000
t: 0.0000 sum: 5.0000
class BlockingMixin(W.Widget):
    "A mixin for widgets that supports blocking custom messages with the front-end."
    _cbs: CallbackDispatcher

    def on_msg(self, cb, remove=False):
        if not hasattr(self, '_cbs'): self._cbs = CallbackDispatcher()
        self._cbs.register_callback(cb, remove=remove)
        super().on_msg(cb, remove=remove)

    def send(self, msg, timeout: float|None=None, buffers=None, 
            sleep: float = 1/15, n: int = 10, show: Callable[[bool], None]|None = None
    ) -> tuple[Any|Empty, Any|Empty]|None:
        "Send `msg` to the front-end. If `timeout` seconds is not None, calling blocks."
        if timeout is None: 
            for cb in self._cbs.callbacks: super().on_msg(cb)
            return super().send(msg, buffers)
        for cb in self._cbs.callbacks: super().on_msg(cb, remove=True)
        res = self._send_msg(msg, buffers,timeout, sleep, n, show)
        # NOTE: restoring normal callbacks now implies that front-end can yet send back a result
        # even if python timeout was triggered.
        for cb in self._cbs.callbacks: super().on_msg(cb)
        return res
    
    async def asend(self, msg, timeout: float=5.0, buffers=None, 
            sleep: float = 1/15, n: int = 10, show: Callable[[bool], None]|None = None
    ) -> tuple[Any|Empty, Any|Empty]|None:
        "Send `msg` to the front-end. Call will end after `timeout` seconds if `timeout` is not None."
        for cb in self._cbs.callbacks: super().on_msg(cb, remove=True)
        res = await self._asend_msg(msg, buffers, timeout, sleep, n, show)
        for cb in self._cbs.callbacks: super().on_msg(cb)
        return res

    def _send_msg(self, msg, buffers=None, timeout: float = 5.0, 
            sleep: float = 1/15, n: int = 10, show: Callable[[bool], None]|None = None
    ) -> tuple[Any|Empty, Any|Empty]:
        "Send blocking `msg` to the front-end. Return response tuple (content, buffers), or (empty, empty) if `timeout`."
        boff = iter(exp_backoff(timeout*0.776, timeout))
        timeout, start_time = next(boff), time.time()
        result = None
        def _on_msg(_, msg, buffers):
            nonlocal result
            if result is None: result = (msg, buffers)
        super().on_msg(_on_msg)  # register transient callback
        try:
            super().send(msg, buffers)
            with ui_events() as ui_poll:
                while True:
                    if sleep: time.sleep(sleep)
                    ui_poll(n)
                    if (time.time() - start_time) > timeout:
                        timeout, start_time = next(boff), time.time()
                        if not timeout: result = (empty, empty)
                    if result is not None:
                        content, buffers = result
                        if content is empty: return (empty, empty)
                        self._cbs(self, content, buffers)
                        return content, buffers
                    if show: show(False)
        except Exception as e:
            if isinstance(e, RuntimeError): raise
            raise RuntimeError(f"Error during message processing: {str(e)}") from e
        finally:
            super().on_msg(_on_msg, True)  # unregister callback
            if show: show(True)

    async def _asend_msg(self, msg, buffers=None, timeout: float = 5.0, 
            sleep: float = 1/15, n: int = 10, show: Callable[[bool], None]|None = None
    ) -> tuple[Any|Empty, Any|Empty]:
        "Send async `msg` to the front-end. Return response tuple (content, buffers), or (empty, empty) if `timeout`."
        boff = iter(exp_backoff(timeout*0.776, timeout))
        timeout, start_time = next(boff), time.time()
        result = None
        def _on_msg(w, msg, buffers):
            nonlocal result
            if result is None: result = (msg, buffers)
        super().on_msg(_on_msg)
        try:
            super().send(msg, buffers)
            async with ui_events() as ui_poll:
                while True:
                    await ui_poll(10)
                    if (time.time() - start_time) > timeout:
                        timeout, start_time = next(boff), time.time()
                        if not timeout: result = (empty, empty)
                    if result is not None:
                        content, buffers = result
                        if content is empty: return (empty, empty)
                        self._cbs(self, content, buffers)
                        return (content, buffers)
                    if sleep: await anyio.sleep(sleep)
                    if show: show(False)
        except Exception as e:
            if isinstance(e, RuntimeError): raise
            raise RuntimeError(f"Error during message processing: {str(e)}") from e
        finally:
            super().on_msg(_on_msg, True)
            if show: show(True)

blocks

functions to block until a condition is met without blocking front-end interaction.


source

blocking

 blocking (timeout:float=1, sleep:float=0.2, n:int=10,
           show:Optional[Callable[[bool],NoneType]]=None)

source

ablocks

 ablocks (pred:Callable[...,bool], timeout:float=1, sleep:float=0.2,
          n:int=10, show:Optional[Callable[[bool],NoneType]]=None)

Return True when pred returns True, or False when at least timeout seconds have passed.


source

blocks

 blocks (pred:Callable[...,bool], timeout:float=1, sleep:float=0.2,
         n:int=10, show:Optional[Callable[[bool],NoneType]]=None)

Block until pred is True, or at least timeout seconds have passed. Return False if timeout.

class Cnt:
    def __init__(self, n=10, sleep=0.0): self.n, self.sleep, self.cnt = n, sleep, 0
    def __call__(self):
        self.cnt += 1
        time.sleep(self.sleep)
        return self.cnt > self.n-1

blocks(cntr := Cnt(10), n=20, sleep=0.1, show=_show)
test_eq(cntr.cnt, 10)
._.
done = await ablocks(cntr := Cnt(10, 0.2), show=_show)
if done: test_eq(cntr.cnt, 10)
else: print('timeout')
._.
timeout
with blocking(n=20, sleep=0.0, show=_show) as pred:
    cntr = Cnt(10)
    test_is(pred(cntr), True)
    test_eq(cntr.cnt, 10)
._.
with blocking(show=_show) as pred:
    cntr = Cnt(10, 0.2)
    done = pred(cntr)
    if done: test_eq(cntr.cnt, 10)
    else: print('timeout')
._.
timeout

source

BlockingMixin

 BlockingMixin (**kwargs)

Mixin for widgets that supports blocking custom messages with the front-end.

class BlockingWidget(anywidget.AnyWidget, BlockingMixin):
    _esm = anysource('// debugger;', '''
    export default { 
        async initialize({ model }) {
            function on_msg(msg) {
                console.log(`Received message:`, msg);
                if (!msg?.timeout) return model.send({ msg_id: msg.msg_id, response: 'no timeout' });
                (function loop(n) {
                    setTimeout(() => {
                        n += 100
                        if (n > msg.timeout*1000) {
                            console.log(`Sending response for msg_id:`, msg.msg_id);
                            model.send({ msg_id: msg.msg_id, response: 'done waiting', error: null });
                            return;
                        }
                        // console.log('.');
                        loop(n);
                    }, 100);
                })(0);
            }
            model.on("msg:custom", on_msg);
            setTimeout(() => {
                console.log(`Initialized.`);
                model.set('_loaded', true); model.save_changes();
            }, 1000);
        }
    };
    ''')

    def test_send(self, msg, buffers=None, *, timeout: float|None=None, 
            sleep: float = 1/15, n: int = 10, show: Callable[[bool], None]|None = None):
        self._start_time = time.time()
        print(f'Sending message {msg}...')
        res = self.send(msg, buffers, timeout=timeout, sleep=sleep, n=n, show=show)
        return None if res == (empty, empty) else res
    
    async def test_asend(self, msg, buffers=None, *, timeout: float=5.0, 
        sleep: float = 1/15, n: int = 10, show: Callable[[bool], None]|None = None
    ):
        self._start_time = time.time()
        print(f'Sending message {msg}...')
        res = await self.asend(msg, buffers, timeout=timeout, sleep=sleep, n=n, show=show)
        return None if res == (empty, empty) else res

    def __init__(self, *args, **kwargs):
        # self.setup_init_on_msg(self._handle_message)
        self.on_msg(self._handle_message)
        super().__init__(*args, **kwargs)
        print('Initializing...')

    def _handle_message(self, _, msg, buffers):
        e = time.time()
        self._last_message = e, msg
        print(f'\nelapsed: {e-self._start_time:.3f}s Received message: {msg}, buffers: {buffers}')
cleanupwidgets('w')

w = BlockingWidget()
test_eq(w.loaded(), False)
Initializing...
idx = kounter('blocking')
print(idx)
w.test_send({'msg_id': idx})
1
Sending message {'msg_id': 1}...
cleanupwidgets('w')

w = BlockingWidget.create(show=_show, sleep=0.1)
print(f'loaded={w.loaded()}')
test_eq(w.loaded(), True)
Initializing...
._.
loaded=True
idx = kounter('blocking')
a = w.test_send({'msg_id': idx, 'timeout': 2}, timeout=1, show=_show)
print(f"{idx=} ->", f"Timeout {time.time()-w._start_time:3f}" if not a else f"{a=}")
test_is(a, None)
Sending message {'msg_id': 2, 'timeout': 2}...
._.
idx=2 -> Timeout 1.162399
idx = kounter('blocking')
a = w.test_send({'msg_id': idx, 'timeout': 2}, timeout=3, show=_show)
print(f"{idx=} ->", f"Timeout {time.time()-w._start_time:3f}" if not a else f"{a=}")
test_eq(a[0]['msg_id'], idx)  # type: ignore
Sending message {'msg_id': 3, 'timeout': 2}...
._.
idx=3 -> a=({'msg_id': 3, 'response': 'done waiting', 'error': None}, [])
# cell 95
idx = kounter('blocking')
# a = await w.test_asend({'msg_id': idx, 'timeout': 2}, timeout=1, show=_show)  # async send not working fine in Lab
a = w.test_send({'msg_id': idx, 'timeout': 2}, timeout=1, show=_show)
Sending message {'msg_id': 4, 'timeout': 2}...
._.
print(f"{idx=} ->", f"Timeout {time.time()-w._start_time:3f}" if not a else f"{a=}")
test_is(a, None)
idx=4 -> Timeout 2.071541
# cell 97

idx = kounter('blocking')
# a = await w.test_asend({'msg_id': idx, 'timeout': 2}, timeout=3, show=_show)  # async send not working fine in Lab
a = w.test_send({'msg_id': idx, 'timeout': 2}, timeout=3, show=_show)
Sending message {'msg_id': 5, 'timeout': 2}...
._.
print(f"{idx=} ->", f"Timeout {time.time()-w._start_time:3f}" if not a else f"{a=}")
test_eq(a[0]['msg_id'], idx)  # type: ignore
idx=5 -> a=({'msg_id': 5, 'response': 'done waiting', 'error': None}, [])
cleanupwidgets('w')
from bridget.helpers import find_active_widgets, get_kernel_widgets_comms, get_active_widgets_comms
ww = find_active_widgets()
ww
[{'type': 'BlockingWidget',
  'model_id': 'f6e695c02332468c8fb9eebaa82b209b',
  'comm': False},
 {'type': 'Layout',
  'model_id': '73f02d3c1cf141d2a4729292217c4a32',
  'comm': False},
 {'type': 'BlockingWidget',
  'model_id': 'a7455c7f4ad04efa850f3a8cb964e6d6',
  'comm': False},
 {'type': 'Layout',
  'model_id': '4686dc666c0b464c8ef9047c0a97ea2d',
  'comm': False},
 {'type': 'Cons',
  'model_id': 'd024a861a43e4e6da226d96e6234c25d',
  'comm': False},
 {'type': 'Layout',
  'model_id': '925cf3052d664de8b9f9e2a8a70268de',
  'comm': False}]
import ipywidgets.widgets.widget
ipywidgets.widgets.widget._instances
{}
[_.comm_id for _ in get_active_widgets_comms()]
[]

BridgeWidget

A widget that bundles its ESM source and (optionaly) blocks until loaded.


source

BridgeWidget

 BridgeWidget (*args, **kwargs)

Main AnyWidget base class.

brdimport

Widget to upload the brdimport module to the front-end, the bare minimum we need to start bridging.

# cell 101
brdimport = BridgeImport()
# needed when running all cells if we want to ensure brdimport is in place
blocks(lambda: brdimport._loaded, 3, sleep=0.2, show=_show);
._.
if brdimport._loaded:
    test_is(__brdimport__ is not None, True)
    test_eq(brdimport._modules, ())
confetti_scr = Script('''
const res = await brdimport('https://esm.sh/canvas-confetti@1.6');
console.log(res);
''', type='module')
HTML(confetti_scr)
to = blocks(lambda: len(brdimport._modules) > 0, 5, sleep=0.2)
if not to: test_eq(brdimport._modules, ('https://esm.sh/canvas-confetti@1.6',))
non_existent_scr = Script('''
const res = await brdimport('https://a.com/b/c.js');
console.log(res)
''', type='module')
HTML(non_existent_scr)
test_eq(brdimport._modules, ('https://esm.sh/canvas-confetti@1.6',))
observer_scr = Script('''
const { getObserverManager } = await brdimport('./js/observer.js');
console.log(getObserverManager)
''', type='module')
HTML(observer_scr)
to = blocks(lambda: len(brdimport._modules) > 1, 5, sleep=0.2)
if not to: test_eq(set(brdimport._modules), set(('https://esm.sh/canvas-confetti@1.6', './js/observer.js')))

source

get_brdimport

 get_brdimport ()

brdimport module is unconditionally loaded.

cleanupwidgets(__brdimport__)
from bridget.helpers import find_active_widgets, get_kernel_widgets_comms, get_active_widgets_comms
ww = find_active_widgets()
ww
[{'type': 'BlockingWidget',
  'model_id': 'a7455c7f4ad04efa850f3a8cb964e6d6',
  'comm': False},
 {'type': 'Layout',
  'model_id': '4686dc666c0b464c8ef9047c0a97ea2d',
  'comm': False},
 {'type': 'BridgeImport',
  'model_id': '6b75a2537d514884862cb3519e495d6a',
  'comm': False},
 {'type': 'Layout',
  'model_id': 'eee943ecd57f453590f6eadf4ddaefbd',
  'comm': False},
 {'type': 'Cons',
  'model_id': 'd024a861a43e4e6da226d96e6234c25d',
  'comm': False},
 {'type': 'Layout',
  'model_id': '925cf3052d664de8b9f9e2a8a70268de',
  'comm': False}]
import ipywidgets.widgets.widget
ipywidgets.widgets.widget._instances
{}
[_.comm_id for _ in get_active_widgets_comms()]
[]