Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Your first Webview

from dry import Webview

wv = Webview(
    app_id='com.example.hello',
    title='Hello',
    size=(900, 600),
    html='<h1>Hello from Dry</h1>',
)
wv.run()

Three things are worth knowing before you write anything longer than this.

Every option is a keyword argument

Webview() takes keyword arguments only. Your editor lists what there is, and a typo raises instead of quietly creating an attribute that never applies:

wv = Webview(html='<h1>Hi</h1>')
wv.titel = 'My App'
# AttributeError: 'Webview' object has no attribute 'titel' and no __dict__
# for setting new attributes. Did you mean: 'title'?

Every option is also a property, for the values you only work out later:

wv = Webview(app_id='com.example.hello')
wv.title = f'Report — {report.name}'
wv.html = render_page(report)
wv.run()

Most settings are read once, while the window is being built. Assigning one of those after run() raises a RuntimeError naming it rather than doing nothing — see Window options for which is which.

run() never returns

wv.run() hands the main thread to the platform’s event loop, which does not give it back: closing the window exits the process from inside that loop.

wv.run()
print('goodbye')  # never printed

Nothing after run() executes, and a finally: wrapped around it does not run either. Work that has to happen on the way out belongs in a close hook, in an atexit handler — Dry runs those itself before the process goes — or in a finally: inside a callback.

The corollary is that an application cannot make asyncio.run(main()) its entry point. Dry runs an asyncio loop of its own, on a background thread, and your async def code lives inside Api callables and Event listeners. See The Portal and ADR-0001.

Give it an App id

wv = Webview(app_id='com.example.hello', html=HTML)

The App id decides where cookies, local storage and cache are kept. Leave it out and one is derived from your entry-point script, which is enough to develop against but moves when the script moves. Declare your own before you ship: Where your data lives.

Talking to Python

Give the Webview an api, and the frontend can Call it:

from dry import Webview

HTML = """
<button onclick="greet()">Greet</button>
<p id="out"></p>
<script>
  async function greet() {
    out.textContent = await window.dry.api.hello('World');
  }
</script>
"""


def hello(name: str) -> str:
    return f'Hello, {name}!'


wv = Webview(app_id='com.example.hello', html=HTML, api={'hello': hello})
wv.run()

That is the whole of the Bridge’s Call half. Calls covers what happens when the callable is slow, raises, or is declared async def.