惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Schneier on Security
N
News and Events Feed by Topic
量子位
S
Secure Thoughts
V2EX - 技术
V2EX - 技术
Hugging Face - Blog
Hugging Face - Blog
S
Security Affairs
J
Java Code Geeks
Schneier on Security
Schneier on Security
Google Online Security Blog
Google Online Security Blog
TaoSecurity Blog
TaoSecurity Blog
小众软件
小众软件
S
SegmentFault 最新的问题
www.infosecurity-magazine.com
www.infosecurity-magazine.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Security Archives - TechRepublic
Security Archives - TechRepublic
P
Privacy International News Feed
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
博客园 - 聂微东
T
Tor Project blog
博客园 - Franky
C
CERT Recently Published Vulnerability Notes
Cyberwarzone
Cyberwarzone
罗磊的独立博客
博客园_首页
The Cloudflare Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
大猫的无限游戏
大猫的无限游戏
Forbes - Security
Forbes - Security
V
Vulnerabilities – Threatpost
Security Latest
Security Latest
腾讯CDC
Simon Willison's Weblog
Simon Willison's Weblog
S
Securelist
博客园 - 【当耐特】
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
博客园 - 司徒正美
AWS News Blog
AWS News Blog
WordPress大学
WordPress大学
Jina AI
Jina AI
G
GRAHAM CLULEY
V
V2EX
L
LINUX DO - 最新话题
H
Heimdal Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
IT之家
IT之家

Stonecharioteer on Tech

I Traced My Traffic Through a Home Tailscale Exit Node What Was I Reading Last? In Three Not-So-Easy Pieces Dogfooding Is Hard Code blocks in your books, finally GoForGo v0.9.0 Merrilin - We built an app to read books I use a Macbook now Data Structures & Algorithms - Preparing for Interviews Using a local DNS namespace for local service discovery Direction KOllector - Publishing KOReader Highlights gbt: branches touched in the last 24 hours A Soiree into Symbols in Ruby Some Smalltalk about Ruby Loops Ruby Blocks Returning from Ruby Blocks, Procs and Lambdas My Linux Laptop Finally Works: How Claude Helped Me Fix Years of Annoyances TIL: Watchexec - Modern File Watching for Development Workflows A Less Busy Mind GoForGo - Learn Go through live examples Migrating My Old Blog to Hugo with Claude The Qtile Window Manager: A Python-Powered Tiling Experience Read the RFCs that Built the Internet Py-x-Protobuf - Or How I Learned to Stop Worrying and Love Protocol Buffers Python Reverse a List New Beginnings Leaving ChainSafe Systems Screen Lock for Cinnamon Desktop using Zenity and Terminal Commands Crews Not Teams A System for Getting Better at LeetCode So Far So Rust Retrying HTTP Requests with Rust A Primer on Control Charts Learning Rust Explicit is Better than Implicit: Rust for Pythonistas Using Custom Delimiters in Jinja Templates TIL: Creating Fixed Length Iterables in Python Documentation Without Assumption Vagrant Python - A Reflection in 2022 Learning Golang No, A Virtual Machine Is Not Enough: Why Developers Need Native Linux Empathy in Tech For Those Who Came in Late A Weekend With PostgreSQL TIL: Gooey and Python Fire for Quick GUIs and CLIs TIL: 2ality - Dr. Axel Rauschmayer's JavaScript Blog TIL: MassDNS - High-Performance Bulk DNS Lookups TIL: Matomo Analytics, Google Tech Writing, Memory Programming, and NES TV Signals TIL: MontyDB - MongoDB Implemented in Python Returning to the Craft of Programming TIL: CPUFetch, OneFetch, and Learn CSS TIL: DNS Performance Testing and Pi-hole with Unbound TIL: Eli Bendersky's Blog, Awesome By Example, NoCoDB, and Martin Kleppmann TIL: CRDTs, Extreme HTTP Performance, and BYTEPATH Game TIL: AutoInvent, ASGI, Python Packaging, RAPIDS GPU Computing, and FlaskCon TIL: MangaDesk - Terminal Client for MangaDex TIL: McFly - Smart Shell History Search TIL: Siege Load Testing and Awesome FastAPI Resources TIL: Ventoy Bootable USB and Justniffer Network Analysis TIL: CLI Code Review, Git Split Diffs, and Internal Combustion Engine TIL: Benford's Law, Web Security Headers, Event Sourcing, and Mozilla Security Guidelines How to Write Documentation - The README.md File The Importance of Documentation TIL: NNgroup UX Research, SponsorBlock, and Labella Python Library TIL: The Little Book of Rust Macros and Rust Performance Book TIL: Git-Bug Distributed Issue Tracker and Omni Kubernetes Monitoring TIL: Zellij - Modern Terminal Multiplexer TIL: How Discord Handles 2.5 Million Concurrent Voice Users TIL: Volumio - The Audiophile Music Player TIL: Areopagitica - Milton's Defense of Free Speech TIL: Fast Node Manager, Zoxide Smart CD, Technical Writing, PyO3, and Qubes OS TIL: Slurm Workload Manager for HPC Clusters TIL: Data Visualization Guide and Oso Authorization Academy TIL: CORS Deep Dive, Piku Tiny PaaS, Rust Strings, and Deno Standard Library TIL: Raspberry Pi OS Development, Vim Beginner Guide, Password Management, and QueryBook TIL: uBlock Origin Performance Optimization on Firefox TIL: Breaking PostgreSQL at Scale and LeetCode Problem Patterns TIL: Awesome Tmux Resources for Terminal Multiplexing TIL: Grit - A Multitree-Based Personal Task Manager TIL: Lens 4.2 Kubernetes IDE, Shell Scripting Guide, and Dark HTTP Server Do The Job You Hate So You Won't Hate The Job You Love TIL: Innernet VPN Solution and NoteCalc Calculator App TIL: Argo CD for GitOps and Lens Kubernetes IDE TIL: Modern Rust CLI Tools - System Monitoring, HTTP Requests, and DNS TIL: tz - A Time Zone Helper Tool TIL: Distributed Systems Education, Fallacies, and Self-Hosted Internet Archiving TIL: Real-Time Voice Cloning Technology TIL: ChartMuseum for Helm, AMD's Corporate Journey, and Kubernetes Pod Scaling TIL: Docker and Kubernetes Tools - Whaler, Descheduler, and Dive TIL: Post-Mortem Collection, Terminal Plotting, and Technical Twitter TIL: Dark Mode Toggle Web Component by Google Chrome Labs TIL: Python eval(), exec(), and compile() Functions TIL: Camelot PDF Tables, PostgreSQL Row Level Security, Zerodha Varsity, and Write Yourself a Git TIL: fuser Command for Process and File Investigation TIL: i Hate Regex - The Ultimate Regex Cheat Sheet TIL: Dolt - Git for Data and Database Version Control TIL: x86 Assembly Programming and SafeEyes Break Reminder TIL: Comprehensive Distributed Systems Reading List TIL: Cosmopolitan C Library, Distributed Systems Book, High Performance Browser Networking, and Rust Roguelike Tutorial
Building Single Page Applications in React using Dash and Python
2020-09-15 · via Stonecharioteer on Tech

The dash library from Plotly allows you to build webpages that internally serve React components. Let’s see how to use this to build a SPA with authentication, multiple pages and zero Javascript.

Introduction

Dash is a Python library from Plotly that helps you take graphs that are generated using Plotly and then convert them into a web page.

In simple terms, Dash does just this. However, what Dash does internally is take your Python objects that define your views and convert them into React components. This is a powerful way of building React applications without having to write your own React code.

Dash also uses Flask internally, converting all your function calls into Flask API calls. This makes it easy to decouple the application and build a full stack application purely in Python.

