Skip to content

Instantly share code, notes, and snippets.

@Cooops
Created August 14, 2018 15:25
Show Gist options
  • Save Cooops/753e72647411f2624b38b820182062ab to your computer and use it in GitHub Desktop.
Save Cooops/753e72647411f2624b38b820182062ab to your computer and use it in GitHub Desktop.
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from textwrap import dedent as d
import pandas as pd
import plotly.graph_objs as go
import psycopg2
import json
import datetime
#TODO: add static data folder?
global_renames = {
'completed_product_nick': 'Nickname',
'completed_product_titles': 'Card Title',
'completed_product_prices': 'Price',
'completed_product_end': 'End Date',
'completed_product_lst_type': 'Listing Type',
'completed_product_img_url': 'URL',
}
product_colors = {
1: '#414a4c',
0: '#000000',
}
# formatting/style/css
styles = {
'pre': {
'border': 'thin lightgrey solid',
# 'background-color': '#D3D3D3',
'overflowX': 'scroll',
}
}
colors = {
'background': '#000000',
}
test = ['black', 'green', 'blue', 'yellow', 'red', 'orange']
app = dash.Dash()
def fetch_data(query):
"""(query: str) -> str
Takes in a query, connects to the database and returns the result."""
result = pd.read_sql(
sql=query,
con=psycopg2.connect(
"dbname='a' user='b' host='c' password='d' port='e'"))
return result
def get_data_alpha():
"""() -> list
Returns a list of the alpha data in the database."""
# completed_product_start,
query = (
f'''
SELECT completed_product_nick, completed_product_titles, completed_product_prices, completed_product_end, completed_product_lst_type, completed_product_img_url
FROM completed_products
WHERE completed_product_nick IN ('Alpha Black Lotus', 'Alpha Mox Sapphire', 'Alpha Mox Jet', 'Alpha Mox Pearl', 'Alpha Mox Ruby', 'Alpha Mox Emerald', 'Alpha Timetwister', 'Alpha Ancestral Recall', 'Alpha Time Walk')
ORDER BY completed_product_end DESC;
''')
data = fetch_data(query)
return data
def get_data_beta():
"""() -> list
Returns a list of the beta card data in the database."""
query = (
f'''
SELECT completed_product_nick, completed_product_titles, completed_product_prices, completed_product_end, completed_product_lst_type, completed_product_img_url
FROM completed_products
WHERE completed_product_nick IN ('Beta Black Lotus MTG', 'Beta Mox Sapphire', 'Beta Mox Jet', 'Beta Mox Pearl', 'Beta Mox Ruby', 'Beta Mox Emerald', 'Beta Timetwister', 'Beta Ancestral Recall', 'Beta Time Walk')
ORDER BY completed_product_end DESC;
''')
data = fetch_data(query)
return data
def get_data_unlimited():
"""() -> list
Returns a list of the unlimited card data in the database."""
query = (
f'''
SELECT completed_product_nick, completed_product_titles, completed_product_prices, completed_product_end, completed_product_lst_type, completed_product_img_url
FROM completed_products
WHERE completed_product_nick IN ('Unlimited Black Lotus MTG', 'Unlimited Mox Sapphire', 'Unlimited Mox Jet', 'Unlimited Mox Pearl', 'Unlimited Mox Ruby', 'Unlimited Mox Emerald', 'Unlimited Timetwister', 'Unlimited Ancestral Recall', 'Unlimited Time Walk')
ORDER BY completed_product_end DESC;
''')
data = fetch_data(query)
return data
def get_data_alpha_avg():
"""() -> list
Returns a list of the alpha data in the database."""
query = (
f'''
SELECT completed_product_index_avg
FROM completed_products_index
WHERE completed_product_set_id = '1'
ORDER BY primary_ids DESC
LIMIT 2
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_avg'])
calc = ((data[0]-data[1])/data[1])*100
return f'${data[0]:,.0f} (+{calc:,.2f}%)'
def get_data_alpha_min():
"""() -> list
Returns a list of the alpha data in the database."""
query = (
f'''
SELECT completed_product_index_min
FROM completed_products_index
WHERE completed_product_set_id = '1'
ORDER BY primary_ids DESC
LIMIT 1
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_min'])
return f'${data[0]:,.0f}'
def get_data_alpha_max():
"""() -> list
Returns a list of the alpha data in the database."""
query = (
f'''
SELECT completed_product_index_max
FROM completed_products_index
WHERE completed_product_set_id = '1'
ORDER BY primary_ids DESC
LIMIT 1
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_max'])
return f'${data[0]:,.0f}'
def get_data_beta_avg():
"""() -> list
Returns a list of the beta data in the database."""
query = (
f'''
SELECT completed_product_index_avg
FROM completed_products_index
WHERE completed_product_set_id = '2'
ORDER BY primary_ids DESC
LIMIT 2
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_avg'])
calc = ((data[0]-data[1])/data[1])*100
return f'${data[0]:,.0f} (+{calc:,.2f}%)'
def get_data_beta_min():
"""() -> list
Returns a list of the beta data in the database."""
query = (
f'''
SELECT completed_product_index_min
FROM completed_products_index
WHERE completed_product_set_id = '2'
ORDER BY primary_ids DESC
LIMIT 1
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_min'])
return f'${data[0]:,.0f}'
def get_data_beta_max():
"""() -> list
Returns a list of the beta data in the database."""
query = (
f'''
SELECT completed_product_index_max
FROM completed_products_index
WHERE completed_product_set_id = '2'
ORDER BY primary_ids DESC
LIMIT 1
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_max'])
return f'${data[0]:,.0f}'
def get_data_unlimited_avg():
"""() -> list
Returns a list of the unlimited data in the database."""
query = (
f'''
SELECT completed_product_index_avg
FROM completed_products_index
WHERE completed_product_set_id = '3'
ORDER BY primary_ids DESC
LIMIT 2
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_avg'])
calc = ((data[0]-data[1])/data[1])*100
return f'${data[0]:,.0f} (+{calc:,.2f}%)'
def get_data_unlimited_min():
"""() -> list
Returns a list of the unlimited data in the database."""
query = (
f'''
SELECT completed_product_index_min
FROM completed_products_index
WHERE completed_product_set_id = '3'
ORDER BY primary_ids DESC
LIMIT 1
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_min'])
return f'${data[0]:,.0f}'
def get_data_unlimited_max():
"""() -> list
Returns a list of the unlimited data in the database."""
query = (
f'''
SELECT completed_product_index_max
FROM completed_products_index
WHERE completed_product_set_id = '3'
ORDER BY primary_ids DESC
LIMIT 1
'''
)
data = fetch_data(query)
data = list(data['completed_product_index_max'])
return f'${data[0]:,.0f}'
def get_data_single_stats(value):
"""() -> list
Returns a list of the alpha data in the database."""
query = (
f'''
SELECT completed_product_nick, completed_product_avg, completed_product_min, completed_product_max
FROM completed_products_stats
WHERE completed_product_nick = '{value}'
ORDER BY primary_ids DESC
LIMIT 1;
''')
data = fetch_data(query)
return data.values
def generate_table(dataframe, max_rows=10):
"""(dataframe: dataframe, max_row: int) -> table"""
return html.Table(
# Header
[html.Tr([html.Th(col) for col in dataframe.columns])] +
# Body
[html.Tr([
html.Td(dataframe.iloc[i][col]) for col in dataframe.columns
]) for i in range(min(len(dataframe), max_rows))]
)
#TODO: pretty this up(?) -- this loads the initial dataframe
df = get_data_alpha()
# DF_GAPMINDER = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
app.layout = html.Div([
# Page Header
html.Div(className='text', children=[
#TODO: markdown here?
html.Div('P9 Price Tracker...without the nonsense.', className='first-header'),
# html.Div('without the nonsense.', className='first-underline'),
dcc.Link('Go to listing...', href='http://www.ebay.com'),
# html.Img()
# html.Div(className='header-image')
]),
# # Page Time
# html.Div([
# html.Div(f'{str(datetime.datetime.now())}', className='time')
# ]),
# Dropdown
html.Div([
#Dropdown -- #TODO: wrap this in a div? _&_ Make it a button? -- center this too w/ css
dcc.Dropdown(
id='dropdown',
options=[
{'label': 'Alpha', 'value': 'Alpha'},
{'label': 'Beta', 'value': 'Beta'},
{'label': 'Unlimited', 'value': 'Unlimited'}
],
value=['Alpha'], # this decides what is initially loaded
className='three columns',
# style={'color': colors['background']},
# labelStyle={'display': 'inline-block'},
)
]),
# Index Prices
html.Div(
[
html.Div(f'Alpha Index Average: {get_data_alpha_avg()}',),
html.Div(f'Alpha Index Min: {get_data_alpha_min()}'),
html.Div(f'Alpha Index Max: {get_data_alpha_max()}'),
# html.Img(src="https://d1rw89lz12ur5s.cloudfront.net/photo/discordiagamesstore/file/273063/large/alpha.png", style={'width': 25, 'height': 22}),
],
className='three columns'),
html.Div(
[
html.Div(f'Beta Index Average: {get_data_beta_avg()}'),
html.Div(f'Beta Index Min: {get_data_beta_min()}'),
html.Div(f'Beta Index Max: {get_data_beta_max()}'),
],
className='three columns',
style={'color': colors['background']},
),
html.Div(
[
html.Div(f'Unlimited Index Average: {get_data_unlimited_avg()}'),
html.Div(f'Unlimited Index Min: {get_data_unlimited_min()}'),
html.Div(f'Unlimited Index Max: {get_data_unlimited_max()}'),
],
className='three columns'),
#TODO: Change formatting on hover data to actually display something readable
# Scatter plot
html.Div(
[
dcc.Graph(
className='eight columns',
config={'displayModeBar': False},
id='price-vs-time',
style={'height': '65vh',},
figure={
'data': [
go.Scatter(
x=df[df['completed_product_nick'] == i]['completed_product_end'],
y=df[df['completed_product_nick'] == i]['completed_product_prices'],
text=df[df['completed_product_nick'] == i]['completed_product_titles'],
customdata=df[df['completed_product_nick'] == i]['completed_product_nick'],
mode='markers',
opacity=0.7,
marker={
'size': 10,
'line': {'width': 0.5, 'color': 'white'},
},
name=i,
) for i in df.completed_product_nick.unique()
],
'layout': go.Layout(
xaxis={
# 'title': 'Date',
'color': 'rgb(214,236,255)',
'tickcolor': 'white',
},
yaxis={
# 'title': 'Price',
'color': 'rgb(214,236,255)',
},
margin={'l': 40, 'b': 40, 't': 15, 'r': 15},
legend={'x': 0, 'y': 1},
hovermode='closest', # compare
plot_bgcolor='rgb(0, 12, 77)',
paper_bgcolor='rgb(0, 12, 77)',
font=dict(color='rgb(214,236,255)'),
)
}
),
]),
html.Div(className='row', children=[
html.Div([
# dcc.Markdown(d("""**![img_thumb](https://d1rw89lz12ur5s.cloudfront.net/photo/discordiagamesstore/file/273063/large/alpha.png)**""")),
# style=setyles['pre']
dcc.Markdown(d("""**Click on any of the data points in the chart for more information.**""")),
html.Pre(id='click-data', style=styles['pre']),
], className='four columns'),
html.Div([
# Output Results Table
html.Div(
html.Table(id='output-container'),
className='twelve columns'),
],
),
]),
])
@app.callback(
dash.dependencies.Output('output-container', 'children'),
[dash.dependencies.Input('dropdown', 'value')]
)
def update_table(value):
if value == 'Alpha':
results = get_data_alpha().rename(columns=global_renames)
return generate_table(results, max_rows=25)
elif value == 'Beta':
results = get_data_beta().rename(columns=global_renames)
return generate_table(results, max_rows=25)
elif value == 'Unlimited':
results = get_data_unlimited().rename(columns=global_renames)
return generate_table(results, max_rows=25)
else:
results = get_data_alpha().rename(columns=global_renames)
return generate_table(results, max_rows=25)
@app.callback(
dash.dependencies.Output('price-vs-time', component_property='figure'),
[dash.dependencies.Input('dropdown', component_property='value'),])
def update_graph(value):
if value == 'Alpha':
df = get_data_alpha()
return {
'data': [go.Scatter(
x=df[df['completed_product_nick'] == i]['completed_product_end'],
y=df[df['completed_product_nick'] == i]['completed_product_prices'],
text=df[df['completed_product_nick'] == i]['completed_product_titles'],
customdata=df[df['completed_product_nick'] == i]['completed_product_nick'],
mode='markers',
opacity=0.7,
marker={
'size': 10,
'line': {'width': 0.5, 'color': 'white'},
# 'color': ['black'],
},
name=i,
# Change the name of the legends
# name=[i for i in df.name.unique()]
) for i in df.completed_product_nick.unique()
],
'layout': go.Layout(
xaxis={
# 'title': 'Date',
'color': 'rgb(214,236,255)',
'tickcolor': 'white',
# 'linecolor': 'rgb(214,236,255)',
},
yaxis={
# 'title': 'Price ($)',
'color': 'rgb(214,236,255)',
# 'linecolor': 'rgb(214,236,255)',
},
margin={'l': 40, 'b': 40, 't': 15, 'r': 15},
legend=dict(
x=0,
y=1,
# traceorder='normal',
# font=dict(
# # family='sans-serif',
# size=10,
# # color='#000'
# ),
# bgcolor='#E2E2E2',
# bordercolor='#FFFFFF',
# borderwidth=2
),
hovermode='closest', # compare
# plot_bgcolor='#f5f5f5',
plot_bgcolor='rgb(0, 12, 77)',
paper_bgcolor='rgb(0, 12, 77)',
font=dict(color='rgb(214,236,255)'),
)
}
elif value == 'Beta':
df = get_data_beta()
return {
'data': [go.Scatter(
x=df[df['completed_product_nick'] == i]['completed_product_end'],
y=df[df['completed_product_nick'] == i]['completed_product_prices'],
text=df[df['completed_product_nick'] == i]['completed_product_titles'],
# customdata={
# 'test': '1',
# 'test2': '2',
# },
customdata=df[df['completed_product_nick'] == i]['completed_product_nick'],
# text=df[df['completed_product_titles'] == i]['name'],
mode='markers',
opacity=0.7,
marker={
'size': 10,
'line': {'width': 0.5, 'color': 'white'},
# 'color': ['yellow' for i in df.completed_product_nick.unique()],
},
name=i,
# Change the name of the legends
# name=[i for i in df.name.unique()]
) for i in df.completed_product_nick.unique()
],
'layout': go.Layout(
xaxis={
# 'title': 'Date',
'color': 'rgb(214,236,255)',
'tickcolor': 'white',
# 'linecolor': 'rgb(214,236,255)',
},
yaxis={
# 'title': 'Price',
'color': 'rgb(214,236,255)',
# 'linecolor': 'rgb(214,236,255)',
},
margin={'l': 40, 'b': 40, 't': 15, 'r': 15},
legend={'x': 0, 'y': 1},
hovermode='closest', # compare
#TODO: this changes the background color
plot_bgcolor='rgb(0, 12, 77)',
paper_bgcolor='rgb(0, 12, 77)',
font=dict(color='rgb(214,236,255)'),
# plot_bgcolor='rgb(187, 179, 159)',
# paper_bgcolor='rgb(108, 85, 87)',
# font=dict(family='sans-serif', size=12, color='#000'),
# paper_bgcolor='rgba(255, 0, 0, 0.8)',
)
}
elif value == 'Unlimited':
df = get_data_unlimited()
return {
'data': [go.Scatter(
#TODO: make this display a pretty date and prices?
x=df[df['completed_product_nick'] == i]['completed_product_end'],
y=df[df['completed_product_nick'] == i]['completed_product_prices'],
text=df[df['completed_product_nick'] == i]['completed_product_titles'],
# customdata='test',
customdata=df[df['completed_product_nick'] == i]['completed_product_nick'],
#mode='lines+markers',
mode='markers',
opacity=0.7,
marker={
'size': 10,
'line': {'width': 0.5, 'color': 'white'},
# 'color': ['black'],
},
# name=i,
name=i,
# Change the name of the legends
# name=[i for i in df.name.unique()]
) for i in df.completed_product_nick.unique()
],
'layout': go.Layout(
xaxis={
# 'title': 'Date',
'color': 'rgb(214,236,255)',
'tickcolor': 'white',
# 'linecolor': 'rgb(214,236,255)',
},
yaxis={
# 'title': 'Price',
'color': 'rgb(214,236,255)',
# 'tickcolor': 'white',
# 'linecolor': 'rgb(214,236,255)',
},
margin={'l': 40, 'b': 40, 't': 15, 'r': 15},
legend={'x': 0, 'y': 1},
hovermode='closest', # compare
plot_bgcolor='rgb(0, 12, 77)',
paper_bgcolor='rgb(0, 12, 77)',
font=dict(color='rgb(214,236,255)'),
)
}
else:
df = get_data_alpha()
return {
'data': [go.Scatter(
x=df[df['completed_product_nick'] == i]['completed_product_end'],
y=df[df['completed_product_nick'] == i]['completed_product_prices'],
text=df[df['completed_product_nick'] == i]['completed_product_titles'],
customdata=df[df['completed_product_nick'] == i]['completed_product_nick'],
mode='markers',
opacity=0.7,
marker={
'size': 10,
'line': {'width': 0.5, 'color': 'white'},
# 'color': ['black'],
},
name=i,
# Change the name of the legends
# name=[i for i in df.name.unique()]
) for i in df.completed_product_nick.unique()
],
'layout': go.Layout(
xaxis={
# 'title': 'Date',
'color': 'rgb(214,236,255)',
'tickcolor': 'white',
# 'linecolor': 'rgb(214,236,255)',
},
yaxis={
# 'title': 'Price ($)',
'color': 'rgb(214,236,255)',
# 'linecolor': 'rgb(214,236,255)',
},
margin={'l': 40, 'b': 40, 't': 15, 'r': 15},
legend=dict(
x=0,
y=1,
# traceorder='normal',
# font=dict(
# # family='sans-serif',
# size=10,
# # color='#000'
# ),
# bgcolor='#E2E2E2',
# bordercolor='#FFFFFF',
# borderwidth=2
),
hovermode='closest', # compare
# plot_bgcolor='#f5f5f5',
plot_bgcolor='rgb(0, 12, 77)',
paper_bgcolor='rgb(0, 12, 77)',
font=dict(color='rgb(214,236,255)'),
)
}
@app.callback(
Output('click-data', 'children'),
# TODO: can be clickData as well
[Input('price-vs-time', 'clickData')])
def display_click_data(clickData):
# TODO: send query(?) and return avg/min/max etc for respective clicked data point
# TODO: can easily add more data-points here -- give this json api its own page perhaps?
# TODO: Fix this section, it's incredibly redundant and sloppy...but works for now @ 8/12/2018
# TODO: update index averages as well (same callback or new one?)
text = clickData['points'][0]['customdata']
alpha_avg_index = get_data_alpha_avg().split('$')[1].split(' ')[0].split('%')[0]
alpha_avg_index = alpha_avg_index.lstrip().rstrip()
beta_avg_index = get_data_beta_avg().split('$')[1].split(' ')[0].split('%')[0]
unlimited_avg_index = get_data_unlimited_avg().split('$')[1].split(' ')[0].split('%')[0]
if text == 'Alpha Black Lotus':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / ((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
# "percent of avg index": f"{float(get_data_beta_avg()):,.2f}%",
"percent of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
# "start": f"{df['completed_product_start']}",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Alpha Mox Sapphire':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Alpha Mox Jet':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Alpha Mox Pearl':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Alpha Mox Ruby':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Alpha Mox Emerald':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Alpha Timetwister':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Alpha Ancestral Recall':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Alpha Time Walk':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(alpha_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Beta Black Lotus MTG':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Beta Mox Sapphire':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Beta Mox Jet':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
# WHERE completed_product_nick IN ('Unlimited Black Lotus MTG', 'Unlimited Mox Sapphire', 'Unlimited Mox Jet', 'Unlimited Mox Pearl', 'Unlimited Mox Ruby', 'Unlimited Mox Emerald', 'Unlimited Timetwister', 'Unlimited Ancestral Recall', 'Unlimited Time Walk')
if text == 'Beta Mox Pearl':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Beta Mox Ruby':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Beta Mox Emerald':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Beta Timetwister':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Beta Ancestral Recall':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
if text == 'Beta Time Walk':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(beta_avg_index.replace(',', ''))) * 100
# spreadCalc = (stats[0][3]-stats[0][2])/stats[0][2]*100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Black Lotus MTG':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']:,.2f}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Mox Sapphire':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Mox Jet':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y'])/((stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"name": f"{clickData['points'][0]['customdata']}",
"title": f"{clickData['points'][0]['text']}",
"price": f"${clickData['points'][0]['y']}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Mox Pearl':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Mox Ruby':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Mox Emerald':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Timetwister':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Ancestral Recall':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
elif text == 'Unlimited Time Walk':
stats = get_data_single_stats(text)
percentAvg = (stats[0][1] / float(unlimited_avg_index.replace(',', ''))) * 100
percentDiff = 100 * (stats[0][3] - clickData['points'][0]['y']) / (
(stats[0][3] + clickData['points'][0]['y']) / 2)
dt = datetime.datetime.strptime(clickData['points'][0]['x'].split(' ')[0], '%Y-%m-%d')
data = {
"title": f"{clickData['points'][0]['text']}",
"name": f"{clickData['points'][0]['customdata']}",
"price": f"${clickData['points'][0]['y']}",
"avg": f"${stats[0][1]:,.2f}",
"min": f"${stats[0][2]:,.2f}",
"max": f"${stats[0][3]:,.2f}",
"% of avg index": f"{percentAvg:,.2f}%",
"price to max diff": f"{percentDiff:,.2f}%",
"end": f"{dt.month}/{dt.day}/{dt.year}",
}
return json.dumps(data, indent=2)
external_css = [
# "https://codepen.io/anon/pen/pVNqEz.css",
# "https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.css",
# "https://codepen.io/anon/pen/RBvqzG.css",
# "https://codepen.io/chriddyp/pen/bWLwgP.css",
# "https://fonts.googleapis.com/css?family=Economica",
# "https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css",
# "https://codepen.io/anon/pen/VBRaey.css",
# "https://codepen.io/anon/pen/qyvZNj.css",
# "https://codepen.io/anon/pen/ejodba.css",
# "https://codepen.io/anon/pen/PBgbPR.css",
"https://codepen.io/anon/pen/xJeRWq.css",
]
for css in external_css:
app.css.append_css({"external_url": css})
if __name__ == '__main__':
app.run_server(debug=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment