├── VERSION
├── tests
├── __init__.py
├── test_client.py
└── test_api.py
├── Procfile
├── docs
├── vue-logo.png
├── altair-logo.png
├── flask-logo.png
├── python-logo.png
└── vuetify-logo.png
├── public
├── favicon.ico
└── index.html
├── src
├── assets
│ ├── logo.png
│ ├── flask-logo.png
│ ├── vue-logo.png
│ ├── altair-logo.png
│ ├── python-logo.png
│ ├── vuetify_logo.png
│ └── logo.svg
├── plugins
│ └── vuetify.js
├── views
│ ├── Home.vue
│ ├── Api.vue
│ └── Landing.vue
├── main.js
├── filters.js
├── router.js
├── backend.js
├── store.js
├── components
│ ├── VegaCars.vue
│ ├── VueInfo.vue
│ └── Books.vue
└── App.vue
├── .bumpversion.cfg
├── tox.ini
├── run.py
├── .flaskenv
├── HISTORY.md
├── app
├── client.py
├── __init__.py
├── api
│ ├── __init__.py
│ ├── security.py
│ └── resources.py
└── config.py
├── .gitignore
├── Pipfile
├── .travis.yml
├── app.json
├── vue.config.js
├── LICENSE.md
├── package.json
├── README.md
└── Pipfile.lock
/VERSION:
--------------------------------------------------------------------------------
1 | 0.3.0
2 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn app:app --log-file -
2 |
--------------------------------------------------------------------------------
/docs/vue-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/docs/vue-logo.png
--------------------------------------------------------------------------------
/docs/altair-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/docs/altair-logo.png
--------------------------------------------------------------------------------
/docs/flask-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/docs/flask-logo.png
--------------------------------------------------------------------------------
/docs/python-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/docs/python-logo.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/docs/vuetify-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/docs/vuetify-logo.png
--------------------------------------------------------------------------------
/src/assets/flask-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/src/assets/flask-logo.png
--------------------------------------------------------------------------------
/src/assets/vue-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/src/assets/vue-logo.png
--------------------------------------------------------------------------------
/src/assets/altair-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/src/assets/altair-logo.png
--------------------------------------------------------------------------------
/src/assets/python-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/src/assets/python-logo.png
--------------------------------------------------------------------------------
/src/assets/vuetify_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xujiboy/flask-altair-vuejs-vuetify-example/HEAD/src/assets/vuetify_logo.png
--------------------------------------------------------------------------------
/.bumpversion.cfg:
--------------------------------------------------------------------------------
1 | [bumpversion]
2 | current_version = 0.3.0
3 | commit = True
4 | tag = True
5 |
6 | [bumpversion:file:VERSION]
7 |
8 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | addopts=--maxfail=2 --cov app --cov-report=html
3 | testpaths=tests
4 | [testenv]
5 | deps=pytest
6 | commands=pytest
7 |
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | import os
2 | from app import app
3 |
4 | app.run(port=5000)
5 |
6 | # To Run:
7 | # python run.py
8 | # or
9 | # python -m flask run
10 |
--------------------------------------------------------------------------------
/.flaskenv:
--------------------------------------------------------------------------------
1 | # Production Enviroment should be set to 'production'
2 | FLASK_ENV = "development"
3 | FLASK_APP = "app"
4 | # Uncomment this to debug:
5 | # FLASK_DEBUG=1
6 |
--------------------------------------------------------------------------------
/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify/lib'
3 | import 'vuetify/src/stylus/app.styl'
4 |
5 | Vue.use(Vuetify, {
6 | iconfont: 'md'
7 | })
8 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | ### 0.3.0
2 | * Npm > Yarn
3 | * Updated to Flask 1.0
4 | * Clean up config, use env vars
5 | * Simplified client view
6 | * Flatten Templated Folder Structure
7 | * Removed Cli Commads
8 |
9 | ### 0.2.3
10 | * Updated to Vue Cli 3
11 |
12 | ### 0.1.0
13 | * Initial Release
14 |
--------------------------------------------------------------------------------
/tests/test_client.py:
--------------------------------------------------------------------------------
1 | """ pytests for Flask """
2 |
3 | import pytest
4 | from app import app
5 |
6 | @pytest.fixture(scope="module")
7 | def client():
8 | app.config['TESTING'] = True
9 | return app.test_client()
10 |
11 | def test_api(client):
12 | resp = client.get('/')
13 | assert resp.status_code == 200
14 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 | import './plugins/vuetify'
6 |
7 | import './filters'
8 |
9 | Vue.config.productionTip = false
10 |
11 | new Vue({
12 | router,
13 | store,
14 | render: h => h(App)
15 | }).$mount('#app')
16 |
--------------------------------------------------------------------------------
/app/client.py:
--------------------------------------------------------------------------------
1 | """ Client App """
2 |
3 | import os
4 | from flask import Blueprint, render_template
5 |
6 | client_bp = Blueprint('client_app', __name__,
7 | url_prefix='',
8 | static_url_path='',
9 | static_folder='./dist/static/',
10 | template_folder='./dist/',
11 | )
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .cache
3 | .mypy_cache\
4 | .env
5 |
6 | /dist
7 | .coverage
8 | .pytest_cache/
9 | htmlcov/
10 |
11 | .DS_Store
12 | node_modules/
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 | selenium-debug.log
17 |
18 | # Editor directories and files
19 | .idea
20 | .vscode
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *security-sgps.py
26 | *security_backend.py
27 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.python.org/simple"
3 | verify_ssl = true
4 | name = "pypi"
5 |
6 | [packages]
7 | gunicorn = "==19.7.1"
8 | flask-restplus = "*"
9 | python-dotenv = "*"
10 | flask = "*"
11 | altair = "*"
12 | vega-datasets = "*"
13 |
14 | [dev-packages]
15 | pytest = "*"
16 | bumpversion = "*"
17 | pytest-sugar = "*"
18 | pytest-cov = "*"
19 | pylint = "*"
20 |
21 | [requires]
22 | python_version = "3.6"
23 |
--------------------------------------------------------------------------------
/src/filters.js:
--------------------------------------------------------------------------------
1 | // Vue.js Filters
2 | // https://vuejs.org/v2/guide/filters.html
3 |
4 | import Vue from 'vue'
5 |
6 | let filters = {
7 |
8 | formatTimestamp (timestamp) {
9 | let datetime = new Date(timestamp)
10 | return datetime.toLocaleTimeString('en-US')
11 | }
12 | }
13 |
14 | // Register All Filters on import
15 | Object.keys(filters).forEach(function (filterName) {
16 | Vue.filter(filterName, filters[filterName])
17 | })
18 |
--------------------------------------------------------------------------------
/src/views/Api.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Backend Resources Demo
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
23 |
24 |
26 |
--------------------------------------------------------------------------------
/src/views/Landing.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | +
6 |
7 | +
8 |
9 | +
10 | Vega
11 |
12 |
13 |
14 |
15 |
21 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | git:
3 | depth: 2
4 | python:
5 | # - 3.5
6 | - 3.6
7 | branches:
8 | only:
9 | - master
10 | - dev/main
11 | script:
12 | - pytest --cov=app --cov-report=xml
13 | install:
14 | - "pip install --upgrade pip"
15 | - "pip install pipenv"
16 | - "pipenv install --dev"
17 | - "pip install codecov"
18 | after_success:
19 | - codecov
20 | cache: yarn
21 | before_install:
22 | - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.9.4
23 | - export PATH="$HOME/.yarn/bin:$PATH"
24 | - yarn install
25 | - yarn build
26 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Home from './views/Home.vue'
4 | import Api from './views/Api.vue'
5 | import Landing from './views/Landing.vue'
6 |
7 | Vue.use(Router)
8 |
9 | export default new Router({
10 | routes: [
11 | {
12 | path: '/',
13 | name: 'landing',
14 | component: Landing
15 | },
16 | {
17 | path: '/home',
18 | name: 'home',
19 | component: Home
20 | },
21 | {
22 | path: '/api',
23 | name: 'api',
24 | component: Api
25 | }
26 | ]
27 | })
28 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | from flask import Flask, current_app, send_file
3 |
4 | from .api import api_bp
5 | from .client import client_bp
6 |
7 | app = Flask(__name__, static_folder='../dist/static')
8 | app.register_blueprint(api_bp)
9 | # app.register_blueprint(client_bp)
10 |
11 | from .config import Config
12 | app.logger.info('>>> {}'.format(Config.FLASK_ENV))
13 |
14 | @app.route('/')
15 | def index_client():
16 | dist_dir = current_app.config['DIST_DIR']
17 | entry = os.path.join(dist_dir, 'index.html')
18 | return send_file(entry)
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "name": "Flask VueJs Template",
4 | "description": "",
5 | "repository": "https://github.com/gtalarico/flask-vuejs-template",
6 | "logo": "https://raw.githubusercontent.com/gtalarico/flask-vuejs-template/master/app/client/app/src/assets/img/logo.png",
7 | "keywords": ["flask", "vue"],
8 | "env": {
9 | "FLASK_CONFIG": {
10 | "description": "Flask Enviroment Config",
11 | "value": "Production"
12 | },
13 | "SECRET": {
14 | "description": "Flask Secret Key",
15 | "value": "YourKeyHere"
16 | }
17 | },
18 | "addons": [
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/app/api/__init__.py:
--------------------------------------------------------------------------------
1 | """ API Blueprint Application """
2 |
3 | from flask import Blueprint, current_app
4 | from flask_restplus import Api
5 |
6 | api_bp = Blueprint('api_bp', __name__, url_prefix='/api') #/api is the new endpoint
7 | api_rest = Api(api_bp, version='1.0', doc='/doc') # doc=False to turn off SwaggerUI
8 |
9 | @api_bp.after_request
10 | def add_header(response):
11 | response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
12 | return response
13 |
14 |
15 | # Import resources to ensure view is registered
16 | from .resources import * # NOQA
17 |
--------------------------------------------------------------------------------
/app/api/security.py:
--------------------------------------------------------------------------------
1 | """ Security Related things """
2 | from functools import wraps
3 | from flask import request
4 | from flask_restplus import abort
5 |
6 |
7 | def require_auth(func):
8 | """ Secure method decorator """
9 | @wraps(func)
10 | def wrapper(*args, **kwargs):
11 | # Verify if User is Authenticated
12 | # Authentication logic goes here
13 | if request.headers.get('authorization'):
14 | return func(*args, **kwargs)
15 | else:
16 | return abort(code=401, message='Authorization Required.')
17 | return wrapper
18 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | // const IS_PRODUCTION = process.env.NODE_ENV === 'production'
2 |
3 | module.exports = {
4 | outputDir: 'dist',
5 | assetsDir: 'static',
6 | // baseUrl: IS_PRODUCTION
7 | // ? 'http://cdn123.com'
8 | // : '/',
9 | // For Production, replace set baseUrl to CDN
10 | // And set the CDN origin to `yourdomain.com/static`
11 | // Whitenoise will serve once to CDN which will then cache
12 | // and distribute
13 | devServer: {
14 | proxy: {
15 | '/api*': {
16 | // Forward frontend dev server request for /api to django dev server
17 | target: 'http://localhost:5000/'
18 | }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/config.py:
--------------------------------------------------------------------------------
1 | """
2 | Global Flask Application Setting
3 |
4 | set FLASK_CONFIG to 'development
5 | """
6 |
7 | import os
8 | from app import app
9 |
10 |
11 | class Config(object):
12 | # If not set fall back to production for safety
13 | FLASK_ENV = os.getenv('FLASK_ENV', 'production')
14 | # Set FLASK_SECRET on your production Environment
15 | SECRET_KEY = os.getenv('FLASK_SECRET', 'Secret')
16 |
17 | APP_DIR = os.path.dirname(__file__)
18 | ROOT_DIR = os.path.dirname(APP_DIR)
19 | DIST_DIR = os.path.join(ROOT_DIR, 'dist')
20 |
21 | if not os.path.exists(DIST_DIR):
22 | raise Exception(
23 | 'DIST_DIR not found: {}'.format(DIST_DIR))
24 |
25 | app.config.from_object('app.config.Config')
26 | app.config['SWAGGER_UI_JSONEDITOR'] = True # turn on JSON editor in swaggerUI
27 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Flask + Vega + Vue.js + Vuetify Example
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [2018] [Gui Talarico]
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 |
--------------------------------------------------------------------------------
/src/backend.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | let $axios = axios.create({
4 | baseURL: '/api/',
5 | timeout: 5000,
6 | headers: {'Content-Type': 'application/json'}
7 | })
8 |
9 | // Request Interceptor
10 | $axios.interceptors.request.use(function (config) {
11 | config.headers['Authorization'] = 'Fake Token'
12 | return config
13 | })
14 |
15 | // Response Interceptor to handle and log errors
16 | $axios.interceptors.response.use(function (response) {
17 | return response
18 | }, function (error) {
19 | // Handle Error
20 | console.log(error)
21 | return Promise.reject(error)
22 | })
23 |
24 | export default {
25 |
26 | fetchResource () {
27 | return $axios.get(`resource/q`)
28 | .then(response => response.data)
29 | },
30 |
31 | fetchSecureResource () {
32 | return $axios.get(`secure-resource/xxx`)
33 | .then(response => response.data)
34 | },
35 |
36 | fetchResourceTest () {
37 | return $axios.get(`test`)
38 | .then(response => response.data)
39 | },
40 |
41 | fetchBooks () {
42 | return $axios.get(`books`)
43 | .then(response => response.data)
44 | },
45 |
46 | fetchCars () {
47 | return $axios.get(`vega_cars`)
48 | .then(response => response)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/test_api.py:
--------------------------------------------------------------------------------
1 | """ pytests for Flask """
2 |
3 | import pytest
4 | from app import app
5 |
6 | @pytest.fixture(scope="module")
7 | def client():
8 | app.config['TESTING'] = True
9 | return app.test_client()
10 |
11 | def test_api(client):
12 | resp = client.get('/api/')
13 | assert resp.status_code == 200
14 |
15 | def test_resource_one(client):
16 | resp = client.get('/api/resource/one')
17 | assert resp.status_code == 200
18 |
19 | def test_resource_one_post(client):
20 | resp = client.post('/api/resource/one')
21 | assert resp.status_code == 201
22 |
23 | def test_resource_one_patch(client):
24 | resp = client.patch('/api/resource/one')
25 | assert resp.status_code == 405
26 |
27 | def test_secure_resource_fail(client):
28 | resp = client.get('/api/secure-resource/two')
29 | assert resp.status_code == 401
30 |
31 | def test_secure_resource_pass(client):
32 | resp = client.get('/api/secure-resource/two',
33 | headers={'authorization': 'Bearer x'})
34 | assert resp.status_code == 200
35 |
36 | @pytest.fixture(scope="module")
37 | def request_context():
38 | return app.test_request_context('')
39 |
40 | def test_session(request_context):
41 | with request_context:
42 | # Do something that requires request context
43 | assert True
44 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import $backend from './backend.js'
4 |
5 | Vue.use(Vuex)
6 |
7 | export default new Vuex.Store({
8 | state: {
9 | cars: null,
10 | books: [],
11 | error: ''
12 | },
13 | mutations: { // must be synchronous
14 | SET_BOOKS: (state, books) => {
15 | state.books = books
16 | },
17 | CLEAR_BOOKS: (state) => {
18 | state.books = []
19 | },
20 | SET_CARS: (state, cars) => {
21 | state.cars = cars
22 | },
23 | CLEAR_CARS: (state) => {
24 | state.cars = null
25 | },
26 | SET_ERROR: (state, error) => {
27 | state.error = error
28 | }
29 | },
30 | actions: { // can be asynchronous
31 | setCars: (context) => {
32 | $backend.fetchCars()
33 | .then(response => context.commit('SET_CARS', response.data))
34 | .catch(error => context.commit('SET_ERROR', error))
35 | },
36 | clearCars: (context) => {
37 | context.commit('CLEAR_CARS')
38 | },
39 | setBooks: (context) => {
40 | $backend.fetchBooks()
41 | .then(response => context.commit('SET_BOOKS', response.data))
42 | .catch(error => context.commit('SET_ERROR', error))
43 | },
44 | clearBooks: (context) => {
45 | context.commit('CLEAR_BOOKS')
46 | }
47 | }
48 | })
49 |
--------------------------------------------------------------------------------
/src/components/VegaCars.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | C
7 | VegaCars
8 |
9 |
10 |
11 | Cars
12 |
13 |
14 |
15 | add_circle
16 |
17 |
18 |
19 | remove_circle
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue_app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve --open",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint",
9 | "postinstall": "yarn build"
10 | },
11 | "dependencies": {
12 | "axios": "^0.18.0",
13 | "material-design-icons-iconfont": "^4.0.2",
14 | "swagger-ui": "^3.19.5",
15 | "vega": "^4.3.0",
16 | "vega-embed": "^3.24.1",
17 | "vega-lite": "^3.0.0-rc8",
18 | "vue": "^2.5.13",
19 | "vue-router": "^3.0.1",
20 | "vuetify": "^1.3.8",
21 | "vuex": "^3.0.1"
22 | },
23 | "devDependencies": {
24 | "@vue/cli-plugin-babel": "^3.0.0-beta.6",
25 | "@vue/cli-plugin-eslint": "^3.0.0-beta.6",
26 | "@vue/cli-service": "^3.0.0-beta.6",
27 | "@vue/eslint-config-standard": "^3.0.0-beta.6",
28 | "lint-staged": "^6.0.0",
29 | "node-sass": "^4.7.2",
30 | "sass-loader": "^6.0.6",
31 | "stylus": "^0.54.5",
32 | "stylus-loader": "^3.0.1",
33 | "vue-cli-plugin-vuetify": "^0.4.6",
34 | "vue-template-compiler": "^2.5.13",
35 | "vuetify-loader": "^1.0.5"
36 | },
37 | "babel": {
38 | "presets": [
39 | "@vue/app"
40 | ]
41 | },
42 | "eslintConfig": {
43 | "root": true,
44 | "extends": [
45 | "plugin:vue/essential",
46 | "@vue/standard"
47 | ]
48 | },
49 | "postcss": {
50 | "plugins": {
51 | "autoprefixer": {}
52 | }
53 | },
54 | "browserslist": [
55 | "> 1%",
56 | "last 2 versions",
57 | "not ie <= 8"
58 | ],
59 | "gitHooks": {
60 | "pre-commit": "lint-staged"
61 | },
62 | "lint-staged": {
63 | "*.js": [
64 | "vue-cli-service lint",
65 | "git add"
66 | ],
67 | "*.vue": [
68 | "vue-cli-service lint",
69 | "git add"
70 | ]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/VueInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
8 |
9 |
Installed CLI Plugins
10 |
14 |
Essential Links
15 |
21 |
Ecosystem
22 |
29 |
30 |
31 |
32 |
40 |
41 |
42 |
58 |
--------------------------------------------------------------------------------
/src/components/Books.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | C
7 | Books
8 |
9 |
10 |
11 |
12 | Books ({{books.length}} are shown)
13 |
14 |
15 |
16 | add_circle
17 |
18 |
19 |
20 | remove_circle
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {{error}}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
78 |
--------------------------------------------------------------------------------
/app/api/resources.py:
--------------------------------------------------------------------------------
1 | """
2 | REST API Resource Routing
3 | http://flask-restplus.readthedocs.io
4 | """
5 |
6 | from datetime import datetime
7 | from flask import request
8 | from flask_restplus import Resource, fields
9 |
10 | from .security import require_auth
11 | from . import api_rest
12 |
13 | import altair
14 | from vega_datasets import data
15 | cars = data.cars()
16 |
17 |
18 | class SecureResource(Resource):
19 | """ Calls require_auth decorator on all requests """
20 | method_decorators = [require_auth]
21 |
22 |
23 | @api_rest.route('/resource/')
24 | class ResourceOne(Resource):
25 | """ Unsecure Resource Class: Inherit from Resource """
26 |
27 | def get(self, resource_id):
28 | timestamp = datetime.utcnow().isoformat()
29 | return {'timestamp': timestamp}
30 |
31 | def post(self, resource_id):
32 | json_payload = request.json
33 | return {'timestamp': json_payload}, 201
34 |
35 |
36 | @api_rest.route('/secure-resource/')
37 | class SecureResourceOne(SecureResource):
38 | """ Unsecure Resource Class: Inherit from Resource """
39 |
40 | def get(self, resource_id):
41 | timestamp = datetime.utcnow().isoformat()
42 | return {'timestamp': timestamp}
43 |
44 |
45 | model_book = api_rest.model('Books', {
46 | 'title' : fields.String('Title of the book.'),
47 | 'author' : fields.String('Author of the book.')
48 | })
49 |
50 | books = [
51 | {'title': 'The Adventures of Huckleberry Finn', 'author': 'Mark Twain'},
52 | {'title': 'Frankenstein', 'author': 'Mary Shelley'},
53 | {'title': 'Pride and Prejudice', 'author': 'Jane Austen'},
54 | {'title': 'The Little Prince', 'author': 'Antoine de Saint-Exupéry'},
55 | {'title': 'The Color Purple', 'author': 'Alice Walker'},
56 | {'title': 'Little Women', 'author': 'Louisa May Alcott'},
57 | {'title': 'Great Expectations', 'author': 'Charles Dickens'},
58 | {'title': 'The Scarlet Letter', 'author': 'Nathaniel Hawthorne'},
59 | {'title': 'Gone with the Wind', 'author': 'Margaret Mitchell'},
60 | {'title': 'The Picture of Dorian Gray', 'author': 'Oscar Wilde'},
61 | {'title': 'The Metamorphosis', 'author': 'Franz Kafka'}
62 | ]
63 |
64 |
65 | @api_rest.route('/books')
66 | class Books(Resource):
67 |
68 | @api_rest.marshal_with(model_book, envelope='data')
69 | def get(self):
70 | return books
71 |
72 | # @api_rest.expect(model_book)
73 | # def post(self):
74 | # new_book = api_rest.payload
75 | # new_book['id'] = len(books) + 1
76 | # books.append(new_book)
77 | # return {'result' : 'book added'}, 201
78 |
79 | @api_rest.route('/vega_cars')
80 | class VegaCars(Resource):
81 |
82 | def get(self):
83 | chart = altair.Chart(cars).mark_point().encode(
84 | x='Horsepower',
85 | y='Miles_per_Gallon',
86 | color='Origin',
87 | tooltip='Name'
88 | ).interactive()
89 | return chart.to_dict()
90 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
15 |
16 | {{ item.icon }}
17 |
18 | {{ item.title }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{ appTitle }}
32 |
33 |
34 |
35 |
36 |
37 |
42 | {{ item.icon }}
43 | {{ item.title }}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | © 2018
65 |
66 |
67 |
68 |
69 |
70 |
84 |
85 |
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flask-Altair-VueJs-Vuetify Example
2 |
3 | _An demonstration of data-visualization web app with basic dashboard UI_
4 |
5 | 
6 | 
7 | 
8 | 
9 |
10 | ## Intro
11 | This example is built upon Gui Talarico's [Flask-Vuejs-Template](https://github.com/gtalarico/flask-vuejs-template) with additional inspirations from these following online resources:
12 | * [xhochy/altair-vue-vega-example](https://github.com/xhochy/altair-vue-vega-example)
13 | * [oleg-agapov/flask-vue-spa](https://github.com/oleg-agapov/flask-vue-spa)
14 | * [oleg-agapov/basic-spa-vue-firebase](https://github.com/oleg-agapov/basic-spa-vue-firebase/tree/part-1)
15 |
16 |
17 | ## Features
18 | * Flask backend with [Flask-RestPlus](http://flask-restplus.readthedocs.io) API
19 | * [Altair](https://altair-viz.github.io/) plotting and Vega spec json object serving from backend
20 | * [Vega](https://vega.github.io/vega/) and [Vega-Embed](https://github.com/vega/vega-embed) for plot rendering at frontend
21 | * [vue-cli 3](https://github.com/vuejs/vue-cli/blob/dev/docs/README.md) + yarn
22 | * [Vuex](https://vuex.vuejs.org/) state management
23 | * [Vue Router](https://router.vuejs.org/)
24 | * [Vuetify material design](https://vuetifyjs.com/en/) for dashboard UI
25 | * [Axios](https://vuex.vuejs.org/) for backend communication
26 | * Heroku Configuration with one-click deployment + Gunicorn
27 |
28 | ## Demo
29 | [Live Demo](https://flask-altair-vuejs-vuetify-exp.herokuapp.com/#/api)
30 |
31 | ## Repo Structure and Usage
32 |
33 | The details on the directory structure and usage can be found in the `README.md` file of Gui Talarico's [repo](https://github.com/gtalarico/flask-vuejs-template). For reader's convenience, here I will only copy and paste the [key files](#Key-Files) and some basic [installation](Installation) instructions below.
34 |
35 | #### Key Files
36 |
37 | | Location | Content |
38 | |----------------------|--------------------------------------------|
39 | | `/app` | Flask Application |
40 | | `/app/api` | Flask Rest Api (`/api`) |
41 | | `/app/client.py` | Flask Client (`/`) |
42 | | `/src` | Vue App . |
43 | | `/src/main.js` | JS Application Entry Point |
44 | | `/public/index.html` | Html Application Entry Point (`/`) |
45 | | `/public/static` | Static Assets |
46 | | `/dist/` | Bundled Assets Output (generated at `yarn build` |
47 |
48 |
49 | ## Installation
50 |
51 | ##### Before you start
52 |
53 | Before getting started, you should have the following installed and running:
54 |
55 | - [X] Yarn - [instructions](https://yarnpkg.com/en/docs/install#mac-stable)
56 | - [X] Vue Cli 3 - [instructions](https://cli.vuejs.org/guide/installation.html)
57 | - [X] Python 3
58 | - [X] Pipenv (optional)
59 | - [X] Heroku Cli (if deploying to Heroku)
60 |
61 | ##### Template and Dependencies
62 |
63 | * Clone this repository:
64 |
65 | ```
66 | $ git clone git@github.com:xujiboy/flask-vega-vuejs-vuetify-example.git
67 | ```
68 |
69 | * Setup virtual environment, install dependencies, and activate it:
70 |
71 | ```
72 | $ pipenv install --dev
73 | $ pipenv shell
74 | ```
75 |
76 | * Install JS dependencies
77 |
78 | ```
79 | $ yarn install
80 | ```
81 |
82 |
83 | ## Development Server
84 |
85 | Run Flask Api development server:
86 |
87 | ```
88 | $ python run.py
89 | ```
90 |
91 | From another tab in the same directory, start the webpack dev server:
92 |
93 | ```
94 | $ yarn serve
95 | ```
96 |
97 | The Vuejs application will be served from `localhost:8080` and the Flask Api
98 | and static files will be served from `localhost:5000`.
99 |
100 | The dual dev-server setup allows you to take advantage of
101 | webpack's development server with hot module replacement.
102 |
103 | Proxy config in `vue.config.js` is used to route the requests
104 | back to Flask's Api on port 5000.
105 |
106 | If you would rather run a single dev server, you can run Flask's
107 | development server only on `:5000`, but you have to build build the Vue app first
108 | and the page will not reload on changes.
109 |
110 | ```
111 | $ yarn build
112 | $ python run.py
113 | ```
114 |
115 |
116 | ## Production Server and Heroku Deployment
117 |
118 | Please refer to the `README.md` file of Gui Talarico's [repo](https://github.com/gtalarico/flask-vuejs-template).
119 |
--------------------------------------------------------------------------------
/Pipfile.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_meta": {
3 | "hash": {
4 | "sha256": "888a7c368fb141f0ebdf0fa3e39ef9ae87ea8b8f5314329de8c8aa98a2c093b2"
5 | },
6 | "pipfile-spec": 6,
7 | "requires": {
8 | "python_version": "3.6"
9 | },
10 | "sources": [
11 | {
12 | "name": "pypi",
13 | "url": "https://pypi.python.org/simple",
14 | "verify_ssl": true
15 | }
16 | ]
17 | },
18 | "default": {
19 | "altair": {
20 | "hashes": [
21 | "sha256:6bdbc62a9b3fc22212e548a2a5f068c0dd378a9f5d99f1016346d7637894cfa9",
22 | "sha256:c158699026eb5a19f95c1ca742e2e82bc20c27013ef5785f10836283e2233f8a"
23 | ],
24 | "index": "pypi",
25 | "version": "==2.2.2"
26 | },
27 | "aniso8601": {
28 | "hashes": [
29 | "sha256:547e7bc88c19742e519fb4ca39f4b8113fdfb8fca322e325f16a8bfc6cfc553c",
30 | "sha256:e7560de91bf00baa712b2550a2fdebf0188c5fce2fcd1162fbac75c19bb29c95"
31 | ],
32 | "version": "==4.0.1"
33 | },
34 | "click": {
35 | "hashes": [
36 | "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
37 | "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
38 | ],
39 | "version": "==7.0"
40 | },
41 | "entrypoints": {
42 | "hashes": [
43 | "sha256:10ad569bb245e7e2ba425285b9fa3e8178a0dc92fc53b1e1c553805e15a8825b",
44 | "sha256:d2d587dde06f99545fb13a383d2cd336a8ff1f359c5839ce3a64c917d10c029f"
45 | ],
46 | "version": "==0.2.3"
47 | },
48 | "flask": {
49 | "hashes": [
50 | "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
51 | "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05"
52 | ],
53 | "index": "pypi",
54 | "version": "==1.0.2"
55 | },
56 | "flask-restplus": {
57 | "hashes": [
58 | "sha256:3fad697e1d91dfc13c078abcb86003f438a751c5a4ff41b84c9050199d2eab62",
59 | "sha256:cdc27b5be63f12968a7f762eaa355e68228b0c904b4c96040a314ba7dc6d0e69"
60 | ],
61 | "index": "pypi",
62 | "version": "==0.12.1"
63 | },
64 | "gunicorn": {
65 | "hashes": [
66 | "sha256:75af03c99389535f218cc596c7de74df4763803f7b63eb09d77e92b3956b36c6",
67 | "sha256:eee1169f0ca667be05db3351a0960765620dad53f53434262ff8901b68a1b622"
68 | ],
69 | "index": "pypi",
70 | "version": "==19.7.1"
71 | },
72 | "itsdangerous": {
73 | "hashes": [
74 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
75 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
76 | ],
77 | "version": "==1.1.0"
78 | },
79 | "jinja2": {
80 | "hashes": [
81 | "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd",
82 | "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"
83 | ],
84 | "version": "==2.10"
85 | },
86 | "jsonschema": {
87 | "hashes": [
88 | "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08",
89 | "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"
90 | ],
91 | "version": "==2.6.0"
92 | },
93 | "markupsafe": {
94 | "hashes": [
95 | "sha256:048ef924c1623740e70204aa7143ec592504045ae4429b59c30054cb31e3c432",
96 | "sha256:130f844e7f5bdd8e9f3f42e7102ef1d49b2e6fdf0d7526df3f87281a532d8c8b",
97 | "sha256:19f637c2ac5ae9da8bfd98cef74d64b7e1bb8a63038a3505cd182c3fac5eb4d9",
98 | "sha256:1b8a7a87ad1b92bd887568ce54b23565f3fd7018c4180136e1cf412b405a47af",
99 | "sha256:1c25694ca680b6919de53a4bb3bdd0602beafc63ff001fea2f2fc16ec3a11834",
100 | "sha256:1f19ef5d3908110e1e891deefb5586aae1b49a7440db952454b4e281b41620cd",
101 | "sha256:1fa6058938190ebe8290e5cae6c351e14e7bb44505c4a7624555ce57fbbeba0d",
102 | "sha256:31cbb1359e8c25f9f48e156e59e2eaad51cd5242c05ed18a8de6dbe85184e4b7",
103 | "sha256:3e835d8841ae7863f64e40e19477f7eb398674da6a47f09871673742531e6f4b",
104 | "sha256:4e97332c9ce444b0c2c38dd22ddc61c743eb208d916e4265a2a3b575bdccb1d3",
105 | "sha256:525396ee324ee2da82919f2ee9c9e73b012f23e7640131dd1b53a90206a0f09c",
106 | "sha256:52b07fbc32032c21ad4ab060fec137b76eb804c4b9a1c7c7dc562549306afad2",
107 | "sha256:52ccb45e77a1085ec5461cde794e1aa037df79f473cbc69b974e73940655c8d7",
108 | "sha256:5c3fbebd7de20ce93103cb3183b47671f2885307df4a17a0ad56a1dd51273d36",
109 | "sha256:5e5851969aea17660e55f6a3be00037a25b96a9b44d2083651812c99d53b14d1",
110 | "sha256:5edfa27b2d3eefa2210fb2f5d539fbed81722b49f083b2c6566455eb7422fd7e",
111 | "sha256:7d263e5770efddf465a9e31b78362d84d015cc894ca2c131901a4445eaa61ee1",
112 | "sha256:83381342bfc22b3c8c06f2dd93a505413888694302de25add756254beee8449c",
113 | "sha256:857eebb2c1dc60e4219ec8e98dfa19553dae33608237e107db9c6078b1167856",
114 | "sha256:98e439297f78fca3a6169fd330fbe88d78b3bb72f967ad9961bcac0d7fdd1550",
115 | "sha256:bf54103892a83c64db58125b3f2a43df6d2cb2d28889f14c78519394feb41492",
116 | "sha256:d9ac82be533394d341b41d78aca7ed0e0f4ba5a2231602e2f05aa87f25c51672",
117 | "sha256:e982fe07ede9fada6ff6705af70514a52beb1b2c3d25d4e873e82114cf3c5401",
118 | "sha256:edce2ea7f3dfc981c4ddc97add8a61381d9642dc3273737e756517cc03e84dd6",
119 | "sha256:efdc45ef1afc238db84cb4963aa689c0408912a0239b0721cb172b4016eb31d6",
120 | "sha256:f137c02498f8b935892d5c0172560d7ab54bc45039de8805075e19079c639a9c",
121 | "sha256:f82e347a72f955b7017a39708a3667f106e6ad4d10b25f237396a7115d8ed5fd",
122 | "sha256:fb7c206e01ad85ce57feeaaa0bf784b97fa3cad0d4a5737bc5295785f5c613a1"
123 | ],
124 | "version": "==1.1.0"
125 | },
126 | "numpy": {
127 | "hashes": [
128 | "sha256:0df89ca13c25eaa1621a3f09af4c8ba20da849692dcae184cb55e80952c453fb",
129 | "sha256:154c35f195fd3e1fad2569930ca51907057ae35e03938f89a8aedae91dd1b7c7",
130 | "sha256:18e84323cdb8de3325e741a7a8dd4a82db74fde363dce32b625324c7b32aa6d7",
131 | "sha256:1e8956c37fc138d65ded2d96ab3949bd49038cc6e8a4494b1515b0ba88c91565",
132 | "sha256:23557bdbca3ccbde3abaa12a6e82299bc92d2b9139011f8c16ca1bb8c75d1e95",
133 | "sha256:24fd645a5e5d224aa6e39d93e4a722fafa9160154f296fd5ef9580191c755053",
134 | "sha256:36e36b6868e4440760d4b9b44587ea1dc1f06532858d10abba98e851e154ca70",
135 | "sha256:3d734559db35aa3697dadcea492a423118c5c55d176da2f3be9c98d4803fc2a7",
136 | "sha256:416a2070acf3a2b5d586f9a6507bb97e33574df5bd7508ea970bbf4fc563fa52",
137 | "sha256:4a22dc3f5221a644dfe4a63bf990052cc674ef12a157b1056969079985c92816",
138 | "sha256:4d8d3e5aa6087490912c14a3c10fbdd380b40b421c13920ff468163bc50e016f",
139 | "sha256:4f41fd159fba1245e1958a99d349df49c616b133636e0cf668f169bce2aeac2d",
140 | "sha256:561ef098c50f91fbac2cc9305b68c915e9eb915a74d9038ecf8af274d748f76f",
141 | "sha256:56994e14b386b5c0a9b875a76d22d707b315fa037affc7819cda08b6d0489756",
142 | "sha256:73a1f2a529604c50c262179fcca59c87a05ff4614fe8a15c186934d84d09d9a5",
143 | "sha256:7da99445fd890206bfcc7419f79871ba8e73d9d9e6b82fe09980bc5bb4efc35f",
144 | "sha256:99d59e0bcadac4aa3280616591fb7bcd560e2218f5e31d5223a2e12a1425d495",
145 | "sha256:a4cc09489843c70b22e8373ca3dfa52b3fab778b57cf81462f1203b0852e95e3",
146 | "sha256:a61dc29cfca9831a03442a21d4b5fd77e3067beca4b5f81f1a89a04a71cf93fa",
147 | "sha256:b1853df739b32fa913cc59ad9137caa9cc3d97ff871e2bbd89c2a2a1d4a69451",
148 | "sha256:b1f44c335532c0581b77491b7715a871d0dd72e97487ac0f57337ccf3ab3469b",
149 | "sha256:b261e0cb0d6faa8fd6863af26d30351fd2ffdb15b82e51e81e96b9e9e2e7ba16",
150 | "sha256:c857ae5dba375ea26a6228f98c195fec0898a0fd91bcf0e8a0cae6d9faf3eca7",
151 | "sha256:cf5bb4a7d53a71bb6a0144d31df784a973b36d8687d615ef6a7e9b1809917a9b",
152 | "sha256:db9814ff0457b46f2e1d494c1efa4111ca089e08c8b983635ebffb9c1573361f",
153 | "sha256:df04f4bad8a359daa2ff74f8108ea051670cafbca533bb2636c58b16e962989e",
154 | "sha256:ecf81720934a0e18526177e645cbd6a8a21bb0ddc887ff9738de07a1df5c6b61",
155 | "sha256:edfa6fba9157e0e3be0f40168eb142511012683ac3dc82420bee4a3f3981b30e"
156 | ],
157 | "version": "==1.15.4"
158 | },
159 | "pandas": {
160 | "hashes": [
161 | "sha256:11975fad9edbdb55f1a560d96f91830e83e29bed6ad5ebf506abda09818eaf60",
162 | "sha256:12e13d127ca1b585dd6f6840d3fe3fa6e46c36a6afe2dbc5cb0b57032c902e31",
163 | "sha256:1c87fcb201e1e06f66e23a61a5fea9eeebfe7204a66d99df24600e3f05168051",
164 | "sha256:242e9900de758e137304ad4b5663c2eff0d798c2c3b891250bd0bd97144579da",
165 | "sha256:26c903d0ae1542890cb9abadb4adcb18f356b14c2df46e4ff657ae640e3ac9e7",
166 | "sha256:2e1e88f9d3e5f107b65b59cd29f141995597b035d17cc5537e58142038942e1a",
167 | "sha256:31b7a48b344c14691a8e92765d4023f88902ba3e96e2e4d0364d3453cdfd50db",
168 | "sha256:4fd07a932b4352f8a8973761ab4e84f965bf81cc750fb38e04f01088ab901cb8",
169 | "sha256:5b24ca47acf69222e82530e89111dd9d14f9b970ab2cd3a1c2c78f0c4fbba4f4",
170 | "sha256:647b3b916cc8f6aeba240c8171be3ab799c3c1b2ea179a3be0bd2712c4237553",
171 | "sha256:66b060946046ca27c0e03e9bec9bba3e0b918bafff84c425ca2cc2e157ce121e",
172 | "sha256:6efa9fa6e1434141df8872d0fa4226fc301b17aacf37429193f9d70b426ea28f",
173 | "sha256:be4715c9d8367e51dbe6bc6d05e205b1ae234f0dc5465931014aa1c4af44c1ba",
174 | "sha256:bea90da782d8e945fccfc958585210d23de374fa9294a9481ed2abcef637ebfc",
175 | "sha256:d318d77ab96f66a59e792a481e2701fba879e1a453aefeebdb17444fe204d1ed",
176 | "sha256:d785fc08d6f4207437e900ffead930a61e634c5e4f980ba6d3dc03c9581748c7",
177 | "sha256:de9559287c4fe8da56e8c3878d2374abc19d1ba2b807bfa7553e912a8e5ba87c",
178 | "sha256:f4f98b190bb918ac0bc0e3dd2ab74ff3573da9f43106f6dba6385406912ec00f",
179 | "sha256:f71f1a7e2d03758f6e957896ed696254e2bc83110ddbc6942018f1a232dd9dad",
180 | "sha256:fb944c8f0b0ab5c1f7846c686bc4cdf8cde7224655c12edcd59d5212cd57bec0"
181 | ],
182 | "version": "==0.23.4"
183 | },
184 | "python-dateutil": {
185 | "hashes": [
186 | "sha256:063df5763652e21de43de7d9e00ccf239f953a832941e37be541614732cdfc93",
187 | "sha256:88f9287c0174266bb0d8cedd395cfba9c58e87e5ad86b2ce58859bc11be3cf02"
188 | ],
189 | "version": "==2.7.5"
190 | },
191 | "python-dotenv": {
192 | "hashes": [
193 | "sha256:122290a38ece9fe4f162dc7c95cae3357b983505830a154d3c98ef7f6c6cea77",
194 | "sha256:4a205787bc829233de2a823aa328e44fd9996fedb954989a21f1fc67c13d7a77"
195 | ],
196 | "index": "pypi",
197 | "version": "==0.9.1"
198 | },
199 | "pytz": {
200 | "hashes": [
201 | "sha256:31cb35c89bd7d333cd32c5f278fca91b523b0834369e757f4c5641ea252236ca",
202 | "sha256:8e0f8568c118d3077b46be7d654cc8167fa916092e28320cde048e54bfc9f1e6"
203 | ],
204 | "version": "==2018.7"
205 | },
206 | "six": {
207 | "hashes": [
208 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
209 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
210 | ],
211 | "version": "==1.11.0"
212 | },
213 | "toolz": {
214 | "hashes": [
215 | "sha256:929f0a7ea7f61c178bd951bdae93920515d3fbdbafc8e6caf82d752b9b3b31c9"
216 | ],
217 | "version": "==0.9.0"
218 | },
219 | "typing": {
220 | "hashes": [
221 | "sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d",
222 | "sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4",
223 | "sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a"
224 | ],
225 | "version": "==3.6.6"
226 | },
227 | "vega-datasets": {
228 | "hashes": [
229 | "sha256:1fa672ba89ded093b30c6d59fce10aca3ac7c927df254e588da7b6d14f695181",
230 | "sha256:4d93760d2ca439180a8f4d70e9bd9a4fe695113757dbde9d8dcaafe3676b0e84"
231 | ],
232 | "index": "pypi",
233 | "version": "==0.5.0"
234 | },
235 | "werkzeug": {
236 | "hashes": [
237 | "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
238 | "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
239 | ],
240 | "version": "==0.14.1"
241 | }
242 | },
243 | "develop": {
244 | "astroid": {
245 | "hashes": [
246 | "sha256:292fa429e69d60e4161e7612cb7cc8fa3609e2e309f80c224d93a76d5e7b58be",
247 | "sha256:c7013d119ec95eb626f7a2011f0b63d0c9a095df9ad06d8507b37084eada1a8d"
248 | ],
249 | "version": "==2.0.4"
250 | },
251 | "atomicwrites": {
252 | "hashes": [
253 | "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
254 | "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
255 | ],
256 | "version": "==1.2.1"
257 | },
258 | "attrs": {
259 | "hashes": [
260 | "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
261 | "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
262 | ],
263 | "version": "==18.2.0"
264 | },
265 | "bumpversion": {
266 | "hashes": [
267 | "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
268 | "sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57"
269 | ],
270 | "index": "pypi",
271 | "version": "==0.5.3"
272 | },
273 | "coverage": {
274 | "hashes": [
275 | "sha256:09e47c529ff77bf042ecfe858fb55c3e3eb97aac2c87f0349ab5a7efd6b3939f",
276 | "sha256:0a1f9b0eb3aa15c990c328535655847b3420231af299386cfe5efc98f9c250fe",
277 | "sha256:0cc941b37b8c2ececfed341444a456912e740ecf515d560de58b9a76562d966d",
278 | "sha256:10e8af18d1315de936d67775d3a814cc81d0747a1a0312d84e27ae5610e313b0",
279 | "sha256:1b4276550b86caa60606bd3572b52769860a81a70754a54acc8ba789ce74d607",
280 | "sha256:1e8a2627c48266c7b813975335cfdea58c706fe36f607c97d9392e61502dc79d",
281 | "sha256:2b224052bfd801beb7478b03e8a66f3f25ea56ea488922e98903914ac9ac930b",
282 | "sha256:447c450a093766744ab53bf1e7063ec82866f27bcb4f4c907da25ad293bba7e3",
283 | "sha256:46101fc20c6f6568561cdd15a54018bb42980954b79aa46da8ae6f008066a30e",
284 | "sha256:4710dc676bb4b779c4361b54eb308bc84d64a2fa3d78e5f7228921eccce5d815",
285 | "sha256:510986f9a280cd05189b42eee2b69fecdf5bf9651d4cd315ea21d24a964a3c36",
286 | "sha256:5535dda5739257effef56e49a1c51c71f1d37a6e5607bb25a5eee507c59580d1",
287 | "sha256:5a7524042014642b39b1fcae85fb37556c200e64ec90824ae9ecf7b667ccfc14",
288 | "sha256:5f55028169ef85e1fa8e4b8b1b91c0b3b0fa3297c4fb22990d46ff01d22c2d6c",
289 | "sha256:6694d5573e7790a0e8d3d177d7a416ca5f5c150742ee703f3c18df76260de794",
290 | "sha256:6831e1ac20ac52634da606b658b0b2712d26984999c9d93f0c6e59fe62ca741b",
291 | "sha256:77f0d9fa5e10d03aa4528436e33423bfa3718b86c646615f04616294c935f840",
292 | "sha256:828ad813c7cdc2e71dcf141912c685bfe4b548c0e6d9540db6418b807c345ddd",
293 | "sha256:85a06c61598b14b015d4df233d249cd5abfa61084ef5b9f64a48e997fd829a82",
294 | "sha256:8cb4febad0f0b26c6f62e1628f2053954ad2c555d67660f28dfb1b0496711952",
295 | "sha256:a5c58664b23b248b16b96253880b2868fb34358911400a7ba39d7f6399935389",
296 | "sha256:aaa0f296e503cda4bc07566f592cd7a28779d433f3a23c48082af425d6d5a78f",
297 | "sha256:ab235d9fe64833f12d1334d29b558aacedfbca2356dfb9691f2d0d38a8a7bfb4",
298 | "sha256:b3b0c8f660fae65eac74fbf003f3103769b90012ae7a460863010539bb7a80da",
299 | "sha256:bab8e6d510d2ea0f1d14f12642e3f35cefa47a9b2e4c7cea1852b52bc9c49647",
300 | "sha256:c45297bbdbc8bb79b02cf41417d63352b70bcb76f1bbb1ee7d47b3e89e42f95d",
301 | "sha256:d19bca47c8a01b92640c614a9147b081a1974f69168ecd494687c827109e8f42",
302 | "sha256:d64b4340a0c488a9e79b66ec9f9d77d02b99b772c8b8afd46c1294c1d39ca478",
303 | "sha256:da969da069a82bbb5300b59161d8d7c8d423bc4ccd3b410a9b4d8932aeefc14b",
304 | "sha256:ed02c7539705696ecb7dc9d476d861f3904a8d2b7e894bd418994920935d36bb",
305 | "sha256:ee5b8abc35b549012e03a7b1e86c09491457dba6c94112a2482b18589cc2bdb9"
306 | ],
307 | "version": "==4.5.2"
308 | },
309 | "isort": {
310 | "hashes": [
311 | "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
312 | "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
313 | "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
314 | ],
315 | "version": "==4.3.4"
316 | },
317 | "lazy-object-proxy": {
318 | "hashes": [
319 | "sha256:350173a273e734c9233d1a6141f998e1864adc189b9c05996f5bb3ef53562283"
320 | ],
321 | "version": "==1.3.1"
322 | },
323 | "mccabe": {
324 | "hashes": [
325 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
326 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
327 | ],
328 | "version": "==0.6.1"
329 | },
330 | "more-itertools": {
331 | "hashes": [
332 | "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
333 | "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
334 | "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
335 | ],
336 | "version": "==4.3.0"
337 | },
338 | "packaging": {
339 | "hashes": [
340 | "sha256:0886227f54515e592aaa2e5a553332c73962917f2831f1b0f9b9f4380a4b9807",
341 | "sha256:f95a1e147590f204328170981833854229bb2912ac3d5f89e2a8ccd2834800c9"
342 | ],
343 | "version": "==18.0"
344 | },
345 | "pluggy": {
346 | "hashes": [
347 | "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095",
348 | "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"
349 | ],
350 | "version": "==0.8.0"
351 | },
352 | "py": {
353 | "hashes": [
354 | "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
355 | "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
356 | ],
357 | "version": "==1.7.0"
358 | },
359 | "pylint": {
360 | "hashes": [
361 | "sha256:1d6d3622c94b4887115fe5204982eee66fdd8a951cf98635ee5caee6ec98c3ec",
362 | "sha256:31142f764d2a7cd41df5196f9933b12b7ee55e73ef12204b648ad7e556c119fb"
363 | ],
364 | "index": "pypi",
365 | "version": "==2.1.1"
366 | },
367 | "pyparsing": {
368 | "hashes": [
369 | "sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b",
370 | "sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592"
371 | ],
372 | "version": "==2.3.0"
373 | },
374 | "pytest": {
375 | "hashes": [
376 | "sha256:488c842647bbeb350029da10325cb40af0a9c7a2fdda45aeb1dda75b60048ffb",
377 | "sha256:c055690dfefa744992f563e8c3a654089a6aa5b8092dded9b6fafbd70b2e45a7"
378 | ],
379 | "index": "pypi",
380 | "version": "==4.0.0"
381 | },
382 | "pytest-cov": {
383 | "hashes": [
384 | "sha256:513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7",
385 | "sha256:e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"
386 | ],
387 | "index": "pypi",
388 | "version": "==2.6.0"
389 | },
390 | "pytest-sugar": {
391 | "hashes": [
392 | "sha256:26cf8289fe10880cbbc130bd77398c4e6a8b936d8393b116a5c16121d95ab283",
393 | "sha256:fcd87a74b2bce5386d244b49ad60549bfbc4602527797fac167da147983f58ab"
394 | ],
395 | "index": "pypi",
396 | "version": "==0.9.2"
397 | },
398 | "six": {
399 | "hashes": [
400 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
401 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
402 | ],
403 | "version": "==1.11.0"
404 | },
405 | "termcolor": {
406 | "hashes": [
407 | "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"
408 | ],
409 | "version": "==1.1.0"
410 | },
411 | "typed-ast": {
412 | "hashes": [
413 | "sha256:668d0cec391d9aed1c6a388b0d5b97cd22e6073eaa5fbaa6d2946603b4871efe"
414 | ],
415 | "markers": "python_version < '3.7' and implementation_name == 'cpython'",
416 | "version": "==1.1.0"
417 | },
418 | "wrapt": {
419 | "hashes": [
420 | "sha256:7bb495f09894e0a375d0a867c1f3f0d444eed0dfed0c2a498023383012fe7f9a"
421 | ],
422 | "version": "==1.10.11"
423 | }
424 | }
425 | }
426 |
--------------------------------------------------------------------------------