Week 1: Workshop - Putting it all together

Integrating Web Fundamentals, Git, and Docker in a Complete Workflow

From Individual Tools to Integrated Workflow

Welcome to our integration workshop! Throughout this week, we've explored individual components of the web development ecosystem: HTTP and web fundamentals, Git for version control, VS Code as our editor, and Docker for containerization. Now it's time to see how these pieces work together in a cohesive workflow.

Think of this workshop as assembling a vehicle. We've studied the engine (Python), the steering system (Git), the chassis (Docker), and the dashboard (VS Code) separately. Now we'll connect these components to create a functional development vehicle that can take us from concept to deployment.

By the end of this hands-on session, you'll have experienced a complete development cycle using all the tools we've learned about, preparing you for the more complex work to come in the following weeks.

Workshop Goals and Structure

In this workshop, we'll create a simple "Hello World" web application using Flask, a lightweight Python web framework. We'll implement proper project structure, version control, containerization, and deployment – all while following development best practices.

Workshop Objectives:

Workshop Flow:

  1. Project planning and structure creation (20 minutes)
  2. Setting up the development environment (30 minutes)
  3. Implementing the basic application (30 minutes)
  4. Containerizing with Docker (30 minutes)
  5. Testing and debugging (20 minutes)
  6. Documentation and reflection (20 minutes)

This hands-on approach mimics real-world development practices, where tools and concepts don't exist in isolation but form an integrated ecosystem.

Project Planning and Structure

Before writing a single line of code, professional developers plan their project structure. This upfront investment pays dividends in maintainability and scalability.

Our Application: Flask Hello World

We'll create a simple Flask web application that:

Project Structure

We'll use a structure that can grow with application complexity:

flask_hello_world/
├── .git/                   # Git repository (created by git init)
├── .gitignore              # Git ignore file
├── .vscode/                # VS Code configuration
│   ├── launch.json         # Debug configuration
│   └── settings.json       # Editor settings
├── app/                    # Application package
│   ├── __init__.py         # Package initializer and app factory
│   ├── routes.py           # Route definitions
│   ├── templates/          # HTML templates
│   │   └── index.html      # Homepage template
│   └── static/             # Static files (CSS, JS, images)
│       └── style.css       # Basic styling
├── docker/                 # Docker configuration
│   ├── Dockerfile          # Main Dockerfile
│   └── docker-compose.yml  # Docker Compose configuration
├── tests/                  # Test directory
│   └── test_app.py         # Basic tests
├── .env                    # Environment variables (not in Git)
├── .env.example            # Example environment variables (in Git)
├── requirements.txt        # Python dependencies
└── README.md               # Project documentation

Why this structure matters:

This organization follows the principle of separation of concerns. Like organizing a kitchen with separate areas for prep, cooking, and plating, this structure separates different aspects of the application:

Real-world parallel: This structure is a simplified version of what you'd see in production Flask applications at companies like Pinterest and LinkedIn, which use Flask for certain microservices.

Setting Up the Development Environment

With our plan in place, let's create the development environment that will support our project.

Project Initialization

Let's create our project directory and initialize Git:

# Create project directory
mkdir flask_hello_world
cd flask_hello_world

# Initialize Git repository
git init

# Create basic project structure
mkdir -p app/templates app/static .vscode docker tests
touch app/__init__.py app/routes.py app/templates/index.html app/static/style.css
touch .gitignore README.md requirements.txt .env.example
touch docker/Dockerfile docker/docker-compose.yml
touch tests/test_app.py
touch .vscode/settings.json .vscode/launch.json

VS Code Configuration

Let's configure VS Code for optimal Python development by creating proper settings.json and launch.json files:

settings.json:

{
    "python.linting.enabled": true,
    "python.linting.flake8Enabled": true,
    "python.formatting.provider": "black",
    "python.formatting.blackArgs": ["--line-length", "88"],
    "editor.formatOnSave": true,
    "python.testing.pytestEnabled": true,
    "python.testing.unittestEnabled": false,
    "python.testing.nosetestsEnabled": false,
    "python.testing.pytestArgs": ["tests"],
    "editor.rulers": [88],
    "files.exclude": {
        "**/__pycache__": true,
        "**/.pytest_cache": true,
        "**/*.pyc": true
    }
}

launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Flask",
            "type": "python",
            "request": "launch",
            "module": "flask",
            "env": {
                "FLASK_APP": "app",
                "FLASK_ENV": "development",
                "FLASK_DEBUG": "1"
            },
            "args": [
                "run",
                "--no-debugger",
                "--host=0.0.0.0"
            ],
            "jinja": true
        },
        {
            "name": "Python: Current File",
            "type": "python",
            "request": "launch",
            "program": "${file}",
            "console": "integratedTerminal"
        }
    ]
}

Git Configuration

Create a .gitignore file to avoid tracking unnecessary files:

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# Virtual Environment
venv/
ENV/

# VS Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# Environment Variables
.env
.env.*
!.env.example

# Docker
.docker/

# Tests
.coverage
htmlcov/
.pytest_cache/

Python Virtual Environment

Create a virtual environment and install dependencies:

# Create a virtual environment
python -m venv venv

# Activate the virtual environment
# On Windows:
venv\Scripts\activate
# On macOS/Linux:
source venv/bin/activate

# Create requirements.txt with dependencies
cat > requirements.txt << EOL
flask==2.3.3
python-dotenv==1.0.0
pytest==7.4.0
black==23.7.0
flake8==6.1.0
EOL

# Install dependencies
pip install -r requirements.txt

Analogy: Setting up the environment is like preparing a kitchen before cooking. You ensure all utensils and ingredients are available and organized before starting to cook. Similarly, we prepare all our development tools before writing code.

Implementing the Basic Application

Now let's implement our Flask application following best practices and proper structure.

Application Factory Pattern

We'll use the application factory pattern to allow for easier testing and multiple instances:

app/__init__.py:

from flask import Flask

def create_app(test_config=None):
    """Create and configure the Flask application."""
    app = Flask(__name__, instance_relative_config=True)
    
    # Default configuration
    app.config.from_mapping(
        SECRET_KEY='dev',
    )
    
    if test_config is None:
        # Load the instance config, if it exists, when not testing
        app.config.from_pyfile('config.py', silent=True)
    else:
        # Load the test config if passed in
        app.config.from_mapping(test_config)
    
    # Register routes
    from app import routes
    routes.register_routes(app)
    
    return app

# This allows running the app with 'flask run'
app = create_app()

Routes Module

app/routes.py:

from flask import render_template, jsonify

def register_routes(app):
    """Register all application routes."""
    
    @app.route('/')
    def index():
        """Render the homepage."""
        return render_template('index.html', title="Flask Hello World")
    
    @app.route('/api/hello')
    def hello_api():
        """Return a JSON greeting."""
        return jsonify({
            'message': 'Hello, World!',
            'status': 'success'
        })
    
    @app.route('/about')
    def about():
        """About page."""
        return render_template('index.html', title="About", 
                               content="This is a simple Flask application created during our workshop.")

HTML Template

app/templates/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }}</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <header>
        <h1>{{ title }}</h1>
        <nav>
            <a href="{{ url_for('index') }}">Home</a>
            <a href="{{ url_for('about') }}">About</a>
        </nav>
    </header>
    
    <main>
        {% if content %}
            <p>{{ content }}</p>
        {% else %}
            <h2>Welcome to our Flask Hello World application!</h2>
            <p>This application was created during the Week 1 Workshop of our Python Full Stack Developer Course.</p>
            
            <h3>API Example</h3>
            <button id="apiButton">Get JSON Greeting</button>
            <pre id="apiResponse"></pre>
        {% endif %}
    </main>
    
    <footer>
        <p>© 2025 Python Full Stack Developer Course</p>
    </footer>
    
    <script>
        document.getElementById('apiButton').addEventListener('click', async () => {
            const response = await fetch('/api/hello');
            const data = await response.json();
            document.getElementById('apiResponse').textContent = JSON.stringify(data, null, 2);
        });
    </script>
</body>
</html>

CSS Styling

app/static/style.css:

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}

header {
    margin-bottom: 20px;
    padding-bottom: 10px;
    border-bottom: 1px solid #eee;
}

nav {
    margin-top: 10px;
}

nav a {
    margin-right: 15px;
    text-decoration: none;
    color: #0066cc;
}

