Using the pyami AMI Client in a Jupyter Notebook¶
This guide shows how to use pyami-3forge interactively in Jupyter / JupyterLab, from
one-off SQL queries that render as DataFrames, to live, self-updating dashboards driven by
3forge Enterprise realtime and Center feeds.
It assumes you already know the API basics; for the full reference see the user guide. For the SQLAlchemy dialect specifically, see the SQLAlchemy guide.
Install in a Notebook¶
Use the %pip magic so the package installs into the kernel's interpreter (not
whatever pip happens to be on PATH):
Then restart the kernel (Kernel -> Restart) so the freshly installed native extension is importable.
Python 3.14 required. The native
pyami.rt.ami_clientextension is compiled for CPython 3.14 (wheels are taggedcp314). If%pip installreports "no matching distribution", your kernel is on a different Python: check with:
Confirm the extension imports:
Query into a DataFrame (JDBC)¶
The simplest notebook use is a synchronous SQL query rendered as a pandas DataFrame.
sql() returns a column-oriented dict that pd.DataFrame(...) consumes directly:
Remember AMI SQL's quirks (== for equality, "double quotes" for string literals):
see Run SQL.
If you only need read-only query-to-DataFrame access and would rather not load the native
extension, a pure-Python client works on any platform and any Python 3. It is in the source repository only, and pip install pyami-3forge does not install it:
You can also go through the SQLAlchemy dialect and let pandas drive it: see pandas integration.
The Golden Rule: Callbacks Run on Background Threads¶
Every listener callback (on_update, on_connect, on_message_received, …) and every
sql_async result callback is invoked on an internal C++ I/O thread, not on the
notebook's event loop. This has 2 practical consequences in a notebook:
- Never touch a widget (Panel/ipywidgets) directly from a callback. Widget state must be mutated on the front-end's IOLoop thread. Marshal the update instead:
- Never call back into the client re-entrantly from a callback. Calling
subscribe()(or other blocking client methods) directly insideon_connectcan deadlock or crash the C++ I/O thread. Enqueue the request and perform it from your own loop instead:
Both patterns come from the multiplex demo, which installs with pyami-3forge (see Runnable Demos).
If you only read plain Python data (no widgets, no re-entrant client calls), a callback can do its work inline, just keep it short and non-blocking, because it holds up the I/O thread while it runs.
Async SQL Without Blocking the Kernel¶
sql() blocks the cell until the server responds. For long queries, or to keep an
interactive cell responsive, use sql_async() and marshal the result to a display:
Live Dashboards with Panel¶
Panel + its Tabulator widget is the path of least
resistance for a live table in a notebook. Load the extension once at the top of the
notebook:
The recommended pattern (used by the demos) is:
- Build a
Tabulatorfrom an initial DataFrame and display it. - On each incoming AmiCore update, compute the changed cells.
- Marshal a
Tabulator.patch({...})call to the IOLoop: patching individual cells is far cheaper than reassigning.value.
For richer styling (conditional colors, flash-on-change, multi-source columns), study
demo.crypto_ticker_nb and demo.multiplex_crypto_ticker, which install with the package
(see Runnable Demos).
Subscribing to a Center Table Live¶
The Center delivers a snapshot followed by row-level deltas. In a notebook, connect with
sync=True so the cell blocks until the connection is up, then subscribe:
update.action distinguishes the delta type (1=ADD, 2=UPD, 3=DEL). An UPD carries only the
columns that changed, so key rows on update.id and merge each UPD into the row already held: see
Subscriptions. Guard access to string fields that
may carry binary payloads: see Subscribe.
Cell Re-runs, Lifecycle, and Cleanup¶
Notebooks invite re-running cells. Each AmiClient owns background threads and sockets, so
re-running a "connect" cell without cleaning up leaks connections and stacks duplicate
callbacks. 2 habits keep this sane:
Tear down before reconnecting. Make the connect cell idempotent:
Guard background loops with a running flag so a re-run does not start a second timer
(the demos' CryptoTicker.start() does exactly this):
When you are done, stop your producer and drop the clusters:
Gotchas¶
| Symptom | Cause / fix |
|---|---|
%pip install → "no matching distribution" |
Kernel is not CPython 3.14. The wheels are cp314-tagged. Check sys.version. |
| Widget never updates from a feed | You mutated the widget from a callback thread. Marshal via ioloop.add_callback(...). |
| Kernel hangs / crashes after connect | You called subscribe() (or another blocking client call) directly inside on_connect. Enqueue it and run from the IOLoop instead. |
| Duplicate / doubled updates after re-running cells | A previous AmiClient is still alive. remove_cluster(...) before reconnecting. |
Cell hangs forever on add_cluster(..., sync=True) |
Cannot reach the server, or wrong port/SSL. Try use_ssl=False, verify host/port. |
UnicodeDecodeError reading update.table_name/fields |
Binary payload in a string field. Wrap access in try/except UnicodeDecodeError. |
| Callbacks make the UI sluggish | Heavy work on the I/O thread. Keep callbacks short; offload to your own queue/loop. |
Runnable Demos¶
Two notebook-oriented demos install with pyami-3forge, as modules of the demo package alongside pyami. They are the most complete worked examples of the patterns on this page:
| Module | Shows |
|---|---|
demo.crypto_ticker_nb |
A Panel Tabulator live ticker driven by an asyncio loop, with no connection to a Center: the display and styling pattern on its own. |
demo.multiplex_crypto_ticker |
A complete integration: publish to one Relay, receive the Center's incremental updates on a listener, forward transformed updates to a second instance, and update Panel safely from callback threads. |
A notebook cell wires the multiplex demo in one call:
By default the multiplex demo connects to 2 instances on localhost, with Relay ports 3289 and 4289 and Center ports 3271 and 4271, and uses the table crypto. Pass hosts, relay_ports, center_ports, or table_name to create_default_multiplex_stack to point it elsewhere.