Skip to main content

Flask Continuous Integration

Introduction

Continuous Integration (CI) is a development practice where developers integrate their code changes into a shared repository frequently, typically several times a day. Each integration is then automatically verified by building the application and running automated tests. This process helps detect errors quickly and locate them more easily.

For Flask applications, implementing CI ensures that your web application remains stable and functional as new features are added or existing code is modified. This guide will walk you through setting up continuous integration for your Flask applications, helping you maintain code quality and streamline your development workflow.

Why Continuous Integration Matters for Flask Apps

Before diving into the implementation details, let's understand why CI is particularly important for Flask applications:

  1. Quality Assurance: Automatically tests your code to catch bugs early
  2. Consistent Deployments: Ensures your application deploys correctly across different environments
  3. Collaborative Development: Makes it easier for teams to work on the same codebase
  4. Time Savings: Automates repetitive tasks like testing and building
  5. Documentation: Creates a record of builds and test results

Essential Components of a Flask CI Pipeline

A typical CI pipeline for a Flask application includes:

  1. Code Linting: Checks code style and quality
  2. Unit Testing: Tests individual functions and methods
  3. Integration Testing: Tests how different components work together
  4. Security Scanning: Checks for vulnerabilities
  5. Deployment Preview: Creates staging environments for review

Setting Up CI for Flask with GitHub Actions

GitHub Actions is a popular CI/CD platform that integrates directly with your GitHub repository. Let's set up a basic CI pipeline for a Flask application:

Step 1: Create a GitHub Actions Workflow File

In your Flask project repository, create a directory structure: .github/workflows/. Inside this directory, create a file named flask-ci.yml:

yaml
name: Flask CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov flake8
- name: Lint with flake8
run: |
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- name: Test with pytest
run: |
pytest --cov=app tests/

Step 2: Ensure Your Flask Project Structure is CI-Ready

A CI-friendly Flask project might have a structure like this:

my_flask_app/
├── .github/
│ └── workflows/
│ └── flask-ci.yml
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── routes.py
│ └── templates/
├── tests/
│ ├── __init__.py
│ ├── test_models.py
│ └── test_routes.py
├── requirements.txt
├── config.py
└── run.py

Step 3: Write Testable Flask Code

For your Flask application to work well with CI, write code that's easy to test. Here's a simple example:

python
# app/__init__.py
from flask import Flask

def create_app(config_name="default"):
app = Flask(__name__)

# Load configurations based on environment
if config_name == "testing":
app.config.from_object('config.TestingConfig')
else:
app.config.from_object('config.DevelopmentConfig')

# Register blueprints
from app.routes import main
app.register_blueprint(main)

return app

Step 4: Write Tests for Your Flask Application

Create tests that will run in your CI pipeline:

python
# tests/test_routes.py
import pytest
from app import create_app

@pytest.fixture
def client():
app = create_app("testing")
with app.test_client() as client:
yield client

def test_home_page(client):
response = client.get('/')
assert response.status_code == 200
assert b'Welcome to Flask App' in response.data

Implementing More Advanced CI Features

Once you have a basic CI setup working, you can add more advanced features:

Database Testing

To test database interactions, you can set up a test database in your CI environment:

yaml
# Add to your GitHub Actions workflow
- name: Set up test database
run: |
sudo apt-get -y install postgresql
sudo service postgresql start
sudo -u postgres psql -c "CREATE USER flaskuser WITH PASSWORD 'flaskpass';"
sudo -u postgres psql -c "CREATE DATABASE flasktest OWNER flaskuser;"

Then in your tests:

python
# tests/test_models.py
import pytest
from app import create_app, db
from app.models import User

@pytest.fixture
def app():
app = create_app("testing")
with app.app_context():
db.create_all()
yield app
db.session.remove()
db.drop_all()

def test_user_creation(app):
user = User(username='testuser', email='[email protected]')
db.session.add(user)
db.session.commit()
assert User.query.filter_by(username='testuser').first() is not None

Security Scanning

Add security scanning to your CI pipeline:

yaml
# Add to your GitHub Actions workflow
- name: Security scan with Bandit
run: |
pip install bandit
bandit -r app/ -f json -o security-report.json
- name: Upload security report
uses: actions/upload-artifact@v3
with:
name: security-report
path: security-report.json

Code Coverage Reporting

Add code coverage reporting:

yaml
# Add to your GitHub Actions workflow
- name: Upload coverage report
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: false

