我正在努力使使用bokeh的flask应用程序在linode服务器上运行。我已经成功地实现了在本地工作的bokeh,但是在Linode服务器上使其与gunicorn一起运行时遇到了麻烦。据我了解,您需要在server_document()函数中指定正在运行的url bokeh。对我来说,我相信它正在'/ bkapp'上运行,但是我的错误却相反。这一切都发生在我的本地计算机上。我尚未在linode上尝试过它,因为我什至无法使其首先在本地运行。
这是其中包含散景图的脚本。
dashboard.py
from flask import render_template,request,flash,redirect,url_for
from flask_login import current_user,login_user,logout_user,login_required
from werkzeug.urls import url_parse
from app.models import User,Dashboard
from app import db
from app.backend.company import Company
from app.backend.summary.compliance import getcompliance
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
from bokeh.plotting import figure
from bokeh.embed import server_document
from bokeh.models import ColumnDataSource,Select,CheckboxGroup,CustomJS,Range1d,LinearAxis,Span,Label
from bokeh.models.axes import LogAxis
from bokeh.layouts import layout
from bokeh.server.server import BaseServer
from bokeh.server.tornado import BokehTornado
from bokeh.server.util import bind_sockets
from threading import Thread
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
import asyncio
import pandas as pd
import numpy as np
import os
# global df being used for the graph (changes with the dropdowns)
df = None
sentiment_df = None
current_feature = None
current_period = None
companyObject = None
def dashboard(companyName):
# setup
print('getting bulk data...')
print('Getting ticker symbol...')
global companyObject
companyObject = Company(companyName)
if not companyObject.found:
print('Company not found!')
return render_template('companyNotFound.html',title='Company Not Found',companyName=companyName)
def create_stocks_graph(doc):
global df,sentiment_df,current_feature,current_period,companyObject
feature_names = ['Open','High','Low','Close']
periods = ['1d','5d','1mo','3mo','6mo','1y','2y','5y','10y','ytd','max']
default_period = '3mo'
default_feature = 'Close'
current_feature = default_feature
current_period = default_period
base = os.getcwd()
sentiment_path = base + "\\app\\static\\sentiment_csv\\" + companyObject.ticker + ".csv"
sentiment_df = pd.read_csv(sentiment_path)
df = companyObject.getHistoricalPricesDf(period=default_period)
source = ColumnDataSource(data={
'x' : df.index,'y' : df['Close']
})
p = figure(title=f"Stocks at {default_feature} for past {default_period}",x_axis_label='Date',y_axis_label=default_feature,tools="pan,wheel_zoom,reset,box_zoom",plot_width=920,plot_height=600,x_axis_type="datetime")
# x ticks cleaning
df.index = pd.to_datetime(df.index)
df.index.name = 'Date'
df.sort_index(inplace=True)
sentiment_df['Time'] = pd.to_datetime(sentiment_df['Time'])
# shift sentiment graph
earliest_sentiment = sentiment_df['Time'].iloc[0]
df_subset = df[df.index >= earliest_sentiment]
avg_df_subset = df_subset['Close'].mean()
sentiment_df['Sentiment'] *= 10
sentiment_df['Sentiment'] += avg_df_subset
# make line graphs
stock_line = p.line(x='x',y='y',legend_label = "Stock",line_width=2,line_alpha=0.6,line_color='blue',source=source)
sentiment_line = p.line(x=sentiment_df['Time'],y=sentiment_df['Sentiment'],legend_label = "Sentiment",line_color='red')
# sentiment reference lines
neutralline = Span(location=avg_df_subset + 5,dimension='width',line_color='black',line_dash='dashed',line_width=1,line_alpha=0.5)
neutralLabel = Label(x=70,y=avg_df_subset + 5.2,x_units='screen',text='Neutral Sentiment',render_mode='css',border_line_alpha=0,background_fill_color='white',background_fill_alpha=1.0)
p.renderers.extend([neutralline,neutralLabel])
# figure styling
p.outline_line_color = 'black'
p.legend.visible = True
p.legend.click_policy="hide"
checkbox = CheckboxGroup(labels=["Stock","Sentiment"],active=[0,1])
callback = CustomJS(code="""
stock_line.visible = false;
sentiment_line.visible = false;
neutralline.visible = false;
neutralLabel.visible = false;
// p.xaxis.visible = false;
// cb_obj injected in by the callback
if (cb_obj.active.includes(0)){
stock_line.visible = true;
// p.xaxis.visible = true;
} // 0 index box is stock_line
if (cb_obj.active.includes(1))
{sentiment_line.visible = true;
neutralline.visible = true;
neutralLabel.visible = true;
}
""",args={'stock_line': stock_line,'sentiment_line': sentiment_line,'neutralline': neutralline,'p':p,'neutralLabel':neutralLabel})
checkbox.js_on_change('active',callback)
def update_plot(attr,old,new):
global df,current_period
if new in feature_names:
# change feature
print('updating feature')
print('old:',old)
print('new:',new)
current_feature = new
p.yaxis.axis_label = current_feature # update y-axis
# p.title = f"Stocks at {current_feature} for past {current_period}"
source.data = {
'x' : df.index,'y' : df[current_feature]
}
else:
# change period
print('updating period')
print('old:',new)
current_period = new
df = companyObject.getHistoricalPricesDf(period=new)
df.index = pd.to_datetime(df.index)
df.index.name = 'Date'
df.sort_index(inplace=True)
x_ticks = [d.strftime("%m/%d/%Y)")[:-1] for d in df.index.date]
# p.title = f"Stocks at {current_feature} for past {current_period}"
source.data = {
'x' : df.index,'y' : df[current_feature]
}
# add selects
period_select = Select(title="Period",value=default_period,options=periods)
feature_select = Select(title="Feature",value=default_feature,options=feature_names)
period_select.on_change("value",update_plot) # update period
feature_select.on_change("value",update_plot) # update feature
doc.add_root(layout(
[feature_select,period_select],[checkbox],[p]))
# can't use shortcuts here,since we are passing to low level BokehTornado
bkapp = Application(FunctionHandler(create_stocks_graph))
# This is so that if this app is run using something like "gunicorn -w 4" then
# each process will listen on its own port
sockets,port = bind_sockets("localhost",0)
# initiate bokeh server
def bk_worker():
# https://github.com/bokeh/bokeh/blob/1.1.0/examples/howto/server_embed/flask_embed.py
asyncio.set_event_loop(asyncio.new_event_loop())
bokeh_tornado = BokehTornado({'/bkapp': bkapp}),extra_websocket_origins=["localhost:8000"])
bokeh_http = HTTPServer(bokeh_tornado)
bokeh_http.add_sockets(sockets)
server = BaseServer(IOLoop.current(),bokeh_tornado,bokeh_http)
server.start()
server.io_loop.start()
Thread(target=bk_worker).start()
############### stocks ########################
keys = ['bid','ask','weeklyHigh','weeklyLow','dailyMvmt']
stockInfo = dict.fromkeys(keys,0)
print('Getting stock price...')
stockInfo['bid'],stockInfo['ask'] = companyObject.getStockPrice()
print('Getting historical data...')
infoDF = companyObject.getHistoricalPricesDf(period='5d')
stockInfo['weeklyHigh'] = round(max(infoDF['High']),2)
stockInfo['weeklyLow'] = round(min(infoDF['Low']),2)
print('Getting Stock Price Change...')
stockInfo['dailyMvmt'] = companyObject.getStockPriceChange()
# stocks graph script
script = server_document(url=r'/bkapp',relative_urls=True)
############################ summaries #####################
print('getting summaries...')
summaries = getcompliance(companyObject,multiplier=3) # update later when we add preferences
print('Dashboard Ready!')
# add finished dashboard to database
dash = Dashboard(company_name=companyObject.displayName,author=current_user)
db.session.add(dash)
db.session.commit()
return render_template('dashboard.html',ticker=companyObject.ticker,stockInfo=stockInfo,script=script,summaries=summaries,displayName=companyObject.displayName)
这是我的错误日志
...
Dashboard Ready!
127.0.0.1 - - [16/Jun/2020 19:53:25] "POST /dashboard HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [16/Jun/2020 19:53:25] "POST /dashboard HTTP/1.1" 200 -
127.0.0.1 - - [16/Jun/2020 19:53:26] "GET /bkapp/autoload.js?bokeh-autoload-element=1001&bokeh-app
-path=/bkapp HTTP/1.1" 404 -
INFO:werkzeug:127.0.0.1 - - [16/Jun/2020 19:53:26] "GET /bkapp/autoload.js?bokeh-autoload-element=
1001&bokeh-app-path=/bkapp HTTP/1.1" 404 -
是像更改url端点一样简单,还是我错过了很多?
谢谢