Python API Reference¶
panel_live
¶
Accessible imports for the panel_live package.
__all__ = ['PanelLive', '__version__', 'content_hash', 'pre_render']
module-attribute
¶
__version__ = importlib.metadata.version(__name__)
module-attribute
¶
PanelLive
¶
Bases: JSComponent
Run Python code in the browser via Pyodide, with bidirectional server communication.
Wraps the <panel-live> web component as a Panel JSComponent.
Code executes client-side in a Pyodide Web Worker; the value
parameter provides a bidirectional data channel between server and
browser.
The panel-live JS/CSS bundle is loaded automatically from CDN by
default. To use local assets instead, call :meth:configure before
creating any instances::
PanelLive.configure(js_url="./pl/panel-live.js")
Attributes:
| Name | Type | Description |
|---|---|---|
code |
str
|
Python code to execute in Pyodide. |
requirements |
list[str]
|
Packages to install via micropip before execution. |
mode |
str
|
Display mode:
|
theme |
str
|
Color theme: |
layout |
str
|
Editor/output arrangement: |
auto_run |
bool
|
If |
code_visibility |
str
|
Code editor visibility: |
code_position |
str
|
Code panel position relative to output: |
value |
object
|
Bidirectional value for server→client and client→server data.
Supports JSON-serializable types ( |
input |
object
|
Server→client data channel. Setting |
output |
object
|
Client→server data channel (read-only from server perspective).
Updated when client-side code sets |
status |
str
|
Current execution status (read-only from user perspective). |
error |
str
|
Last error message from execution. |
stdout |
str
|
Captured stdout from last execution. |
auto_run = param.Boolean(default=True, doc='Run code automatically on load')
class-attribute
instance-attribute
¶
code = param.String(default='', doc='Python code to execute in Pyodide')
class-attribute
instance-attribute
¶
code_position = param.Selector(default='first', objects=['first', 'last'], doc="Code panel position relative to output: 'first' (before) or 'last' (after).")
class-attribute
instance-attribute
¶
code_visibility = param.Selector(default='visible', objects=['visible', 'collapsed', 'hidden'])
class-attribute
instance-attribute
¶
error = param.String(default='', doc='Last error message')
class-attribute
instance-attribute
¶
input = param.Parameter(doc="Server-to-client data. Setting this pushes data to the client's server.input param.")
class-attribute
instance-attribute
¶
layout = param.Selector(default='vertical', objects=['vertical', 'horizontal'])
class-attribute
instance-attribute
¶
mode = param.Selector(default='editor', objects=['app', 'editor', 'playground', 'headless', 'progress', 'debug'], doc="Display mode: 'editor' (code + output), 'app' (output only), 'playground' (editor + examples), 'headless' (invisible 0px), 'progress' (spinning Python icon, evaluate queue on hover), 'debug' (stdout/stderr visible).")
class-attribute
instance-attribute
¶
output = param.Parameter(doc='Client-to-server data. Updated when Pyodide code sets server.output.')
class-attribute
instance-attribute
¶
requirements = param.List(default=[], item_type=str, doc='Packages to install via micropip')
class-attribute
instance-attribute
¶
status = param.Selector(default='idle', objects=['idle', 'loading', 'running', 'ready', 'error'])
class-attribute
instance-attribute
¶
stdout = param.String(default='', doc='Captured stdout from last execution')
class-attribute
instance-attribute
¶
theme = param.Selector(default='auto', objects=['auto', 'light', 'dark'])
class-attribute
instance-attribute
¶
value = param.Parameter(doc='Bidirectional value. JSON-serializable types: str, int, float, dict, list, None.')
class-attribute
instance-attribute
¶
configure(*, js_url=None, css_url=None)
classmethod
¶
Override the panel-live JS and/or CSS asset URLs.
Call before creating any PanelLive instances::
PanelLive.configure(js_url="./pl/panel-live.js", css_url="./pl/panel-live.css")
HTTP(S) URLs are loaded via __javascript__ / __css__.
Relative URLs (for local --static-dirs serving) are injected
via pn.config.js_files / pn.config.css_files.
evaluate(code, timeout=30.0, **kwargs)
async
¶
Evaluate Python code in the browser and return the result.
Executes code in the client-side Pyodide worker without rendering
any UI. Returns the value of the last expression, like Python's
built-in :func:eval.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
Python code to evaluate in Pyodide. |
required |
timeout
|
float
|
Maximum seconds to wait for a result (default 30). |
30.0
|
**kwargs
|
Any
|
JSON-serializable keyword arguments injected as globals in the Pyodide execution namespace. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The result of the last expression (must be JSON-serializable). |
Raises:
| Type | Description |
|---|---|
TimeoutError
|
If the evaluation does not complete within |
RuntimeError
|
If the client-side execution raises an error. |
run(code=None, timeout=60.0)
async
¶
Trigger the full render pipeline (same as clicking "Run").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str or None
|
If provided, updates |
None
|
timeout
|
float
|
Maximum seconds to wait for the render to complete. |
60.0
|
Raises:
| Type | Description |
|---|---|
TimeoutError
|
If rendering does not complete within |
RuntimeError
|
If client-side execution raises an error. |
send(data)
¶
Send data from server to client-side Pyodide code.
The data must be JSON-serializable. On the client side, the data
is available via the pl-server-data event on the <panel-live>
element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
JSON-serializable data to send to the client. |
required |
content_hash(code)
¶
Return a SHA-256 hex digest for code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
The full source code string (including any prepended setup code). |
required |
Returns:
| Type | Description |
|---|---|
str
|
64-character hexadecimal digest. |
pre_render(code, cache_dir, *, setup_code='', timeout=120)
¶
Pre-render code and return the embedded JSON string.
Uses a content-hash cache so repeated builds with unchanged code are nearly instant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
The user-visible source code. |
required |
cache_dir
|
Path or str
|
Directory for the content-hash cache (e.g. |
required |
setup_code
|
str
|
Optional code prepended before code (e.g. |
''
|
timeout
|
int
|
Maximum seconds to wait for the subprocess. |
120
|
Returns:
| Type | Description |
|---|---|
str or None
|
Bokeh JSON string on success, |
__main__
¶
Allow running panel-live as python -m panel_live.
cli
¶
Command-line interface for panel-live utilities.
Usage::
panel-live serve --port 5008
panel-live pre-render CODE
panel-live pre-render --file script.py
panel-live pre-render CODE --cache-dir .cache --setup-code "import panel as pn" --timeout 60
panel-live mcp
panel-live mcp --transport streamable-http --port 5002
panel-live --version
The serve command starts a Panel server with the showcase example app,
demonstrating all PanelLive display modes.
The pre-render command executes Panel code and prints the resulting
Bokeh JSON to stdout. Exit code 0 on success, 1 on failure.
The mcp command starts the MCP server for use with VS Code Copilot Chat,
Claude Desktop, or other MCP-compatible clients.
main(argv=None)
¶
Entry point for python -m panel_live and panel-live CLI.
component
¶
PanelLive server component.
A JSComponent that wraps the <panel-live> web component, enabling
Panel server applications to run Python code in the browser via Pyodide
with bidirectional data exchange.
PanelLive
¶
Bases: JSComponent
Run Python code in the browser via Pyodide, with bidirectional server communication.
Wraps the <panel-live> web component as a Panel JSComponent.
Code executes client-side in a Pyodide Web Worker; the value
parameter provides a bidirectional data channel between server and
browser.
The panel-live JS/CSS bundle is loaded automatically from CDN by
default. To use local assets instead, call :meth:configure before
creating any instances::
PanelLive.configure(js_url="./pl/panel-live.js")
Attributes:
| Name | Type | Description |
|---|---|---|
code |
str
|
Python code to execute in Pyodide. |
requirements |
list[str]
|
Packages to install via micropip before execution. |
mode |
str
|
Display mode:
|
theme |
str
|
Color theme: |
layout |
str
|
Editor/output arrangement: |
auto_run |
bool
|
If |
code_visibility |
str
|
Code editor visibility: |
code_position |
str
|
Code panel position relative to output: |
value |
object
|
Bidirectional value for server→client and client→server data.
Supports JSON-serializable types ( |
input |
object
|
Server→client data channel. Setting |
output |
object
|
Client→server data channel (read-only from server perspective).
Updated when client-side code sets |
status |
str
|
Current execution status (read-only from user perspective). |
error |
str
|
Last error message from execution. |
stdout |
str
|
Captured stdout from last execution. |
auto_run = param.Boolean(default=True, doc='Run code automatically on load')
class-attribute
instance-attribute
¶
code = param.String(default='', doc='Python code to execute in Pyodide')
class-attribute
instance-attribute
¶
code_position = param.Selector(default='first', objects=['first', 'last'], doc="Code panel position relative to output: 'first' (before) or 'last' (after).")
class-attribute
instance-attribute
¶
code_visibility = param.Selector(default='visible', objects=['visible', 'collapsed', 'hidden'])
class-attribute
instance-attribute
¶
error = param.String(default='', doc='Last error message')
class-attribute
instance-attribute
¶
input = param.Parameter(doc="Server-to-client data. Setting this pushes data to the client's server.input param.")
class-attribute
instance-attribute
¶
layout = param.Selector(default='vertical', objects=['vertical', 'horizontal'])
class-attribute
instance-attribute
¶
mode = param.Selector(default='editor', objects=['app', 'editor', 'playground', 'headless', 'progress', 'debug'], doc="Display mode: 'editor' (code + output), 'app' (output only), 'playground' (editor + examples), 'headless' (invisible 0px), 'progress' (spinning Python icon, evaluate queue on hover), 'debug' (stdout/stderr visible).")
class-attribute
instance-attribute
¶
output = param.Parameter(doc='Client-to-server data. Updated when Pyodide code sets server.output.')
class-attribute
instance-attribute
¶
requirements = param.List(default=[], item_type=str, doc='Packages to install via micropip')
class-attribute
instance-attribute
¶
status = param.Selector(default='idle', objects=['idle', 'loading', 'running', 'ready', 'error'])
class-attribute
instance-attribute
¶
stdout = param.String(default='', doc='Captured stdout from last execution')
class-attribute
instance-attribute
¶
theme = param.Selector(default='auto', objects=['auto', 'light', 'dark'])
class-attribute
instance-attribute
¶
value = param.Parameter(doc='Bidirectional value. JSON-serializable types: str, int, float, dict, list, None.')
class-attribute
instance-attribute
¶
configure(*, js_url=None, css_url=None)
classmethod
¶
Override the panel-live JS and/or CSS asset URLs.
Call before creating any PanelLive instances::
PanelLive.configure(js_url="./pl/panel-live.js", css_url="./pl/panel-live.css")
HTTP(S) URLs are loaded via __javascript__ / __css__.
Relative URLs (for local --static-dirs serving) are injected
via pn.config.js_files / pn.config.css_files.
evaluate(code, timeout=30.0, **kwargs)
async
¶
Evaluate Python code in the browser and return the result.
Executes code in the client-side Pyodide worker without rendering
any UI. Returns the value of the last expression, like Python's
built-in :func:eval.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
Python code to evaluate in Pyodide. |
required |
timeout
|
float
|
Maximum seconds to wait for a result (default 30). |
30.0
|
**kwargs
|
Any
|
JSON-serializable keyword arguments injected as globals in the Pyodide execution namespace. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The result of the last expression (must be JSON-serializable). |
Raises:
| Type | Description |
|---|---|
TimeoutError
|
If the evaluation does not complete within |
RuntimeError
|
If the client-side execution raises an error. |
run(code=None, timeout=60.0)
async
¶
Trigger the full render pipeline (same as clicking "Run").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str or None
|
If provided, updates |
None
|
timeout
|
float
|
Maximum seconds to wait for the render to complete. |
60.0
|
Raises:
| Type | Description |
|---|---|
TimeoutError
|
If rendering does not complete within |
RuntimeError
|
If client-side execution raises an error. |
send(data)
¶
Send data from server to client-side Pyodide code.
The data must be JSON-serializable. On the client side, the data
is available via the pl-server-data event on the <panel-live>
element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
JSON-serializable data to send to the client. |
required |
examples
¶
Example Panel apps using the PanelLive component.
panes
¶
panel-live in HTML/Markdown panes — demonstrates embedding
Run with::
panel-live serve --port 5008
# then open http://localhost:5008/panes
Or directly::
pixi run panel serve src/panel_live/examples/panes.py \
--static-dirs pl=dist docs=docs \
--port 5008
CDN = 'https://panel-extensions.github.io/panel-live/assets'
module-attribute
¶
DOCS_BASE = 'https://panel-extensions.github.io/panel-live'
module-attribute
¶
HEADER_MD = pn.pane.Markdown(f'# panel-live in HTML & Markdown PanesEmbed `<panel-live>` directly inside `pn.pane.HTML`, `pn.pane.Markdown`, and`pn.chat.ChatInterface` — no `PanelLive` JSComponent required.<div style="display:flex; align-items:center; gap:18px; justify-content:center; padding:8px 0;"><a href="https://panel.holoviz.org" target="_blank" title="Panel"><img src="https://panel.holoviz.org/_static/logo_stacked.svg" alt="Panel" style="height:48px;"></a><span style="font-size:28px; color:#aaa;">+</span><a href="https://pyodide.org" target="_blank" title="Pyodide"><img src="https://raw.githubusercontent.com/pyodide/pyodide-artwork/refs/heads/main/logo-quadratic.svg" alt="Pyodide" style="height:48px;"></a></div>Bokeh renders Panel panes inside a Shadow DOM — `<panel-live>` detects thisautomatically and patches `document.getElementById` so Bokeh's renderingpipeline can find the output container.[How-to Guide]({DOCS_BASE}/how-to/panel-panes/) ·[PanelLive Component]({DOCS_BASE}/how-to/panel-component/) ·[GitHub](https://github.com/panel-extensions/panel-live)', width=WIDTH)
module-attribute
¶
SIZING = {'sizing_mode': 'stretch_width'}
module-attribute
¶
WIDTH = 800
module-attribute
¶
accordion = pn.Accordion(('1. HTML Pane', pn.Column('Bokeh renders Panel panes inside Shadow DOM — `<panel-live>` detects this automatically.', html_pane)), ('2. Markdown Pane', pn.Column('`pn.pane.Markdown` passes raw HTML blocks through unchanged. Shadow DOM detection is automatic.', md_pane)), ('3. ChatInterface', pn.Column(f'Type {_KEYWORDS} — the callback returns a `pn.pane.HTML` wrapping `<panel-live>`. Shadow DOM detection is automatic.', pn.pane.Markdown('Type **slider**, **plot**, or **table** to see a live app.'), chat_interface)), width=WIDTH, active=[0])
module-attribute
¶
chat_interface = pn.chat.ChatInterface(callback=_chat_respond, show_send=True, placeholder_text='Type: slider, plot, or table')
module-attribute
¶
html_pane = pn.pane.HTML(f'<panel-live mode="editor" style="width:100%">{_HTML_CODE}</panel-live>', **SIZING)
module-attribute
¶
md_pane = pn.pane.Markdown(f'<panel-live mode="app" auto-run="true" style="width:100%">{_MD_CODE}</panel-live>', **SIZING)
module-attribute
¶
showcase
¶
PanelLive Showcase — demonstrates all modes and communication patterns.
Run with::
panel-live serve --port 5008
Or directly::
pixi run panel serve src/panel_live/examples/showcase.py --static-dirs pl=quarto/_extensions/panel-live docs=docs --port 5008
DOCS_BASE = 'https://panel-extensions.github.io/panel-live'
module-attribute
¶
EVAL_CASES = [('10 / 2', 'division'), ('def divide(a, b):\n return a / b\ndivide(1, 0)', 'ZeroDivisionError'), ("int('hello')", 'ValueError'), ('sum(range(100))', 'sum'), ('import nonexistent', 'ModuleNotFoundError')]
module-attribute
¶
HEADER_MD = pn.pane.Markdown(f'# PanelLive ShowcaseRun **Python in the browser** with [Pyodide](https://pyodide.org) — no server required for execution.<div style="display:flex; align-items:center; gap:18px; justify-content:center; padding:8px 0;"><a href="https://panel.holoviz.org" target="_blank" title="Panel"><img src="https://panel.holoviz.org/_static/logo_stacked.svg" alt="Panel" style="height:48px;"></a><span style="font-size:28px; color:#aaa;">+</span><a href="https://pyodide.org" target="_blank" title="Pyodide"><img src="https://raw.githubusercontent.com/pyodide/pyodide-artwork/refs/heads/main/logo-quadratic.svg" alt="Pyodide" style="height:48px;"></a></div>This demo shows all six display modes, server-side RPC, and bidirectional data exchangebetween a Panel server and client-side Pyodide code.[Getting Started]({DOCS_BASE}/tutorials/getting-started-panel/) ·[How-to Guide]({DOCS_BASE}/how-to/panel-component/) ·[Architecture]({DOCS_BASE}/explanation/panel-component/) ·[GitHub](https://github.com/panel-extensions/panel-live)', width=800)
module-attribute
¶
SIZING = {'sizing_mode': 'stretch_width'}
module-attribute
¶
WIDTH = 800
module-attribute
¶
accordion = pn.Accordion(('1. Editor Mode', pn.Column('Interactive code editor with live Pyodide output.', editor)), ('2. App Mode', pn.Column('Output only — no code editor visible.', app_mode)), ('3. Progress Mode', pn.Column('Sends 5 concurrent `evaluate()` calls (each sleeps 1s in the browser). The Python icon spins while active — **hover over it** to see the queue depth. Results arrive one by one as each completes.', pn.Row(btn_progress, progress_target), eval_results)), ('4. Progress Mode — Error Handling', pn.Column('Sends 5 evaluations — 2 succeed, 3 raise exceptions. Errors propagate as `RuntimeError` on the server without crashing the batch.', pn.Row(btn_mixed, error_target), error_results)), ('5. Debug Mode', pn.Column('Shows stdout/stderr — useful during development.', debug)), ('6. Playground Mode', pn.Column('Editor with examples selector — ideal for interactive exploration.', playground)), ('7. Headless Mode', pn.Column('Invisible (0px) — pure background compute. The element below is present but hidden:', headless)), ('8. Server RPC', pn.Column('Test `evaluate()` and `run()` — server-side methods that execute code in the client Pyodide worker.', pn.Row(btn_evaluate, btn_run), rpc_status, rpc_target)), ('9. Server→Client Reactive Push', pn.Column('Server sets `input` param — client reacts via `@pn.depends(server.param.input)`, no re-run needed.', pn.Row(btn_reactive_send), reactive_status, reactive_target)), ('10. Server→Client Periodic Push', pn.Column('Server pushes slider value via `input` param. Client displays datetime + `server.input` every 200ms.', slider, periodic_target)), ('11. Client→Server Data', pn.Column("Client sets `server.output` to push data back to the server's `output` param.", output_target, output_display)), width=WIDTH, active=[0, 2, 9])
module-attribute
¶
app_mode = PanelLive(code='import panel as pn\npn.pane.Markdown("## App Mode\\n\\nNo editor visible — output only.").servable()\n', mode='app', auto_run=True, **SIZING)
module-attribute
¶
btn_evaluate = pn.widgets.Button(name='Test evaluate()', button_type='primary')
module-attribute
¶
btn_mixed = pn.widgets.Button(name='Send 5 mixed evaluations', button_type='primary')
module-attribute
¶
btn_progress = pn.widgets.Button(name='Send 5 evaluations', button_type='primary')
module-attribute
¶
btn_reactive_send = pn.widgets.Button(name='Send Data to Client', button_type='primary')
module-attribute
¶
btn_run = pn.widgets.Button(name='Test run()', button_type='success')
module-attribute
¶
debug = PanelLive(code='print("stdout: debug mode active")\nresult = sum(range(100))\nprint(f"Computed sum(range(100)) = {result}")\nprint(f"Python version: {__import__(\'sys\').version}")\n', mode='debug', auto_run=True, **SIZING)
module-attribute
¶
editor = PanelLive(code='import panel as pn\n\nslider = pn.widgets.IntSlider(name="Pick a number", start=1, end=100, value=42)\n\npn.Column(\n slider,\n pn.bind(lambda v: f"### You picked **{v}**", slider),\n).servable()\n', mode='editor', auto_run=True, **SIZING)
module-attribute
¶
error_results = pn.pane.Str('', sizing_mode='stretch_width')
module-attribute
¶
error_target = PanelLive(code='pass', mode='progress', auto_run=True, **SIZING)
module-attribute
¶
eval_counter = 0
module-attribute
¶
eval_results = pn.pane.Str('', sizing_mode='stretch_width')
module-attribute
¶
headless = PanelLive(code='print("headless: invisible execution")', mode='headless', auto_run=True, **SIZING)
module-attribute
¶
output_display = pn.pane.JSON({}, name='Received Output', depth=2)
module-attribute
¶
output_target = PanelLive(code='import panel as pn\n\nbtn = pn.widgets.Button(name="Send to Server", button_type="primary")\ncount = 0\n\ndef on_click(event):\n global count\n count += 1\n server.output = {"count": count, "source": "browser"}\n\nbtn.on_click(on_click)\npn.Column(btn, "Click to send data to the server\'s `output` param").servable()\n', mode='editor', auto_run=True, **SIZING)
module-attribute
¶
periodic_target = PanelLive(code='import panel as pn\nimport datetime\n\ntime_pane = pn.pane.Str("Waiting...")\ndata_pane = pn.pane.Str("No server data yet")\n\ndef update():\n time_pane.object = f"Time: {datetime.datetime.now().strftime(\'%H:%M:%S.%f\')[:-3]}"\n data_pane.object = f"Server data: {server.input}"\n\npn.state.add_periodic_callback(update, period=200)\npn.Column(time_pane, data_pane).servable()\n', mode='editor', auto_run=True, **SIZING)
module-attribute
¶
playground = PanelLive(code='import panel as pn\n\nname = pn.widgets.TextInput(name="Your name", value="World")\n\npn.Column(\n name,\n pn.bind(lambda n: f"### Hello, **{n}**!", name),\n).servable()\n', mode='playground', auto_run=True, **SIZING)
module-attribute
¶
progress_target = PanelLive(code='pass', mode='progress', auto_run=True, **SIZING)
module-attribute
¶
reactive_counter = 0
module-attribute
¶
reactive_status = pn.widgets.TextInput(name='Status', value='Ready', disabled=True)
module-attribute
¶
reactive_target = PanelLive(code='import panel as pn\n\n@pn.depends(server.param.input)\ndef message(value):\n return value or {"message": "No data received yet"}\n\npn.pane.JSON(message, name="Server Data", depth=2).servable()\n', mode='editor', auto_run=True, **SIZING)
module-attribute
¶
rpc_status = pn.widgets.TextInput(name='Status', value='Ready', disabled=True)
module-attribute
¶
rpc_target = PanelLive(code='import panel as pn\npn.pane.Markdown("Waiting for server command...").servable()', mode='editor', auto_run=True, **SIZING)
module-attribute
¶
run_counter = 0
module-attribute
¶
slider = pn.widgets.IntSlider(name='Server value', start=0, end=100, value=42)
module-attribute
¶
fences
¶
Custom pymdownx.superfences fence for panel-live code blocks in MkDocs.
Allows writing interactive Panel apps in markdown using fenced code blocks:
```panel
import panel as pn
pn.panel("Hello").servable()
```
or with attributes:
```{.panel mode="editor" theme="dark" height="500px"}
import panel as pn
pn.panel("Hello").servable()
```
Pre-rendering can be enabled via :func:configure so that Panel code is
executed at MkDocs build time and the output is embedded as static HTML.
log = logging.getLogger('panel-live')
module-attribute
¶
configure(*, pre_render=False, cache_dir='.panel-live', setup_code='', timeout=120, docs_dir='docs')
¶
Configure pre-rendering for the MkDocs fence formatter.
Call this from a MkDocs hook (on_startup or on_config) to enable
build-time pre-rendering of panel fenced code blocks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pre_render
|
bool
|
Enable pre-rendering (default |
False
|
cache_dir
|
str
|
Directory for the content-hash cache (default |
'.panel-live'
|
setup_code
|
str
|
Python code prepended before every fence's code. |
''
|
timeout
|
int
|
Maximum seconds to wait for each subprocess (default |
120
|
docs_dir
|
str
|
Path to the MkDocs docs directory (default |
'docs'
|
formatter(source, language, css_class, options, md, **kwargs)
¶
Wrap source in a <panel-live> HTML element.
Called by pymdownx.superfences after validator has run.
Builds the <panel-live> tag with attributes from options
and the fenced source code as escaped inner text.
If mode="org" is set, delegates to the standard
pymdownx.superfences.fence_code_format to render a plain
syntax-highlighted code block instead of a <panel-live> element.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str
|
The raw content inside the fenced code block. |
required |
language
|
str
|
The fence language identifier (e.g. |
required |
css_class
|
str
|
CSS class assigned by |
required |
options
|
dict
|
Validated options produced by |
required |
md
|
Markdown
|
The Markdown processor instance. |
required |
**kwargs
|
Additional keyword arguments from |
{}
|
Returns:
| Type | Description |
|---|---|
str
|
An HTML string containing a |
prerender_formatter(source, language, css_class, options, md, **kwargs)
¶
Wrap source in a <panel-live> element with pre-rendering forced on.
Drop-in replacement for :func:formatter that always pre-renders,
regardless of the global configure() setting. Use this in your
superfences configuration when you want every fence to be
pre-rendered without needing a MkDocs hook::
custom_fences = [
{
"name": "panel",
"class": "panel-live",
"validator": "panel_live.fences.validator",
"format": "panel_live.fences.prerender_formatter",
}
]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
Same as :func: |
required | |
language
|
Same as :func: |
required | |
css_class
|
Same as :func: |
required | |
options
|
Same as :func: |
required | |
md
|
Same as :func: |
required | |
**kwargs
|
Same as :func: |
required |
Returns:
| Type | Description |
|---|---|
str
|
An HTML string containing a |
validator(language, inputs, options, attrs, md)
¶
Parse fence attributes into options for the formatter.
Called by pymdownx.superfences when a panel fence is encountered.
Known attributes (see _KNOWN_ATTRS) are popped from inputs and
stored in options so that formatter can read them later.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
language
|
str
|
The fence language identifier (e.g. |
required |
inputs
|
dict
|
Raw key-value pairs parsed from the fence opening line. Known attributes are popped and moved into options. |
required |
options
|
dict
|
Mutable dict that carries validated options to the formatter. |
required |
attrs
|
dict
|
Additional attributes from |
required |
md
|
Markdown
|
The Markdown processor instance. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
mcp
¶
MCP server for panel-live.
Provides the show_panel_live tool for rendering interactive Panel apps
in MCP Apps-capable clients (VS Code Copilot Chat, Claude.ai, etc.).
Start the server via CLI::
panel-live mcp # stdio (default)
panel-live mcp --transport http # SSE for testing
Or run directly with FastMCP::
fastmcp run panel_live.mcp:mcp
.. note::
This module intentionally avoids importing panel, bokeh, or
panel_live.__init__ to keep MCP server startup fast (~0.5 s instead
of 5-10 s).
RESOURCE_URI = 'ui://panel-live/show.html'
module-attribute
¶
TEMPLATE_PATH = Path(__file__).parent / 'templates' / 'show.html'
module-attribute
¶
mcp = create_mcp_server()
module-attribute
¶
create_mcp_server()
¶
Create and return the panel-live MCP server.
Returns:
| Type | Description |
|---|---|
FastMCP
|
A configured FastMCP server instance with the |
prerender
¶
Shared pre-rendering utilities for panel-live.
Executes Panel code at build time and produces Bokeh JSON output that can be
embedded inside <panel-live> elements for instant display before Pyodide
loads. Used by the Sphinx extension, the MkDocs fence formatter, and the CLI.
The pipeline is:
- Hash the code (including any setup code) with SHA-256.
- Check a content-hash cache on disk.
- On cache miss, spawn a subprocess that runs the code via
panel.io.mime_render.exec_with_return()and serializes the result to Bokeh JSON viastandalone_docs_json_and_render_items(). - Store the JSON in the cache and return it.
- The caller wraps the JSON in a
<script>tag for embedding.
log = logging.getLogger('panel-live')
module-attribute
¶
content_hash(code)
¶
Return a SHA-256 hex digest for code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
The full source code string (including any prepended setup code). |
required |
Returns:
| Type | Description |
|---|---|
str
|
64-character hexadecimal digest. |
embed_script_tag(json_str)
¶
Wrap json_str in a <script> tag for embedding inside <panel-live>.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
json_str
|
str
|
The Bokeh JSON string returned by :func: |
required |
Returns:
| Type | Description |
|---|---|
str
|
An HTML |
execution_process(code, conn)
¶
Run code in a subprocess and send back Bokeh JSON via conn.
This function is the target of a multiprocessing.Process spawned
with get_context('spawn').
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
Python source code to execute. |
required |
conn
|
Connection
|
The child end of a |
required |
model_json(obj)
¶
Serialize a Bokeh/Panel object to JSON for embedding.
Mirrors the pattern used by nbsite.pyodide._model_json().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
object
|
A Panel |
required |
Returns:
| Type | Description |
|---|---|
str or None
|
A JSON string containing |
pre_render(code, cache_dir, *, setup_code='', timeout=120)
¶
Pre-render code and return the embedded JSON string.
Uses a content-hash cache so repeated builds with unchanged code are nearly instant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
The user-visible source code. |
required |
cache_dir
|
Path or str
|
Directory for the content-hash cache (e.g. |
required |
setup_code
|
str
|
Optional code prepended before code (e.g. |
''
|
timeout
|
int
|
Maximum seconds to wait for the subprocess. |
120
|
Returns:
| Type | Description |
|---|---|
str or None
|
Bokeh JSON string on success, |
sphinx
¶
Sphinx extension for embedding interactive Panel apps via <panel-live>.
Registers a configurable RST directive (default panel-live, switchable
to pyodide or python) that transforms directive content into
<panel-live> HTML elements. Injects the panel-live JS/CSS and a
window.PANEL_LIVE_CONFIG script into every page that uses the directive.
Optionally pre-renders Panel output at build time using a subprocess,
aligned with the pattern in nbsite.pyodide.
Configuration
In conf.py::
extensions = ["panel_live.sphinx"]
panel_live_conf = {
"directive_name": "panel-live", # or "pyodide", "python"
"panel_live_js": "https://cdn.jsdelivr.net/npm/@panel-extensions/panel-live@latest/dist/panel-live.js",
"panel_live_css": "https://cdn.jsdelivr.net/npm/@panel-extensions/panel-live@latest/dist/panel-live.css",
"mini_coi": True, # inject mini-coi.js for SharedArrayBuffer
"pyodide_version": "v0.28.2",
"panel_version": "1.8.7",
"bokeh_version": "3.8.2",
"panel_cdn": "https://cdn.holoviz.org/panel/",
"bokeh_cdn": "https://cdn.bokeh.org/bokeh/release/",
"requirements": ["panel"],
"requires": {},
"setup_code": "",
"pre_render": True,
"default_mode": "editor",
}
Asset loading
panel_live_js and panel_live_css accept either absolute URLs
(https://...) or Sphinx-relative _static/ paths (e.g.
_static/panel-live.js). For local development, copy the built JS/CSS
into your project's _static/ directory and use local paths.
mini_coi (default True) copies a bundled mini-coi.js service
worker to the build root for COOP/COEP headers needed by Pyodide.
PanelLiveDirective
¶
Bases: Directive
RST directive that produces <panel-live> HTML elements.
Options map to HTML attributes on the <panel-live> element.
has_content = True
class-attribute
instance-attribute
¶
option_spec = {'mode': directives.unchanged, 'theme': directives.unchanged, 'height': directives.unchanged, 'layout': directives.unchanged, 'auto-run': directives.unchanged, 'label': directives.unchanged, 'code-visibility': directives.unchanged, 'code-position': directives.unchanged, 'requirements': directives.unchanged, 'pre-render': directives.unchanged, 'preview': directives.unchanged}
class-attribute
¶
optional_arguments = 0
class-attribute
instance-attribute
¶
required_arguments = 0
class-attribute
instance-attribute
¶
run()
¶
Parse directive options and return a raw HTML node.
setup(app)
¶
Register the panel-live Sphinx extension.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
Sphinx
|
The Sphinx application instance. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Extension metadata. |