Setting Up CI with Travis CI

GitHub Actions is just one option. Travis CI is another popular choice for Flask applications:

yaml
# .travis.yml
language: python
python:
- "3.10"

# Install dependencies
install:
- pip install -r requirements.txt
- pip install pytest pytest-cov flake8

# Run tests
script:
- flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- pytest --cov=app tests/

# If you want to deploy after successful tests
deploy:
provider: heroku
api_key:
secure: YOUR_ENCRYPTED_API_KEY
app: your-flask-app-name

Real-World Example: Complete CI/CD Pipeline for a Flask Blog Application

Let's walk through a comprehensive example for a blog application built with Flask:

Project Requirements

Our blog application has the following features:

  • User authentication
  • Blog post creation and editing
  • Comments
  • Tag categorization

CI Pipeline Implementation

Here's a complete GitHub Actions workflow for our blog application:

yaml
name: Flask Blog CI/CD

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]

jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov flake8 bandit

- name: Lint with flake8
run: |
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics

- name: Security scan with Bandit
run: |
bandit -r app/ -f json -o security-report.json

- name: Test with pytest
run: |
pytest --cov=app tests/ --cov-report=xml
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test_db
SECRET_KEY: test_secret_key

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml
fail_ci_if_error: false

deploy-staging:
needs: test
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: akhileshns/heroku-[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: "flask-blog-staging"
heroku_email: ${{ secrets.HEROKU_EMAIL }}

deploy-production:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: akhileshns/heroku-[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: "flask-blog-production"
heroku_email: ${{ secrets.HEROKU_EMAIL }}

How This Pipeline Works

  1. Triggered on Changes: The pipeline runs whenever code is pushed to main or develop branches, or when pull requests target these branches
  2. Sets Up Testing Environment: Includes a PostgreSQL service for database tests
  3. Runs Multiple Checks:
    • Code quality with flake8
    • Security vulnerabilities with Bandit
    • Tests with pytest including code coverage
  4. Automated Deployment:
    • Pushes to develop branch deploy to staging
    • Pushes to main branch deploy to production
    • Both deployments only happen if tests pass

Best Practices for Flask CI

To get the most out of your CI implementation:

  1. Keep Tests Fast: CI should provide quick feedback
  2. Use Mocks for External Services: Don't depend on external APIs in tests
  3. Test Database Migrations: Ensure schema changes don't break your app
  4. Parallelize When Possible: Run independent tests concurrently
  5. Cache Dependencies: Speed up builds by caching pip packages
  6. Use Environment Variables: Store sensitive information as secrets
  7. Implement Branch Protection: Require CI to pass before merging

Troubleshooting Common CI Issues

When working with Flask CI, you might encounter these common issues:

Tests Pass Locally But Fail in CI

This often happens due to environment differences. Solutions include:

  • Use Docker to create consistent environments
  • Explicitly set environment variables in CI
  • Check for path differences between operating systems

Example fix in GitHub Actions:

yaml
- name: Fix path issues
run: |
export PYTHONPATH=$PYTHONPATH:$(pwd)
echo "PYTHONPATH=$PYTHONPATH" >> $GITHUB_ENV

Database Connection Issues

In CI environments, database connections might be different:

python
# config.py
import os

class TestingConfig:
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite:///:memory:'

Slow CI Builds

To speed up your CI pipeline:

yaml
# Add caching to your GitHub Actions workflow
- uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

Summary

Continuous Integration is an essential practice for modern Flask development, helping ensure your web applications remain stable and functional as they evolve. By automating testing and deployment processes, CI frees developers to focus on adding features rather than fixing regressions.

In this guide, we've covered:

  • The fundamentals of CI for Flask applications
  • Setting up CI with GitHub Actions and Travis CI
  • Writing testable Flask code
  • Implementing advanced CI features like security scanning and code coverage
  • Troubleshooting common CI issues

By implementing CI for your Flask projects, you're taking an important step toward more professional and maintainable web applications.

Additional Resources

Exercises

  1. Set up a basic CI pipeline for a simple Flask application that has a home page and an about page.
  2. Extend your CI pipeline to include database testing with SQLite.
  3. Add code coverage reporting to your CI pipeline and aim for at least 80% coverage.
  4. Create a CI pipeline that deploys your Flask application to Heroku after tests pass.
  5. Implement branch protection rules in your GitHub repository that require CI to pass before merging pull requests.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)