├── tests ├── __init__.py └── test_cookiecutter.py ├── {{cookiecutter.repo_name}} ├── runtime.txt ├── Procfile ├── {{cookiecutter.package_name}} │ ├── exceptions.py │ ├── test_{{cookiecutter.package_name}}.py │ ├── .env.example │ └── app.py ├── utility │ └── setup_virtualenv_and_repo.sh ├── requirements.txt ├── .gitignore ├── .codeclimate.yml ├── .travis.yml ├── codecov.yml ├── .pyup.yml ├── README.md └── LICENSE ├── .gitignore ├── .codeclimate.yml ├── .travis.yml ├── codecov.yml ├── cookiecutter.json ├── requirements.txt ├── .pyup.yml ├── LICENSE └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.6.4 -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn {{cookiecutter.package_name}}.app:server --bind 0.0.0.0:$PORT --timeout 300 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editors 2 | **/__pycache__/ 3 | .idea/ 4 | 5 | # pytest 6 | .cache/ 7 | tests/.cache/ 8 | .pytest_cache/ 9 | 10 | .coverage 11 | 12 | # environment variables 13 | *.env 14 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.package_name}}/exceptions.py: -------------------------------------------------------------------------------- 1 | class ImproperlyConfigured(Exception): 2 | """Raise this exception when an environment variable is not set. 3 | """ 4 | pass 5 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/utility/setup_virtualenv_and_repo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkvirtualenv {{cookiecutter.repo_name}} --python=python3.6 3 | pip install -r requirements.txt 4 | git init 5 | git add . 6 | git commit -m "Initial commit" 7 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | pep8: 3 | enabled: true 4 | duplication: 5 | enabled: true 6 | config: 7 | languages: 8 | - python 9 | fixme: 10 | enabled: true 11 | ratings: 12 | paths: 13 | - "**.py" -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements.txt: -------------------------------------------------------------------------------- 1 | dash==0.21.1 2 | dash-core-components==0.23.0 3 | dash-html-components==0.11.0 4 | dash-renderer==0.13.0 5 | gunicorn==19.8.1 # for the deployment on Heroku 6 | plotly==2.6.0 7 | pytest==3.6.0 8 | python-dotenv==0.8.2 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | branches: 3 | only: 4 | - master 5 | language: python 6 | python: 7 | - 3.6 8 | install: 9 | - pip install -r requirements.txt 10 | script: 11 | # - python setup.py install 12 | - coverage run -m pytest tests -v 13 | after_success: 14 | - codecov 15 | notifications: 16 | email: 17 | on_success: change 18 | on_failure: always -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Editors 2 | **/__pycache__/ 3 | # exclude all PyCharm stuff but keep all shared run configurations 4 | # See here for this pattern: https://stackoverflow.com/a/5534865 5 | !/.idea/ 6 | /.idea/* 7 | !/.idea/runConfigurations/ 8 | 9 | # pytest 10 | .cache/ 11 | tests/.cache/ 12 | .pytest_cache/ 13 | 14 | # environment variables 15 | *.env 16 | 17 | *.log 18 | *.db 19 | *.pyc 20 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | pep8: 3 | enabled: true 4 | eslint: 5 | enabled: true 6 | csslint: 7 | enabled: true 8 | scss-lint: 9 | enabled: true 10 | duplication: 11 | enabled: true 12 | config: 13 | languages: 14 | - python 15 | - javascript 16 | fixme: 17 | enabled: true 18 | ratings: 19 | paths: 20 | - "**.py" 21 | - "**.js" 22 | - "**.css" 23 | - "**.scss" 24 | exclude_paths: [] -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | branches: 3 | only: 4 | - master 5 | language: python 6 | env: 7 | - FLASK_APP=autoapp.py FLASK_DEBUG=1 8 | python: 9 | - 3.5 10 | - 3.6 11 | install: 12 | - pip install -r requirements.txt 13 | script: 14 | # - python setup.py install 15 | # - flask test 16 | - coverage run -m pytest -v tests 17 | after_success: 18 | - codecov 19 | notifications: 20 | email: 21 | on_success: change 22 | on_failure: always -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | # This codecov.yml is the default configuration for 2 | # all repositories on Codecov. You may adjust the settings 3 | # below in your own codecov.yml in your repository. 4 | # 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: 70...100 9 | 10 | status: 11 | # Learn more at http://docs.codecov.io/docs/codecov-yaml 12 | project: true 13 | patch: true 14 | changes: false 15 | 16 | comment: 17 | layout: "header, diff" 18 | behavior: default # update if exists else create new 19 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_name": "My Dash App", 3 | "repo_name": "{{ cookiecutter.project_name.lower()|replace(' ', '-') }}", 4 | "package_name": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_') }}", 5 | "author_name": "Giacomo Debidda", 6 | "github_username": "jackdbd", 7 | "email": "you@example.com", 8 | "description": "A short description of the project.", 9 | "version": "0.1.0", 10 | "open_source_license": ["MIT", "BSD", "GPLv3", "Not open source"] 11 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | arrow==0.12.1 2 | attrs==18.1.0 3 | binaryornot==0.4.4 4 | black==18.5b1 5 | certifi==2018.4.16 6 | chardet==3.0.4 7 | click==6.7 8 | codecov==2.0.15 9 | cookiecutter==1.6.0 10 | coverage==4.5.1 11 | future==0.16.0 12 | idna==2.6 13 | Jinja2==2.10 14 | jinja2-time==0.2.0 15 | MarkupSafe==1.0 16 | more-itertools==4.2.0 17 | pluggy==0.6.0 18 | poyo==0.4.1 19 | py==1.5.3 20 | pytest==3.6.0 21 | pytest-cookies==0.3.0 22 | python-dateutil==2.7.3 23 | requests==2.18.4 24 | six==1.11.0 25 | urllib3==1.22 26 | whichcraft==0.4.1 27 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/codecov.yml: -------------------------------------------------------------------------------- 1 | # This codecov.yml is the default configuration for 2 | # all repositories on Codecov. You may adjust the settings 3 | # below in your own codecov.yml in your repository. 4 | # 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: 70...100 9 | 10 | status: 11 | # Learn more at http://docs.codecov.io/docs/codecov-yaml 12 | project: true 13 | patch: true 14 | changes: false 15 | 16 | comment: 17 | layout: "header, diff" 18 | behavior: default # update if exists else create new 19 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.package_name}}/test_{{cookiecutter.package_name}}.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import dash_html_components as html 3 | from app import app 4 | from exceptions import ImproperlyConfigured 5 | 6 | 7 | def f(): 8 | raise ImproperlyConfigured('TODO') 9 | 10 | def test_exception_is_raised(): 11 | with pytest.raises(ImproperlyConfigured): 12 | f() 13 | 14 | def test_debug_is_disabled(): 15 | assert app.server.debug == False 16 | 17 | def test_layout_is_a_function_that_returns_a_div_element(): 18 | assert isinstance(app.layout(), html.Div) 19 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # See documentation here: https://pyup.io/docs/bot/config/ 2 | 3 | # configure updates globally 4 | # default: all 5 | # allowed: all, insecure, False 6 | update: all 7 | 8 | # configure dependency pinning globally 9 | # default: True 10 | # allowed: True, False 11 | pin: True 12 | 13 | # set the default branch 14 | # default: empty, the default branch on GitHub 15 | branch: master 16 | 17 | # update schedule 18 | # default: empty 19 | # allowed: "every day", "every week", .. 20 | schedule: "every week" 21 | 22 | # search for requirement files 23 | # default: True 24 | # allowed: True, False 25 | search: True 26 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.pyup.yml: -------------------------------------------------------------------------------- 1 | # See documentation here: https://pyup.io/docs/bot/config/ 2 | 3 | # configure updates globally 4 | # default: all 5 | # allowed: all, insecure, False 6 | update: all 7 | 8 | # configure dependency pinning globally 9 | # default: True 10 | # allowed: True, False 11 | pin: True 12 | 13 | # set the default branch 14 | # default: empty, the default branch on GitHub 15 | branch: master 16 | 17 | # update schedule 18 | # default: empty 19 | # allowed: "every day", "every week", .. 20 | schedule: "every day" 21 | 22 | # search for requirement files 23 | # default: True 24 | # allowed: True, False 25 | search: True 26 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.package_name}}/.env.example: -------------------------------------------------------------------------------- 1 | # This file is just an example. It illustrates how to define environment variables in your Dash app. 2 | # Create a copy of this file, put it in this directory, and name it .env. 3 | # DON'T TRACK your .env file under version control. 4 | 5 | # Flask secret key 6 | # Some suggestions for strong passwords: 7 | # https://www.grc.com/passwords.htm 8 | # https://www.howtogeek.com/howto/30184/10-ways-to-generate-a-random-password-from-the-command-line/ 9 | # http://www.miniwebtool.com/django-secret-key-generator/ 10 | # Note: Heroku seems to have issues with some special characters. 11 | SECRET_KEY=your-secret-key-here 12 | 13 | PLOTLY_USERNAME=your-plotly-username 14 | PLOTLY_API_KEY=your-plotly-api-key 15 | 16 | # Mapbox access token (see here: https://www.mapbox.com/help/define-access-token/) 17 | MAPBOX_ACCESS_TOKEN=your-mapbox-access-token 18 | 19 | PORT=8080 20 | 21 | # more environment variables... 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Giacomo Debidda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/test_cookiecutter.py: -------------------------------------------------------------------------------- 1 | """Tests for this cookiecutter. 2 | 3 | See Also: https://github.com/pydanny/cookiecutter-django 4 | """ 5 | import os 6 | import re 7 | import pytest 8 | from binaryornot.check import is_binary 9 | 10 | PATTERN = "{{(\s?cookiecutter)[.](.*?)}}" 11 | RE_OBJ = re.compile(PATTERN) 12 | 13 | 14 | @pytest.fixture 15 | def context(): 16 | return { 17 | "project_name": "My Test Project", 18 | "repo_name": "my-test-project", 19 | "package_name": "my_test_project", 20 | "author_name": "Test Author", 21 | "github_username": "jackdbd", 22 | "email": "you@example.com", 23 | "description": "A short description of the project.", 24 | "version": "0.1.0", 25 | "open_source_license": "MIT", 26 | } 27 | 28 | 29 | def build_files_list(root_dir): 30 | """Build a list containing absolute paths to the generated files.""" 31 | return [ 32 | os.path.join(dirpath, file_path) 33 | for dirpath, subdirs, files in os.walk(root_dir) 34 | for file_path in files 35 | ] 36 | 37 | 38 | def check_paths(paths): 39 | """Method to check all paths have correct substitutions, 40 | used by other tests cases 41 | """ 42 | # Assert that no match is found in any of the files 43 | for path in paths: 44 | if is_binary(path): 45 | continue 46 | 47 | for line in open(path, "r"): 48 | match = RE_OBJ.search(line) 49 | msg = "cookiecutter variable not replaced in {}" 50 | assert match is None, msg.format(path) 51 | 52 | 53 | def test_default_configuration(cookies, context): 54 | result = cookies.bake(extra_context=context) 55 | assert result.exit_code == 0 56 | assert result.exception is None 57 | assert result.project.basename == context["repo_name"] 58 | assert result.project.isdir() 59 | paths = build_files_list(str(result.project)) 60 | assert paths 61 | check_paths(paths) 62 | 63 | 64 | def test_heroku_procfile(cookies, context): 65 | result = cookies.bake(extra_context=context) 66 | procfile_path = os.path.join(result.project, 'Procfile') 67 | with open(procfile_path, mode='r') as f: 68 | heroku_config = f.readline() 69 | server_config = heroku_config.split()[2] 70 | assert server_config == 'my_test_project.app:server' 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cookiecutter Dash 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Build Status](https://travis-ci.org/jackdbd/cookiecutter-dash.svg?branch=master)](https://travis-ci.org/jackdbd/cookiecutter-dash) [![Updates](https://pyup.io/repos/github/jackdbd/cookiecutter-dash/shield.svg)](https://pyup.io/repos/github/jackdbd/cookiecutter-dash/) [![Python 3](https://pyup.io/repos/github/jackdbd/cookiecutter-dash/python-3-shield.svg)](https://pyup.io/repos/github/jackdbd/cookiecutter-dash/) [![Coverage](https://codecov.io/github/jackdbd/cookiecutter-dash/coverage.svg?branch=master)](https://codecov.io/github/jackdbd/cookiecutter-dash?branch=master) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) 3 | 4 | Powered by [Cookiecutter](https://github.com/audreyr/cookiecutter), Cookiecutter Dash creates a minimal project skeleton for a Dash app. 5 | 6 | 7 | ## Usage 8 | Create a new Dash is as easy as 1-2-3. 9 | 10 | 1. If you don't already have it, install `cookiecutter` globally: 11 | 12 | ```shell 13 | pip install cookiecutter 14 | ``` 15 | 16 | 2. Run the following command to create the skeleton of your Dash app: 17 | 18 | ```shell 19 | cookiecutter https://github.com/jackdbd/cookiecutter-dash 20 | ``` 21 | 22 | 3. Follow the instructions in the `README.md` of your generated project. 23 | 24 | 25 | ## Features 26 | Your generated Dash app will have: 27 | 28 | - Environment variables loaded from an `.env` file, with [python-dotenv](https://github.com/theskumar/python-dotenv) 29 | - `Procfile` to deploy on Heroku 30 | - Continuous Integration with `.travis.yml` 31 | - Code Quality with `.codeclimate.yml` 32 | - Test coverage with `codecov.yml` 33 | - Python dependencies management with `.pyup.yml` 34 | - Python code formatting with [black](https://github.com/ambv/black) 35 | - A utility shell script to create a Python virtual environment and create your `Initial commit` 36 | 37 | 38 | ## TODO 39 | - create more tests for this cookiecutter with [pytest-cookies](https://github.com/hackebrot/pytest-cookies) 40 | - create tests for the generated project 41 | - create simple dash callbacks 42 | - support different CSS frameworks (e.g. [bulma](https://bulma.io/), [milligram](https://milligram.io/), [iota](https://github.com/korywakefield/iota)) 43 | - Better README 44 | - More modular project tree? 45 | - [tox](https://tox.readthedocs.io/en/latest/) integration? 46 | - Error tracking? (e.g. [Sentry](https://sentry.io/for/python/), [Rollbar](https://rollbar.com/error-tracking/python/)) 47 | - Docker integration? 48 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/README.md: -------------------------------------------------------------------------------- 1 | # {{cookiecutter.project_name}} 2 | {% if cookiecutter.open_source_license == "MIT" %} [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT){% elif cookiecutter.open_source_license == "BSD" %} [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause){% elif cookiecutter.open_source_license == "GPLv3" %} [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0){% elif cookiecutter.open_source_license == "Apache Software License 2.0" %} [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0){% endif %} [![Build Status](https://travis-ci.org/{{cookiecutter.github_username}}/{{cookiecutter.repo_name}}.svg?branch=master)](https://travis-ci.org/{{cookiecutter.github_username}}/{{cookiecutter.repo_name}}) [![Updates](https://pyup.io/repos/github/{{cookiecutter.github_username}}/{{cookiecutter.repo_name}}/shield.svg)](https://pyup.io/repos/github/{{cookiecutter.github_username}}/{{cookiecutter.repo_name}}/) [![Python 3](https://pyup.io/repos/github/{{cookiecutter.github_username}}/{{cookiecutter.repo_name}}/python-3-shield.svg)](https://pyup.io/repos/github/{{cookiecutter.github_username}}/{{cookiecutter.repo_name}}/) [![Coverage](https://codecov.io/github/{{cookiecutter.github_username}}/{{cookiecutter.repo_name}}/coverage.svg?branch=master)](https://codecov.io/github/{{cookiecutter.github_username}}/{{cookiecutter.repo_name}}?branch=master) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) 3 | 4 | 5 | {{cookiecutter.description}} 6 | 7 | 8 | ## Usage 9 | If you are on Linux and you have [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/) installed you can run the script `utility/setup_virtualenv_and_repo.sh` to: 10 | 11 | - create a python virtual environment and activate it 12 | - install all project dependencies from `requirements.txt` 13 | - create a git repository 14 | - create your `Initial commit` 15 | 16 | Here is how you run the script: 17 | 18 | ```shell 19 | cd {{cookiecutter.repo_name}} 20 | # mind the dot! 21 | . utility/setup_virtualenv_and_repo.sh 22 | ``` 23 | 24 | Then you will need to create an `.env` file where to store your environment variables (SECRET key, plotly credentials, API keys, etc). Do NOT TRACK this `.env` file. See `.env.example`. 25 | 26 | Run all tests with a simple: 27 | 28 | ``` 29 | pytest -v 30 | ``` 31 | 32 | 33 | ## Run your Dash app 34 | Check that the virtual environment is activated, then run: 35 | 36 | ```shell 37 | cd {{cookiecutter.package_name}} 38 | python app.py 39 | ``` 40 | 41 | ## Code formatting 42 | To format all python files, run: 43 | 44 | ```shell 45 | black . 46 | ``` 47 | 48 | ## Pin your dependencies 49 | 50 | ```shell 51 | pip freeze > requirements.txt 52 | ``` 53 | 54 | ## Deploy on Heroku 55 | Follow the [Dash deployment guide](https://dash.plot.ly/deployment) or have a look at the [dash-heroku-template](https://github.com/plotly/dash-heroku-template) 56 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/LICENSE: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.open_source_license == 'MIT' %} 2 | The MIT License (MIT) 3 | Copyright (c) {% now 'utc', '%Y' %}, {{ cookiecutter.author_name }} 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | {% elif cookiecutter.open_source_license == 'BSD' %} 11 | Copyright (c) {% now 'utc', '%Y' %}, {{ cookiecutter.author_name }} 12 | All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without modification, 15 | are permitted provided that the following conditions are met: 16 | 17 | * Redistributions of source code must retain the above copyright notice, this 18 | list of conditions and the following disclaimer. 19 | 20 | * Redistributions in binary form must reproduce the above copyright notice, this 21 | list of conditions and the following disclaimer in the documentation and/or 22 | other materials provided with the distribution. 23 | 24 | * Neither the name of {{ cookiecutter.project_name }} nor the names of its 25 | contributors may be used to endorse or promote products derived from this 26 | software without specific prior written permission. 27 | 28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 29 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 30 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 31 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 32 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 33 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 35 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 36 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 37 | OF THE POSSIBILITY OF SUCH DAMAGE. 38 | {% elif cookiecutter.open_source_license == 'GPLv3' %} 39 | Copyright (c) {% now 'utc', '%Y' %}, {{ cookiecutter.author_name }} 40 | 41 | This program is free software: you can redistribute it and/or modify 42 | it under the terms of the GNU General Public License as published by 43 | the Free Software Foundation, either version 3 of the License, or 44 | (at your option) any later version. 45 | 46 | This program is distributed in the hope that it will be useful, 47 | but WITHOUT ANY WARRANTY; without even the implied warranty of 48 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 49 | GNU General Public License for more details. 50 | 51 | You should have received a copy of the GNU General Public License 52 | along with this program. If not, see . 53 | {% endif %} -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.package_name}}/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import dash_core_components as dcc 3 | import dash_html_components as html 4 | import plotly.plotly as py 5 | import plotly.graph_objs as go 6 | from flask import Flask 7 | from dash import Dash 8 | from dash.dependencies import Input, Output, State 9 | from dotenv import load_dotenv 10 | from exceptions import ImproperlyConfigured 11 | 12 | DOTENV_PATH = os.path.join(os.path.dirname(__file__), ".env") 13 | load_dotenv(DOTENV_PATH) 14 | 15 | if "DYNO" in os.environ: 16 | # the app is on Heroku 17 | debug = False 18 | # google analytics with the tracking ID for this app 19 | # external_js.append('https://codepen.io/jackdbd/pen/rYmdLN.js') 20 | else: 21 | debug = True 22 | dotenv_path = os.path.join(os.path.dirname(__file__), ".env") 23 | load_dotenv(dotenv_path) 24 | 25 | try: 26 | py.sign_in(os.environ["PLOTLY_USERNAME"], os.environ["PLOTLY_API_KEY"]) 27 | except KeyError: 28 | raise ImproperlyConfigured("Plotly credentials not set in .env") 29 | 30 | app_name = "{{cookiecutter.project_name}}" 31 | server = Flask(app_name) 32 | 33 | try: 34 | server.secret_key = os.environ["SECRET_KEY"] 35 | except KeyError: 36 | raise ImproperlyConfigured("SECRET KEY not set in .env:") 37 | 38 | app = Dash(name=app_name, server=server, csrf_protect=False) 39 | 40 | external_js = [] 41 | 42 | external_css = [ 43 | # dash stylesheet 44 | "https://codepen.io/chriddyp/pen/bWLwgP.css", 45 | "https://fonts.googleapis.com/css?family=Lobster|Raleway", 46 | "//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css", 47 | ] 48 | 49 | theme = {"font-family": "Lobster", "background-color": "#e0e0e0"} 50 | 51 | 52 | def create_header(): 53 | header_style = {"background-color": theme["background-color"], "padding": "1.5rem"} 54 | header = html.Header(html.H1(children=app_name, style=header_style)) 55 | return header 56 | 57 | 58 | def create_content(): 59 | content = html.Div( 60 | children=[ 61 | # range slider with start date and end date 62 | html.Div( 63 | children=[ 64 | dcc.RangeSlider( 65 | id="year-slider", 66 | min=1990, 67 | max=2018, 68 | value=[2010, 2015], 69 | marks={(i): f"{i}" for i in range(1990, 2018, 2)}, 70 | ) 71 | ], 72 | style={"margin-bottom": 20}, 73 | ), 74 | html.Hr(), 75 | html.Div( 76 | children=[ 77 | dcc.Graph( 78 | id="graph-0", 79 | figure={ 80 | "data": [ 81 | { 82 | "x": [1, 2, 3], 83 | "y": [4, 1, 2], 84 | "type": "bar", 85 | "name": "SF", 86 | }, 87 | { 88 | "x": [1, 2, 3], 89 | "y": [2, 4, 5], 90 | "type": "bar", 91 | "name": u"Montréal", 92 | }, 93 | ], 94 | "layout": {"title": "Dash Data Visualization"}, 95 | }, 96 | ) 97 | ], 98 | className="row", 99 | style={"margin-bottom": 20}, 100 | ), 101 | html.Div( 102 | children=[ 103 | html.Div(dcc.Graph(id="graph-1"), className="six columns"), 104 | html.Div( 105 | dcc.Markdown( 106 | """ 107 | This is a markdown description created with a Dash Core Component. 108 | 109 | > A {number} days of training to develop. 110 | > Ten {number} days of training to polish. 111 | > 112 | > — Miyamoto Musashi 113 | 114 | *** 115 | """.format( 116 | number="thousand" 117 | ).replace( 118 | " ", "" 119 | ) 120 | ), 121 | className="six columns", 122 | ), 123 | ], 124 | className="row", 125 | style={"margin-bottom": 20}, 126 | ), 127 | html.Hr(), 128 | ], 129 | id="content", 130 | style={"width": "100%", "height": "100%"}, 131 | ) 132 | return content 133 | 134 | 135 | def create_footer(): 136 | footer_style = {"background-color": theme["background-color"], "padding": "0.5rem"} 137 | p0 = html.P( 138 | children=[ 139 | html.Span("Built with "), 140 | html.A( 141 | "Plotly Dash", href="https://github.com/plotly/dash", target="_blank" 142 | ), 143 | ] 144 | ) 145 | p1 = html.P( 146 | children=[ 147 | html.Span("Data from "), 148 | html.A("some website", href="https://some-website.com/", target="_blank"), 149 | ] 150 | ) 151 | a_fa = html.A( 152 | children=[ 153 | html.I([], className="fa fa-font-awesome fa-2x"), html.Span("Font Awesome") 154 | ], 155 | style={"text-decoration": "none"}, 156 | href="http://fontawesome.io/", 157 | target="_blank", 158 | ) 159 | 160 | div = html.Div([p0, p1, a_fa]) 161 | footer = html.Footer(children=div, style=footer_style) 162 | return footer 163 | 164 | 165 | def serve_layout(): 166 | layout = html.Div( 167 | children=[create_header(), create_content(), create_footer()], 168 | className="container", 169 | style={"font-family": theme["font-family"]}, 170 | ) 171 | return layout 172 | 173 | 174 | app.layout = serve_layout 175 | for js in external_js: 176 | app.scripts.append_script({"external_url": js}) 177 | for css in external_css: 178 | app.css.append_css({"external_url": css}) 179 | 180 | 181 | # TODO: callbacks 182 | 183 | if __name__ == "__main__": 184 | port = int(os.environ.get("PORT", 5000)) 185 | app.run_server(debug=debug, port=port, threaded=True) 186 | --------------------------------------------------------------------------------