nav a:hover {
    text-decoration: underline;
}

h1, h2, h3 {
    margin-bottom: 15px;
}

main {
    margin-bottom: 30px;
}

button {
    background-color: #0066cc;
    color: white;
    border: none;
    padding: 8px 16px;
    border-radius: 4px;
    cursor: pointer;
    margin: 10px 0;
}

button:hover {
    background-color: #0055aa;
}

pre {
    background-color: #f4f4f4;
    padding: 15px;
    border-radius: 4px;
    overflow-x: auto;
    margin: 10px 0;
    min-height: 20px;
}

footer {
    margin-top: 20px;
    padding-top: 10px;
    border-top: 1px solid #eee;
    text-align: center;
    font-size: 0.8rem;
    color: #666;
}

Environment Variables

.env.example:

FLASK_APP=app
FLASK_ENV=development
FLASK_DEBUG=1

Create your actual .env file (not committed to Git):

cp .env.example .env

First Git Commit

Now let's commit our initial application:

git add .
git commit -m "feat: Initial implementation of Flask Hello World application"

Real-world practice: This structured approach to implementing a Flask application reflects patterns used in production environments. The application factory pattern, for example, is used by companies like Mozilla and Twilio in their Flask applications to allow for better testing and configuration management.

Containerizing with Docker

Now that our application works locally, let's containerize it for consistent deployment.

Dockerfile

docker/Dockerfile:

FROM python:3.10-slim

WORKDIR /app

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV FLASK_APP=app

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

# Copy application code
COPY . .

# Expose port
EXPOSE 5000

# Command to run when container starts
CMD ["flask", "run", "--host=0.0.0.0"]

Docker Compose Configuration

docker/docker-compose.yml:

version: '3.8'

services:
  web:
    build:
      context: ..
      dockerfile: docker/Dockerfile
    ports:
      - "5000:5000"
    volumes:
      - ..:/app
    environment:
      - FLASK_APP=app
      - FLASK_ENV=development
      - FLASK_DEBUG=1
    command: flask run --host=0.0.0.0

Building and Running with Docker

Let's build and run our container:

# Navigate to the docker directory
cd docker

# Build and start the container
docker-compose up -d --build

# Check that the container is running
docker-compose ps

Now you can access the application at http://localhost:5000

Git Commit for Docker Configuration

git add .
git commit -m "feat: Add Docker configuration for containerization"

Analogy: Containerizing an application is like vacuum-sealing a meal – it preserves all the ingredients and preparations in exactly the state you intended, ensuring it will be the same when "opened" in any environment.

Real-world application: At companies like Uber and Airbnb, containerization is a standard practice for ensuring consistency between development and production environments, especially for microservices architectures where dozens or hundreds of small applications must work together seamlessly.

Testing and Debugging

Professional developers ensure their code works correctly through testing. Let's add some tests and learn debugging techniques.

Basic Tests

tests/test_app.py:

import pytest
from app import create_app

@pytest.fixture
def app():
    """Create application for the tests."""
    app = create_app({
        'TESTING': True,
    })
    
    yield app

@pytest.fixture
def client(app):
    """A test client for the app."""
    return app.test_client()

def test_index_page(client):
    """Test that the index page loads correctly."""
    response = client.get('/')
    assert response.status_code == 200
    assert b'Welcome to our Flask Hello World application!' in response.data

def test_about_page(client):
    """Test that the about page loads correctly."""
    response = client.get('/about')
    assert response.status_code == 200
    assert b'This is a simple Flask application' in response.data

def test_api_endpoint(client):
    """Test that the API endpoint returns correct JSON."""
    response = client.get('/api/hello')
    assert response.status_code == 200
    json_data = response.get_json()
    assert json_data['message'] == 'Hello, World!'
    assert json_data['status'] == 'success'

Running Tests

Let's run our tests to ensure everything works:

# Run tests
pytest -v tests/

Debugging with VS Code

Now let's try debugging our application using VS Code's debug configuration that we set up earlier:

  1. Stop the Docker container if it's running: docker-compose down
  2. Open VS Code in the project directory: code .
  3. Set a breakpoint in routes.py, for example on the return line in the index function
  4. Press F5 or click the "Run and Debug" button in VS Code
  5. Select the "Flask" configuration
  6. Open http://localhost:5000 in your browser
  7. VS Code will pause execution at your breakpoint, allowing you to inspect variables, step through code, etc.

