backtrader.bokeh.webapp 源代码
#!/usr/bin/env python
"""Web application server.
Provides Bokeh Server wrapper."""
import logging
import webbrowser
try:
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
from bokeh.server.server import Server
BOKEH_AVAILABLE = True
except ImportError:
BOKEH_AVAILABLE = False
_logger = logging.getLogger(__name__)
[文档]
class Webapp:
"""Web application server.
Wraps Bokeh Server, providing the following features:
- Auto-start server
- Session management
- Auto-open browser
Attributes:
title: Page title
template: Template filename
scheme: Theme instance
address: Server address
port: Server port
"""
[文档]
def __init__(
self,
title,
template,
scheme,
on_root_model,
on_session_destroyed=None,
autostart=True,
address="localhost",
port=8999,
):
"""Initialize web application.
Args:
title: Page title
template: Template filename
scheme: Theme instance
on_root_model: Callback function to build root model
on_session_destroyed: Session destroyed callback (optional)
autostart: Whether to auto-start
address: Server address
port: Server port
"""
self._title = title
self._template = template
self._scheme = scheme
self._on_root_model = on_root_model
self._on_session_destroyed = on_session_destroyed
self._autostart = autostart
self._address = address
self._port = port
self._server = None
def _make_document(self, doc):
"""Create Bokeh document.
Args:
doc: Bokeh document instance
"""
# Set title
doc.title = self._title
# Set session destroyed callback
if self._on_session_destroyed is not None:
doc.on_session_destroyed(self._on_session_destroyed)
# Build root model
root_model = self._on_root_model(doc)
if root_model is not None:
doc.add_root(root_model)
# Apply theme styles
if self._scheme is not None:
self._apply_theme(doc)
def _apply_theme(self, doc):
"""Apply theme to document.
Args:
doc: Bokeh document instance
"""
# Set background color and other styles
if hasattr(self._scheme, "body_background_color"):
# Set page styles via CSS
from bokeh.models import Div
style_html = f"""
<style>
body {{
background-color: {self._scheme.body_background_color};
color: {getattr(self._scheme, 'text_color', '#333')};
}}
.bk-root {{
background-color: {self._scheme.body_background_color};
}}
</style>
"""
style_div = Div(text=style_html, visible=False)
doc.add_root(style_div)
[文档]
def start(self, loop=None):
"""Start server.
Args:
loop: Tornado IOLoop (optional)
"""
if not BOKEH_AVAILABLE:
_logger.error("Bokeh is not available. Cannot start server.")
return
_logger.info(f"Starting Bokeh server at http://{self._address}:{self._port}")
# Create application
handler = FunctionHandler(self._make_document)
app = Application(handler)
# Create server
self._server = Server(
{"/": app},
address=self._address,
port=self._port,
allow_websocket_origin=[
f"{self._address}:{self._port}",
"localhost:" + str(self._port),
],
)
self._server.start()
# Auto-open browser
if self._autostart:
url = f"http://{self._address}:{self._port}/"
_logger.info(f"Opening browser at {url}")
webbrowser.open(url)
# Start IOLoop
if loop is not None:
loop.start()
else:
self._server.io_loop.start()
[文档]
def stop(self):
"""Stop server."""
if self._server is not None:
self._server.stop()
_logger.info("Bokeh server stopped")