Python Dash를 활용한 대시보드 만들기 with Heroku

Page content

강의 소개

  • 필자의 강의를 소개합니다.

개요

  • 대시보드 프로젝트를 진행한다.
  • Heroku에 배포까지 진행하는 것을 목적으로 한다.
  • 참조: https://realpython.com/python-dash/
    • 여기에 있는 내용을 최대한 간결하게 한글로 재 작성하였다. 중간에 없는 코드들도 있으니, 가급적 본 소스코드를 활용한다.

1. 데이터 수집

C:\Users\1\Desktop\dashboard-project21>tree /f
폴더 PATH 목록입니다.
볼륨 일련 번호는 E657-CFA3입니다.
C:.
  README.md

└─data
        avocado.csv
  • 파일 경로를 주의해서 보도록 합니다.

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

  • conda를 활용하여 가상환경 설정을 합니다.
  • (dashboard-project21) 형태로 터미널 명령어가 바뀌어 있어야 합니다.
$ conda create --name dashboard-project21 python=3.8
.
.
$ conda activate dashboard-project21
(dashboard-project21) C:\Users\1\Desktop\dashboard-project21>
  • 이번에는 dash 라이브러리를 설치한다.
$ conda install dash
$ conda install pandas
$ conda install colorama

3. 대시 보드 코드 작성

(1) 데이터 수집 및 Dash 클래스 정의

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd

# step 1. Data Import
data = pd.read_csv("avocado.csv", index_col=0)
data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)

# step 2. Dash Class
app = dash.Dash(__name__)
  • dash 라이브러리는 대시보드 어플리케이션 초기화를 담당한다.
  • dash_core_components 동적 구성요소들(예: 그래프, 드롭다운 메뉴, 날짜 기간 등) 작성할 수 있도록 도와주는 기능을 제공한다.
  • dash_html_components 은 html 태그에 접근하도록 한다.
  • pandas 데이터 수집 및 가공을 제공할 수 있는 함수들을 지원한다.
  • 코드 설명
    • step 1
      • 데이터를 avocado.csv 데이터를 수집한 후, type = conventional 과, region = Albany 만 추출하는 행을 추출한다.
      • 그 이후 날짜의 오름차순으로 정렬하는 코드를 작성한다.
    • step 2
      • Dash 클래스를 정의하여 app이라는 객체를 별도 생성한 것을 의미한다.

(2) 대시보드 HTML Layout 정의

  • 이전 코드에 이어서 작성을 하도록 한다.
# step 3. HTML
app.layout = html.Div(
	  # Header Message
    children=[
        html.H1(children="Temp Analytics",),
        html.P(
            children="Temp",
        ),
        # 그래프		
        dcc.Graph(
            figure={
                "data": [
                    {
                        "x": data["Date"],
                        "y": data["AveragePrice"],
                        "type": "lines",
                    },
                ],
                "layout": {"title": "Title_1"},
            },
        ),
        dcc.Graph(
            figure={
                "data": [
                    {
                        "x": data["Date"],
                        "y": data["Total Volume"],
                        "type": "lines",
                    },
                ],
                "layout": {"title": "Title_2"},
            },
        ),
    ]
)
  • Dash는 크게 2가지로 구성이 된다.

    • Dash HTML Components : HTML components용 Wrappers를 제공합니다. 이 라이브러리를 사용하여 문단, 제목 또는 목록과 같은 요소를 작성할 수 있다.
    • Dash Core Components : 대화형 사용자 인터페이스를 만들기 위한 Python 추상화를 제공합니다. 그래프, 슬라이더 또는 드롭다운과 같은 interactive elements를 만드는 데 사용할 수 있다.
  • 코드를 분석하면 다음과 같다.

    • # Header Message : html.div 는 일종의 parent component라고 볼 수 있다. 그리고, html.h1은 h1 태그를 말하며, html.p은 p 태그를 의미한다.
      • HTML 코드로 변환하면 아래와 같다.
    <div>
      <h1>Temp Analytics</h1>
      <p>
        Temp
      </p>
      <!-- Rest of the app -->
    </div>
    

