In [0]:
!pip install -U plotly
!pip install dash
!pip install dash-html-components
!pip install dash-core-components
!pip install dash-table
!pip install dash_bootstrap_components
!pip install pycountry
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import os
import plotly
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import plotly.express as px
import imageio
import time
import dash
import dash_core_components as dcc
import dash_html_components as html 
from flask import request
import json
import pycountry
from google.colab import drive
from os.path import join

ROOT = '/content/drive'
PROJ = 'My Drive/Coronavirus/coronavirus-data/data'

GIT_USERNAME = "jvonk"
GIT_TOKEN = "b8d5c3aa010122878932823b884f9a35e770b78f"
GIT_REPOSITORY = "coronavirus-data"

drive.mount(ROOT)

PROJECT_PATH = join(ROOT, PROJ)
!mkdir "{PROJECT_PATH}"

GIT_PATH = f"https://{GIT_TOKEN}@github.com/{GIT_USERNAME}/{GIT_REPOSITORY}.git"

!mkdir ./temp
!git clone "{GIT_PATH}"
!mv ./temp/* "{PROJECT_PATH}"
!rm -rf ./temp
%cd "{PROJECT_PATH}"
if not os.path.isfile("ngrok"):
    !wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
    !unzip ngrok-stable-linux-amd64.zip


Requirement already up-to-date: plotly in /usr/local/lib/python3.6/dist-packages (4.6.0)
Requirement already satisfied, skipping upgrade: six in /usr/local/lib/python3.6/dist-packages (from plotly) (1.12.0)
Requirement already satisfied, skipping upgrade: retrying>=1.3.3 in /usr/local/lib/python3.6/dist-packages (from plotly) (1.3.3)
Requirement already satisfied: dash in /usr/local/lib/python3.6/dist-packages (1.11.0)
Requirement already satisfied: Flask>=1.0.2 in /usr/local/lib/python3.6/dist-packages (from dash) (1.1.2)
Requirement already satisfied: future in /usr/local/lib/python3.6/dist-packages (from dash) (0.16.0)
Requirement already satisfied: dash-renderer==1.4.0 in /usr/local/lib/python3.6/dist-packages (from dash) (1.4.0)
Requirement already satisfied: dash-html-components==1.0.3 in /usr/local/lib/python3.6/dist-packages (from dash) (1.0.3)
Requirement already satisfied: plotly in /usr/local/lib/python3.6/dist-packages (from dash) (4.6.0)
Requirement already satisfied: flask-compress in /usr/local/lib/python3.6/dist-packages (from dash) (1.4.0)
Requirement already satisfied: dash-table==4.6.2 in /usr/local/lib/python3.6/dist-packages (from dash) (4.6.2)
Requirement already satisfied: dash-core-components==1.9.1 in /usr/local/lib/python3.6/dist-packages (from dash) (1.9.1)
Requirement already satisfied: Jinja2>=2.10.1 in /usr/local/lib/python3.6/dist-packages (from Flask>=1.0.2->dash) (2.11.1)
Requirement already satisfied: itsdangerous>=0.24 in /usr/local/lib/python3.6/dist-packages (from Flask>=1.0.2->dash) (1.1.0)
Requirement already satisfied: Werkzeug>=0.15 in /usr/local/lib/python3.6/dist-packages (from Flask>=1.0.2->dash) (1.0.1)
Requirement already satisfied: click>=5.1 in /usr/local/lib/python3.6/dist-packages (from Flask>=1.0.2->dash) (7.1.1)
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from plotly->dash) (1.12.0)
Requirement already satisfied: retrying>=1.3.3 in /usr/local/lib/python3.6/dist-packages (from plotly->dash) (1.3.3)
Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.6/dist-packages (from Jinja2>=2.10.1->Flask>=1.0.2->dash) (1.1.1)
Requirement already satisfied: dash-html-components in /usr/local/lib/python3.6/dist-packages (1.0.3)
Requirement already satisfied: dash-core-components in /usr/local/lib/python3.6/dist-packages (1.9.1)
Requirement already satisfied: dash-table in /usr/local/lib/python3.6/dist-packages (4.6.2)
Requirement already satisfied: dash_bootstrap_components in /usr/local/lib/python3.6/dist-packages (0.9.2)
Requirement already satisfied: dash>=1.9.0 in /usr/local/lib/python3.6/dist-packages (from dash_bootstrap_components) (1.11.0)
Requirement already satisfied: dash-table==4.6.2 in /usr/local/lib/python3.6/dist-packages (from dash>=1.9.0->dash_bootstrap_components) (4.6.2)
Requirement already satisfied: dash-html-components==1.0.3 in /usr/local/lib/python3.6/dist-packages (from dash>=1.9.0->dash_bootstrap_components) (1.0.3)
Requirement already satisfied: future in /usr/local/lib/python3.6/dist-packages (from dash>=1.9.0->dash_bootstrap_components) (0.16.0)
Requirement already satisfied: dash-renderer==1.4.0 in /usr/local/lib/python3.6/dist-packages (from dash>=1.9.0->dash_bootstrap_components) (1.4.0)
Requirement already satisfied: Flask>=1.0.2 in /usr/local/lib/python3.6/dist-packages (from dash>=1.9.0->dash_bootstrap_components) (1.1.2)
Requirement already satisfied: flask-compress in /usr/local/lib/python3.6/dist-packages (from dash>=1.9.0->dash_bootstrap_components) (1.4.0)
Requirement already satisfied: plotly in /usr/local/lib/python3.6/dist-packages (from dash>=1.9.0->dash_bootstrap_components) (4.6.0)
Requirement already satisfied: dash-core-components==1.9.1 in /usr/local/lib/python3.6/dist-packages (from dash>=1.9.0->dash_bootstrap_components) (1.9.1)
Requirement already satisfied: click>=5.1 in /usr/local/lib/python3.6/dist-packages (from Flask>=1.0.2->dash>=1.9.0->dash_bootstrap_components) (7.1.1)
Requirement already satisfied: itsdangerous>=0.24 in /usr/local/lib/python3.6/dist-packages (from Flask>=1.0.2->dash>=1.9.0->dash_bootstrap_components) (1.1.0)
Requirement already satisfied: Jinja2>=2.10.1 in /usr/local/lib/python3.6/dist-packages (from Flask>=1.0.2->dash>=1.9.0->dash_bootstrap_components) (2.11.1)
Requirement already satisfied: Werkzeug>=0.15 in /usr/local/lib/python3.6/dist-packages (from Flask>=1.0.2->dash>=1.9.0->dash_bootstrap_components) (1.0.1)
Requirement already satisfied: six in /usr/local/lib/python3.6/dist-packages (from plotly->dash>=1.9.0->dash_bootstrap_components) (1.12.0)
Requirement already satisfied: retrying>=1.3.3 in /usr/local/lib/python3.6/dist-packages (from plotly->dash>=1.9.0->dash_bootstrap_components) (1.3.3)
Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.6/dist-packages (from Jinja2>=2.10.1->Flask>=1.0.2->dash>=1.9.0->dash_bootstrap_components) (1.1.1)
Requirement already satisfied: pycountry in /usr/local/lib/python3.6/dist-packages (19.8.18)
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
Cloning into 'coronavirus-data'...
remote: Enumerating objects: 17, done.
remote: Counting objects: 100% (17/17), done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 17 (delta 5), reused 13 (delta 3), pack-reused 0
Unpacking objects: 100% (17/17), done.
mv: cannot stat './temp/*': No such file or directory
/content/drive/My Drive/Coronavirus/coronavirus-data/data
--2020-04-13 21:24:18--  https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
Resolving bin.equinox.io (bin.equinox.io)... 35.172.19.128, 52.44.143.247, 52.86.206.127, ...
Connecting to bin.equinox.io (bin.equinox.io)|35.172.19.128|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 13773305 (13M) [application/octet-stream]
Saving to: ‘ngrok-stable-linux-amd64.zip’

ngrok-stable-linux- 100%[===================>]  13.13M  29.7MB/s    in 0.4s    

2020-04-13 21:24:19 (29.7 MB/s) - ‘ngrok-stable-linux-amd64.zip’ saved [13773305/13773305]

Archive:  ngrok-stable-linux-amd64.zip
  inflating: ngrok                   

In [0]:
!git config --global user.email "johan.d.s.vonk@gmail.com"
!git config --global user.name "Johan Vonk"

In [0]:
processed = os.path.isfile("processed.xlsx")
!wget -N https://raw.github.com/Perishleaf/data-visualisation-scripts/master/dash-2019-coronavirus/data.xls
xl_file = pd.ExcelFile('processed.xlsx' if processed else 'data.xls',)
dfs = {sheet_name: xl_file.parse(sheet_name) for sheet_name in xl_file.sheet_names}
if not processed:
    dfs = {xl_file.sheet_names[key]:dfs[xl_file.sheet_names[key]] for key in range(len(xl_file.sheet_names)) if xl_file.sheet_names[key-1][:10] != xl_file.sheet_names[key][:10]}
keyList = list(dfs.keys());


--2020-04-13 21:32:52--  https://raw.github.com/Perishleaf/data-visualisation-scripts/master/dash-2019-coronavirus/data.xls
Resolving raw.github.com (raw.github.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
Connecting to raw.github.com (raw.github.com)|151.101.0.133|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://raw.githubusercontent.com/Perishleaf/data-visualisation-scripts/master/dash-2019-coronavirus/data.xls [following]
--2020-04-13 21:32:52--  https://raw.githubusercontent.com/Perishleaf/data-visualisation-scripts/master/dash-2019-coronavirus/data.xls
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 14458880 (14M) [application/octet-stream]
Saving to: ‘data.xls’

data.xls            100%[===================>]  13.79M  68.3MB/s    in 0.2s    

Last-modified header missing -- time-stamps turned off.
2020-04-13 21:32:52 (68.3 MB/s) - ‘data.xls’ saved [14458880/14458880]


In [0]:
if not processed:
    GeoDB = pd.read_csv('coordinatesDB.csv')
    with pd.ExcelWriter('processed.xlsx') as writer, open('name_to_alpha_2.json') as first, open('iso3.json') as second:
        name_to_alpha_2=json.loads(first.read())
        alpha_2_to_3=json.loads(second.read())
        for key, df in dfs.items():
            dfs[key].loc[:,'Confirmed'].fillna(value=0, inplace=True)
            dfs[key].loc[:,'Deaths'].fillna(value=0, inplace=True)
            dfs[key].loc[:,'Recovered'].fillna(value=0, inplace=True)
            dfs[key]=dfs[key].astype({'Confirmed':'int64', 'Deaths':'int64', 'Recovered':'int64'})
            # Change as China for coordinate search
            dfs[key]=dfs[key].replace({'Country/Region':'Mainland China'}, 'China')
            dfs[key]=dfs[key].replace({'Province/State':'Queensland'}, 'Brisbane')
            dfs[key]=dfs[key].replace({'Province/State':'New South Wales'}, 'Sydney')
            dfs[key]=dfs[key].replace({'Province/State':'Victoria'}, 'Melbourne')
            dfs[key] = pd.merge(dfs[key], GeoDB, how='left', on=['Province/State', 'Country/Region'] )
            dfs[key]['Days_since_start'] = [(datetime.strptime(d, '%m/%d/%Y %H:%M')-(datetime.strptime('2020-01-21 00:00:00','%Y-%m-%d %H:%M:%S'))).days for d in ('0' + dfs[key]['Last Update'])]
            dfs[key]['iso_alpha']=[alpha_2_to_3[name_to_alpha_2[country]] if country in name_to_alpha_2 else None for country in dfs[key]['Country/Region']]
            dfs[key].to_excel(writer,sheet_name=key)

In [0]:
print(os.getcwd())
!wget -N "https://population.un.org/wpp/Download/Files/1_Indicators%20(Standard)/EXCEL_FILES/1_Population/WPP2019_POP_F01_1_TOTAL_POPULATION_BOTH_SEXES.xlsx"
population_xl = pd.ExcelFile('WPP2019_POP_F01_1_TOTAL_POPULATION_BOTH_SEXES.xlsx')
num_to_alpha={"4":"af","8":"al","10":"aq","12":"dz","16":"as","20":"ad","24":"ao","28":"ag","31":"az","32":"ar","36":"au","40":"at","44":"bs","48":"bh","50":"bd","51":"am","52":"bb","56":"be","60":"bm","64":"bt","68":"bo","70":"ba","72":"bw","74":"bv","76":"br","84":"bz","86":"io","90":"sb","92":"vg","96":"bn","100":"bg","104":"mm","108":"bi","112":"by","116":"kh","120":"cm","124":"ca","132":"cv","136":"ky","140":"cf","144":"lk","148":"td","152":"cl","156":"cn","158":"tw","162":"cx","166":"cc","170":"co","174":"km","175":"yt","178":"cg","180":"cd","184":"ck","188":"cr","191":"hr","192":"cu","196":"cy","203":"cz","204":"bj","208":"dk","212":"dm","214":"do","218":"ec","222":"sv","226":"gq","231":"et","232":"er","233":"ee","234":"fo","238":"fk","239":"gs","242":"fj","246":"fi","248":"ax","249":"fx","250":"fr","254":"gf","258":"pf","260":"tf","262":"dj","266":"ga","268":"ge","270":"gm","275":"ps","276":"de","288":"gh","292":"gi","296":"ki","300":"gr","304":"gl","308":"gd","312":"gp","316":"gu","320":"gt","324":"gn","328":"gy","332":"ht","334":"hm","336":"va","340":"hn","344":"hk","348":"hu","352":"is","356":"in","360":"id","364":"ir","368":"iq","372":"ie","376":"il","380":"it","384":"ci","388":"jm","392":"jp","398":"kz","400":"jo","404":"ke","408":"kp","410":"kr","414":"kw","417":"kg","418":"la","422":"lb","426":"ls","428":"lv","430":"lr","434":"ly","438":"li","440":"lt","442":"lu","446":"mo","450":"mg","454":"mw","458":"my","462":"mv","466":"ml","470":"mt","474":"mq","478":"mr","480":"mu","484":"mx","492":"mc","496":"mn","498":"md","499":"me","500":"ms","504":"ma","508":"mz","512":"om","516":"na","520":"nr","524":"np","528":"nl","530":"an","533":"aw","540":"nc","548":"vu","554":"nz","558":"ni","562":"ne","566":"ng","570":"nu","574":"nf","578":"no","580":"mp","581":"um","583":"fm","584":"mh","585":"pw","586":"pk","591":"pa","598":"pg","600":"py","604":"pe","608":"ph","612":"pn","616":"pl","620":"pt","624":"gw","626":"tl","630":"pr","634":"qa","638":"re","642":"ro","643":"ru","646":"rw","654":"sh","659":"kn","660":"ai","662":"lc","666":"pm","670":"vc","674":"sm","678":"st","682":"sa","686":"sn","688":"rs","690":"sc","694":"sl","702":"sg","703":"sk","704":"vn","705":"si","706":"so","710":"za","716":"zw","724":"es","728":"ss","729":"sd","732":"eh","736":"sd","740":"sr","744":"sj","748":"sz","752":"se","756":"ch","760":"sy","762":"tj","764":"th","768":"tg","772":"tk","776":"to","780":"tt","784":"ae","788":"tn","792":"tr","795":"tm","796":"tc","798":"tv","800":"ug","804":"ua","807":"mk","818":"eg","826":"gb","834":"tz","840":"us","850":"vi","854":"bf","858":"uy","860":"uz","862":"ve","876":"wf","882":"ws","887":"ye","891":"cs","894":"zm"}
population = population_xl.parse(population_xl.sheet_names[0])
population.columns=population.iloc[15]
population=population.iloc[16:][population['Type'] == 'Country/Area'][['Country code','2020']]
population['iso_alpha']=population['Country code'].map(str).map(num_to_alpha).str.upper().map(alpha_2_to_3)
population=population[['iso_alpha','2020']].rename({'2020': 'Population'}, axis=1)


/content/drive/My Drive/Coronavirus/coronavirus-data/data
--2020-04-13 21:34:51--  https://population.un.org/wpp/Download/Files/1_Indicators%20(Standard)/EXCEL_FILES/1_Population/WPP2019_POP_F01_1_TOTAL_POPULATION_BOTH_SEXES.xlsx
Resolving population.un.org (population.un.org)... 157.150.185.69
Connecting to population.un.org (population.un.org)|157.150.185.69|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2521187 (2.4M) [application/vnd.openxmlformats-officedocument.spreadsheetml.sheet]
Saving to: ‘WPP2019_POP_F01_1_TOTAL_POPULATION_BOTH_SEXES.xlsx’

WPP2019_POP_F01_1_T 100%[===================>]   2.40M  2.96MB/s    in 0.8s    

2020-04-13 21:34:52 (2.96 MB/s) - ‘WPP2019_POP_F01_1_TOTAL_POPULATION_BOTH_SEXES.xlsx’ saved [2521187/2521187]

/usr/local/lib/python3.6/dist-packages/ipykernel_launcher.py:7: UserWarning:

Boolean Series key will be reindexed to match DataFrame index.


In [0]:
frames=[]
for key in reversed(keyList):
    frames.append(pd.DataFrame().from_dict(dfs[key]))
df=pd.merge(pd.concat(frames,sort=False)
              .drop(['Last Update','lat','lon','City'], 1)
              .groupby(['Days_since_start','Country/Region'])
              .aggregate({**dict.fromkeys(['Province/State','Country/Region','Days_since_start','iso_alpha'],'first'),
                          **dict.fromkeys(['Confirmed', 'Deaths', 'Recovered', 'Suspected'],'sum')})
              .reset_index(drop=True),
            population, on='iso_alpha', how='left')
df['Rate']=(df['Confirmed']/df['Population']*1000000).fillna(0).apply(lambda x: x if x < 0.01 else np.log(x)*10).astype(int)
df['Death Rate']=(df['Deaths']/df['Population']*1000000).fillna(0).apply(lambda x: x if x < 0.01 else np.log(x)*10).astype(int)

In [0]:
scatter_confirmed = px.scatter_geo(df, title="Cases as a Scatter", locations="iso_alpha", color_discrete_sequence=["#cb181d"], size="Confirmed", animation_frame="Days_since_start", hover_name="Country/Region")
scatter_death = px.scatter_geo(df, title="Deaths as a Scatter", locations="iso_alpha", color_discrete_sequence=["#cb181d"], size="Deaths", animation_frame="Days_since_start", hover_name="Country/Region")

In [0]:
choropleth_confirmed = px.choropleth(df, title="Cases as a Choropleth", locations="iso_alpha", color="Rate", animation_frame="Days_since_start", hover_name="Country/Region")
choropleth_death = px.choropleth(df, title="Deaths as a Choropleth", locations="iso_alpha", color="Death Rate", animation_frame="Days_since_start", hover_name="Country/Region")

In [0]:
traces = (scatter_confirmed, scatter_death, choropleth_confirmed, choropleth_death)
buttons = {"Cases and Deaths":[True]*4,"Cases":[True, False]*2,"Deaths":[False,True]*2,**{traces[j].layout.title.text:[i is j for i in range(4)] for j in range(len(traces))}}
fig = go.Figure({
    "data": [t.data[0] for t in traces], 
    "frames": [{
        "name": f,
        "data": [trace.frames[f].data[0] for trace in traces],
        "traces": [0,1,2,3]}
        for f in range(len(keyList))], 
    "layout": {
        "title_text": "COVID 19 - Cases and Deaths",
        "updatemenus": scatter_confirmed.layout.updatemenus + ({
            "buttons": [{
                "args": [{"visible": visible},
                         {"title": "COVID 19 - Time vs. "+label+" Map"}],
                "label": label.replace(" as a",""),
                "method": "update"
            } for label, visible in buttons.items()],
            "active":0,
            "direction":"up",
            'x': 0.1,
            'y': 0,
            'pad': {'r': 83, 't': 70},
        },),
        "sliders": scatter_confirmed.layout.sliders,
        "showlegend": False,
        "coloraxis": {
            "showscale": False,
            "colorscale": "Reds",
            "cmax": df_countries['Rate'].max(),
            "cmin": 0
        }
    }
})
fig.show()



In [0]:
%cd ../docs
fig.write_html("index.html",include_plotlyjs='cdn',include_mathjax='cdn',default_width="100%",default_height="100%")
%cd ..
!git add --all
!git commit -am "Updated docs/index.html to reflect changes"
!git push
%cd data


/content/drive/My Drive/Coronavirus/coronavirus-data/docs
/content/drive/My Drive/Coronavirus/coronavirus-data
[master 31805a5] Updated docs/index.html to reflect changes
 1 file changed, 5 insertions(+), 5 deletions(-)
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 494 bytes | 247.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/jvonk/coronavirus-data.git
   8ab4604..31805a5  master -> master
/content/drive/My Drive/Coronavirus/coronavirus-data/data

In [0]:
app = dash.Dash(__name__, assets_folder='./assets/',
    meta_tags=[
        {"name": "viewport", "content": "width=device-width, height=device-height, initial-scale=1.0"}
1    ]
)

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])

def shutdown():
    func = request.environ.get('werkzeug.server.shutdown')
    if func is None:
        raise RuntimeError('Not running with the Werkzeug Server')
    func()

@app.callback( dash.dependencies.Output('page-content', 'children'),
              [dash.dependencies.Input('url', 'pathname')])

def display_page(pathname):
    if pathname =='/shutdown':
        shutdown()
    return [dcc.Graph(figure=choropleth), dcc.Graph(figure=choropleth)]

In [0]:
get_ipython().system_raw('./ngrok http 8050 &')
! curl -s http://localhost:4040/api/tunnels | python3 -c \
    "import sys, json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])"


--2020-04-10 20:00:25--  https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
Resolving bin.equinox.io (bin.equinox.io)... 34.226.171.201, 52.73.120.139, 52.20.185.228, ...
Connecting to bin.equinox.io (bin.equinox.io)|34.226.171.201|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 13773305 (13M) [application/octet-stream]
Saving to: ‘ngrok-stable-linux-amd64.zip.1’

ngrok-stable-linux- 100%[===================>]  13.13M  36.5MB/s    in 0.4s    

2020-04-10 20:00:26 (36.5 MB/s) - ‘ngrok-stable-linux-amd64.zip.1’ saved [13773305/13773305]

Archive:  ngrok-stable-linux-amd64.zip
replace ngrok? [y]es, [n]o, [A]ll, [N]one, [r]ename: y
  inflating: ngrok                   
http://2dae3848.ngrok.io

In [0]:
f=open('output.html', 'w')
f.write(html)
f.close()

In [0]:
if __name__ == '__main__':
    app.run_server()


 * Serving Flask app "__main__" (lazy loading)
 * Environment: production
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-28-a9f1491d6625> in <module>()
      1 if __name__ == '__main__':
----> 2     app.run_server()

/usr/local/lib/python3.6/dist-packages/dash/dash.py in run_server(self, host, port, debug, dev_tools_ui, dev_tools_props_check, dev_tools_serve_dev_bundles, dev_tools_hot_reload, dev_tools_hot_reload_interval, dev_tools_hot_reload_watch_interval, dev_tools_hot_reload_max_retry, dev_tools_silence_routes_logging, dev_tools_prune_errors, **flask_run_options)
   1507             self.logger.info("Debugger PIN: %s", debugger_pin)
   1508 
-> 1509         self.server.run(host=host, port=port, debug=debug, **flask_run_options)

/usr/local/lib/python3.6/dist-packages/flask/app.py in run(self, host, port, debug, load_dotenv, **options)
    983         options.setdefault("threaded", True)
    984 
--> 985         cli.show_server_banner(self.env, self.debug, self.name, False)
    986 
    987         from werkzeug.serving import run_simple

/usr/local/lib/python3.6/dist-packages/flask/cli.py in show_server_banner(env, debug, app_import_path, eager_loading)
    670         click.echo(message)
    671 
--> 672     click.echo(" * Environment: {0}".format(env))
    673 
    674     if env == "production":

/usr/local/lib/python3.6/dist-packages/click/utils.py in echo(message, file, nl, err, color)
    270 
    271     if message:
--> 272         file.write(message)
    273     file.flush()
    274 

/usr/local/lib/python3.6/dist-packages/ipykernel/iostream.py in flush(self)
    347                 self.pub_thread.schedule(evt.set)
    348                 # and give a timeout to avoid
--> 349                 if not evt.wait(self.flush_timeout):
    350                     # write directly to __stderr__ instead of warning because
    351                     # if this is happening sys.stderr may be the problem.

/usr/lib/python3.6/threading.py in wait(self, timeout)
    549             signaled = self._flag
    550             if not signaled:
--> 551                 signaled = self._cond.wait(timeout)
    552             return signaled
    553 

/usr/lib/python3.6/threading.py in wait(self, timeout)
    297             else:
    298                 if timeout > 0:
--> 299                     gotit = waiter.acquire(True, timeout)
    300                 else:
    301                     gotit = waiter.acquire(False)

KeyboardInterrupt: