├── .gitignore
├── .idea
├── deploy-dash-with-gcp.iml
├── encodings.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── Dockerfile
├── README.md
├── app
├── .gcloudignore
├── __init__.py
├── app.yaml
├── assets
│ ├── base.css
│ ├── dsc-logo2.png
│ └── dsc.css
├── main.py
└── requirements.txt
├── images
├── add_people.png
├── dash_app.png
├── members.png
└── new_project.png
├── simple-dash-app-engine-app
├── .gcloudignore
├── README.md
├── app.yaml
├── assets
│ ├── base.css
│ ├── dsc-logo2.png
│ └── dsc.css
├── data
│ └── data.csv
├── main.py
└── requirements.txt
└── simple-dash-app-using-a-bucket
├── .gcloudignore
├── README.md
├── __init__.py
├── app.yaml
├── assets
├── base.css
├── dsc-logo2.png
└── dsc.css
├── data
├── __init__.py
├── data.csv
├── dataDownloader.py
└── dataUpload.py
├── main.py
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | # History files
2 | .Rhistory
3 | .Rapp.history
4 | .Rproj.user
5 | .RData
6 | .Ruserdata
7 | .DS_Store
8 | .gitmodules
9 |
10 | # Byte-compiled / optimized / DLL files
11 | __pycache__/
12 | *.py[cod]
13 | *$py.class
14 |
15 | # C extensions
16 | *.so
17 |
18 | # config
19 | config.json
20 |
21 | # Distribution / packaging
22 | .Python
23 | build/
24 | develop-eggs/
25 | dist/
26 | downloads/
27 | eggs/
28 | .eggs/
29 | lib/
30 | lib64/
31 | parts/
32 | sdist/
33 | var/
34 | wheels/
35 | *.egg-info/
36 | .installed.cfg
37 | *.egg
38 | MANIFEST
39 |
40 | # PyInstaller
41 | # Usually these files are written by a python script from a template
42 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
43 | *.manifest
44 | *.spec
45 |
46 | # Installer logs
47 | pip-log.txt
48 | pip-delete-this-directory.txt
49 |
50 | # Unit test / coverage reports
51 | htmlcov/
52 | .tox/
53 | .coverage
54 | .coverage.*
55 | .cache
56 | nosetests.xml
57 | coverage.xml
58 | *.cover
59 | .hypothesis/
60 | .pytest_cache/
61 |
62 | # Translations
63 | *.mo
64 | *.pot
65 |
66 | # Django stuff:
67 | *.log
68 | local_settings.py
69 | db.sqlite3
70 |
71 | # Flask stuff:
72 | instance/
73 | .webassets-cache
74 |
75 | # Scrapy stuff:
76 | .scrapy
77 |
78 | # Sphinx documentation
79 | docs/_build/
80 |
81 | # PyBuilder
82 | target/
83 |
84 | # Jupyter Notebook
85 | .ipynb_checkpoints
86 |
87 | # pyenv
88 | .python-version
89 |
90 | # celery beat schedule file
91 | celerybeat-schedule
92 |
93 | # SageMath parsed files
94 | *.sage.py
95 |
96 | # Environments
97 | .env
98 | .venv
99 | env/
100 | venv/
101 | ENV/
102 | env.bak/
103 | venv.bak/
104 |
105 | # Spyder project settings
106 | .spyderproject
107 | .spyproject
108 |
109 | # Rope project settings
110 | .ropeproject
111 |
112 | # mkdocs documentation
113 | /site
114 |
115 | # mypy
116 | .mypy_cache/
117 |
118 | # Directories
119 | *.json
120 | output/aligned_images
121 | data/key/*
--------------------------------------------------------------------------------
/.idea/deploy-dash-with-gcp.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Move to /dash
2 |
3 | FROM python:3.6
4 |
5 | WORKDIR /
6 |
7 | COPY . /
8 |
9 | RUN pip install --trusted-host pypi.python.org -r requirements.txt
10 |
11 | EXPOSE 8000
12 |
13 | ENTRYPOINT ["gunicorn","--bind=0.0.0.0:8080","main:server"]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deploy Dash with GCP
2 |
3 | This repository contains two simple dash applications that can be deployed with Google Cloud Platform (GCP).
4 |
5 | 1. The first is one that reads in a locally-stored file in `data` folder and creates a graph from adding and multiplying the numbers in `data.csv`. This can be found in the `/simple-dash-app-engine-app` folder, including a [README](https://github.com/datasciencecampus/deploy-dash-with-gcp/blob/master/simple-dash-app-engine-app/README.md) guide.
6 | 2. The second is an example whereby we link the app to a GCP bucket containing the data. This can be found in the `/simple-dash-app-using-a-bucket` folder. This also has a [README](https://github.com/datasciencecampus/deploy-dash-with-gcp/blob/master/simple-dash-app-using-a-bucket/README.md) guide.
7 |
8 | More training materials can be found on our [GitHub pages training page](https://datasciencecampus.github.io/training/).
9 |
10 | To run these examples, clone this repository using:
11 |
12 | ```
13 | git clone https://github.com/datasciencecampus/deploy-dash-with-gcp.git
14 | ```
15 |
--------------------------------------------------------------------------------
/app/.gcloudignore:
--------------------------------------------------------------------------------
1 | # This file specifies files that are *not* uploaded to Google Cloud Platform
2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of
3 | # "#!include" directives (which insert the entries of the given .gitignore-style
4 | # file at that point).
5 | #
6 | # For more information, run:
7 | # $ gcloud topic gcloudignore
8 | #
9 | .gcloudignore
10 | # If you would like to upload your .git directory, .gitignore file or files
11 | # from your .gitignore file, remove the corresponding line
12 | # below:
13 | .git
14 | .gitignore
15 |
16 | # Python pycache:
17 | __pycache__/
18 | # Ignored by the build system
19 | /setup.cfg
20 |
21 | # images
22 | /images/*
23 |
24 | # key
25 | data/key/*
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/app/__init__.py
--------------------------------------------------------------------------------
/app/app.yaml:
--------------------------------------------------------------------------------
1 | runtime: python37
2 |
3 | basic_scaling:
4 | max_instances: 2
5 | idle_timeout: 10m
6 |
7 | resources:
8 | cpu: 1
9 | memory_gb: 1
10 | disk_size_gb: 10
11 |
12 | entrypoint: gunicorn -b 0.0.0.0:8080 main:server
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/assets/base.css:
--------------------------------------------------------------------------------
1 | /* Table of contents
2 | ––––––––––––––––––––––––––––––––––––––––––––––––––
3 | - Plotly.js
4 | - Grid
5 | - Base Styles
6 | - Typography
7 | - Links
8 | - Buttons
9 | - Forms
10 | - Lists
11 | - Code
12 | - Tables
13 | - Spacing
14 | - Utilities
15 | - Clearing
16 | - Media Queries
17 | */
18 |
19 |
20 | /* Grid
21 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
22 | .container {
23 | position: relative;
24 | width: 100%;
25 | max-width: 960px;
26 | margin: 0 auto;
27 | padding: 0 20px;
28 | box-sizing: border-box; }
29 | .column,
30 | .columns {
31 | width: 100%;
32 | float: left;
33 | box-sizing: border-box; }
34 |
35 | /* For devices larger than 400px */
36 | @media (min-width: 400px) {
37 | .container {
38 | width: 85%;
39 | padding: 0; }
40 | }
41 |
42 | /* For devices larger than 550px */
43 | @media (min-width: 550px) {
44 | .container {
45 | width: 80%; }
46 | .column,
47 | .columns {
48 | margin-left: 2%; }
49 | .column:first-child,
50 | .columns:first-child {
51 | margin-left: 1%; }
52 |
53 | .one.column,
54 | .one.columns { width: 4.66666666667%; }
55 | .two.columns { width: 13.3333333333%; }
56 | .three.columns { width: 22%; }
57 | .four.columns { width: 30.6666666667%; }
58 | .five.columns { width: 39.3333333333%; }
59 | .six.columns { width: 48%; }
60 | .seven.columns { width: 56.6666666667%; }
61 | .eight.columns { width: 65.3333333333%; }
62 | .nine.columns { width: 74.0%; }
63 | .ten.columns { width: 82.6666666667%; }
64 | .eleven.columns { width: 91.3333333333%; }
65 | .twelve.columns { width: 100%; margin-left: 0; }
66 |
67 | .one-third.column { width: 30.6666666667%; }
68 | .two-thirds.column { width: 65.3333333333%; }
69 |
70 | .one-half.column { width: 48%; }
71 |
72 | /* Offsets */
73 | .offset-by-one.column,
74 | .offset-by-one.columns { margin-left: 8.66666666667%; }
75 | .offset-by-two.column,
76 | .offset-by-two.columns { margin-left: 17.3333333333%; }
77 | .offset-by-three.column,
78 | .offset-by-three.columns { margin-left: 26%; }
79 | .offset-by-four.column,
80 | .offset-by-four.columns { margin-left: 34.6666666667%; }
81 | .offset-by-five.column,
82 | .offset-by-five.columns { margin-left: 43.3333333333%; }
83 | .offset-by-six.column,
84 | .offset-by-six.columns { margin-left: 52%; }
85 | .offset-by-seven.column,
86 | .offset-by-seven.columns { margin-left: 60.6666666667%; }
87 | .offset-by-eight.column,
88 | .offset-by-eight.columns { margin-left: 69.3333333333%; }
89 | .offset-by-nine.column,
90 | .offset-by-nine.columns { margin-left: 78.0%; }
91 | .offset-by-ten.column,
92 | .offset-by-ten.columns { margin-left: 86.6666666667%; }
93 | .offset-by-eleven.column,
94 | .offset-by-eleven.columns { margin-left: 95.3333333333%; }
95 |
96 | .offset-by-one-third.column,
97 | .offset-by-one-third.columns { margin-left: 34.6666666667%; }
98 | .offset-by-two-thirds.column,
99 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
100 |
101 | .offset-by-one-half.column,
102 | .offset-by-one-half.columns { margin-left: 52%; }
103 |
104 | }
105 |
106 |
107 | /* Base Styles
108 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
109 | /* NOTE
110 | html is set to 62.5% so that all the REM measurements throughout Skeleton
111 | are based on 10px sizing. So basically 1.5rem = 15px :) */
112 | html {
113 | font-size: 62.5%; }
114 |
115 | body {
116 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
117 | line-height: 1.6;
118 | font-weight: 400;
119 | font-family: "Open Sans", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
120 | color: rgb(50, 50, 50);
121 | margin: 0;
122 | }
123 |
124 |
125 | /* Typography
126 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
127 | h1, h2, h3, h4, h5, h6 {
128 | margin-top: 0;
129 | margin-bottom: 0;
130 | font-weight: 300; }
131 | h1 { font-size: 4.5rem; line-height: 1.2; letter-spacing: -.1rem; margin-bottom: 2rem; }
132 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; margin-bottom: 1.8rem; margin-top: 1.8rem;}
133 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; margin-bottom: 1.5rem; margin-top: 1.5rem;}
134 | h4 { font-size: 2.6rem; line-height: 1.35; letter-spacing: -.08rem; margin-bottom: 1.2rem; margin-top: 1.2rem;}
135 | h5 { font-size: 2.2rem; line-height: 1.5; letter-spacing: -.05rem; margin-bottom: 0.6rem; margin-top: 0.6rem;}
136 | h6 { font-size: 2.0rem; line-height: 1.6; letter-spacing: 0; margin-bottom: 0.75rem; margin-top: 0.75rem;}
137 |
138 | p {
139 | margin-top: 0; }
140 |
141 |
142 | /* Blockquotes
143 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
144 | blockquote {
145 | border-left: 4px lightgrey solid;
146 | padding-left: 1rem;
147 | margin-top: 2rem;
148 | margin-bottom: 2rem;
149 | margin-left: 0rem;
150 | }
151 |
152 |
153 | /* Links
154 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
155 | a {
156 | color: #1EAEDB;
157 | text-decoration: underline;
158 | cursor: pointer;}
159 | a:hover {
160 | color: #0FA0CE; }
161 |
162 |
163 | /* Buttons
164 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
165 | .button,
166 | button,
167 | input[type="submit"],
168 | input[type="reset"],
169 | input[type="button"] {
170 | display: inline-block;
171 | height: 38px;
172 | padding: 0 30px;
173 | color: #555;
174 | text-align: center;
175 | font-size: 11px;
176 | font-weight: 600;
177 | line-height: 38px;
178 | letter-spacing: .1rem;
179 | text-transform: uppercase;
180 | text-decoration: none;
181 | white-space: nowrap;
182 | background-color: transparent;
183 | border-radius: 4px;
184 | border: 1px solid #bbb;
185 | cursor: pointer;
186 | box-sizing: border-box; }
187 | .button:hover,
188 | button:hover,
189 | input[type="submit"]:hover,
190 | input[type="reset"]:hover,
191 | input[type="button"]:hover,
192 | .button:focus,
193 | button:focus,
194 | input[type="submit"]:focus,
195 | input[type="reset"]:focus,
196 | input[type="button"]:focus {
197 | color: #333;
198 | border-color: #888;
199 | outline: 0; }
200 | .button.button-primary,
201 | button.button-primary,
202 | input[type="submit"].button-primary,
203 | input[type="reset"].button-primary,
204 | input[type="button"].button-primary {
205 | color: #FFF;
206 | background-color: #33C3F0;
207 | border-color: #33C3F0; }
208 | .button.button-primary:hover,
209 | button.button-primary:hover,
210 | input[type="submit"].button-primary:hover,
211 | input[type="reset"].button-primary:hover,
212 | input[type="button"].button-primary:hover,
213 | .button.button-primary:focus,
214 | button.button-primary:focus,
215 | input[type="submit"].button-primary:focus,
216 | input[type="reset"].button-primary:focus,
217 | input[type="button"].button-primary:focus {
218 | color: #FFF;
219 | background-color: #1EAEDB;
220 | border-color: #1EAEDB; }
221 |
222 |
223 | /* Forms
224 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
225 | input[type="email"],
226 | input[type="number"],
227 | input[type="search"],
228 | input[type="text"],
229 | input[type="tel"],
230 | input[type="url"],
231 | input[type="password"],
232 | textarea,
233 | select {
234 | height: 38px;
235 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
236 | background-color: #fff;
237 | border: 1px solid #D1D1D1;
238 | border-radius: 4px;
239 | box-shadow: none;
240 | box-sizing: border-box;
241 | font-family: inherit;
242 | font-size: inherit; /*https://stackoverflow.com/questions/6080413/why-doesnt-input-inherit-the-font-from-body*/}
243 | /* Removes awkward default styles on some inputs for iOS */
244 | input[type="email"],
245 | input[type="number"],
246 | input[type="search"],
247 | input[type="text"],
248 | input[type="tel"],
249 | input[type="url"],
250 | input[type="password"],
251 | textarea {
252 | -webkit-appearance: none;
253 | -moz-appearance: none;
254 | appearance: none; }
255 | textarea {
256 | min-height: 65px;
257 | padding-top: 6px;
258 | padding-bottom: 6px; }
259 | input[type="email"]:focus,
260 | input[type="number"]:focus,
261 | input[type="search"]:focus,
262 | input[type="text"]:focus,
263 | input[type="tel"]:focus,
264 | input[type="url"]:focus,
265 | input[type="password"]:focus,
266 | textarea:focus,
267 | /*select:focus {*/
268 | /* border: 1px solid #33C3F0;*/
269 | /* outline: 0; }*/
270 | label,
271 | legend {
272 | display: block;
273 | margin-bottom: 0px; }
274 | fieldset {
275 | padding: 0;
276 | border-width: 0; }
277 | input[type="checkbox"],
278 | input[type="radio"] {
279 | display: inline; }
280 | label > .label-body {
281 | display: inline-block;
282 | margin-left: .5rem;
283 | font-weight: normal; }
284 |
285 |
286 | /* Lists
287 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
288 | ul {
289 | list-style: circle inside; }
290 | ol {
291 | list-style: decimal inside; }
292 | ol, ul {
293 | padding-left: 0;
294 | margin-top: 0; }
295 | ul ul,
296 | ul ol,
297 | ol ol,
298 | ol ul {
299 | margin: 1.5rem 0 1.5rem 3rem;
300 | font-size: 90%; }
301 | li {
302 | margin-bottom: 1rem; }
303 |
304 |
305 | /* Tables
306 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
307 | table {
308 | border-collapse: collapse;
309 | }
310 | th,
311 | td {
312 | padding: 12px 15px;
313 | text-align: left;
314 | border-bottom: 1px solid #E1E1E1; }
315 | th:first-child,
316 | td:first-child {
317 | padding-left: 0; }
318 | th:last-child,
319 | td:last-child {
320 | padding-right: 0; }
321 |
322 |
323 | /* Spacing
324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
325 | button,
326 | .button {
327 | margin-bottom: 0rem; }
328 | input,
329 | textarea,
330 | select,
331 | fieldset {
332 | margin-bottom: 0rem; }
333 | pre,
334 | dl,
335 | figure,
336 | table,
337 | form {
338 | margin-bottom: 0rem; }
339 | p,
340 | ul,
341 | ol {
342 | margin-bottom: 0.75rem; }
343 |
344 | /* Utilities
345 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
346 | .u-full-width {
347 | width: 100%;
348 | box-sizing: border-box; }
349 | .u-max-full-width {
350 | max-width: 100%;
351 | box-sizing: border-box; }
352 | .u-pull-right {
353 | float: right; }
354 | .u-pull-left {
355 | float: left; }
356 |
357 |
358 | /* Misc
359 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
360 | hr {
361 | margin-top: 3rem;
362 | margin-bottom: 3.5rem;
363 | border-width: 0;
364 | border-top: 1px solid #E1E1E1; }
365 |
366 |
367 | /* Clearing
368 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
369 |
370 | /* Self Clearing Goodness */
371 | .container:after,
372 | .row:after,
373 | .u-cf {
374 | content: "";
375 | display: table;
376 | clear: both; }
377 |
378 |
379 | /* Media Queries
380 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
381 | /*
382 | Note: The best way to structure the use of media queries is to create the queries
383 | near the relevant code. For example, if you wanted to change the styles for buttons
384 | on small devices, paste the mobile query code up in the buttons section and style it
385 | there.
386 | */
387 |
388 |
389 | /* Larger than mobile */
390 | @media (min-width: 400px) {}
391 |
392 | /* Larger than phablet (also point when grid becomes active) */
393 | @media (min-width: 550px) {}
394 |
395 | /* Larger than tablet */
396 | @media (min-width: 750px) {}
397 |
398 | /* Larger than desktop */
399 | @media (min-width: 1000px) {}
400 |
401 | /* Larger than Desktop HD */
402 | @media (min-width: 1200px) {}
403 |
--------------------------------------------------------------------------------
/app/assets/dsc-logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/app/assets/dsc-logo2.png
--------------------------------------------------------------------------------
/app/assets/dsc.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,800');
2 | @import url('https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300,700');
3 |
4 | .container.scalable {
5 | width: 95%;
6 | max-width: none;
7 | }
8 |
9 | @media print {
10 | body {-webkit-print-color-adjust: exact;}
11 | }
12 |
13 | /* Remove Undo
14 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
15 | ._dash-undo-redo {
16 | display: none;
17 | }
18 |
19 |
20 | @media screen {
21 |
22 | .banner Img {
23 | height: 5rem;
24 | margin-bottom: 1rem;
25 | }
26 |
27 | #instructions {
28 | font-size: 2rem;
29 | font-family: 'Open Sans', sans-serif;
30 | font-weight: 300;
31 | color: #001d4d;
32 | margin: 0rem 0rem 2rem ;
33 | }
34 | }
35 |
36 | @media print {
37 |
38 | #instructions {
39 | font-size: 2rem;
40 | font-family: 'Open Sans', sans-serif;
41 | font-weight: 300;
42 | color: #001d4d;
43 | margin: 0rem 0rem 2rem ;
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/app/main.py:
--------------------------------------------------------------------------------
1 | import dash
2 | import dash_core_components as dcc
3 | import dash_html_components as html
4 | import pandas as pd
5 | from io import StringIO
6 | import os
7 |
8 | from data.dataDownloader import GCPDownloaderLocal, GCPDownloaderCloud
9 |
10 | # -------------------------- PYTHON FUNCTIONS ---------------------------- #
11 |
12 |
13 | def add_numbers(first_num,second_num):
14 | new_num = first_num + second_num
15 | return new_num
16 |
17 | def multiply_numbers(first_num,second_num):
18 | new_num = first_num * second_num
19 | return new_num
20 |
21 |
22 | def build_banner():
23 | return html.Div(
24 | id='banner',
25 | className='banner',
26 | children=[
27 | html.Img(src=app.get_asset_url('dsc-logo2.png')),
28 | ],
29 | )
30 |
31 | def data_in():
32 |
33 | if cloud == False:
34 | data = os.path.join('data/data.csv')
35 |
36 | else:
37 | project = 'dash-example-265811'
38 | project_name = 'dash-example-265811.appspot.com'
39 | folder_name = 'data'
40 | file_name = 'data.csv'
41 |
42 | if local == True:
43 | GCP = GCPDownloaderLocal() # run locally
44 | else:
45 | GCP = GCPDownloaderCloud() # run on cloud
46 |
47 | bytes_file = GCP.getData(project, project_name, folder_name, file_name)
48 | s = str(bytes_file, encoding='utf-8')
49 | data = StringIO(s)
50 |
51 | data_df = pd.read_csv(data)
52 |
53 | add_num_list = []
54 | multiply_num_list = []
55 |
56 | for index, row in data_df.iterrows():
57 | add_num_list.append(add_numbers(row['first_num'], row['second_num']))
58 | multiply_num_list.append(multiply_numbers(row['first_num'], row['second_num']))
59 |
60 | data_df['add_num'] = add_num_list
61 | data_df['multiply_num'] = multiply_num_list
62 |
63 | return data_df
64 |
65 | def app_layout():
66 | data_df = data_in()
67 | return html.Div(children=[
68 | html.H1(
69 | children=[
70 | build_banner(),
71 | html.P(
72 | id='instructions',
73 | children=dash_text),
74 | ]
75 | ),
76 |
77 | dcc.Graph(
78 | id='example-graph',
79 | figure={
80 | 'data': [
81 | {'x': data_df.index.values.tolist(), 'y': data_df['add_num'], 'type': 'bar', 'name': 'Add Numbers'},
82 | {'x': data_df.index.values.tolist(), 'y': data_df['multiply_num'], 'type': 'bar', 'name': 'Multiply Numbers'},
83 | ],
84 | 'layout': {
85 | 'title': 'Dash Data Visualization'
86 | }
87 | }
88 | )
89 | ])
90 |
91 |
92 | # -------------------------- TEXT ---------------------------- #
93 |
94 |
95 | dash_text = '''
96 |
97 | This is an example of a DSC dashboard.
98 | '''
99 |
100 |
101 | # -------------------------- DASH ---------------------------- #
102 |
103 |
104 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
105 |
106 | app = dash.Dash(__name__, external_stylesheets=external_stylesheets, assets_folder='assets')
107 | server = app.server
108 |
109 | local = False
110 | cloud = True
111 |
112 | data_df = data_in()
113 |
114 | app.layout = app_layout
115 | app.config.suppress_callback_exceptions = True
116 |
117 |
118 | # -------------------------- MAIN ---------------------------- #
119 |
120 |
121 | if __name__ == '__main__':
122 | app.run_server(host='0.0.0.0', port=8080, debug=True, use_reloader=False)
--------------------------------------------------------------------------------
/app/requirements.txt:
--------------------------------------------------------------------------------
1 | Click==7.0
2 | dash==1.6.0
3 | dash-core-components==1.5.0
4 | dash-html-components==1.0.1
5 | dash-renderer==1.2.0
6 | Flask==1.1.1
7 | Flask-Compress==1.4.0
8 | future==0.18.2
9 | itsdangerous==1.1.0
10 | Jinja2==2.10.3
11 | MarkupSafe==1.1.1
12 | numpy==1.16.5
13 | pandas==0.24.2
14 | pytz==2019.3
15 | retrying==1.3.3
16 | six==1.13.0
17 | Werkzeug==0.16.0
18 | gunicorn>=19.5.0
19 | google-api-core==1.16.0
20 | google-auth==1.10.1
21 | google-cloud-core==1.2.0
22 | google-cloud-storage==1.25.0
23 | google-resumable-media==0.5.0
24 | googleapis-common-protos==1.51.0
--------------------------------------------------------------------------------
/images/add_people.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/images/add_people.png
--------------------------------------------------------------------------------
/images/dash_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/images/dash_app.png
--------------------------------------------------------------------------------
/images/members.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/images/members.png
--------------------------------------------------------------------------------
/images/new_project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/images/new_project.png
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/.gcloudignore:
--------------------------------------------------------------------------------
1 | # This file specifies files that are *not* uploaded to Google Cloud Platform
2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of
3 | # "#!include" directives (which insert the entries of the given .gitignore-style
4 | # file at that point).
5 | #
6 | # For more information, run:
7 | # $ gcloud topic gcloudignore
8 | #
9 | .gcloudignore
10 | # If you would like to upload your .git directory, .gitignore file or files
11 | # from your .gitignore file, remove the corresponding line
12 | # below:
13 | .git
14 | .gitignore
15 |
16 | # Python pycache:
17 | __pycache__/
18 | # Ignored by the build system
19 | /setup.cfg
20 |
21 | # images
22 | /images/*
23 |
24 | # key
25 | data/key/*
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/README.md:
--------------------------------------------------------------------------------
1 | This guide has been created to help users deploy a dash application using Google Cloud Platform (GCP) App Engine, using a locally-stored file. For more information, visit the [GitHub repository](https://github.com/datasciencecampus/deploy-dash-with-gcp).
2 |
3 | *Note: This blog has been updated since it was first posted. See the Version control section at the bottom of the post for changes.*
4 |
5 | ## Step 1: Creating your Dash Application
6 |
7 | Visit our [GitHub repository](https://github.com/datasciencecampus/deploy-dash-with-gcp/tree/master/simple-dash-app-engine-app) to view all the files.
8 |
9 | Important files:
10 |
11 | * `main.py` is the Dash application
12 | * `.gcloudignore` is like `.gitignore` for GitHub, it tells GCP what not to upload (for example here, I don't want to upload all the screenshots used in this guide)
13 | * `app.yaml` is used to run the Dash app on GCP using [gunicorn](https://gunicorn.org/), which is needed for GCP
14 | * `requirements.txt` comprises the packages needed to run the Dash app (important: gunicorn is required in this file at the bare minimum)
15 |
16 | #### main.py
17 |
18 | The `main.py` python script comprises the following, which are split into sections below. The full script can be found at the bottom of this post.
19 |
20 | **Python Functions**
21 |
22 | This section has three functions: add numbers, multiply numbers and build banner. The first two are self-explinatory, the last just creates an image banner based on a file in the `assets` folder.
23 |
24 | **Load Data**
25 |
26 | This section loads the csv file from the `/data` folder, creates a pandas dataframe and then applies the add and multiply number functions.
27 |
28 | **Text**
29 |
30 | This section just contains free text to append to the Dash app.
31 |
32 | **Dash**
33 |
34 | This contains the Dash setup values, including the `/assets` folder.
35 |
36 | **Project Dashboard**
37 |
38 | This is the core of the Dash application.
39 |
40 | ```
41 | app.layout = html.Div(children=[
42 | html.H1(
43 | children=[
44 | build_banner(),
45 | html.P(
46 | id='instructions',
47 | children=dash_text),
48 | ]
49 | ),
50 |
51 | dcc.Graph(
52 | id='example-graph',
53 | figure={
54 | 'data': [
55 | {'x': data_df.index.values.tolist(), 'y': data_df['add_num'], 'type': 'bar', 'name': 'Add Numbers'},
56 | {'x': data_df.index.values.tolist(), 'y': data_df['multiply_num'], 'type': 'bar', 'name': 'Multiply Numbers'},
57 | ],
58 | 'layout': {
59 | 'title': 'Dash Data Visualization'
60 | }
61 | }
62 | )
63 | ])
64 | ```
65 |
66 | This simply creates the image banner, then adds the free text (which has an `id` whose style can be edited in the `/assets/dsc.css` file, then adds the graph based on the loaded and manipulated data from `/data/datacsv`.
67 |
68 | **main**
69 |
70 | ```
71 | if __name__ == '__main__':
72 | app.server(host='0.0.0.0', port=8080, debug=True)
73 | ```
74 |
75 | It is important you make a note of the port number and host!
76 |
77 | #### requirements.txt
78 |
79 | Here, this is the `requirements.txt` file, which tells GCP which packages to install.
80 |
81 | ```
82 | Click==7.0
83 | dash==1.6.0
84 | dash-core-components==1.5.0
85 | dash-html-components==1.0.1
86 | dash-renderer==1.2.0
87 | Flask==1.1.1
88 | Flask-Compress==1.4.0
89 | future==0.18.2
90 | itsdangerous==1.1.0
91 | Jinja2==2.10.3
92 | MarkupSafe==1.1.1
93 | numpy==1.16.5
94 | pandas==0.24.2
95 | pytz==2019.3
96 | retrying==1.3.3
97 | six==1.13.0
98 | Werkzeug==0.16.0
99 | gunicorn==19.3.0
100 | ```
101 |
102 | #### app.yaml
103 |
104 | This is a really important file, and needs to replicate what is put in your `main.py` script.
105 |
106 | ```
107 | service: default
108 | runtime: python37
109 |
110 | basic_scaling:
111 | max_instances: 2
112 | idle_timeout: 10m
113 |
114 | resources:
115 | cpu: 1
116 | memory_gb: 1
117 | disk_size_gb: 10
118 |
119 | entrypoint: gunicorn -b 0.0.0.0:8080 main:server
120 | ```
121 |
122 | This file tells GCP how to create the application. The first line specifies the service name, which becomes the prefix of the URL the app will run on. Yhe second line specifies that we want to build using python 3.7. The basic_scaling and resources blocks tell App Engine what the environment should be. Here we are limiting the instances to 2, on a machine with 1 CPU and 1 GB of RAM. The entrypoint line must replicate what is at the end of `main.py` (the host and port numbers), as well as what you call the python script (here `main.py` = `main`). This is the most likely file to corrupt a build.
123 |
124 | ## Step 2: Deploy your Application to Google Cloud Platform
125 |
126 | This guide builds on other guides such as [Jamie Phillips'](https://www.phillipsj.net/posts/deploying-dash-to-google-app-engine/). However, we were not able to successfully deploy a Dash app following Jamie's, or others, examples without heavy tweaks. We also include additional python functions to load data in our example here (Goodbye, World).
127 |
128 | The following steps are to deploy a Dash application to GCP. If your app doesn't work locally, you should fix that first as it won't work on GCP (even if you pray real hard). If it works locally, but it doesn't deploy, the majority of the time it will be due to the `app.yaml` file.
129 |
130 | ### Step 2.1: Make a Project on GCP
131 |
132 | Using the CLI or the Console Interface online (which we use below), create a new project with a suitable project name (here we call it `dash-example`).
133 |
134 | 
135 |
136 | ### Step 2.2: Make Yourself the Owner of Project
137 |
138 | Make sure the project you've just created is selected on the console, then click 'ADD PEOPLE TO THIS PROJECT'.
139 |
140 | 
141 |
142 | Then input your user name and set the role to `Project` > `Owner`.
143 |
144 | 
145 |
146 | That's it for now on the Google Cloud Platform Console.
147 |
148 | ### Step 2.3: Deploy Using gcloud Command Line Tool
149 |
150 | If you haven't installed the [gcloud command line tool](https://cloud.google.com/sdk/gcloud/) do so now.
151 |
152 | Next, check your project is active in gcloud using:
153 |
154 | `gcloud config get-value project`
155 |
156 | Which will print the following on screen:
157 |
158 | ```
159 | Your active configuration is: [default]
160 |
161 | my-project-id
162 | ```
163 |
164 | To change the project to your desired project, type:
165 |
166 | `gcloud config set project project-id`
167 |
168 | Next, to deploy, type:
169 |
170 | `gcloud app deploy`
171 |
172 | Then select your desired region (we use `europe-west2`, which is the London region)
173 |
174 | If you have setup your configuration correctly then it will deploy the Dash app (after a while), which will be available at:
175 |
176 | `https://project-id.appspot.com/`
177 |
178 | The example app above is hosted [here](https://simple-dash-app-engine-app-dot-dash-example-265811.appspot.com/).
179 |
180 | Visit our [GitHub repository](https://github.com/datasciencecampus/deploy-dash-with-gcp/tree/master/simple-dash-app-engine-app) to view all the files.
181 |
182 | ## Step 3: Restrict Access to your Application
183 |
184 | By default your application will be accessible to anyone in the world. To restrict the access you can use [Firewall Rules](https://cloud.google.com/blog/products/gcp/introducing-app-engine-firewall-an-easy-way-to-control-access-to-your-app).
185 |
186 | ## main.py script
187 |
188 | ```
189 | import dash
190 | import dash_core_components as dcc
191 | import dash_html_components as html
192 | import pandas as pd
193 | import os
194 |
195 |
196 | # -------------------------- PYTHON FUNCTIONS ---------------------------- #
197 |
198 |
199 | def add_numbers(first_num,second_num):
200 | new_num = first_num + second_num
201 | return new_num
202 |
203 | def multiply_numbers(first_num,second_num):
204 | new_num = first_num * second_num
205 | return new_num
206 |
207 |
208 | def build_banner():
209 | return html.Div(
210 | id='banner',
211 | className='banner',
212 | children=[
213 | html.Img(src=app.get_asset_url('dsc-logo2.png')),
214 | ],
215 | )
216 |
217 |
218 | # -------------------------- LOAD DATA ---------------------------- #
219 |
220 |
221 | csv_files_path = os.path.join('data/data.csv')
222 |
223 | data_df = pd.read_csv(csv_files_path)
224 |
225 | add_num_list = []
226 | multiply_num_list = []
227 |
228 | for index, row in data_df.iterrows():
229 | add_num_list.append(add_numbers(row['first_num'], row['second_num']))
230 | multiply_num_list.append(multiply_numbers(row['first_num'], row['second_num']))
231 |
232 | data_df['add_num'] = add_num_list
233 | data_df['multiply_num'] = multiply_num_list
234 |
235 |
236 | # -------------------------- TEXT ---------------------------- #
237 |
238 |
239 | dash_text = '''
240 |
241 | This is an example of a DSC dashboard.
242 | '''
243 |
244 |
245 | # -------------------------- DASH ---------------------------- #
246 |
247 |
248 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
249 |
250 | app = dash.Dash(__name__, external_stylesheets=external_stylesheets, assets_folder='assets')
251 | server = app.server
252 |
253 | app.config.suppress_callback_exceptions = True
254 |
255 |
256 | # -------------------------- PROJECT DASHBOARD ---------------------------- #
257 |
258 |
259 | app.layout = html.Div(children=[
260 | html.H1(
261 | children=[
262 | build_banner(),
263 | html.P(
264 | id='instructions',
265 | children=dash_text),
266 | ]
267 | ),
268 |
269 | dcc.Graph(
270 | id='example-graph',
271 | figure={
272 | 'data': [
273 | {'x': data_df.index.values.tolist(), 'y': data_df['add_num'], 'type': 'bar', 'name': 'Add Numbers'},
274 | {'x': data_df.index.values.tolist(), 'y': data_df['multiply_num'], 'type': 'bar', 'name': 'Multiply Numbers'},
275 | ],
276 | 'layout': {
277 | 'title': 'Dash Data Visualization'
278 | }
279 | }
280 | )
281 | ])
282 |
283 |
284 |
285 | # -------------------------- MAIN ---------------------------- #
286 |
287 |
288 | if __name__ == '__main__':
289 | app.run_server(host='0.0.0.0', port=8080, debug=True, use_reloader=False)
290 |
291 | ```
292 |
293 | ## Version control
294 |
295 | - v.1 of this blog post used a `flex` environment in the `app.yaml` file. However, this was causing the application to continuously run on GCP, causing increased costs.
296 | - v.2 is the current blog post, which uses a `standard` environment.
297 |
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/app.yaml:
--------------------------------------------------------------------------------
1 | service: simple-dash-app-engine-app
2 | runtime: python37
3 |
4 | basic_scaling:
5 | max_instances: 2
6 | idle_timeout: 10m
7 |
8 | resources:
9 | cpu: 1
10 | memory_gb: 1
11 | disk_size_gb: 10
12 |
13 | entrypoint: gunicorn -b 0.0.0.0:8080 main:server
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/assets/base.css:
--------------------------------------------------------------------------------
1 | /* Table of contents
2 | ––––––––––––––––––––––––––––––––––––––––––––––––––
3 | - Plotly.js
4 | - Grid
5 | - Base Styles
6 | - Typography
7 | - Links
8 | - Buttons
9 | - Forms
10 | - Lists
11 | - Code
12 | - Tables
13 | - Spacing
14 | - Utilities
15 | - Clearing
16 | - Media Queries
17 | */
18 |
19 |
20 | /* Grid
21 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
22 | .container {
23 | position: relative;
24 | width: 100%;
25 | max-width: 960px;
26 | margin: 0 auto;
27 | padding: 0 20px;
28 | box-sizing: border-box; }
29 | .column,
30 | .columns {
31 | width: 100%;
32 | float: left;
33 | box-sizing: border-box; }
34 |
35 | /* For devices larger than 400px */
36 | @media (min-width: 400px) {
37 | .container {
38 | width: 85%;
39 | padding: 0; }
40 | }
41 |
42 | /* For devices larger than 550px */
43 | @media (min-width: 550px) {
44 | .container {
45 | width: 80%; }
46 | .column,
47 | .columns {
48 | margin-left: 2%; }
49 | .column:first-child,
50 | .columns:first-child {
51 | margin-left: 1%; }
52 |
53 | .one.column,
54 | .one.columns { width: 4.66666666667%; }
55 | .two.columns { width: 13.3333333333%; }
56 | .three.columns { width: 22%; }
57 | .four.columns { width: 30.6666666667%; }
58 | .five.columns { width: 39.3333333333%; }
59 | .six.columns { width: 48%; }
60 | .seven.columns { width: 56.6666666667%; }
61 | .eight.columns { width: 65.3333333333%; }
62 | .nine.columns { width: 74.0%; }
63 | .ten.columns { width: 82.6666666667%; }
64 | .eleven.columns { width: 91.3333333333%; }
65 | .twelve.columns { width: 100%; margin-left: 0; }
66 |
67 | .one-third.column { width: 30.6666666667%; }
68 | .two-thirds.column { width: 65.3333333333%; }
69 |
70 | .one-half.column { width: 48%; }
71 |
72 | /* Offsets */
73 | .offset-by-one.column,
74 | .offset-by-one.columns { margin-left: 8.66666666667%; }
75 | .offset-by-two.column,
76 | .offset-by-two.columns { margin-left: 17.3333333333%; }
77 | .offset-by-three.column,
78 | .offset-by-three.columns { margin-left: 26%; }
79 | .offset-by-four.column,
80 | .offset-by-four.columns { margin-left: 34.6666666667%; }
81 | .offset-by-five.column,
82 | .offset-by-five.columns { margin-left: 43.3333333333%; }
83 | .offset-by-six.column,
84 | .offset-by-six.columns { margin-left: 52%; }
85 | .offset-by-seven.column,
86 | .offset-by-seven.columns { margin-left: 60.6666666667%; }
87 | .offset-by-eight.column,
88 | .offset-by-eight.columns { margin-left: 69.3333333333%; }
89 | .offset-by-nine.column,
90 | .offset-by-nine.columns { margin-left: 78.0%; }
91 | .offset-by-ten.column,
92 | .offset-by-ten.columns { margin-left: 86.6666666667%; }
93 | .offset-by-eleven.column,
94 | .offset-by-eleven.columns { margin-left: 95.3333333333%; }
95 |
96 | .offset-by-one-third.column,
97 | .offset-by-one-third.columns { margin-left: 34.6666666667%; }
98 | .offset-by-two-thirds.column,
99 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
100 |
101 | .offset-by-one-half.column,
102 | .offset-by-one-half.columns { margin-left: 52%; }
103 |
104 | }
105 |
106 |
107 | /* Base Styles
108 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
109 | /* NOTE
110 | html is set to 62.5% so that all the REM measurements throughout Skeleton
111 | are based on 10px sizing. So basically 1.5rem = 15px :) */
112 | html {
113 | font-size: 62.5%; }
114 |
115 | body {
116 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
117 | line-height: 1.6;
118 | font-weight: 400;
119 | font-family: "Open Sans", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
120 | color: rgb(50, 50, 50);
121 | margin: 0;
122 | }
123 |
124 |
125 | /* Typography
126 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
127 | h1, h2, h3, h4, h5, h6 {
128 | margin-top: 0;
129 | margin-bottom: 0;
130 | font-weight: 300; }
131 | h1 { font-size: 4.5rem; line-height: 1.2; letter-spacing: -.1rem; margin-bottom: 2rem; }
132 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; margin-bottom: 1.8rem; margin-top: 1.8rem;}
133 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; margin-bottom: 1.5rem; margin-top: 1.5rem;}
134 | h4 { font-size: 2.6rem; line-height: 1.35; letter-spacing: -.08rem; margin-bottom: 1.2rem; margin-top: 1.2rem;}
135 | h5 { font-size: 2.2rem; line-height: 1.5; letter-spacing: -.05rem; margin-bottom: 0.6rem; margin-top: 0.6rem;}
136 | h6 { font-size: 2.0rem; line-height: 1.6; letter-spacing: 0; margin-bottom: 0.75rem; margin-top: 0.75rem;}
137 |
138 | p {
139 | margin-top: 0; }
140 |
141 |
142 | /* Blockquotes
143 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
144 | blockquote {
145 | border-left: 4px lightgrey solid;
146 | padding-left: 1rem;
147 | margin-top: 2rem;
148 | margin-bottom: 2rem;
149 | margin-left: 0rem;
150 | }
151 |
152 |
153 | /* Links
154 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
155 | a {
156 | color: #1EAEDB;
157 | text-decoration: underline;
158 | cursor: pointer;}
159 | a:hover {
160 | color: #0FA0CE; }
161 |
162 |
163 | /* Buttons
164 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
165 | .button,
166 | button,
167 | input[type="submit"],
168 | input[type="reset"],
169 | input[type="button"] {
170 | display: inline-block;
171 | height: 38px;
172 | padding: 0 30px;
173 | color: #555;
174 | text-align: center;
175 | font-size: 11px;
176 | font-weight: 600;
177 | line-height: 38px;
178 | letter-spacing: .1rem;
179 | text-transform: uppercase;
180 | text-decoration: none;
181 | white-space: nowrap;
182 | background-color: transparent;
183 | border-radius: 4px;
184 | border: 1px solid #bbb;
185 | cursor: pointer;
186 | box-sizing: border-box; }
187 | .button:hover,
188 | button:hover,
189 | input[type="submit"]:hover,
190 | input[type="reset"]:hover,
191 | input[type="button"]:hover,
192 | .button:focus,
193 | button:focus,
194 | input[type="submit"]:focus,
195 | input[type="reset"]:focus,
196 | input[type="button"]:focus {
197 | color: #333;
198 | border-color: #888;
199 | outline: 0; }
200 | .button.button-primary,
201 | button.button-primary,
202 | input[type="submit"].button-primary,
203 | input[type="reset"].button-primary,
204 | input[type="button"].button-primary {
205 | color: #FFF;
206 | background-color: #33C3F0;
207 | border-color: #33C3F0; }
208 | .button.button-primary:hover,
209 | button.button-primary:hover,
210 | input[type="submit"].button-primary:hover,
211 | input[type="reset"].button-primary:hover,
212 | input[type="button"].button-primary:hover,
213 | .button.button-primary:focus,
214 | button.button-primary:focus,
215 | input[type="submit"].button-primary:focus,
216 | input[type="reset"].button-primary:focus,
217 | input[type="button"].button-primary:focus {
218 | color: #FFF;
219 | background-color: #1EAEDB;
220 | border-color: #1EAEDB; }
221 |
222 |
223 | /* Forms
224 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
225 | input[type="email"],
226 | input[type="number"],
227 | input[type="search"],
228 | input[type="text"],
229 | input[type="tel"],
230 | input[type="url"],
231 | input[type="password"],
232 | textarea,
233 | select {
234 | height: 38px;
235 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
236 | background-color: #fff;
237 | border: 1px solid #D1D1D1;
238 | border-radius: 4px;
239 | box-shadow: none;
240 | box-sizing: border-box;
241 | font-family: inherit;
242 | font-size: inherit; /*https://stackoverflow.com/questions/6080413/why-doesnt-input-inherit-the-font-from-body*/}
243 | /* Removes awkward default styles on some inputs for iOS */
244 | input[type="email"],
245 | input[type="number"],
246 | input[type="search"],
247 | input[type="text"],
248 | input[type="tel"],
249 | input[type="url"],
250 | input[type="password"],
251 | textarea {
252 | -webkit-appearance: none;
253 | -moz-appearance: none;
254 | appearance: none; }
255 | textarea {
256 | min-height: 65px;
257 | padding-top: 6px;
258 | padding-bottom: 6px; }
259 | input[type="email"]:focus,
260 | input[type="number"]:focus,
261 | input[type="search"]:focus,
262 | input[type="text"]:focus,
263 | input[type="tel"]:focus,
264 | input[type="url"]:focus,
265 | input[type="password"]:focus,
266 | textarea:focus,
267 | /*select:focus {*/
268 | /* border: 1px solid #33C3F0;*/
269 | /* outline: 0; }*/
270 | label,
271 | legend {
272 | display: block;
273 | margin-bottom: 0px; }
274 | fieldset {
275 | padding: 0;
276 | border-width: 0; }
277 | input[type="checkbox"],
278 | input[type="radio"] {
279 | display: inline; }
280 | label > .label-body {
281 | display: inline-block;
282 | margin-left: .5rem;
283 | font-weight: normal; }
284 |
285 |
286 | /* Lists
287 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
288 | ul {
289 | list-style: circle inside; }
290 | ol {
291 | list-style: decimal inside; }
292 | ol, ul {
293 | padding-left: 0;
294 | margin-top: 0; }
295 | ul ul,
296 | ul ol,
297 | ol ol,
298 | ol ul {
299 | margin: 1.5rem 0 1.5rem 3rem;
300 | font-size: 90%; }
301 | li {
302 | margin-bottom: 1rem; }
303 |
304 |
305 | /* Tables
306 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
307 | table {
308 | border-collapse: collapse;
309 | }
310 | th,
311 | td {
312 | padding: 12px 15px;
313 | text-align: left;
314 | border-bottom: 1px solid #E1E1E1; }
315 | th:first-child,
316 | td:first-child {
317 | padding-left: 0; }
318 | th:last-child,
319 | td:last-child {
320 | padding-right: 0; }
321 |
322 |
323 | /* Spacing
324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
325 | button,
326 | .button {
327 | margin-bottom: 0rem; }
328 | input,
329 | textarea,
330 | select,
331 | fieldset {
332 | margin-bottom: 0rem; }
333 | pre,
334 | dl,
335 | figure,
336 | table,
337 | form {
338 | margin-bottom: 0rem; }
339 | p,
340 | ul,
341 | ol {
342 | margin-bottom: 0.75rem; }
343 |
344 | /* Utilities
345 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
346 | .u-full-width {
347 | width: 100%;
348 | box-sizing: border-box; }
349 | .u-max-full-width {
350 | max-width: 100%;
351 | box-sizing: border-box; }
352 | .u-pull-right {
353 | float: right; }
354 | .u-pull-left {
355 | float: left; }
356 |
357 |
358 | /* Misc
359 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
360 | hr {
361 | margin-top: 3rem;
362 | margin-bottom: 3.5rem;
363 | border-width: 0;
364 | border-top: 1px solid #E1E1E1; }
365 |
366 |
367 | /* Clearing
368 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
369 |
370 | /* Self Clearing Goodness */
371 | .container:after,
372 | .row:after,
373 | .u-cf {
374 | content: "";
375 | display: table;
376 | clear: both; }
377 |
378 |
379 | /* Media Queries
380 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
381 | /*
382 | Note: The best way to structure the use of media queries is to create the queries
383 | near the relevant code. For example, if you wanted to change the styles for buttons
384 | on small devices, paste the mobile query code up in the buttons section and style it
385 | there.
386 | */
387 |
388 |
389 | /* Larger than mobile */
390 | @media (min-width: 400px) {}
391 |
392 | /* Larger than phablet (also point when grid becomes active) */
393 | @media (min-width: 550px) {}
394 |
395 | /* Larger than tablet */
396 | @media (min-width: 750px) {}
397 |
398 | /* Larger than desktop */
399 | @media (min-width: 1000px) {}
400 |
401 | /* Larger than Desktop HD */
402 | @media (min-width: 1200px) {}
403 |
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/assets/dsc-logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/simple-dash-app-engine-app/assets/dsc-logo2.png
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/assets/dsc.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,800');
2 | @import url('https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300,700');
3 |
4 | .container.scalable {
5 | width: 95%;
6 | max-width: none;
7 | }
8 |
9 | @media print {
10 | body {-webkit-print-color-adjust: exact;}
11 | }
12 |
13 | /* Remove Undo
14 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
15 | ._dash-undo-redo {
16 | display: none;
17 | }
18 |
19 |
20 | @media screen {
21 |
22 | .banner Img {
23 | height: 5rem;
24 | margin-bottom: 1rem;
25 | }
26 |
27 | #instructions {
28 | font-size: 2rem;
29 | font-family: 'Open Sans', sans-serif;
30 | font-weight: 300;
31 | color: #001d4d;
32 | margin: 0rem 0rem 2rem ;
33 | }
34 | }
35 |
36 | @media print {
37 |
38 | #instructions {
39 | font-size: 2rem;
40 | font-family: 'Open Sans', sans-serif;
41 | font-weight: 300;
42 | color: #001d4d;
43 | margin: 0rem 0rem 2rem ;
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/data/data.csv:
--------------------------------------------------------------------------------
1 | ID,first_num,second_num
2 | 1,1,1
3 | 2,2,3
4 | 3,3,5
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/main.py:
--------------------------------------------------------------------------------
1 | import dash
2 | import dash_core_components as dcc
3 | import dash_html_components as html
4 | import pandas as pd
5 | import os
6 |
7 |
8 | # -------------------------- PYTHON FUNCTIONS ---------------------------- #
9 |
10 |
11 | def add_numbers(first_num,second_num):
12 | new_num = first_num + second_num
13 | return new_num
14 |
15 | def multiply_numbers(first_num,second_num):
16 | new_num = first_num * second_num
17 | return new_num
18 |
19 |
20 | def build_banner():
21 | return html.Div(
22 | id='banner',
23 | className='banner',
24 | children=[
25 | html.Img(src=app.get_asset_url('dsc-logo2.png')),
26 | ],
27 | )
28 |
29 |
30 | # -------------------------- LOAD DATA ---------------------------- #
31 |
32 |
33 | csv_files_path = os.path.join('data/data.csv')
34 |
35 | data_df = pd.read_csv(csv_files_path)
36 |
37 | add_num_list = []
38 | multiply_num_list = []
39 |
40 | for index, row in data_df.iterrows():
41 | add_num_list.append(add_numbers(row['first_num'], row['second_num']))
42 | multiply_num_list.append(multiply_numbers(row['first_num'], row['second_num']))
43 |
44 | data_df['add_num'] = add_num_list
45 | data_df['multiply_num'] = multiply_num_list
46 |
47 |
48 | # -------------------------- TEXT ---------------------------- #
49 |
50 |
51 | dash_text = '''
52 |
53 | This is an example of a DSC dashboard.
54 | '''
55 |
56 |
57 | # -------------------------- DASH ---------------------------- #
58 |
59 |
60 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
61 |
62 | app = dash.Dash(__name__, external_stylesheets=external_stylesheets, assets_folder='assets')
63 | server = app.server
64 |
65 | app.config.suppress_callback_exceptions = True
66 |
67 |
68 | # -------------------------- PROJECT DASHBOARD ---------------------------- #
69 |
70 |
71 | app.layout = html.Div(children=[
72 | html.H1(
73 | children=[
74 | build_banner(),
75 | html.P(
76 | id='instructions',
77 | children=dash_text),
78 | ]
79 | ),
80 |
81 | dcc.Graph(
82 | id='example-graph',
83 | figure={
84 | 'data': [
85 | {'x': data_df.index.values.tolist(), 'y': data_df['add_num'], 'type': 'bar', 'name': 'Add Numbers'},
86 | {'x': data_df.index.values.tolist(), 'y': data_df['multiply_num'], 'type': 'bar', 'name': 'Multiply Numbers'},
87 | ],
88 | 'layout': {
89 | 'title': 'Dash Data Visualization'
90 | }
91 | }
92 | )
93 | ])
94 |
95 |
96 |
97 | # -------------------------- MAIN ---------------------------- #
98 |
99 |
100 | if __name__ == '__main__':
101 | app.run_server(host='0.0.0.0', port=8080, debug=True, use_reloader=False)
--------------------------------------------------------------------------------
/simple-dash-app-engine-app/requirements.txt:
--------------------------------------------------------------------------------
1 | Click==7.0
2 | dash==1.6.0
3 | dash-core-components==1.5.0
4 | dash-html-components==1.0.1
5 | dash-renderer==1.2.0
6 | Flask==1.1.1
7 | Flask-Compress==1.4.0
8 | future==0.18.2
9 | itsdangerous==1.1.0
10 | Jinja2==2.10.3
11 | MarkupSafe==1.1.1
12 | numpy==1.16.5
13 | pandas==0.24.2
14 | pytz==2019.3
15 | retrying==1.3.3
16 | six==1.13.0
17 | Werkzeug==0.16.0
18 | gunicorn>=19.5.0
19 | google-api-core==1.16.0
20 | google-auth==1.10.1
21 | google-cloud-core==1.2.0
22 | google-cloud-storage==1.25.0
23 | google-resumable-media==0.5.0
24 | googleapis-common-protos==1.51.0
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/.gcloudignore:
--------------------------------------------------------------------------------
1 | # This file specifies files that are *not* uploaded to Google Cloud Platform
2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of
3 | # "#!include" directives (which insert the entries of the given .gitignore-style
4 | # file at that point).
5 | #
6 | # For more information, run:
7 | # $ gcloud topic gcloudignore
8 | #
9 | .gcloudignore
10 | # If you would like to upload your .git directory, .gitignore file or files
11 | # from your .gitignore file, remove the corresponding line
12 | # below:
13 | .git
14 | .gitignore
15 |
16 | # Python pycache:
17 | __pycache__/
18 | # Ignored by the build system
19 | /setup.cfg
20 |
21 | # images
22 | /images/*
23 |
24 | # key
25 | data/key/*
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/README.md:
--------------------------------------------------------------------------------
1 | This guide builds on our previous post about [how to deploy your dash application with Google Cloud Platform's App Engine](https://datasciencecampus.github.io/deploy-dash-with-gcp/). This time we want to use Google Cloud's storage bucket to load data, so when we change the original data, the app updates automatically on a page refresh.
2 |
3 | ## Step 1: Making Your Dash App Load Data on Page Refresh
4 |
5 | In my first draft I skipped straight to uploading your data to GCP. However, this is the easy bit. The hardest bit of this pipeline is to make your Dash application refresh the input data on page refresh. There are multiple website suggesting that using global variables, as I did here, is not the way to use Dash. This is because changes to the data will be pushed to all users of the application. This is fine for updated, initial raw data, but very bad for if filtered data is pushed to all.
6 |
7 | Here, we only push changes to the initial, raw data. Therefore, we use global variables to solve this problem.
8 |
9 | The major changes to our application from previous guides comes in the form of two python functions: `data_in()` and `app_layout()`. The full script for `main.py` can be found at the bottom of this post.
10 |
11 | **data_in()**
12 |
13 | This function replaces the 'Load Data' section in the previous guides. We also introduce a better way of deciding if we want to load the data locally, or from the cloud (and then running locally, or on the cloud). It then provides the data pandas DataFrame as before.
14 |
15 | ```
16 | def data_in():
17 |
18 | if cloud == False:
19 | data = os.path.join('data/data.csv')
20 |
21 | else:
22 | project = 'dash-example-265811'
23 | project_name = 'dash-example-265811.appspot.com'
24 | folder_name = 'data'
25 | file_name = 'data.csv'
26 |
27 | if local == True:
28 | GCP = GCPDownloaderLocal() # run locally
29 | else:
30 | GCP = GCPDownloaderCloud() # run on cloud
31 |
32 | bytes_file = GCP.getData(project, project_name, folder_name, file_name)
33 | s = str(bytes_file, encoding='utf-8')
34 | data = StringIO(s)
35 |
36 | data_df = pd.read_csv(data)
37 |
38 | add_num_list = []
39 | multiply_num_list = []
40 |
41 | for index, row in data_df.iterrows():
42 | add_num_list.append(add_numbers(row['first_num'], row['second_num']))
43 | multiply_num_list.append(multiply_numbers(row['first_num'], row['second_num']))
44 |
45 | data_df['add_num'] = add_num_list
46 | data_df['multiply_num'] = multiply_num_list
47 |
48 | return data_df
49 |
50 | ```
51 |
52 | **app_layout()**
53 |
54 | This is the magic behind Dash's ability to update the site with your new data. In essence, we have just copied what was in the 'Project Dashboard' section previously, and called the `data_in()` function at the beginning.
55 |
56 | ```
57 | def app_layout():
58 | data_df = data_in()
59 | return html.Div(children=[
60 | html.H1(
61 | children=[
62 | build_banner(),
63 | html.P(
64 | id='instructions',
65 | children=dash_text),
66 | ]
67 | ),
68 |
69 | dcc.Graph(
70 | id='example-graph',
71 | figure={
72 | 'data': [
73 | {'x': data_df.index.values.tolist(), 'y': data_df['add_num'], 'type': 'bar', 'name': 'Add Numbers'},
74 | {'x': data_df.index.values.tolist(), 'y': data_df['multiply_num'], 'type': 'bar', 'name': 'Multiply Numbers'},
75 | ],
76 | 'layout': {
77 | 'title': 'Dash Data Visualization'
78 | }
79 | }
80 | )
81 | ])
82 | ```
83 |
84 | **Other Updates**
85 |
86 | In the 'Dash' section four lines of code have been added.
87 |
88 | ```
89 | local = False
90 | cloud = True
91 |
92 | data_df = data_in()
93 |
94 | app.layout = app_layout
95 | ```
96 |
97 | The first two are just global variables specifying where you want to load and run the data. Then the data DataFrame is created initially before `app_layout` is run to update the application. Note, `app_layout` here should not have parentheses at the end.
98 |
99 | **Test and Update Data**
100 |
101 | To test your application, simply run `main.py`. The application should be available at [0.0.0.0:8080](0.0.0.0:8080). Next, alter the data in `data/data.csv`. Refresh [0.0.0.0:8080](0.0.0.0:8080) to see the graph change.
102 |
103 | ## Step 2: Upload Your Data to GCP
104 |
105 | Google's documentation provides a detailed guide on [how to upload objects](https://cloud.google.com/storage/docs/uploading-objects), here we simply this into the three main ways we use:
106 |
107 | **Using the GCP Online Browser**
108 |
109 | From Google's documentation:
110 |
111 | 1. Open the [Cloud Storage browser in the Google Cloud Console](https://console.cloud.google.com/storage/browser).
112 | 2. In the list of buckets, click on the name of the bucket that you want to upload an object to. You may want to create a separate folder within the bucket too.
113 | 3. In the Objects tab for the bucket, either:
114 | * Drag and drop the desired files from your desktop or file manager to the main pane in the Cloud Console.
115 | * Click the Upload Files button, select the files you want to upload in the dialog that appears, and click Open.
116 |
117 | **Using gsutil command line**
118 |
119 | This is probably the easiest method ([install gsutil guide](https://cloud.google.com/storage/docs/gsutil_install)). Simply type into your command line:
120 |
121 | ```
122 | gsutil cp [OBJECT_LOCATION] gs://[DESTINATION_BUCKET_NAME]/
123 | ```
124 |
125 | **Using Python**
126 |
127 | In our [GitHub repository](https://github.com/datasciencecampus/deploy-dash-with-gcp) linked to this guide we provide a python file called `dataUpload.py` (code at the bottom of this post, also). This can be used to upload data to a specified bucket. However, this involves multiple additional steps to authorise your credentials - however, you will need to do this to download the data anyway!
128 |
129 | 1. Open the [Cloud Storage browser in the Google Cloud Console](https://console.cloud.google.com/storage/browser).
130 | 2. Hover over `APIs & Services` on the left hand side, and then click `Credentials`
131 | 3. Click `Create credentials`, then `Service account key`.
132 | 
133 | 4. Select `New service account`, then enter the service account name, change the role to Owner, and make sure the download is set to JSON.
134 | 
135 | 5. Save in a safe place (not `/Downloads`!)
136 |
137 | Lastly, you want to tell `dataUpload.py` where to look for the key. To avoid exposing key locations online, create a new file called `key_location.json` in `/data/keys` with the structure:
138 |
139 |
140 | ```
141 | {
142 | "data": {
143 | "key_location": "/path/to/file.json"
144 | }
145 | }
146 | ```
147 |
148 | Note, `key_location.json` will not be uploaded to GitHub or GCP as it is included in `.gitignore` and `.googleignore` files. However, if you delete or edit these, it may.
149 |
150 | ## Step 3: Link the Data to Your Dash App and Test Locally
151 |
152 | Before pushing the changes back to GCP. It is worth checking your data can be retrieved and loaded correctly. If you haven't downloaded the credential JSON key as shown above (and created the `key_location.json` file to show where the key can be found), please do so now.
153 |
154 | We can then link the data in `main.py` by changing the global variables '`local` and `cloud` to both be `True`.
155 |
156 | This will call the `GCPDownloaderLocal` class from the `dataDownloader.py` python script and obtain the data. This python script is available at our [GitHub repository](https://github.com/datasciencecampus/deploy-dash-with-gcp) and the bottom of this post.
157 |
158 | Your Dash app should be available as normal at [0.0.0.0:8000](0.0.0.0:8000).
159 |
160 | ## Step 4: Push the Changes to GCP
161 |
162 | Whereas when you run the code locally you require the JSON authentication, you do not when the data and dash application are part of the same project on Google Cloud Platform.
163 |
164 | In `main.py` change the global variable '`local` to be `False` and `cloud` to be `True`. This will call the `GCPDownloaderCloud` class in `dataDownloader.py`.
165 |
166 | This will call the `downloadDataCloud.py` python script.
167 |
168 | Next, push the changes to GCP using the gcloud command line tool.
169 |
170 | First, check your project is active in gcloud using:
171 |
172 | `gcloud config get-value project`
173 |
174 | Which will print the following on screen:
175 |
176 | ```
177 | Your active configuration is: [default]
178 |
179 | my-project-id
180 | ```
181 |
182 | To change the project to your desired project, type:
183 |
184 | `gcloud config set project project-id`
185 |
186 | Next, to deploy, type:
187 |
188 | `gcloud app deploy`
189 |
190 | Click `y` when prompted. Your app will be available at:
191 |
192 | `https://project-id.appspot.com/`
193 |
194 | The screenshot below shows the original data in `data.csv`.
195 |
196 | 
197 |
198 | The screenshot below shows the edited data in `data.csv`, which we pushed to the GCP bucket.
199 |
200 | 
201 |
202 | This app is running [here](https://simple-dash-app-with-a-bucket-dot-dash-example-265811.appspot.com).
203 |
204 | Visit our [GitHub repository](https://github.com/datasciencecampus/deploy-dash-with-gcp/tree/master/simple-dash-app-using-a-bucket) to view all the files.
205 |
206 | ## main.py script
207 |
208 | ```
209 | import dash
210 | import dash_core_components as dcc
211 | import dash_html_components as html
212 | import pandas as pd
213 | from io import StringIO
214 | import os
215 |
216 | from data.dataDownloader import GCPDownloaderLocal, GCPDownloaderCloud
217 |
218 | # -------------------------- PYTHON FUNCTIONS ---------------------------- #
219 |
220 |
221 | def add_numbers(first_num,second_num):
222 | new_num = first_num + second_num
223 | return new_num
224 |
225 | def multiply_numbers(first_num,second_num):
226 | new_num = first_num * second_num
227 | return new_num
228 |
229 |
230 | def build_banner():
231 | return html.Div(
232 | id='banner',
233 | className='banner',
234 | children=[
235 | html.Img(src=app.get_asset_url('dsc-logo2.png')),
236 | ],
237 | )
238 |
239 | def data_in():
240 |
241 | if cloud == False:
242 | data = os.path.join('data/data.csv')
243 |
244 | else:
245 | project = 'dash-example-265811'
246 | project_name = 'dash-example-265811.appspot.com'
247 | folder_name = 'data'
248 | file_name = 'data.csv'
249 |
250 | if local == True:
251 | GCP = GCPDownloaderLocal() # run locally
252 | else:
253 | GCP = GCPDownloaderCloud() # run on cloud
254 |
255 | bytes_file = GCP.getData(project, project_name, folder_name, file_name)
256 | s = str(bytes_file, encoding='utf-8')
257 | data = StringIO(s)
258 |
259 | data_df = pd.read_csv(data)
260 |
261 | add_num_list = []
262 | multiply_num_list = []
263 |
264 | for index, row in data_df.iterrows():
265 | add_num_list.append(add_numbers(row['first_num'], row['second_num']))
266 | multiply_num_list.append(multiply_numbers(row['first_num'], row['second_num']))
267 |
268 | data_df['add_num'] = add_num_list
269 | data_df['multiply_num'] = multiply_num_list
270 |
271 | return data_df
272 |
273 | def app_layout():
274 | data_df = data_in()
275 | return html.Div(children=[
276 | html.H1(
277 | children=[
278 | build_banner(),
279 | html.P(
280 | id='instructions',
281 | children=dash_text),
282 | ]
283 | ),
284 |
285 | dcc.Graph(
286 | id='example-graph',
287 | figure={
288 | 'data': [
289 | {'x': data_df.index.values.tolist(), 'y': data_df['add_num'], 'type': 'bar', 'name': 'Add Numbers'},
290 | {'x': data_df.index.values.tolist(), 'y': data_df['multiply_num'], 'type': 'bar', 'name': 'Multiply Numbers'},
291 | ],
292 | 'layout': {
293 | 'title': 'Dash Data Visualization'
294 | }
295 | }
296 | )
297 | ])
298 |
299 |
300 | # -------------------------- TEXT ---------------------------- #
301 |
302 |
303 | dash_text = '''
304 |
305 | This is an example of a DSC dashboard.
306 | '''
307 |
308 |
309 | # -------------------------- DASH ---------------------------- #
310 |
311 |
312 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
313 |
314 | app = dash.Dash(__name__, external_stylesheets=external_stylesheets, assets_folder='assets')
315 | server = app.server
316 |
317 | local = False
318 | cloud = True
319 |
320 | data_df = data_in()
321 |
322 | app.layout = app_layout
323 | app.config.suppress_callback_exceptions = True
324 |
325 |
326 | # -------------------------- MAIN ---------------------------- #
327 |
328 |
329 | if __name__ == '__main__':
330 | app.run_server(host='0.0.0.0', port=8080, debug=True, use_reloader=False)
331 |
332 | ```
333 |
334 | ## app.yaml files
335 |
336 | ```
337 | service: default
338 | runtime: python37
339 |
340 | basic_scaling:
341 | max_instances: 2
342 | idle_timeout: 10m
343 |
344 | resources:
345 | cpu: 1
346 | memory_gb: 1
347 | disk_size_gb: 10
348 |
349 | entrypoint: gunicorn -b 0.0.0.0:8080 main:server
350 | ```
351 |
352 | ## dataDownloader.py script
353 |
354 | ```
355 | from google.cloud import storage
356 | from google.auth import compute_engine
357 | import os
358 | import json
359 |
360 |
361 | class GCPDownloaderLocal:
362 | def __init__(self):
363 | self.__key_location_file = os.path.abspath(os.path.join(os.path.dirname(__file__),'key'))
364 | with open(self.__key_location_file + '/key_location.json', 'r') as json_file:
365 | self.__data = json.loads(json_file.read())
366 | self.__key_path = self.__data['data']['key_location']
367 | self.__storage_client = storage.Client.from_service_account_json(self.__key_path)
368 |
369 | def getData(self, project, project_name, folder_name, file_name):
370 | bucket = self.__storage_client.get_bucket(project_name)
371 | blob = bucket.blob(folder_name + '/' + file_name)
372 | content = blob.download_as_string()
373 | return content
374 |
375 |
376 | class GCPDownloaderCloud:
377 |
378 | def getData(self, project, project_name, folder_name, file_name):
379 | credentials = compute_engine.Credentials()
380 | storage_client = storage.Client(credentials=credentials, project=project)
381 | bucket = storage_client.get_bucket(project_name)
382 | blob = bucket.blob(folder_name + '/' + file_name)
383 | content = blob.download_as_string()
384 | return content
385 | ```
386 |
387 | ## dataUpload.py script
388 |
389 | ```
390 | from google.cloud import storage
391 | import os
392 | import json
393 |
394 |
395 | class GCPUploader:
396 | def __init__(self):
397 | self.__key_location_file = os.path.abspath(os.path.join(os.path.dirname(__file__),'key'))
398 | with open(self.__key_location_file + '/key_location.json', 'r') as json_file:
399 | self.__data = json.loads(json_file.read())
400 | self.__key_path = self.__data['data']['key_location']
401 | self.__storage_client = storage.Client.from_service_account_json(self.__key_path)
402 |
403 | def upload_blob(self, project_name, df, destination_blob_name):
404 | """Uploads a file to the bucket."""
405 |
406 | bucket = self.__storage_client.bucket(project_name)
407 | blob = bucket.blob(destination_blob_name)
408 | blob.upload_from_string(df.to_csv(), 'text/csv')
409 |
410 | ```
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/simple-dash-app-using-a-bucket/__init__.py
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/app.yaml:
--------------------------------------------------------------------------------
1 | service: simple-dash-app-with-a-bucket
2 | runtime: python37
3 |
4 | basic_scaling:
5 | max_instances: 2
6 | idle_timeout: 10m
7 |
8 | resources:
9 | cpu: 1
10 | memory_gb: 1
11 | disk_size_gb: 10
12 |
13 | entrypoint: gunicorn -b 0.0.0.0:8080 main:server
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/assets/base.css:
--------------------------------------------------------------------------------
1 | /* Table of contents
2 | ––––––––––––––––––––––––––––––––––––––––––––––––––
3 | - Plotly.js
4 | - Grid
5 | - Base Styles
6 | - Typography
7 | - Links
8 | - Buttons
9 | - Forms
10 | - Lists
11 | - Code
12 | - Tables
13 | - Spacing
14 | - Utilities
15 | - Clearing
16 | - Media Queries
17 | */
18 |
19 |
20 | /* Grid
21 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
22 | .container {
23 | position: relative;
24 | width: 100%;
25 | max-width: 960px;
26 | margin: 0 auto;
27 | padding: 0 20px;
28 | box-sizing: border-box; }
29 | .column,
30 | .columns {
31 | width: 100%;
32 | float: left;
33 | box-sizing: border-box; }
34 |
35 | /* For devices larger than 400px */
36 | @media (min-width: 400px) {
37 | .container {
38 | width: 85%;
39 | padding: 0; }
40 | }
41 |
42 | /* For devices larger than 550px */
43 | @media (min-width: 550px) {
44 | .container {
45 | width: 80%; }
46 | .column,
47 | .columns {
48 | margin-left: 2%; }
49 | .column:first-child,
50 | .columns:first-child {
51 | margin-left: 1%; }
52 |
53 | .one.column,
54 | .one.columns { width: 4.66666666667%; }
55 | .two.columns { width: 13.3333333333%; }
56 | .three.columns { width: 22%; }
57 | .four.columns { width: 30.6666666667%; }
58 | .five.columns { width: 39.3333333333%; }
59 | .six.columns { width: 48%; }
60 | .seven.columns { width: 56.6666666667%; }
61 | .eight.columns { width: 65.3333333333%; }
62 | .nine.columns { width: 74.0%; }
63 | .ten.columns { width: 82.6666666667%; }
64 | .eleven.columns { width: 91.3333333333%; }
65 | .twelve.columns { width: 100%; margin-left: 0; }
66 |
67 | .one-third.column { width: 30.6666666667%; }
68 | .two-thirds.column { width: 65.3333333333%; }
69 |
70 | .one-half.column { width: 48%; }
71 |
72 | /* Offsets */
73 | .offset-by-one.column,
74 | .offset-by-one.columns { margin-left: 8.66666666667%; }
75 | .offset-by-two.column,
76 | .offset-by-two.columns { margin-left: 17.3333333333%; }
77 | .offset-by-three.column,
78 | .offset-by-three.columns { margin-left: 26%; }
79 | .offset-by-four.column,
80 | .offset-by-four.columns { margin-left: 34.6666666667%; }
81 | .offset-by-five.column,
82 | .offset-by-five.columns { margin-left: 43.3333333333%; }
83 | .offset-by-six.column,
84 | .offset-by-six.columns { margin-left: 52%; }
85 | .offset-by-seven.column,
86 | .offset-by-seven.columns { margin-left: 60.6666666667%; }
87 | .offset-by-eight.column,
88 | .offset-by-eight.columns { margin-left: 69.3333333333%; }
89 | .offset-by-nine.column,
90 | .offset-by-nine.columns { margin-left: 78.0%; }
91 | .offset-by-ten.column,
92 | .offset-by-ten.columns { margin-left: 86.6666666667%; }
93 | .offset-by-eleven.column,
94 | .offset-by-eleven.columns { margin-left: 95.3333333333%; }
95 |
96 | .offset-by-one-third.column,
97 | .offset-by-one-third.columns { margin-left: 34.6666666667%; }
98 | .offset-by-two-thirds.column,
99 | .offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
100 |
101 | .offset-by-one-half.column,
102 | .offset-by-one-half.columns { margin-left: 52%; }
103 |
104 | }
105 |
106 |
107 | /* Base Styles
108 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
109 | /* NOTE
110 | html is set to 62.5% so that all the REM measurements throughout Skeleton
111 | are based on 10px sizing. So basically 1.5rem = 15px :) */
112 | html {
113 | font-size: 62.5%; }
114 |
115 | body {
116 | font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
117 | line-height: 1.6;
118 | font-weight: 400;
119 | font-family: "Open Sans", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
120 | color: rgb(50, 50, 50);
121 | margin: 0;
122 | }
123 |
124 |
125 | /* Typography
126 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
127 | h1, h2, h3, h4, h5, h6 {
128 | margin-top: 0;
129 | margin-bottom: 0;
130 | font-weight: 300; }
131 | h1 { font-size: 4.5rem; line-height: 1.2; letter-spacing: -.1rem; margin-bottom: 2rem; }
132 | h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; margin-bottom: 1.8rem; margin-top: 1.8rem;}
133 | h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; margin-bottom: 1.5rem; margin-top: 1.5rem;}
134 | h4 { font-size: 2.6rem; line-height: 1.35; letter-spacing: -.08rem; margin-bottom: 1.2rem; margin-top: 1.2rem;}
135 | h5 { font-size: 2.2rem; line-height: 1.5; letter-spacing: -.05rem; margin-bottom: 0.6rem; margin-top: 0.6rem;}
136 | h6 { font-size: 2.0rem; line-height: 1.6; letter-spacing: 0; margin-bottom: 0.75rem; margin-top: 0.75rem;}
137 |
138 | p {
139 | margin-top: 0; }
140 |
141 |
142 | /* Blockquotes
143 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
144 | blockquote {
145 | border-left: 4px lightgrey solid;
146 | padding-left: 1rem;
147 | margin-top: 2rem;
148 | margin-bottom: 2rem;
149 | margin-left: 0rem;
150 | }
151 |
152 |
153 | /* Links
154 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
155 | a {
156 | color: #1EAEDB;
157 | text-decoration: underline;
158 | cursor: pointer;}
159 | a:hover {
160 | color: #0FA0CE; }
161 |
162 |
163 | /* Buttons
164 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
165 | .button,
166 | button,
167 | input[type="submit"],
168 | input[type="reset"],
169 | input[type="button"] {
170 | display: inline-block;
171 | height: 38px;
172 | padding: 0 30px;
173 | color: #555;
174 | text-align: center;
175 | font-size: 11px;
176 | font-weight: 600;
177 | line-height: 38px;
178 | letter-spacing: .1rem;
179 | text-transform: uppercase;
180 | text-decoration: none;
181 | white-space: nowrap;
182 | background-color: transparent;
183 | border-radius: 4px;
184 | border: 1px solid #bbb;
185 | cursor: pointer;
186 | box-sizing: border-box; }
187 | .button:hover,
188 | button:hover,
189 | input[type="submit"]:hover,
190 | input[type="reset"]:hover,
191 | input[type="button"]:hover,
192 | .button:focus,
193 | button:focus,
194 | input[type="submit"]:focus,
195 | input[type="reset"]:focus,
196 | input[type="button"]:focus {
197 | color: #333;
198 | border-color: #888;
199 | outline: 0; }
200 | .button.button-primary,
201 | button.button-primary,
202 | input[type="submit"].button-primary,
203 | input[type="reset"].button-primary,
204 | input[type="button"].button-primary {
205 | color: #FFF;
206 | background-color: #33C3F0;
207 | border-color: #33C3F0; }
208 | .button.button-primary:hover,
209 | button.button-primary:hover,
210 | input[type="submit"].button-primary:hover,
211 | input[type="reset"].button-primary:hover,
212 | input[type="button"].button-primary:hover,
213 | .button.button-primary:focus,
214 | button.button-primary:focus,
215 | input[type="submit"].button-primary:focus,
216 | input[type="reset"].button-primary:focus,
217 | input[type="button"].button-primary:focus {
218 | color: #FFF;
219 | background-color: #1EAEDB;
220 | border-color: #1EAEDB; }
221 |
222 |
223 | /* Forms
224 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
225 | input[type="email"],
226 | input[type="number"],
227 | input[type="search"],
228 | input[type="text"],
229 | input[type="tel"],
230 | input[type="url"],
231 | input[type="password"],
232 | textarea,
233 | select {
234 | height: 38px;
235 | padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
236 | background-color: #fff;
237 | border: 1px solid #D1D1D1;
238 | border-radius: 4px;
239 | box-shadow: none;
240 | box-sizing: border-box;
241 | font-family: inherit;
242 | font-size: inherit; /*https://stackoverflow.com/questions/6080413/why-doesnt-input-inherit-the-font-from-body*/}
243 | /* Removes awkward default styles on some inputs for iOS */
244 | input[type="email"],
245 | input[type="number"],
246 | input[type="search"],
247 | input[type="text"],
248 | input[type="tel"],
249 | input[type="url"],
250 | input[type="password"],
251 | textarea {
252 | -webkit-appearance: none;
253 | -moz-appearance: none;
254 | appearance: none; }
255 | textarea {
256 | min-height: 65px;
257 | padding-top: 6px;
258 | padding-bottom: 6px; }
259 | input[type="email"]:focus,
260 | input[type="number"]:focus,
261 | input[type="search"]:focus,
262 | input[type="text"]:focus,
263 | input[type="tel"]:focus,
264 | input[type="url"]:focus,
265 | input[type="password"]:focus,
266 | textarea:focus,
267 | /*select:focus {*/
268 | /* border: 1px solid #33C3F0;*/
269 | /* outline: 0; }*/
270 | label,
271 | legend {
272 | display: block;
273 | margin-bottom: 0px; }
274 | fieldset {
275 | padding: 0;
276 | border-width: 0; }
277 | input[type="checkbox"],
278 | input[type="radio"] {
279 | display: inline; }
280 | label > .label-body {
281 | display: inline-block;
282 | margin-left: .5rem;
283 | font-weight: normal; }
284 |
285 |
286 | /* Lists
287 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
288 | ul {
289 | list-style: circle inside; }
290 | ol {
291 | list-style: decimal inside; }
292 | ol, ul {
293 | padding-left: 0;
294 | margin-top: 0; }
295 | ul ul,
296 | ul ol,
297 | ol ol,
298 | ol ul {
299 | margin: 1.5rem 0 1.5rem 3rem;
300 | font-size: 90%; }
301 | li {
302 | margin-bottom: 1rem; }
303 |
304 |
305 | /* Tables
306 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
307 | table {
308 | border-collapse: collapse;
309 | }
310 | th,
311 | td {
312 | padding: 12px 15px;
313 | text-align: left;
314 | border-bottom: 1px solid #E1E1E1; }
315 | th:first-child,
316 | td:first-child {
317 | padding-left: 0; }
318 | th:last-child,
319 | td:last-child {
320 | padding-right: 0; }
321 |
322 |
323 | /* Spacing
324 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
325 | button,
326 | .button {
327 | margin-bottom: 0rem; }
328 | input,
329 | textarea,
330 | select,
331 | fieldset {
332 | margin-bottom: 0rem; }
333 | pre,
334 | dl,
335 | figure,
336 | table,
337 | form {
338 | margin-bottom: 0rem; }
339 | p,
340 | ul,
341 | ol {
342 | margin-bottom: 0.75rem; }
343 |
344 | /* Utilities
345 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
346 | .u-full-width {
347 | width: 100%;
348 | box-sizing: border-box; }
349 | .u-max-full-width {
350 | max-width: 100%;
351 | box-sizing: border-box; }
352 | .u-pull-right {
353 | float: right; }
354 | .u-pull-left {
355 | float: left; }
356 |
357 |
358 | /* Misc
359 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
360 | hr {
361 | margin-top: 3rem;
362 | margin-bottom: 3.5rem;
363 | border-width: 0;
364 | border-top: 1px solid #E1E1E1; }
365 |
366 |
367 | /* Clearing
368 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
369 |
370 | /* Self Clearing Goodness */
371 | .container:after,
372 | .row:after,
373 | .u-cf {
374 | content: "";
375 | display: table;
376 | clear: both; }
377 |
378 |
379 | /* Media Queries
380 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
381 | /*
382 | Note: The best way to structure the use of media queries is to create the queries
383 | near the relevant code. For example, if you wanted to change the styles for buttons
384 | on small devices, paste the mobile query code up in the buttons section and style it
385 | there.
386 | */
387 |
388 |
389 | /* Larger than mobile */
390 | @media (min-width: 400px) {}
391 |
392 | /* Larger than phablet (also point when grid becomes active) */
393 | @media (min-width: 550px) {}
394 |
395 | /* Larger than tablet */
396 | @media (min-width: 750px) {}
397 |
398 | /* Larger than desktop */
399 | @media (min-width: 1000px) {}
400 |
401 | /* Larger than Desktop HD */
402 | @media (min-width: 1200px) {}
403 |
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/assets/dsc-logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/simple-dash-app-using-a-bucket/assets/dsc-logo2.png
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/assets/dsc.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,800');
2 | @import url('https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300,700');
3 |
4 | .container.scalable {
5 | width: 95%;
6 | max-width: none;
7 | }
8 |
9 | @media print {
10 | body {-webkit-print-color-adjust: exact;}
11 | }
12 |
13 | /* Remove Undo
14 | –––––––––––––––––––––––––––––––––––––––––––––––––– */
15 | ._dash-undo-redo {
16 | display: none;
17 | }
18 |
19 |
20 | @media screen {
21 |
22 | .banner Img {
23 | height: 5rem;
24 | margin-bottom: 1rem;
25 | }
26 |
27 | #instructions {
28 | font-size: 2rem;
29 | font-family: 'Open Sans', sans-serif;
30 | font-weight: 300;
31 | color: #001d4d;
32 | margin: 0rem 0rem 2rem ;
33 | }
34 | }
35 |
36 | @media print {
37 |
38 | #instructions {
39 | font-size: 2rem;
40 | font-family: 'Open Sans', sans-serif;
41 | font-weight: 300;
42 | color: #001d4d;
43 | margin: 0rem 0rem 2rem ;
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/data/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/datasciencecampus/deploy-dash-with-gcp/eea72bf55e3a574f869bfa6c0e67a4a76d1bdfa7/simple-dash-app-using-a-bucket/data/__init__.py
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/data/data.csv:
--------------------------------------------------------------------------------
1 | ID,first_num,second_num
2 | 1,1,1
3 | 2,2,3
4 | 3,3,5
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/data/dataDownloader.py:
--------------------------------------------------------------------------------
1 | from google.cloud import storage
2 | from google.auth import compute_engine
3 | import os
4 | import json
5 |
6 |
7 | class GCPDownloaderLocal:
8 | def __init__(self):
9 | self.__key_location_file = os.path.abspath(os.path.join(os.path.dirname(__file__),'key'))
10 | with open(self.__key_location_file + '/key_location.json', 'r') as json_file:
11 | self.__data = json.loads(json_file.read())
12 | self.__key_path = self.__data['data']['key_location']
13 | self.__storage_client = storage.Client.from_service_account_json(self.__key_path)
14 |
15 | def getData(self, project, project_name, folder_name, file_name):
16 | bucket = self.__storage_client.get_bucket(project_name)
17 | blob = bucket.blob(folder_name + '/' + file_name)
18 | content = blob.download_as_string()
19 | return content
20 |
21 |
22 | class GCPDownloaderCloud:
23 |
24 | def getData(self, project, project_name, folder_name, file_name):
25 | credentials = compute_engine.Credentials()
26 | storage_client = storage.Client(credentials=credentials, project=project)
27 | bucket = storage_client.get_bucket(project_name)
28 | blob = bucket.blob(folder_name + '/' + file_name)
29 | content = blob.download_as_string()
30 | return content
31 |
32 |
33 | if __name__ == '__main__':
34 | project_name = 'dash-example-265811.appspot.com'
35 | folder_name = 'data'
36 | file_name = 'data.csv'
37 | GCP = GCPDownloaderLocal()
38 | content = GCP.getData(project_name, folder_name, file_name)
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/data/dataUpload.py:
--------------------------------------------------------------------------------
1 | from google.cloud import storage
2 | import os
3 | import json
4 |
5 |
6 | class GCPUploader:
7 | def __init__(self):
8 | self.__key_location_file = os.path.abspath(os.path.join(os.path.dirname(__file__),'key'))
9 | with open(self.__key_location_file + '/key_location.json', 'r') as json_file:
10 | self.__data = json.loads(json_file.read())
11 | self.__key_path = self.__data['data']['key_location']
12 | self.__storage_client = storage.Client.from_service_account_json(self.__key_path)
13 |
14 | def upload_blob(self, project_name, df, destination_blob_name):
15 | """Uploads a file to the bucket."""
16 |
17 | bucket = self.__storage_client.bucket(project_name)
18 | blob = bucket.blob(destination_blob_name)
19 | blob.upload_from_string(df.to_csv(), 'text/csv')
20 |
21 |
22 | if __name__ == '__main__':
23 |
24 | df = create_data_frame()
25 | project_name =
26 | destination_blob_name =
27 | upload = GCPUploader()
28 | upload.upload_blob(project_name, df, destination_blob_name)
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/main.py:
--------------------------------------------------------------------------------
1 | import dash
2 | import dash_core_components as dcc
3 | import dash_html_components as html
4 | import pandas as pd
5 | from io import StringIO
6 | import os
7 |
8 | from data.dataDownloader import GCPDownloaderLocal, GCPDownloaderCloud
9 |
10 | # -------------------------- PYTHON FUNCTIONS ---------------------------- #
11 |
12 |
13 | def add_numbers(first_num,second_num):
14 | new_num = first_num + second_num
15 | return new_num
16 |
17 | def multiply_numbers(first_num,second_num):
18 | new_num = first_num * second_num
19 | return new_num
20 |
21 |
22 | def build_banner():
23 | return html.Div(
24 | id='banner',
25 | className='banner',
26 | children=[
27 | html.Img(src=app.get_asset_url('dsc-logo2.png')),
28 | ],
29 | )
30 |
31 | def data_in():
32 |
33 | if cloud == False:
34 | data = os.path.join('data/data.csv')
35 |
36 | else:
37 | project = 'dash-example-265811'
38 | project_name = 'dash-example-265811.appspot.com'
39 | folder_name = 'data'
40 | file_name = 'data.csv'
41 |
42 | if local == True:
43 | GCP = GCPDownloaderLocal() # run locally
44 | else:
45 | GCP = GCPDownloaderCloud() # run on cloud
46 |
47 | bytes_file = GCP.getData(project, project_name, folder_name, file_name)
48 | s = str(bytes_file, encoding='utf-8')
49 | data = StringIO(s)
50 |
51 | data_df = pd.read_csv(data)
52 |
53 | add_num_list = []
54 | multiply_num_list = []
55 |
56 | for index, row in data_df.iterrows():
57 | add_num_list.append(add_numbers(row['first_num'], row['second_num']))
58 | multiply_num_list.append(multiply_numbers(row['first_num'], row['second_num']))
59 |
60 | data_df['add_num'] = add_num_list
61 | data_df['multiply_num'] = multiply_num_list
62 |
63 | return data_df
64 |
65 | def app_layout():
66 | data_df = data_in()
67 | return html.Div(children=[
68 | html.H1(
69 | children=[
70 | build_banner(),
71 | html.P(
72 | id='instructions',
73 | children=dash_text),
74 | ]
75 | ),
76 |
77 | dcc.Graph(
78 | id='example-graph',
79 | figure={
80 | 'data': [
81 | {'x': data_df.index.values.tolist(), 'y': data_df['add_num'], 'type': 'bar', 'name': 'Add Numbers'},
82 | {'x': data_df.index.values.tolist(), 'y': data_df['multiply_num'], 'type': 'bar', 'name': 'Multiply Numbers'},
83 | ],
84 | 'layout': {
85 | 'title': 'Dash Data Visualization'
86 | }
87 | }
88 | )
89 | ])
90 |
91 |
92 | # -------------------------- TEXT ---------------------------- #
93 |
94 |
95 | dash_text = '''
96 |
97 | This is an example of a DSC dashboard.
98 | '''
99 |
100 |
101 | # -------------------------- DASH ---------------------------- #
102 |
103 |
104 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
105 |
106 | app = dash.Dash(__name__, external_stylesheets=external_stylesheets, assets_folder='assets')
107 | server = app.server
108 |
109 | local = False
110 | cloud = True
111 |
112 | data_df = data_in()
113 |
114 | app.layout = app_layout
115 | app.config.suppress_callback_exceptions = True
116 |
117 |
118 | # -------------------------- MAIN ---------------------------- #
119 |
120 |
121 | if __name__ == '__main__':
122 | app.run_server(host='0.0.0.0', port=8080, debug=True, use_reloader=False)
--------------------------------------------------------------------------------
/simple-dash-app-using-a-bucket/requirements.txt:
--------------------------------------------------------------------------------
1 | Click==7.0
2 | dash==1.6.0
3 | dash-core-components==1.5.0
4 | dash-html-components==1.0.1
5 | dash-renderer==1.2.0
6 | Flask==1.1.1
7 | Flask-Compress==1.4.0
8 | future==0.18.2
9 | itsdangerous==1.1.0
10 | Jinja2==2.10.3
11 | MarkupSafe==1.1.1
12 | numpy==1.16.5
13 | pandas==0.24.2
14 | pytz==2019.3
15 | retrying==1.3.3
16 | six==1.13.0
17 | Werkzeug==0.16.0
18 | gunicorn>=19.5.0
19 | google-api-core==1.16.0
20 | google-auth==1.10.1
21 | google-cloud-core==1.2.0
22 | google-cloud-storage==1.25.0
23 | google-resumable-media==0.5.0
24 | googleapis-common-protos==1.51.0
--------------------------------------------------------------------------------