import datetime as dt
import os
import pathlib
import time
import weakref
import param
import pytest
import requests
from panel.config import config
from panel.io import state
from panel.io.resources import DIST_DIR
from panel.io.server import get_server, serve, set_curdoc
from panel.layout import Row
from panel.models import HTML as BkHTML
from panel.models.tabulator import TableEditEvent
from panel.pane import Markdown
from panel.reactive import ReactiveHTML
from panel.template import BootstrapTemplate
from panel.widgets import Button, Tabulator, Terminal
def test_get_server(html_server_session):
html, server, session = html_server_session
assert server.port == 6000
root = session.document.roots[0]
assert isinstance(root, BkHTML)
assert root.text == '<h1>Title</h1>'
def test_server_update(html_server_session):
html, server, session = html_server_session
html.object = '
New Title
'
session.pull()
root = session.document.roots[0]
assert isinstance(root, BkHTML)
assert root.text == '<h1>New Title</h1>'
def test_server_change_io_state(html_server_session):
html, server, session = html_server_session
def handle_event(event):
assert state.curdoc is session.document
html.param.watch(handle_event, 'object')
html._server_change(session.document, None, None, 'text', 'Title
', 'New Title
')
def test_server_static_dirs():
html = Markdown('# Title')
static = {'tests': os.path.dirname(__file__)}
port = 6000
serve(html, port=port, threaded=True, static_dirs=static, show=False)
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/tests/test_server.py")
with open(__file__, encoding='utf-8') as f:
assert f.read() == r.content.decode('utf-8').replace('\r\n', '\n')
def test_server_template_static_resources():
template = BootstrapTemplate()
port = 6001
serve({'template': template}, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/static/extensions/panel/bundled/bootstraptemplate/bootstrap.css")
with open(DIST_DIR / 'bundled' / 'bootstraptemplate' / 'bootstrap.css', encoding='utf-8') as f:
assert f.read() == r.content.decode('utf-8').replace('\r\n', '\n')
def test_server_template_static_resources_with_prefix():
template = BootstrapTemplate()
port = 6002
serve({'template': template}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/static/extensions/panel/bundled/bootstraptemplate/bootstrap.css")
with open(DIST_DIR / 'bundled' / 'bootstraptemplate' / 'bootstrap.css', encoding='utf-8') as f:
assert f.read() == r.content.decode('utf-8').replace('\r\n', '\n')
def test_server_template_static_resources_with_prefix_relative_url():
template = BootstrapTemplate()
port = 6003
serve({'template': template}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/template")
content = r.content.decode('utf-8')
assert 'href="static/extensions/panel/bundled/bootstraptemplate/bootstrap.css"' in content
def test_server_template_static_resources_with_subpath_and_prefix_relative_url():
template = BootstrapTemplate()
port = 6004
serve({'/subpath/template': template}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/subpath/template")
content = r.content.decode('utf-8')
assert 'href="../static/extensions/panel/bundled/bootstraptemplate/bootstrap.css"' in content
def test_server_extensions_on_root():
html = Markdown('# Title')
port = 6005
serve(html, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/static/extensions/panel/css/loading.css")
assert r.ok
def test_autoload_js():
html = Markdown('# Title')
port = 6006
app_name = 'test'
serve({app_name: html}, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(0.5)
args = f"bokeh-autoload-element=1002&bokeh-app-path=/{app_name}&bokeh-absolute-url=http://localhost:{port}/{app_name}"
r = requests.get(f"http://localhost:{port}/{app_name}/autoload.js?{args}")
assert r.status_code == 200
assert f"http://localhost:{port}/static/extensions/panel/css/alerts.css" in r.content.decode('utf-8')
def test_server_async_callbacks():
button = Button(name='Click')
counts = []
async def cb(event, count=[0]):
import asyncio
count[0] += 1
counts.append(count[0])
await asyncio.sleep(1)
count[0] -= 1
button.on_click(cb)
port = 6007
serve(button, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
doc = list(button._models.values())[0][0].document
with set_curdoc(doc):
for _ in range(5):
button.clicks += 1
# Wait for callbacks to be scheduled
time.sleep(2)
# Ensure multiple callbacks started concurrently
assert max(counts) > 1
def test_serve_config_per_session_state():
CSS1 = 'body { background-color: red }'
CSS2 = 'body { background-color: green }'
def app1():
config.raw_css = [CSS1]
def app2():
config.raw_css = [CSS2]
port1, port2 = 6008, 6009
serve(app1, port=port1, threaded=True, show=False)
serve(app2, port=port2, threaded=True, show=False)
# Wait for servers to start
time.sleep(1)
r1 = requests.get(f"http://localhost:{port1}/").content.decode('utf-8')
r2 = requests.get(f"http://localhost:{port2}/").content.decode('utf-8')
assert CSS1 not in config.raw_css
assert CSS2 not in config.raw_css
assert CSS1 in r1
assert CSS2 not in r1
assert CSS1 not in r2
assert CSS2 in r2
def test_server_session_info():
with config.set(session_history=-1):
html = Markdown('# Title')
port = 6010
serve(html, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
assert state.session_info['total'] == 1
assert len(state.session_info['sessions']) == 1
sid, session = list(state.session_info['sessions'].items())[0]
assert session['user_agent'].startswith('python-requests')
assert state.session_info['live'] == 0
doc = list(html._documents.keys())[0]
session_context = param.Parameterized()
session_context._document = doc
session_context.id = sid
doc._session_context = weakref.ref(session_context)
state.curdoc = doc
state._init_session(None)
assert state.session_info['live'] == 1
state.curdoc = None
html._server_destroy(session_context)
assert state.session_info['live'] == 0
def test_server_schedule_repeat():
state.cache['count'] = 0
def periodic_cb():
state.cache['count'] += 1
def app():
state.schedule_task('periodic', periodic_cb, period='0.5s')
return '# state.schedule test'
port = 6011
server = serve(app, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
# Wait for periodic execution
time.sleep(1)
assert state.cache['count']
server.stop()
def test_server_schedule_at():
def periodic_cb():
state.cache['at'] = dt.datetime.now()
scheduled = dt.datetime.now() + dt.timedelta(seconds=1.57)
def app():
state.schedule_task('periodic', periodic_cb, at=scheduled)
return '# state.schedule test'
port = 6012
server = serve(app, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
# Wait for callback to be executed
time.sleep(1)
# Check callback was executed within small margin of error
assert 'at' in state.cache
assert abs(state.cache['at'] - scheduled) < dt.timedelta(seconds=0.2)
assert len(state._scheduled) == 0
server.stop()
def test_server_schedule_at_iterator():
state.cache['at'] = []
def periodic_cb():
state.cache['at'].append(dt.datetime.now())
scheduled1 = dt.datetime.now() + dt.timedelta(seconds=1.57)
scheduled2 = dt.datetime.now() + dt.timedelta(seconds=1.86)
def schedule():
yield scheduled1
yield scheduled2
def app():
state.schedule_task('periodic', periodic_cb, at=schedule())
return '# state.schedule test'
port = 6013
server = serve(app, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
# Wait for callbacks to be executed
time.sleep(1)
# Check callbacks were executed within small margin of error
assert len(state.cache['at']) == 2
assert abs(state.cache['at'][0] - scheduled1) < dt.timedelta(seconds=0.2)
assert abs(state.cache['at'][1] - scheduled2) < dt.timedelta(seconds=0.2)
assert len(state._scheduled) == 0
server.stop()
def test_server_schedule_at_callable():
state.cache['at'] = []
def periodic_cb():
state.cache['at'].append(dt.datetime.now())
scheduled = [
dt.datetime.utcnow() + dt.timedelta(seconds=1.57),
dt.datetime.utcnow() + dt.timedelta(seconds=1.86)
]
siter = iter(scheduled)
def schedule(utcnow):
return next(siter)
def app():
state.schedule_task('periodic', periodic_cb, at=schedule)
return '# state.schedule test'
port = 6014
server = serve(app, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
# Wait for callback to be executed
time.sleep(1)
# Convert scheduled times to local time
scheduled = [
s.replace(tzinfo=dt.timezone.utc).astimezone().replace(tzinfo=None)
for s in scheduled
]
# Check callbacks were executed within small margin of error
assert len(state.cache['at']) == 2
assert abs(state.cache['at'][0] - scheduled[0]) < dt.timedelta(seconds=0.2)
assert abs(state.cache['at'][1] - scheduled[1]) < dt.timedelta(seconds=0.2)
assert len(state._scheduled) == 0
server.stop()
def test_show_server_info(html_server_session, markdown_server_session):
server_info = repr(state)
assert "localhost:6000 - HTML" in server_info
assert "localhost:6001 - Markdown" in server_info
def test_kill_all_servers(html_server_session, markdown_server_session):
_, server_1, _ = html_server_session
_, server_2, _ = markdown_server_session
state.kill_all_servers()
assert server_1._stopped
assert server_2._stopped
def test_multiple_titles(multiple_apps_server_sessions):
"""Serve multiple apps with a title per app."""
session1, session2 = multiple_apps_server_sessions(
slugs=('app1', 'app2'), titles={'app1': 'APP1', 'app2': 'APP2'})
assert session1.document.title == 'APP1'
assert session2.document.title == 'APP2'
# Slug names and title keys should match
with pytest.raises(KeyError):
session1, session2 = multiple_apps_server_sessions(
slugs=('app1', 'app2'), titles={'badkey': 'APP1', 'app2': 'APP2'})
def test_serve_can_serve_panel_app_from_file():
path = pathlib.Path(__file__).parent / "io"/"panel_app.py"
server = get_server({"panel-app": path})
assert "/panel-app" in server._tornado.applications
def test_serve_can_serve_bokeh_app_from_file():
path = pathlib.Path(__file__).parent / "io"/"bk_app.py"
server = get_server({"bk-app": path})
assert "/bk-app" in server._tornado.applications
def test_server_thread_pool_change_event(threads):
button = Button(name='Click')
button2 = Button(name='Click')
counts = []
def cb(event, count=[0]):
count[0] += 1
counts.append(count[0])
time.sleep(0.5)
count[0] -= 1
button.on_click(cb)
button2.on_click(cb)
layout = Row(button, button2)
port = 6015
serve(layout, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
model = list(layout._models.values())[0][0]
doc = model.document
with set_curdoc(doc):
button._server_change(doc, model.ref['id'], None, 'clicks', 0, 1)
button2._server_change(doc, model.ref['id'], None, 'clicks', 0, 1)
# Wait for callbacks to be scheduled
time.sleep(1)
# Checks whether Button on_click callback was executed concurrently
assert max(counts) == 2
def test_server_thread_pool_bokeh_event(threads):
import pandas as pd
df = pd.DataFrame([[1, 1], [2, 2]], columns=['A', 'B'])
tabulator = Tabulator(df)
counts = []
def cb(event, count=[0]):
count[0] += 1
counts.append(count[0])
time.sleep(0.5)
count[0] -= 1
tabulator.on_edit(cb)
port = 6016
serve(tabulator, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
model = list(tabulator._models.values())[0][0]
doc = model.document
event = TableEditEvent(model, 'A', 0)
with set_curdoc(doc):
for _ in range(2):
tabulator._server_event(doc, event)
# Wait for callbacks to be scheduled
time.sleep(1)
# Checks whether Tabulator on_edit callback was executed concurrently
assert max(counts) == 2
def test_server_thread_pool_periodic(threads):
button = Button(name='Click')
counts = []
def cb(count=[0]):
count[0] += 1
counts.append(count[0])
time.sleep(0.5)
count[0] -= 1
port = 6017
serve(button, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
doc = list(button._models.values())[0][0].document
with set_curdoc(doc):
state.add_periodic_callback(cb, 100)
# Wait for callbacks to be scheduled
time.sleep(1)
# Checks whether periodic callbacks were executed concurrently
assert max(counts) >= 2
def test_server_thread_pool_onload(threads):
counts = []
def app(count=[0]):
button = Button(name='Click')
def onload():
count[0] += 1
counts.append(count[0])
time.sleep(2)
count[0] -= 1
state.onload(onload)
# Simulate rendering
def loaded():
state._schedule_on_load(None)
state.curdoc.add_next_tick_callback(loaded)
return button
port = 6018
serve(app, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
requests.get(f"http://localhost:{port}/")
requests.get(f"http://localhost:{port}/")
time.sleep(1)
# Checks whether onload callbacks were executed concurrently
assert max(counts) >= 2
class CustomBootstrapTemplate(BootstrapTemplate):
_css = './assets/custom.css'
def test_server_template_custom_resources():
template = CustomBootstrapTemplate()
port = 6019
serve({'template': template}, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/components/panel.tests.test_server/CustomBootstrapTemplate/_css/assets/custom.css")
with open(pathlib.Path(__file__).parent / 'assets' / 'custom.css', encoding='utf-8') as f:
assert f.read() == r.content.decode('utf-8').replace('\r\n', '\n')
def test_server_template_custom_resources_with_prefix():
template = CustomBootstrapTemplate()
port = 6020
serve({'template': template}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/components/panel.tests.test_server/CustomBootstrapTemplate/_css/assets/custom.css")
with open(pathlib.Path(__file__).parent / 'assets' / 'custom.css', encoding='utf-8') as f:
assert f.read() == r.content.decode('utf-8').replace('\r\n', '\n')
def test_server_template_custom_resources_with_prefix_relative_url():
template = CustomBootstrapTemplate()
port = 6021
serve({'template': template}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/template")
content = r.content.decode('utf-8')
assert 'href="components/panel.tests.test_server/CustomBootstrapTemplate/_css/assets/custom.css"' in content
def test_server_template_custom_resources_with_subpath_and_prefix_relative_url():
template = CustomBootstrapTemplate()
port = 6022
serve({'/subpath/template': template}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/subpath/template")
content = r.content.decode('utf-8')
assert 'href="../components/panel.tests.test_server/CustomBootstrapTemplate/_css/assets/custom.css"' in content
class CustomComponent(ReactiveHTML):
__css__ = ['./assets/custom.css']
def test_server_component_custom_resources():
component = CustomComponent()
port = 6023
serve({'component': component}, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/components/panel.tests.test_server/CustomComponent/__css__/assets/custom.css")
with open(pathlib.Path(__file__).parent / 'assets' / 'custom.css', encoding='utf-8') as f:
assert f.read() == r.content.decode('utf-8').replace('\r\n', '\n')
def test_server_component_custom_resources_with_prefix():
component = CustomComponent()
port = 6024
serve({'component': component}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/components/panel.tests.test_server/CustomComponent/__css__/assets/custom.css")
with open(pathlib.Path(__file__).parent / 'assets' / 'custom.css', encoding='utf-8') as f:
assert f.read() == r.content.decode('utf-8').replace('\r\n', '\n')
def test_server_component_custom_resources_with_prefix_relative_url():
component = CustomComponent()
port = 6025
serve({'component': component}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/component")
content = r.content.decode('utf-8')
assert 'href="components/panel.tests.test_server/CustomComponent/__css__/assets/custom.css"' in content
def test_server_component_custom_resources_with_subpath_and_prefix_relative_url():
component = CustomComponent()
port = 6026
serve({'/subpath/component': component}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/subpath/component")
content = r.content.decode('utf-8')
print(content)
assert 'href="../components/panel.tests.test_server/CustomComponent/__css__/assets/custom.css"' in content
def test_server_component_css_with_prefix_relative_url():
component = Terminal()
port = 6027
serve({'component': component}, port=port, threaded=True, show=False)
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/component")
content = r.content.decode('utf-8')
assert 'href="static/extensions/panel/bundled/terminal/xterm@4.11.0/css/xterm.css' in content
def test_server_component_css_with_subpath_and_prefix_relative_url():
component = Terminal()
port = 6028
serve({'/subpath/component': component}, port=port, threaded=True, show=False, prefix='prefix')
# Wait for server to start
time.sleep(1)
r = requests.get(f"http://localhost:{port}/prefix/subpath/component")
content = r.content.decode('utf-8')
assert 'href="../static/extensions/panel/bundled/terminal/xterm@4.11.0/css/xterm.css' in content