Flask-Dash-Heroku 연동

Page content

개요

  • Flask 및 Dash를 활용하여 간단한 대시보드를 생성할 수 있다.
  • 기존 구현한 대시보드를 Heroku에 배포할 수 있다.

사전준비

  • 파이썬 가상환경 설치 및 기존 라이브러리에 대한 이해가 어느정도 있음을 가정한 상태에서 본 블로그를 작성했음을 유의한다.
  • Heroku 회원가입 및 로그인이 되어 있어야 한다.

Step 1. Github Repo생성

  • Github Repo 생성 시, 중복되지 않을 법한 이름으로 생성
    • 필자 Repo : flask-heroku-dash-evan1234
  • 해당 Repo를 로컬로 가져온다.
git clone https://github.com/your_name/your_unique_repo.git

Step 2. 가상환경 설치 및 주요 라이브러리 설치

  • 먼저 가상환경을 설치한다.
virtualenv venv
  • 가상환경에 접속한다.
source venv/Scripts/activate
  • 주요 라이브러리를 설치한다.
    • pandas : 데이터 불러오기 및 가공
    • dash & plotly : 동적 시각화 대시보드 제공 라이브러리
    • Flask : Flask 웹 프레임워크
    • SQLAlchemy : 데이터베이스 연동 프레임워크
pip install dash plotly Flask pandas gunicorn psycopg2-binary SQLAlchemy Flask-SQLAlchemy

Step 3. 기본 배포 테스트

  • 먼저 본격적인 코드에 앞서 기본적으로 배포가 되는지 확인한다.
  • 필요한 파일 app.py, Procfile, runtime.txt, requirements.txt 파일이 필요하다.

(1) app.py

  • 아래와 같이 작성한다.
# -*- coding:utf-8 -*-

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return "Hello World"

(2) Procfile 파일 작성

  • 대문자 및 소문자 정확하게 기재해야 한다.
web: gunicorn app:app

(3) runtime.txt 파일 작성

python-3.9.12

(4) requirements.txt 파일 작성

  • 해당 파일은 기존에 설치했던 라이브러리를 모두 호출하는 형식이어야 한다.
  • 프로젝트 Root 경로에서 아래와 같이 실행한다.
pip freeze > requirements.txt
  • 전체 파일 구조를 확인하면 아래와 같다.
$ ls
app.py  Procfile  README.md  requirements.txt  runtime.txt  venv/

(5) wsgi.py 파일 작성

from app import app

if __name__ == "__main__":
    app.run(threaded=True, port=5000)

(5) 배포 시작

  • 아래 명령어를 순차적으로 입력하여 실행한다.
    • heroku login 시, Web UI에서 실제 ID와 Password를 입력해야 한다.
    • heroku create github repo와 동일하게 작성한다.
heroku login
heroku create your_project_repo
git add .
git commit -m "initial updated"
git push # github repo에 추가
git push heroku main

(6) 배포 사이트 확인

  • 실제로 정상적으로 배포가 완료가 되었다면 실제 웹사이트 URL을 클릭 후, 아래와 같이 확인할 수 있어야 한다.
  • 배포가 진행이 안된다면, 그 다음 코드를 입력하는 것은 의미가 없기 때문에 확인 후 넘어가도록 한다.

Untitled

Step 4. Flask & Dash

  • Flask와 Dash를 연동하는 메인 코드는 다음처럼 작성해야 한다.
  • 기본적으로 1개의 Flask 서버에 다수의 Dash App을 연결하는 방식을 사용한다.
    • 주의) 이 방식이 좋은 방식은 아니다. 추천 방식은 Flask Application Factory를 추천한다.
  • Flask 서버인 app을 생성하고 Dash에서 처리해야 하는 URL은 url_base_pathname 으로 전달한다.
from flask import Flask
from dash import Dash

app = Flask(__name__)
dash_app1 = Dash(__name__, server = app, url_base_pathname='/dashapp1/')
.
.
.

@app.route("/")
def index():
    return "Hello World!"