Note that by saying we are writing Python for the UI, I don’t mean that we are using WASM. This approach has nothing to do with Web Assembly. Instead, it is a simpler way to just take Python code and generate Javascript.

Prerequisites

To understand this article, you should have an understanding of the following items.

  1. Python: Learn how decorators work, how to write class definition and how methods are called.
  2. Flask: Go through a good tutorial on flask. You will need to understand blueprints, the Application factory method approach, and MethodViews.
  3. Plotly: Some minor understanding of the Plotly library is required.
  4. HTML and CSS: While JS is not required to build your own Dash apps, I’d recommend getting to learn as much HTML and CSS as you can since that will help you polish your application.

Resources for all these can be found at the end of this article.

Setup and Installation

Get the source code for this repository. Make sure you get all the tags.

1
2
3
git clone https://github.com/stonecharioteer/dash-spa
cd dash-spa
git fetch --all --tags --prune

Then, checkout the first version of this application.

First, as you always should, make a virtual environment using a Python 3 (I use 3.8).

1
2
3
python3 -m venv env
source env/bin/activate
pip install -r requirements.txt

Running the Application

This application uses gunicorn for deployment. I have provided a sample wsgi.py file for use with gunicorn. So go ahead and use it.

1
gunicorn -w 6 -b 0.0.0.0:10000 wsgi:app

Navigate to http://localhost:10000 to see your application running.

Structure of the Application

This application is broken into two portions. The first portion is the Flask application, while the other portion is the Dash application. Dash builds up an application above a Flask app by default. However, it can be explicitly attached to a Flask application.

I personally recommend the latter method as it provides better control over the application structure.

Flask Structure

The Flask application follows the standard application factory pattern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# app/__init__.py
from flask import Flask
from dash import Dash

def create_app(config_name='development'):
    app = Flask(__name__)

    # Load configuration
    app.config.from_object(config[config_name])

    # Initialize extensions
    from .dash_app import create_dash_app
    create_dash_app(app)

    # Register blueprints
    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)

    return app

The Flask app handles:

  • Authentication and session management
  • API endpoints for data processing
  • Static file serving
  • Configuration management

Dash Structure

The Dash application is created as a separate module that gets attached to the Flask app:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# app/dash_app.py
import dash
from dash import dcc, html, Input, Output
import plotly.express as px
import pandas as pd

def create_dash_app(flask_app):
    dash_app = dash.Dash(
        __name__,
        server=flask_app,
        url_base_pathname='/dashboard/',
        external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css']
    )

    # Define the layout
    dash_app.layout = html.Div([
        html.H1("My Dashboard"),
        dcc.Graph(id='example-graph'),
        dcc.Dropdown(
            id='dropdown',
            options=[
                {'label': 'Option 1', 'value': 'opt1'},
                {'label': 'Option 2', 'value': 'opt2'}
            ],
            value='opt1'
        )
    ])

    # Define callbacks
    @dash_app.callback(
        Output('example-graph', 'figure'),
        Input('dropdown', 'value')
    )
    def update_graph(selected_value):
        # Your data processing logic here
        df = get_data_based_on_selection(selected_value)
        fig = px.bar(df, x='category', y='value')
        return fig

    return dash_app

def get_data_based_on_selection(selection):
    # Mock data function
    return pd.DataFrame({
        'category': ['A', 'B', 'C'],
        'value': [1, 2, 3] if selection == 'opt1' else [3, 2, 1]
    })

Testing the Application

Testing the Utilities

For utility functions, use standard unit tests:

1
2
3
4
5
6
7
8
9
# tests/test_utils.py
import unittest
from app.utils import process_data

class TestUtils(unittest.TestCase):
    def test_process_data(self):
        input_data = {'key': 'value'}
        result = process_data(input_data)
        self.assertEqual(result['processed'], True)

Testing the Flask API

Test Flask routes using the Flask test client:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# tests/test_flask_routes.py
import unittest
from app import create_app