Common Issues and Solutions

Issue 1: Module not found errors

If you get "ModuleNotFoundError" when running the application, check:

Issue 2: Port already in use

If port 5000 is already in use, you'll get an OSError. To resolve:

Issue 3: Docker container won't start

If the Docker container fails to start, check:

Git Commit for Tests

git add .
git commit -m "test: Add basic tests for application functionality"

Real-world practice: Companies like Shopify and Twilio have extensive test suites for their applications, with test coverage requirements before code can be merged. Debugging skills are equally valued, as they significantly reduce time spent identifying and fixing issues in production.

Documentation and Project Reflection

Documentation is critical for both personal reference and team collaboration. Let's create a comprehensive README.md file:

Project README

README.md:

# Flask Hello World

A simple Flask web application created during the Week 1 Workshop of the Python Full Stack Developer Course.

## Features

- Simple web pages with Flask routing
- API endpoint returning JSON data
- Containerized with Docker
- Configured for VS Code development
- Includes basic tests

## Development Setup

### Prerequisites

- Python 3.10 or higher
- Docker and Docker Compose
- Visual Studio Code (recommended)

### Local Setup

1. Clone the repository:
   ```
   git clone <repository-url>
   cd flask_hello_world
   ```

2. Create a virtual environment and install dependencies:
   ```
   python -m venv venv
   source venv/bin/activate  # On Windows: venv\Scripts\activate
   pip install -r requirements.txt
   ```

3. Create a .env file:
   ```
   cp .env.example .env
   ```

4. Run the application:
   ```
   flask run
   ```

5. Access the application at http://localhost:5000

### Docker Setup

1. Build and run with Docker Compose:
   ```
   cd docker
   docker-compose up -d
   ```

2. Access the application at http://localhost:5000

3. Stop the container when finished:
   ```
   docker-compose down
   ```

## Testing

Run tests with pytest:
```
pytest -v tests/
```

## Project Structure

- `app/`: Application package
  - `__init__.py`: Application factory
  - `routes.py`: Route definitions
  - `templates/`: HTML templates
  - `static/`: Static files (CSS, JS)
- `docker/`: Docker configuration
- `tests/`: Test suite
- `.vscode/`: VS Code configuration

## What I Learned

In this project, I learned how to:

- Structure a Flask application using the application factory pattern
- Set up a development environment with VS Code
- Implement proper Git workflow with meaningful commits
- Containerize a Python application with Docker
- Write basic tests for Flask routes
- Configure debugging tools for efficient development

## Next Steps

Future enhancements could include:

- Database integration
- User authentication
- More complex API endpoints
- Frontend framework integration
- Deployment to a cloud platform

Final Git Commit

git add .
git commit -m "docs: Add comprehensive README with setup instructions and project details"

Reflection Questions

Take a few minutes to reflect on this workshop:

Analogy: Good documentation is like a map for a complex city. It may seem unnecessary when you know the area well, but it becomes invaluable when you return after an absence or when guiding someone new.

Real-world importance: In professional environments, documentation is often considered as important as the code itself. Companies like Google and Microsoft have strict documentation requirements, recognizing that code is read far more often than it is written, and that clear documentation reduces onboarding time for new team members.

Workshop Conclusion

Congratulations! You've successfully integrated all the key concepts from Week 1 into a functional workflow. Let's summarize what we've accomplished:

What We Built

Key Skills Practiced

From Here to Week 2

As we move into Week 2, focusing on Python fundamentals, remember that these workflow practices apply to all types of development. The foundation we've built today – structured approach, version control, containerization, and testing – will serve as the backbone for all future projects.

Your weekend assignment will build directly on these skills, challenging you to apply them independently to create a complete containerized Python environment.

Final thought: Just as a chef doesn't just learn ingredients but also kitchen workflow, timing, and presentation, a developer needs more than just programming syntax – they need an integrated workflow that brings efficiency and reliability to their craft. Today, you've taken a significant step toward developing that professional workflow.

Additional Resources

Flask Development

Docker and Containerization

Development Workflow

Project Templates