from flask import Flask
from dash import Dash, dcc, Input, Output
from dash import html
import plotly.express as px

app = Flask(__name__)
dash_app1 = Dash(__name__, server = app, url_base_pathname='/dashapp1/')

# -- dash_app1
dash_app1.layout = html.Div([
    html.H1('Hello Dash'), 
])

@app.route("/")
def index():
    return "Hello World!"
  • 배포를 하면 각 본인의 URL에 /dashapp1/ 을 추가한다.

Untitled

Step 5. 소스코드 업데이트

from flask import Flask
from dash import Dash, dcc, Input, Output
from dash import html
import plotly.express as px

app = Flask(__name__)
dash_app1 = Dash(__name__, server = app, url_base_pathname='/dashapp1/')

# -- dash_app1
dash_app1.layout = html.Div([
    html.H1('Hello Dash'), 
    dcc.Slider(0, 9, marks={i: f'Label{i}' for i in range(10)}, value=5), 
    dcc.Graph(
        figure=dict(
            data=[
                dict(
                    x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                    2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                    y=[219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
                    350, 430, 474, 526, 488, 537, 500, 439],
                    name='Rest of world',
                    marker=dict(
                        color='rgb(55, 83, 109)'
                    )
                ),
                dict(
                    x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                    2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                    y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156, 270,
                    299, 340, 403, 549, 499],
                    name='China',
                    marker=dict(
                        color='rgb(26, 118, 255)'
                    )
                )
            ],
            layout=dict(
                title='US Export of Plastic Scrap',
                showlegend=True,
                legend=dict(
                    x=0,
                    y=1.0
                ),
                margin=dict(l=40, r=0, t=40, b=30)
            )
        ),
        style={'height': 300},
        id='my-graph-example'
    )
])

@app.route("/")
def index():
    return "Hello World!"
  • 다시 배포를 해본다.

Untitled

Step 6. 다중 페이지 생성

  • 이제 다중 페이지를 작성해본다.
  • 기존과 동일한 작업을 한다.
from flask import Flask
from dash import Dash, dcc, Input, Output
from dash import html
import plotly.express as px

app = Flask(__name__)
dash_app1 = Dash(__name__, server = app, url_base_pathname='/dashapp1/')
dash_app2 = Dash(__name__, server = app, url_base_pathname='/dashapp2/')

# -- dash_app1
dash_app1.layout = html.Div([
    html.H1('Hello Dash'), 
    dcc.Slider(0, 9, marks={i: f'Label{i}' for i in range(10)}, value=5), 
    dcc.Graph(
        figure=dict(
            data=[
                dict(
                    x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                    2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                    y=[219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
                    350, 430, 474, 526, 488, 537, 500, 439],
                    name='Rest of world',
                    marker=dict(
                        color='rgb(55, 83, 109)'
                    )
                ),
                dict(
                    x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                    2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                    y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156, 270,
                    299, 340, 403, 549, 499],
                    name='China',
                    marker=dict(
                        color='rgb(26, 118, 255)'
                    )
                )
            ],
            layout=dict(
                title='US Export of Plastic Scrap',
                showlegend=True,
                legend=dict(
                    x=0,
                    y=1.0
                ),
                margin=dict(l=40, r=0, t=40, b=30)
            )
        ),
        style={'height': 300},
        id='my-graph-example'
    )
])

# dash app 5 width pandas
df = px.data.stocks()

dash_app2.layout = html.Div([
    html.Br(),
    html.H2('Time series graph with Dash'),
    html.Br(),
    dcc.Dropdown(
        id="ticker",
        options=[{"label": x, "value": x} for x in df.columns[1:]],
        value=df.columns[1],
        clearable=False,
    ),
    dcc.Graph(id="time-series-chart"),
])

@dash_app2.callback(Output("time-series-chart", "figure"), [Input("ticker", "value")])
def display_time_series(ticker):
    fig = px.line(df, x='date', y=ticker)
    return fig

@app.route("/")
def index():
    return "Hello World!"
  • 배포 후 확인해본다.

Untitled