class TestFlaskRoutes(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.client = self.app.test_client()
        self.ctx = self.app.app_context()
        self.ctx.push()

    def tearDown(self):
        self.ctx.pop()

    def test_home_page(self):
        response = self.client.get('/')
        self.assertEqual(response.status_code, 200)

Testing the Dash UI with Selenium

For end-to-end testing of Dash components, use Selenium:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# tests/test_dash_ui.py
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import threading
import time
from app import create_app

class TestDashUI(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.server_thread = threading.Thread(
            target=self.app.run,
            kwargs={'port': 8050, 'debug': False}
        )
        self.server_thread.daemon = True
        self.server_thread.start()
        time.sleep(2)  # Wait for server to start

        self.driver = webdriver.Chrome()  # or webdriver.Firefox()

    def tearDown(self):
        self.driver.quit()

    def test_dashboard_loads(self):
        self.driver.get('http://localhost:8050/dashboard/')

        # Wait for the page to load
        WebDriverWait(self.driver, 10).until(
            EC.presence_of_element_located((By.ID, "example-graph"))
        )

        # Test dropdown interaction
        dropdown = self.driver.find_element(By.ID, "dropdown")
        dropdown.click()

        # Verify graph updates
        graph = self.driver.find_element(By.ID, "example-graph")
        self.assertTrue(graph.is_displayed())

Note on User Acceptance Tests

User Acceptance Tests (UATs) for Dash applications should focus on:

  • Component interactions work as expected
  • Data visualizations render correctly
  • Responsive design works across devices
  • Performance under various data loads

Dash Callbacks

Callbacks are the heart of Dash interactivity. They define how components communicate:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Advanced callback example
@dash_app.callback(
    [Output('graph-1', 'figure'),
     Output('graph-2', 'figure'),
     Output('status-text', 'children')],
    [Input('date-picker', 'start_date'),
     Input('date-picker', 'end_date'),
     Input('filter-dropdown', 'value')],
    [State('user-input', 'value')]
)
def update_dashboard(start_date, end_date, filter_value, user_input):
    # Process inputs
    filtered_data = filter_data(start_date, end_date, filter_value)

    # Create visualizations
    fig1 = create_time_series(filtered_data)
    fig2 = create_distribution(filtered_data)

    # Update status
    status = f"Showing data from {start_date} to {end_date}"

    return fig1, fig2, status

def filter_data(start_date, end_date, filter_value):
    # Your data filtering logic
    pass

def create_time_series(data):
    # Create time series visualization
    pass

def create_distribution(data):
    # Create distribution visualization
    pass

Callback Best Practices

  • Keep callbacks focused on single responsibilities
  • Use State for values that shouldn’t trigger callbacks
  • Implement error handling for data processing
  • Consider performance implications for large datasets

Deployment

Using systemctl and gunicorn

Create a systemd service file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# /etc/systemd/system/dash-spa.service
[Unit]
Description=Dash SPA Application
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/path/to/your/app
Environment=PATH=/path/to/your/app/env/bin
ExecStart=/path/to/your/app/env/bin/gunicorn -w 4 -b 0.0.0.0:8000 wsgi:app
Restart=always

[Install]
WantedBy=multi-user.target

Enable and start the service:

1
2
3
sudo systemctl daemon-reload
sudo systemctl enable dash-spa
sudo systemctl start dash-spa

Using Docker

Create a Dockerfile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
FROM python:3.8-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "wsgi:app"]

Build and run:

1
2
docker build -t dash-spa .
docker run -p 8000:8000 dash-spa

Using docker-compose

Create a docker-compose.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
version: "3.8"

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - FLASK_ENV=production
    volumes:
      - ./data:/app/data
    depends_on:
      - redis
      - postgres

  redis:
    image: redis:alpine
    ports:
      - "6379:6379"

  postgres:
    image: postgres:13
    environment:
      POSTGRES_DB: dashapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  postgres_data:

Using Kubernetes

Create Kubernetes manifests:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dash-spa
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dash-spa
  template:
    metadata:
      labels:
        app: dash-spa
    spec:
      containers:
        - name: dash-spa
          image: your-registry/dash-spa:latest
          ports:
            - containerPort: 8000
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: app-secrets
                  key: database-url

---
apiVersion: v1
kind: Service
metadata:
  name: dash-spa-service
spec:
  selector:
    app: dash-spa
  ports:
    - port: 80
      targetPort: 8000
  type: LoadBalancer

Deploy to Kubernetes:

1
kubectl apply -f deployment.yaml

Pushing Updates to Settings

For configuration updates without downtime:

1
2
kubectl create configmap app-config --from-file=config.py
kubectl rollout restart deployment/dash-spa

Using the Cookiecutter Template

To streamline future projects, create a cookiecutter template:

1
cookiecutter https://github.com/stonecharioteer/cookiecutter-dash-spa

This will generate a new project with:

  • Proper Flask application factory structure
  • Dash integration setup
  • Testing framework configured
  • Docker configuration
  • CI/CD pipeline templates

Advanced Features

Authentication Integration

1
2
3
4
5
6
7
8
9
from flask_login import LoginManager, login_required

def create_dash_app(flask_app):
    # Protect Dash routes with authentication
    for view_func in flask_app.server.view_functions:
        if view_func.startswith('/dashboard/'):
            flask_app.server.view_functions[view_func] = login_required(
                flask_app.server.view_functions[view_func]
            )

Real-time Updates

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import dash
from dash import dcc, html
from dash.dependencies import Input, Output
import plotly.graph_objs as go
from collections import deque
import random

# For real-time data updates
@dash_app.callback(
    Output('live-graph', 'figure'),
    Input('graph-update', 'n_intervals')
)
def update_graph_live(n):
    # Simulate real-time data
    global data_queue
    data_queue.append(random.randint(1, 100))

    if len(data_queue) > 50:
        data_queue.popleft()

    trace = go.Scatter(
        y=list(data_queue),
        mode='lines+markers'
    )

    return {'data': [trace], 'layout': go.Layout(xaxis=dict(range=[0, 50]))}

Performance Optimization

Caching

1
2
3
4
5
6
7
8
from flask_caching import Cache

cache = Cache()

@cache.memoize(timeout=300)  # Cache for 5 minutes
def expensive_computation(params):
    # Your expensive computation here
    pass

Asynchronous Processing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from celery import Celery

celery = Celery('dash-spa')

@celery.task
def process_large_dataset(data_id):
    # Process data in background
    pass

# In your callback
@dash_app.callback(...)
def trigger_processing(n_clicks):
    if n_clicks:
        process_large_dataset.delay(data_id)
        return "Processing started..."

End Note

Building SPAs with Dash and Python offers a unique approach to web development that leverages Python’s data science ecosystem while providing modern web interfaces. This approach is particularly powerful for:

  • Data-heavy applications
  • Scientific computing interfaces
  • Business intelligence dashboards
  • Rapid prototyping of analytical tools

The combination of Flask’s flexibility and Dash’s component-based architecture creates a robust foundation for scalable web applications without requiring deep JavaScript expertise.

Consider Your Use Case

While Dash is powerful, consider traditional web frameworks for applications that require heavy DOM manipulation, complex user interactions, or when you need maximum performance for client-side operations.

References

  1. Python 101: Classes
  2. Python 101: Decorators
  3. Real Python Article on Decorators
  4. Real Python article on Virtual Environments
  5. Miguel Grinberg’s Flask Mega Tutorial
  6. Explore Flask
  7. Flask create_app example
  8. Plotly Documentation
  9. Dash documentation
  10. Pytest Documentation
  11. Gunicorn Documentation
  12. How to write a systemctl file
  13. Docker tutorial
  14. Docker Flask tutorial
  15. Docker Compose tutorial
  16. k3s documentation
  17. Flask on k8s
  18. Dash Enterprise - For production deployments
  19. Dash Bootstrap Components - For better styling
  20. Plotly Community Forum - For community support