Synchronous API¶
The sync API lives in pgwidgets.sync. Widget constructors and method
calls block until the browser responds.
from pgwidgets.sync import Application
Application¶
app = Application(
ws_port=9500, # WebSocket port
http_port=9501, # HTTP server port
host="127.0.0.1", # bind address
http_server=True, # serve JS/CSS assets
concurrency_handling="per_session", # callback threading model
max_sessions=1, # max concurrent sessions (None=unlimited)
logger=None, # logging.Logger or None
)
Properties:
app.url– URL to open in a browser (e.g.http://127.0.0.1:9501/).app.sessions– dict of active sessions (session_id -> Session).app.static_path– path to the pgwidgets static files directory.app.remote_html– path to theremote.htmlconnector page.
Methods:
app.create_session(session_id=None)– create a session without a browser. Returns aSessionthat can have its widget tree built before any browser connects.
on_connect / on_disconnect¶
Register handlers as decorators or by calling directly.
on_connect fires when a new session is created (not on reconnection).
on_disconnect fires each time a browser disconnects.
@app.on_connect
def setup(session):
Widgets = session.get_widgets()
top = Widgets.TopLevel(title="Hello")
top.show()
@app.on_disconnect
def teardown(session):
print(f"Session {session.id}: browser disconnected "
f"({len(session.connections)} remaining)")
Running¶
# Option 1: run() calls start() automatically and blocks forever
app.run()
# Option 2: start() + your own main loop
app.start()
# ... do other things ...
app.close() shuts down all sessions and stops the servers.
Session¶
Sessions persist independently of browser connections. A session can exist with no browser connected and can have multiple browsers connected simultaneously.
Key methods:
Widgets = session.get_widgets() # widget factory namespace
session.close() # close browser connections (session survives)
session.destroy() # destroy session completely
session.make_timer(duration=0) # create a Timer widget
# Schedule work on the callback thread
session.gui_do(func, *args) # fire-and-forget
result = session.gui_call(func, *args) # block for result
# Widget tree inspection
for widget in session.walk_widget_tree():
print(widget)
Properties:
session.id– unique session identifier (int).session.app– the owningApplication.session.token– security token for reconnection.session.is_connected–Trueif at least one browser is connected.session.connections– list of active WebSocket connections.
Creating Sessions Without a Browser¶
Use app.create_session() to create a session before any browser connects.
Build the widget tree, then connect a browser to see the pre-built UI:
app.start()
session = app.create_session()
Widgets = session.get_widgets()
top = Widgets.TopLevel(title="Pre-built")
top.show()
# Navigate a browser to the session URL to see the UI.
Multi-Browser Support¶
Multiple browsers can connect to the same session by navigating to the session URL (which includes the session ID and security token). All browsers show the same UI and stay synchronized:
State changes in one browser are pushed to all others in real time.
Widget positions, slider values, tab selections, tree expand/collapse state, and more are all synchronized.
Callbacks fire only in response to the originating browser’s action; other browsers receive silent updates that don’t trigger callbacks.
Widget Factory¶
session.get_widgets() returns a namespace with factory methods for all
widget types:
Widgets = session.get_widgets()
top = Widgets.TopLevel(title="Demo", resizable=True)
vbox = Widgets.VBox(spacing=8, padding=10)
btn = Widgets.Button("Click me")
label = Widgets.Label("Status", halign="center")
slider = Widgets.Slider(min=0, max=100, value=50, track=True)
Constructor arguments match the widget definitions: positional args first,
then options as keyword arguments. Extra keyword arguments that do not match
a defined option are applied as set_<name>() calls.
Concurrency Modes¶
The concurrency_handling parameter controls how widget callbacks are
dispatched:
- per_session (default)
Each session gets its own thread. Callbacks within a session are dispatched sequentially. Different sessions run concurrently. No locks needed within a session.
- serialized
All callbacks from all sessions run on the main thread inside
run(). Simple single-threaded model, but one slow callback blocks everything.- concurrent
Each callback fires on its own daemon thread. Maximum concurrency, but you must manage thread safety for shared state.
gui_do and gui_call¶
In per_session and serialized modes, use gui_do and gui_call
to schedule work on the callback thread from other threads:
import threading
def background_work(session):
# This runs on a background thread
result = expensive_computation()
# Schedule UI update on the callback thread
session.gui_do(lambda: label.set_text(str(result)))
threading.Thread(target=background_work, args=(session,)).start()
gui_call blocks until the scheduled function completes and returns its
return value. gui_do returns immediately.
Full Example¶
import logging
from pgwidgets.sync import Application
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("pgwidgets")
app = Application(max_sessions=4, logger=logger)
@app.on_connect
def on_session(session):
Widgets = session.get_widgets()
top = Widgets.TopLevel(title="Sync Demo", resizable=True)
top.resize(400, 300)
vbox = Widgets.VBox(spacing=8, padding=10)
status = Widgets.Label("Click a button!")
hbox = Widgets.HBox(spacing=6)
btn_hello = Widgets.Button("Hello")
btn_world = Widgets.Button("World")
hbox.add_widget(btn_hello, 0)
hbox.add_widget(btn_world, 0)
entry = Widgets.TextEntry(text="Type here", linehistory=5)
slider = Widgets.Slider(min=0, max=100, value=50, track=True)
vbox.add_widget(hbox, 0)
vbox.add_widget(entry, 0)
vbox.add_widget(slider, 0)
vbox.add_widget(status, 1)
top.set_widget(vbox)
top.show()
btn_hello.on("activated", lambda: status.set_text("Hello!"))
btn_world.on("activated", lambda: status.set_text("World!"))
entry.on("activated", lambda text: status.set_text(f"Entered: {text}"))
slider.on("activated", lambda val: status.set_text(f"Slider: {val}"))
app.run()