This will work better the less updates you have, the less interactive your apps is, the less users you have and the less compute costly your “refresh” is.
You can serve the code below via panel serve name_of_script.py.
import datetime
import panel as pn
pn.extension(sizing_mode="stretch_both")
ACCENT_BASE_COLOR = "#DAA520"
text = f"""# Last Update: {datetime.datetime.utcnow().strftime('%H:%M:%S')}"""
streaming_component = pn.pane.Markdown(text)
panel = pn.Row(streaming_component, height=90)
template = pn.template.FastListTemplate(
site="Awesome Panel",
title="Streaming using meta_refresh",
logo="https://panel.holoviz.org/_static/logo_stacked.png",
header_background=ACCENT_BASE_COLOR,
accent_base_color=ACCENT_BASE_COLOR,
meta_refresh="3",
main=[panel],
).servable()
If you need better performance or user experience than meta_refresh can provide you can look into adding a PeriodicCallback using pn.state.add_periodic_callback.
There is also a nice gallery example here which is running live here.
The updated example looks like
and the code is
import datetime
import panel as pn
pn.extension(sizing_mode="stretch_both")
ACCENT_BASE_COLOR = "#DAA520"
streaming_component = pn.pane.Markdown()
panel = pn.Row(streaming_component, height=90)
def update():
text = f"""# Last Update: {datetime.datetime.utcnow().strftime('%H:%M:%S.%f')}"""
print(text)
streaming_component.object = text
pn.state.add_periodic_callback(callback=update, period=500)
template = pn.template.FastListTemplate(
site="Awesome Panel",
title="Streaming using meta_refresh",
logo="https://panel.holoviz.org/_static/logo_stacked.png",
header_background=ACCENT_BASE_COLOR,
accent_base_color=ACCENT_BASE_COLOR,
main=[panel],
).servable()
Sharing the PeriodicCallback across users and session.
UPDATE: I’ve later learned that the callback added using pn.state.add_periodic_callback is not global and will stop when the browser tab is closed. A PR is on the way to add this functionality. #2661. In the mean time you can use threading instead as shown in the posts below. Alternatively you can use tornados add_timeout, call_at or call_later
If you have many users it might not be feasible to run it individually for all your users. Instead you should run it once for your entire application.
You can use pn.state.cache (a globally shared dictionary) to share the PeriodicCallback and the streaming_component across users.
You can serve the below code via panel serve name_of_script.py (dont use --autoreload flag).
import datetime
import panel as pn
pn.extension(sizing_mode="stretch_both")
ACCENT_BASE_COLOR = "#DAA520"
if not "streaming_component" in pn.state.cache:
streaming_component = pn.pane.Markdown("hello")
def update():
text = f"""# Last Update: {datetime.datetime.utcnow().strftime('%H:%M:%S.%f')}"""
print(text)
streaming_component.object = text
pn.state.cache["streaming_component"]=streaming_component
periodic_callback = pn.state.add_periodic_callback(callback=update, period=500)
streaming_component=pn.state.cache["streaming_component"]
panel = pn.Row(streaming_component, height=90)
template = pn.template.FastListTemplate(
site="Awesome Panel",
title="add_periodic_callback",
logo="https://panel.holoviz.org/_static/logo_stacked.png",
header_background=ACCENT_BASE_COLOR,
accent_base_color=ACCENT_BASE_COLOR,
main=[panel],
).servable()
If you want to personalize what is shown to the user, for example based on inputs from sliders etc, that is also possible.
To enable personalization you can update some shared parameter value from another thread. Then each user session can bind to or depend on the updates to the shared parameter value.
Panel and the HoloViz ecosystem provides a lot of features and tools for streaming because there are so many different use cases and requirements. For more check out.
The way it works is that the parameterized app only performs initial loads of data that is not already in the cache.
The cache then periodically updates that data (using threading, as periodic callbacks die after the session is closed like you pointed out) and puts it in a ColumnDataSource (CDS) that is still inside the cache.
The app then removes & re-creates an on_change callback to that CDS every time the CDS changes and/or the app’s widgets state changes (new pair or window selected). It then takes the data from the cache’s CDS and copies it to its internal CDS (I first tried keeping all CDSs in cache and just swapping the plot’s CDS on every widget state change, but the plot didn’t reflect the changes).
The result is that it can bootstrap a data state from disk, and continuously update it in realtime from within the app. Ideally, the CDS would trigger a callback on all users who have subscribed to it. And while the CDS does actually trigger callbacks on each of the connected sessions, only the first session’s chart actually updates:
I’d ideally like to do it this way, but if it can’t be done, I could also create a periodic callback within the app to query the cache for new data.
Unfortunately, I can’t share the exact code as I plan to use this for a product, but the above graphic should provide a high-level overview of how it works.
As you can see, both sessions are updated at the exact same time since they are subscribed to the same CDS that resides in the panel cache.
The trick was to do a next_tick_callback on the session’s own document, rather than the one stored in the cache that is attached to the CDS (for whatever reason):
I got this solution(only for the record) with a parameterized dict , I think it can serve to bring data from a database too
import panel as pn, param, numpy as np
from bokeh.plotting import ColumnDataSource, figure
from threading import Thread
from time import sleep
from functools import partial
class globalDict(param.Parameterized):
gd = param.Dict({})
global_dict = globalDict(gd=dict(a=np.arange(100), b= np.random.random((100))))
def thread_function(global_dict):
while True:
sleep(0.25)
global_dict.gd = dict(a=np.arange(100), b= np.random.random((100)))
th = Thread(target=thread_function, args=(global_dict,))
th.daemon = True
th.start()
if not "dict" in pn.state.cache:
pn.state.cache["dict"] = global_dict
def app():
dict_inner = pn.state.cache['dict']
cds = ColumnDataSource(dict_inner.gd)
doc = pn.state.curdoc
p = figure()
p.circle(x='a', y='b', source=cds)
def update(cds, gd):
cds.data = gd
@pn.depends(dict_inner.param.gd, watch=True)
def scope_function(gd1):
# here the dict gd1 can be filtered according to the particular user
doc.add_next_tick_callback(partial(update,cds=cds,gd=gd1))
return pn.Row(p)
pn.Row(app).servable()