(3) 대시보드 배포 (localhost)

  • 이전 코드에 이어서 작성을 하도록 한다.
if __name__ == "__main__":
    app.run_server(debug=True)
  • 위 코드를 실행하면, Flask 기반의 서버로 작동을 한다. 파라미터 debug=True 를 하게되면, 수정입력을 해도, 서버를 restarting 하지 않고, 새로고침으로 변화를 확인할 수 있다.

  • 위 모든 소스코드를 [app.py](http://app.py) 에 저장 후 실행한다.

  • 파일의 경로는 아래와 같다.

    C:\Users\1\Desktop\dashboard-project21>tree /f
    C:.
      app.py
      README.md
    
    ├─.idea
        .gitignore
        dashboard-project21.iml
        misc.xml
        modules.xml
        vcs.xml
        workspace.xml
      
      └─inspectionProfiles
              profiles_settings.xml
    
    └─data
            avocado.csv
    
    • 이제 app.py을 실행한다. 아래와 같이 나온다면 정상적으로 실행된 것이다.
    (dashboard-project21) C:\Users\1\Desktop\dashboard-project21> python app.py
    Dash is running on http://127.0.0.1:8050/
    
     * Serving Flask app "app" (lazy loading)
     * Environment: production
       WARNING: This is a development server. Do not use it in a production deployment.
       Use a production WSGI server instead.
     * Debug mode: on
    
    • 실행 취소는 ctrl + c 을 하면 된다.
    • 인터넷에서 [http://127.0.0.1:8050/](http://127.0.0.1:8050/) 을 확인하도록 한다.

dash_01.png

  • 대시보드가 구현된 것을 확인할 수 있다.

4. 대시보드에 Style 입히기

  • 기존에 작성된 것으로도 충분히 시각화는 구현할 수 있다.
  • 그러나, 대시보드 개발은 CSS 를 활용할 때, 보다 예쁘게 꾸밀 수 있다.

(1) 태그에 직접 style 입히기

  • H1 태그에 폰트 사이즈와 색상에 변화를 주도록 한다.
    • style={"fontSize": "48px", "color": "blue"} 를 추가한다.
html.H1(
    children="Temp Analytics",
    style={"fontSize": "48px", "color": "blue"},
),
  • Temp Analytics 가 바뀐 것을 확인할 수 있다.

dash_02.png

  • 그러나, 코드 관리를 위해서는 css 파일로 관리하는 것이 적절하다.

  • 따라서, 기존에 추가한 style = ~ 이하 코드는 삭제하고, 아래와 같이 코드를 재 작성한다.

    html.H1(
        children="Temp Analytics",
        className="header_title",
    ),
    
  • CSS 파일을 하나 생성한 후, assets 폴더에 style.css 파일을 추가적으로 생성한다.

    • 폴더의 구조는 아래와 같다.
    (dashboard-project21) C:\Users\1\Desktop\dashboard-project21>tree /f
    폴더 PATH 목록입니다.
    볼륨 일련 번호는 E657-CFA3입니다.
    C:.
      app.py
      README.md
    
    ├─.idea
        .gitignore
        dashboard-project21.iml
        misc.xml
        modules.xml
        vcs.xml
        workspace.xml
      
      └─inspectionProfiles
              profiles_settings.xml
    
    ├─assets
          style.css
    
    ├─data
          avocado.csv
    
    • className에 해당하는 css 코드를 style.css 에 입히면 완성이다.
    • 이 때에는 색상의 변화를 주기 위해, blue 대신에 red 색상을 추가했다.
    .header_title {
      font-size: 48px;
      color: red;
    }
    

/img/python/dash/dash_project/ dash_03.png

(2) 로고 추가하기

(3) External Style Sheet

  • 외부에서 css 파일 등을 가져올 수 있다.
  • 마지막 app.title은 구글 검색 또는 사이트 공유 시 나타나는 타이틀이다.
# step 2. Dash Class
external_stylesheets = [
    {
        "href": "https://fonts.googleapis.com/css2?"
                "family=Lato:wght@400;700&display=swap",
        "rel": "stylesheet",
    },
]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Temp Analytics: Understand Your Data!"

(4) Header Layout 커스텀화

  • 먼저 header 화면과 그래프 구성하는 화면을 div 태그로 구분하는 코드를 작성한다.
  • 이 때 중요한 건 className을 각 태그마다 입력하는 것이다.
.
.
# step 3. HTML
app.layout = html.Div(
    children=[
        html.Div(
            children=[
                html.P(children="📈", className="header_emoji"),
                html.H1(children="Temp Analytics", className="header_title",),
                html.P(children="Temp", className="header_description",),
            ],
            className='header',
        ),
		dcc.Graph(
        figure={
            "data": [
								.
								.
  • 이번에는 css파일을 아래와 같이 수정한다.
.header_emoji {
    font-size: 48px;
    margin: 0 auto;
    text-align: center;
}

.header_title {
    color: #FFFFFF;
    font-size: 48px;
    font-weight: bold;
    text-align: center;
    margin: 0 auto;
}

.header_description {
    color: #CFCFCF;
    margin: 4px auto;
    text-align: center;
    max-width: 384px;
}

.header {
    background-color: #222222;
    height: 256px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}
  • 이제 결과물을 확인한다.

dash_04.png

(5) 그래프 Layout 커스텀화

  • 동일한 방식으로 그래프를 커스텀화 하는 코드를 작성한다.
.
.
.
# step 3. HTML
app.layout = html.Div(
    children=[
        html.Div(
            children=[
                html.P(children="📈", className="header_emoji"),
                html.H1(children="Temp Analytics", className="header_title",),
                html.P(children="Temp", className="header_description",),
            ],
            className='header',
        ),
        html.Div(
            children=[
                html.Div(
                    children=dcc.Graph(
                        id="price-chart",
                        config={"displayModeBar": False},
                        figure={
                            "data": [
                                {
                                    "x": data["Date"],
                                    "y": data["AveragePrice"],
                                    "type": "lines",
                                    "hovertemplate": "$%{y:.2f}"
                                    "<extra></extra>",
                                },
                            ],
                            "layout": {
                                "title": {
                                    "text": "아보카도 평균가격($)",
                                    "x": 0.05,
                                    "xanchor": "center",
                                },
                                "xaxis": {"fixedrange": True},
                                "yaxis": {
                                    "tickprefix": "$",
                                    "fixedrange": True,
                                },
                                "colorway": ["#17B897"],
                            },
                        },
                    ),
                    className="card",
                ),
                html.Div(
                    children=dcc.Graph(
                        id="volume-chart",
                        config={"displayModeBar": False},
                        figure={
                            "data": [
                                {
                                    "x": data["Date"],
                                    "y": data["Total Volume"],
                                    "type": "lines",
                                },
                            ],
                            "layout": {
                                "title": {
                                    "text": "아보카도 판매량",
                                    "x": 0.05,
                                    "xanchor": "center",
                                },
                                "xaxis": {"fixedrange": True},
                                "yaxis": {"fixedrange": True},
                                "colorway": ["#E12D39"],
                            },
                        },
                    ),
                    className="card",
                ),
            ],
            className="wrapper",
        ),
    ]
)
  • 먼저, "hovertemplate": "$%{y:.2f}" "<extra></extra>", 는 마우스를 그래프에 갔다 대면, $표시가 나타나는 옵션이다.
  • 전체적인 그래프에 대한 코드는 card 클래스로 정의했다. 그리고, div 영역은 wrapper 로 구성했다.
  • 이번에는 cardwrapper 를 정의하는 css 코드를 추가한다.
.
.
.wrapper {
    margin-right: auto;
    margin-left: auto;
    max-width: 1024px;
    padding-right: 10px;
    padding-left: 10px;
    margin-top: 32px;
}

.card {
    margin-bottom: 24px;
    box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}
  • 파이썬과 CSS에서 추가된 코드를 통해 다음과 같은 결과물을 확인할 수 있다.

dash_05.png

5. 대시보드에 Interactive 구현하기

  • 날짜를 지정하여 그래프를 작성할 수는 없을까?
  • 지역을 선택할 때마다, 라인 그래프가 변동시킬 수는 없을까?
  • 위와 같은 질문에 대답하기 위해서는 Interactive Component를 구성해야 한다.

(1) 메뉴 구성하기

  • 데이터부터 확인해본다.
import pandas as pd

# step 1. Data Import
data = pd.read_csv("data/avocado.csv", index_col=0)
data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)
print(data.info())
<class 'pandas.core.frame.DataFrame'>
Int64Index: 169 entries, 51 to 0
Data columns (total 13 columns):
 #   Column        Non-Null Count  Dtype
---  ------        --------------  -----
 0   Date          169 non-null    datetime64[ns]
 1   AveragePrice  169 non-null    float64
 2   Total Volume  169 non-null    float64
 3   4046          169 non-null    float64
 4   4225          169 non-null    float64
 5   4770          169 non-null    float64
 6   Total Bags    169 non-null    float64
 7   Small Bags    169 non-null    float64
 8   Large Bags    169 non-null    float64
 9   XLarge Bags   169 non-null    float64
 10  type          169 non-null    object
 11  year          169 non-null    int64
 12  region        169 non-null    object
dtypes: datetime64[ns](1), float64(9), int64(1), object(2)
memory usage: 18.5+ KB
None
  • 여기에서 3가지 컬럼을 메뉴로 활용한다.
    • Region
    • Type of avocado
    • Date range
  • 기존 코드에서 중간 코드만 주석 처리하면, 각각의 type, region 등을 확인할 수 있다.
data = pd.read_csv("data/avocado.csv", index_col=0)
# data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)

# print(data.info())
print(data[['region', 'type', 'Date']].head()) 

                region          type       Date
51           Southeast       organic 2015-01-04
51             Chicago       organic 2015-01-04
51  HarrisburgScranton       organic 2015-01-04
51          Pittsburgh  conventional 2015-01-04
51               Boise       organic 2015-01-04
.
.
import numpy as np
.
.
# step 3. HTML
app.layout = html.Div(
    children=[
        html.Div(
            children=[
                html.P(children="📈", className="header_emoji"),
                html.H1(children="Temp Analytics", className="header_title",),
                html.P(children="Temp", className="header_description",),
            ],
            className='header',
        ),
        html.Div(
            children=[
                html.Div(
                    children=[
                        html.Div(children="Region", className="menu-title"),
                        dcc.Dropdown(
                            id="region-filter",
                            options=[
                                {"label": region, "value": region}
                                for region in np.sort(data.region.unique())
                            ],
                            value="Albany",
                            clearable=False,
                            className="dropdown",
                        ),
                    ]
                ),
                html.Div(
                    children=[
                        html.Div(children="Type", className="menu-title"),
                        dcc.Dropdown(
                            id="type-filter",
                            options=[
                                {"label": avocado_type, "value": avocado_type}
                                for avocado_type in data.type.unique()
                            ],
                            value="organic",
                            clearable=False,
                            searchable=False,
                            className="dropdown",
                        ),
                    ],
                ),
                html.Div(
                    children=[
                        html.Div(
                            children="Date Range",
                            className="menu-title"
                            ),
                        dcc.DatePickerRange(
                            id="date-range",
                            min_date_allowed=data.Date.min().date(),
                            max_date_allowed=data.Date.max().date(),
														initial_visible_month=data.Date.min().date(),
                            start_date=data.Date.min().date(),
                            end_date=data.Date.max().date(),
                        ),
                    ]
                ),
            ],
            className="menu",
        ),
        html.Div(
            children=[
                html.Div(
                    children=dcc.Graph(
                        id="price-chart",
                        config={"displayModeBar": False},
                        figure={
                            "data": [
                                {
                                    "x": data["Date"],
                                    "y": data["AveragePrice"],
                                    "type": "lines",
                                    "hovertemplate": "$%{y:.2f}"
                                    "<extra></extra>",
                                },
                            ],
                            "layout": {
                                "title": {
                                    "text": "아보카도 평균가격($)",
                                    "x": 2,
                                    "xanchor": "center",
                                },
                                "xaxis": {"fixedrange": True},
                                "yaxis": {
                                    "tickprefix": "$",
                                    "fixedrange": True,
                                },
                                "colorway": ["#17B897"],
                            },
                        },
                    ),
                    className="card",
                ),
                html.Div(
                    children=dcc.Graph(
                        id="volume-chart",
                        config={"displayModeBar": False},
                        figure={
                            "data": [
                                {
                                    "x": data["Date"],
                                    "y": data["Total Volume"],
                                    "type": "lines",
                                },
                            ],
                            "layout": {
                                "title": {
                                    "text": "아보카도 판매량",
                                    "x": 0.05,
                                    "xanchor": "left",
                                },
                                "xaxis": {"fixedrange": True},
                                "yaxis": {"fixedrange": True},
                                "colorway": ["#E12D39"],
                            },
                        },
                    ),
                    className="card",
                ),
            ],
            className="wrapper",
        ),
    ]
)

if __name__ == "__main__":
    app.run_server(debug=True)
  • style.css 파일도 변경해야 한다. 아래 코드를 추가한다.
.
.
.
.menu {
    height: 112px;
    max-width: 1024px;
    display: flex;
    justify-content: space-evenly;
    padding-top: 32px;
    margin: 0px auto 0 auto;
    background-color: #FFFFFF;
    box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}

.menu-title {
    margin-bottom: 6px;
    font-weight: bold;
    color: #079A82;
}

.Select-control {
    width: 256px;
    height: 48px;
}

.Select--single > .Select-control .Select-value, .Select-placeholder {
    line-height: 48px;
}

.Select--multi .Select-value-label {
    line-height: 32px;
}
  • .Select-control 등의 CSS 클래스명은 다음 개발자 도구에서 확인하면 된다.

dash_07.png

  • 이 때, 화면을 보면, 아래와 같이 나올 것이다.
  • 메뉴는 정상적으로 나오지만, Line 그래프는 정상적으로 나타나지 않는 것을 확인할 수 있다.

dash_06.png

  • 여기부터가 Reactive Programming이 필요한 순간이다.
  • dcc.Dropdown component에 대한 코드 설명이 필요할 것 같다. 코드를 다시 보자.
html.Div(
    children=[
        html.Div(children="Region", className="menu-title"),
        dcc.Dropdown(
            id="region-filter",
            options=[
                {"label": region, "value": region}
                for region in np.sort(data.region.unique())
            ],
            value="Albany",
            clearable=False,
            className="dropdown",
        ),
    ]
),
  • id: Dropdown 메뉴의 ID를 구성한다. 해당 ID는 향후 Callback을 정의할 때, 같이 활용된다.
  • optinos: Dropdown 메뉴가 최초 선택이 될 때, 값(labesl 또는 values)을 보여줍니다.
  • value: Default된 값을 보여줍니다.
  • className: style.css 이 적용되는 영역이다.

(2) Reactive Programming

  • 간단히 말하면, 실시간으로 반응을 하는 프로그래밍을 말한다.
    • 다른 유저가 페이스북에 ‘좋아요’ 버튼을 누르면 해당 포스트를 보고 있는 사용자는 새로고침 할 것 없이 실시간으로 ‘좋아요’의 개수가 올라가는 것을 말한다.
    • 프로그래밍으로는 비동기 이벤트를 처리한다고 말한다.

https://youtu.be/jjypeFGJC3c

(3) Callbacks

  • Reactive Programming의 핵심이자, 본 Tutorial의 핵심이다.
  • Callback 함수란, 개발자는 이벤트를 등록하기만 할 뿐, 실제 사이트 방문자가 특정 이벤트를 발생시키면, 특정 시점에 도달했을 때 해당 기능을 활성화 시키는 것이다.

/img/python/dash/dash_project/dash_08.png

  • 이제 위 코드를 실행시키는 코드를 추가한다.
  • 먼저 라이브러리를 추가한다. 그리고 이전 코드에 이어서 아래 코드를 추가한다.
.
.
from dash.dependencies import Output, Input
.
.
.
@app.callback(
    [Output("price-chart", "figure"), Output("volume-chart", "figure")],
    [
        Input("region-filter", "value"),
        Input("type-filter", "value"),
        Input("date-range", "start_date"),
        Input("date-range", "end_date"),
    ],
)
def update_charts(region, avocado_type, start_date, end_date):
    mask = (
        (data.region == region)
        & (data.type == avocado_type)
        & (data.Date >= start_date)
        & (data.Date <= end_date)
    )
    filtered_data = data.loc[mask, :]
    price_chart_figure = {
        "data": [
            {
                "x": filtered_data["Date"],
                "y": filtered_data["AveragePrice"],
                "type": "lines",
                "hovertemplate": "$%{y:.2f}<extra></extra>",
            },
        ],
        "layout": {
            "title": {
                "text": "Average Price of Avocados",
                "x": 0.05,
                "xanchor": "left",
            },
            "xaxis": {"fixedrange": True},
            "yaxis": {"tickprefix": "$", "fixedrange": True},
            "colorway": ["#17B897"],
        },
    }

    volume_chart_figure = {
        "data": [
            {
                "x": filtered_data["Date"],
                "y": filtered_data["Total Volume"],
                "type": "lines",
            },
        ],
        "layout": {
            "title": {
                "text": "Avocados Sold",
                "x": 0.05,
                "xanchor": "left"
            },
            "xaxis": {"fixedrange": True},
            "yaxis": {"fixedrange": True},
            "colorway": ["#E12D39"],
        },
    }
    return price_chart_figure, volume_chart_figure
  • 먼저 @app.callback() 은 Decorator에 관한 구문이다.
  • 전체적인 흐름도는 Input과 Output으로 구성되어 있고, 각 ID 이름 값으로 연결이 되는 것을 확인할 수 있다.

Dash_Callbacks.jpg

  • 새롭게 정의된 함수 update_chart는 기존에 정적으로 작성된 두개의 chart 코드를 같이 변환할 수 있도록 담아두었다.
    • [ Input("region-filter", "value"), Input("type-filter", "value"), Input("date-range", "start_date"), Input("date-range", "end_date"), ], 사용자가 각 구성요소를 선택하면, 정의된 변수 value, start_date 등 형태로 저장이 된다.
      • 각각의 값은 html.div 코드에서 options 에 보면, 각각의 값과 연결이 되며, 최종적인 output은 반복문을 통해 구현되었다.
    • [Output("price-chart", "figure"), Output("volume-chart", "figure")], 에서 price-chart와 volume-chart는 dcc.graph ID를 의미하고, figure 는 그래프에 들어가는 각각의 변환되는 데이터를 의미한다.

(4) 최종확인

  • 이제 완성된 그래프를 확인해본다.

dash_10.png

6. 웹 배포하기 with Heroku & Git

  • 웹 배포를 위해서는 Heroku & Git 설치를 해야 한다.
  • 각 버전에 맞는 것을 설치 진행한다.
    • Heroku & git 정상적으로 설치 후 버전 확인을 화면 다음과 같이 확인이 가능하다.
(venv) $ echo 'export PATH="/usr/local/homebrew/opt/heroku-node/bin:$PATH"' >> /Users/evan/.bash_profile
(venv) $ git --version
git version 2.30.0
(venv) $ heroku --version
 ›   Warning: Our terms of service have changed: https://dashboard.heroku.com/terms-of-service
heroku/7.56.1 darwin-x64 node-v12.21.0
  • app server 객체를 생성한다.
    • 해당 코드는 [WSGI server](https://www.python.org/dev/peps/pep-3333/) 에서 앱을 실행한다는 뜻을 가지고 있다.
.
.
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Temp Analytics: Understand Your Data!"
server = app.server # 해당 코드를 새롭게 추가한다. 
.
.
  • 이번에는 runtime.txt 파일을 프로젝트 폴더 내 상단 위치에 생성한 후, 다음과 같이 입력한다.
    • 해당 버전은 독자의 버전과 다를 수 있으니 확인 후 입력한다.
python-3.8.7
  • 이번에는 requirements.txt 라이브러리명과 각 버전을 입력하도록 한다.
  • 현재 virtualenv 환경에서 작업중인 것을 그대로 받으려면 다음과 같이 실행한다.
    • 그러나, 기존에 설치된 라이브러리가 많으면 직접 주요 패키지만 별도로 정리하는 것을 추천한다.
$ pip freeze > requirements.txt
  • 필자는 아래와 같이 추가했다.
colorama==0.4.4
dash==1.21.0
gunicorn==20.1.0
numpy==1.19.4
pandas==1.2.0
  • 이번에는 Procfile 을 생성한 후, 아래 텍스트를 추가한다.
    • Heroku app에서 gunicorn 서버로 대시보드를 운영한다는 뜻이다.
web: gunicorn app:server
  • 이번에는 .gitignore 파일을 생성하여 불필요한 파일들을 추적하지 않도록 한다.
    • 해당 파일만 git commit 을 진행한다.
venv
*.pyc
.DS_Store # 맥 사용자만 추가
  • 전체적인 프로젝트의 파일 구조는 아래와 같다.
dashboard_project/
│
├── assets/
│   ├── favicon.ico
│   └── style.css
│
├── venv/
│
├── app.py
├── data/
│   ├── avocado.csv
├── Procfile
├── requirements.txt
└── runtime.txt
  • 이제 마지막으로 heroku에 앱 배포를 시작한다.
    • 사전에 회원가입 등 진행이 되어 있어야 한다.
    • 이 때, 중요한 것은 프로젝트 폴더명과 Heroku App 이름이 동일해야 한다. dash-project21
$ heroku create dash-project21 # 각자의 이름을 추가한다. 
  • 이제 heroku login을 진행한다.
$ (venv) heroku login
heroku: Press any key to open up the browser to login or q to exit: 
Opening browser to https://cli-auth.heroku.com/auth/cli/browser/9320abcd-b8c6-406d-9198-ca14d1e59a26?requestor=SFMyNTY.g2gDbQAAAA4yMjEuMTU3LjM3LjIxNm4GAGgtTBB7AWIAAVGA.GlyVc8jbyiW6NG0MVzCS0bOjtzBWvYRfjB9-gnkQaoQ
Logging in... done
Logged in as your_email_address
  • 그리고, heroku 싸이트에 접속하여 각 명령어를 순서대로 입력한다.

dash_09.png

  • 이제 repository를 생성한다.
$ heroku git:remote -a dashboard-project21
set git remote heroku to https://git.heroku.com/dashboard-project21.git
  • 이제 배포를 진행한다.
$ git add .
$ git commit -am "make it better"
$ git push 
$ git push heroku main

Reference