Python Full Stack Web Developer Course

Week 2: Python Fundamentals (Part 1)

Friday Morning: Requirements.txt Files

Introduction to requirements.txt Files

Welcome to our session on requirements.txt files! In Python development, effectively managing your project's dependencies is crucial for creating reproducible, shareable, and maintainable code. The requirements.txt file is a cornerstone of Python dependency management—a simple yet powerful tool that helps you specify exactly what packages your project needs.

Think of a requirements.txt file as a shopping list for your Python environment. Just as a detailed shopping list ensures you get exactly what you need at the grocery store—no more, no less—a well-crafted requirements.txt file ensures that anyone (including future you) can recreate your Python environment exactly as needed for your project to run successfully.

Today, we'll explore how to create, maintain, and leverage requirements.txt files effectively as part of your Python development workflow. Whether you're working on solo projects or collaborating in large teams, mastering this aspect of dependency management will save you countless hours of troubleshooting and help you create more professional, distributable Python applications.

Why Use requirements.txt Files?

Before diving into the technical details, let's understand why requirements.txt files are so important in Python projects:

1. Environment Reproducibility

One of the most challenging aspects of software development is ensuring that code works consistently across different environments. A requirements.txt file serves as a precise record of your dependencies, allowing anyone to recreate your environment exactly.

Real-World Scenario: Imagine deploying your application to a production server, only to discover it breaks because the server has different package versions installed. With a requirements.txt file, you can ensure the production environment has exactly the same dependencies as your development environment.

2. Collaboration Facilitation

When working in teams, having a standardized way to manage dependencies is essential for smooth collaboration.

Real-World Scenario: A new developer joins your team and needs to set up the project environment. Instead of asking them to manually install each dependency or providing a lengthy setup document, you can simply direct them to run pip install -r requirements.txt.

3. Version Control Integration

Requirements files are typically plain text and small in size, making them perfect for version control systems like Git.

Real-World Scenario: When you add a new feature that requires a new dependency, committing the updated requirements.txt file allows other team members to see exactly what changed in the dependency landscape.

4. Deployment Automation

Modern deployment pipelines often automatically install dependencies from requirements.txt during the build process.

Real-World Scenario: CI/CD pipelines like GitHub Actions, GitLab CI, or Jenkins can automatically install requirements, run tests, and deploy your application—all using your requirements.txt file to ensure consistent environments.

5. Dependency Documentation

A requirements.txt file serves as documentation of what packages your project depends on, making it easier for others to understand your project's external needs.

Real-World Scenario: When evaluating an open-source project, developers often check the requirements.txt file first to understand what technologies the project uses and whether it might have security concerns or conflicts with their existing environment.

Real-World Analogy: Think of a requirements.txt file as a recipe. A good recipe doesn't just list ingredients—it specifies exact amounts. Similarly, a good requirements.txt doesn't just list packages—it specifies exact versions to ensure consistent results every time.

Basic Usage of requirements.txt

Creating a requirements.txt File

There are several ways to create a requirements.txt file:

Method 1: Manual Creation

You can create a requirements.txt file manually by listing each package on a separate line:

# requirements.txt
flask
requests
numpy
pandas
Method 2: Using pip freeze

The more common approach is to let pip generate the file based on your current environment:

# Generate requirements.txt from your current environment
pip freeze > requirements.txt

This command captures all installed packages and their exact versions:

# requirements.txt generated by pip freeze
flask==2.0.1
requests==2.26.0
numpy==1.21.2
pandas==1.3.3
# ... potentially many more dependencies

Installing Dependencies from requirements.txt

Once you have a requirements.txt file, others can recreate your environment:

# Install all packages listed in requirements.txt
pip install -r requirements.txt

Updating a requirements.txt File

When you add or update dependencies, you should update your requirements.txt file:

# Install a new package
pip install matplotlib

# Update requirements.txt with the new dependency
pip freeze > requirements.txt

Basic Syntax and Options

A requirements.txt file has a simple syntax, but offers several options:

Package Names Only
flask
requests
numpy
Specifying Exact Versions
flask==2.0.1
requests==2.26.0
numpy==1.21.2
Specifying Minimum Versions
flask>=2.0.0
requests>=2.25.0
numpy>=1.20.0
Specifying Version Ranges
flask>=2.0.0,<3.0.0
requests>=2.25.0,<2.27.0
numpy>=1.20.0,<1.22.0
Installing from Version Control
flask==2.0.1
git+https://github.com/psf/requests.git@v2.26.0
numpy==1.21.2
Including Comments
# Web framework
flask==2.0.1

# HTTP library for API requests
requests==2.26.0

# Numerical computing
numpy==1.21.2
Using Environment Markers
flask==2.0.1
requests==2.26.0
numpy==1.21.2
pywin32==301; sys_platform == 'win32'

Best Practices for requirements.txt Files

1. Pin Your Versions

Always specify exact versions of packages to ensure reproducibility.

# Not recommended
flask
requests
numpy

# Recommended
flask==2.0.1
requests==2.26.0
numpy==1.21.2

Explanation: Without version pinning, pip will install the latest available version of each package, which might introduce breaking changes over time. Pinning ensures everyone gets exactly the same versions you tested with.

2. Be Selective About What You Include

Only include direct dependencies, not every package in your environment.

Problem with pip freeze: It includes everything in your environment, including packages you might not need for your project. For example, if you've installed Jupyter Notebook globally, it doesn't mean your web application depends on it.

Consider maintaining your requirements manually or using tools like pip-tools (discussed later).

3. Organize with Comments

Use comments to organize and explain dependencies.

# Web Framework
flask==2.0.1
flask-wtf==1.0.0
flask-sqlalchemy==2.5.1

# Data Processing
numpy==1.21.2
pandas==1.3.3
matplotlib==3.4.3

# Testing - not required for production
pytest==6.2.5
pytest-cov==2.12.1

4. Split Requirements by Environment

For larger projects, consider separating requirements by environment:

Project structure:

requirements/
├── base.txt      # Core dependencies used in all environments
├── development.txt   # Development-specific packages
├── production.txt    # Production-specific packages
└── testing.txt   # Testing-specific packages

Example of requirements/base.txt:

# Core dependencies
flask==2.0.1
sqlalchemy==1.4.25
requests==2.26.0

Example of requirements/development.txt:

# Include base requirements
-r base.txt

# Development tools
flask-debugtoolbar==0.11.0
ipython==7.27.0
black==21.8b0
flake8==3.9.2

Example of requirements/production.txt:

# Include base requirements
-r base.txt

# Production-specific packages
gunicorn==20.1.0
sentry-sdk==1.3.1

Example of requirements/testing.txt:

# Include base requirements
-r base.txt

# Testing tools
pytest==6.2.5
pytest-cov==2.12.1
coverage==5.5

Usage:

# For development
pip install -r requirements/development.txt

# For production
pip install -r requirements/production.txt

# For testing
pip install -r requirements/testing.txt

5. Include a Header Comment

Add documentation at the top of your requirements file:

# Requirements for MyProject
# Python 3.9+
# Generated on 2023-05-15
# Usage: pip install -r requirements.txt

flask==2.0.1
# ... more packages ...

6. Check in Requirements to Version Control

Always commit your requirements.txt file(s) to your version control system. This allows others to reproducibly set up the project and provides a history of dependency changes.

7. Regularly Update and Audit Dependencies

Periodically review your dependencies for updates, especially security patches.

# Check for outdated packages
pip list --outdated

# Use safety to check for security vulnerabilities
pip install safety
safety check -r requirements.txt

Advanced Techniques for Dependency Management

1. Using pip-tools

pip-tools provides a more sophisticated approach to dependency management, separating direct dependencies from the complete dependency tree.

Installation:

pip install pip-tools

Create a requirements.in file with your direct dependencies:

# requirements.in - Direct dependencies only
flask>=2.0.0
requests>=2.25.0
numpy>=1.20.0

Compile it to a pinned requirements.txt:

pip-compile requirements.in

This generates a requirements.txt with all dependencies (including sub-dependencies) pinned:

# requirements.txt
#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile requirements.in
#
click==8.0.1
    # via flask
flask==2.0.1
    # via -r requirements.in
itsdangerous==2.0.1
    # via flask
jinja2==3.0.1
    # via flask
markupsafe==2.0.1
    # via jinja2
numpy==1.21.2
    # via -r requirements.in
requests==2.26.0
    # via -r requirements.in
werkzeug==2.0.1
    # via flask

Upgrading dependencies:

# Upgrade all packages
pip-compile --upgrade requirements.in

# Upgrade specific packages
pip-compile --upgrade-package flask requirements.in

# Install the compiled requirements
pip-sync requirements.txt

2. Using constraints.txt

A constraints.txt file allows you to constrain versions without declaring dependencies.

# constraints.txt
flask==2.0.1
werkzeug==2.0.1

Usage:

# Install dependencies with constraints
pip install -r requirements.txt -c constraints.txt

This is particularly useful when you need to ensure specific versions of transitive dependencies (dependencies of your dependencies).

3. Using Hash Verification

For enhanced security, you can include hash values for packages:

flask==2.0.1 --hash=sha256:7b2fb8e039275d1d88d09fdf6bd64319f1f3e6c4a82178e18a0a0b70a2f7883b
requests==2.26.0 --hash=sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24

Generate requirements with hashes:

pip-compile --generate-hashes requirements.in

Install with hash verification:

pip install --require-hashes -r requirements.txt

This ensures the integrity of packages during installation, preventing supply-chain attacks.

4. Using setup.py with install_requires

For Python packages (not just applications), you can specify dependencies in setup.py:

from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        "flask>=2.0.0,<3.0.0",
        "requests>=2.25.0",
        "numpy>=1.20.0",
    ],
    extras_require={
        "dev": [
            "pytest>=6.0.0",
            "black>=21.5b2",
        ],
        "docs": [
            "sphinx>=4.0.0",
        ],
    },
)

You can then generate a requirements.txt from setup.py:

pip install -e .
pip freeze > requirements.txt

5. Using pyproject.toml (Poetry or Pipenv)

Modern Python projects often use tools like Poetry or Pipenv with pyproject.toml:

Poetry example (pyproject.toml):

[tool.poetry]
name = "myproject"
version = "0.1.0"
description = "My Python Project"
authors = ["Your Name "]

[tool.poetry.dependencies]
python = "^3.9"
flask = "^2.0.1"
requests = "^2.26.0"
numpy = "^1.21.2"

[tool.poetry.dev-dependencies]
pytest = "^6.2.5"
black = "^21.8b0"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

Generate requirements.txt from Poetry:

poetry export -f requirements.txt --output requirements.txt

Pipenv example (Pipfile):

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flask = "==2.0.1"
requests = "==2.26.0"
numpy = "==1.21.2"

[dev-packages]
pytest = "==6.2.5"
black = "==21.8b0"

[requires]
python_version = "3.9"

Generate requirements.txt from Pipenv:

pipenv lock -r > requirements.txt

Common Issues and Solutions

Issue 1: Sub-dependencies Conflicts

Problem: Two packages require different versions of the same dependency.

Example: PackageA requires requests==2.25.0, but PackageB requires requests==2.26.0.

Solutions:

  1. Use constraints: Specify exact versions in constraints.txt
  2. Try compatible versions: Test if both packages work with either version
  3. Use virtual environments: Create separate environments for conflicting packages
  4. Contact maintainers: Report the issue to package maintainers

Issue 2: Platform-Specific Packages

Problem: Some packages are only needed on specific platforms.

Example: You need pywin32 on Windows, but not on Linux or macOS.

Solutions:

  1. Use environment markers:
    pywin32==301; sys_platform == 'win32'
    pyobjc-core==7.3; sys_platform == 'darwin'
  2. Split requirements by platform:
    # requirements_windows.txt
    -r requirements.txt
    pywin32==301

Issue 3: pip freeze Includes Too Many Packages

Problem: pip freeze outputs all installed packages, not just direct dependencies.

Solutions:

  1. Use a clean virtual environment: Create a new environment and install only the packages you need
  2. Use pip-tools: Maintain a requirements.in file with direct dependencies
  3. Manually curate: Maintain requirements.txt by hand, only adding packages you explicitly install
  4. Use pipdeptree: Visualize dependencies to identify direct ones:
    pip install pipdeptree
    pipdeptree --packages flask

Issue 4: Version Conflicts During Installation

Problem: pip install -r requirements.txt fails with dependency conflicts.

Solutions:

  1. Install packages in order: List the most constrained packages first
  2. Use --no-dependencies: Install packages without their dependencies, then resolve manually
  3. Use constraints: Specify compatible versions with constraints.txt
  4. Use pip-tools: pip-compile can resolve dependencies more effectively

Issue 5: Outdated Requirements

Problem: Requirements.txt contains outdated or vulnerable packages.

Solutions:

  1. Regular audits: Check for outdated packages:
    pip list --outdated
  2. Security scanning: Check for vulnerabilities:
    pip install safety
    safety check -r requirements.txt
  3. Automated updates: Use tools like Dependabot, PyUp, or pip-upgrader

Real-World Scenario: Managing Dependencies in a Flask Web Application

Let's walk through a practical example of managing dependencies for a Flask web application project.

Project Structure

flask_project/
├── .gitignore
├── README.md
├── requirements/
│   ├── base.txt
│   ├── development.txt
│   ├── production.txt
│   └── testing.txt
├── app/
│   ├── __init__.py
│   ├── models.py
│   ├── routes.py
│   ├── templates/
│   └── static/
├── config.py
├── run.py
└── tests/

Initial Dependency Setup

Create a virtual environment and install the base packages:

# Create and activate virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install initial packages
pip install flask flask-sqlalchemy flask-wtf pytest

Creating Base Requirements

Create requirements/base.txt with core dependencies:

# Base requirements for Flask Project
# Python 3.9+

# Web Framework
flask==2.0.1

# Database
flask-sqlalchemy==2.5.1
sqlalchemy==1.4.25

# Forms
flask-wtf==1.0.0
wtforms==2.3.3

# Security
flask-login==0.5.0

# API 
flask-restful==0.3.9

Creating Environment-Specific Requirements

Create environment-specific requirements files that include the base requirements:

requirements/development.txt:

# Development requirements for Flask Project
# Includes base requirements
-r base.txt

# Debugging
flask-debugtoolbar==0.11.0

# Code Quality
black==21.8b0
flake8==3.9.2

# Development Server
watchdog==2.1.5

# Development Database
# SQLite is included in Python standard library

requirements/testing.txt:

# Testing requirements for Flask Project
# Includes base requirements
-r base.txt

# Testing
pytest==6.2.5
pytest-cov==2.12.1
pytest-flask==1.2.0

# Mocking
faker==8.13.2

requirements/production.txt:

# Production requirements for Flask Project
# Includes base requirements
-r base.txt

# WSGI Server
gunicorn==20.1.0

# Monitoring and Logging
sentry-sdk==1.3.1

# Performance
flask-caching==1.10.1

Working with Different Environments

Install dependencies for specific environments:

# For development
pip install -r requirements/development.txt

# For testing
pip install -r requirements/testing.txt

# For production
pip install -r requirements/production.txt

Adding New Dependencies

When adding a new dependency, determine which environment it belongs to:

# Install a new package (e.g., for file uploads)
pip install flask-uploads

# Add it to base.txt
echo "flask-uploads==0.2.1  # For file uploads" >> requirements/base.txt

Updating Dependencies

Periodically update dependencies to get security fixes and improvements:

# Check for outdated packages
pip list --outdated

# Update a specific package
pip install --upgrade flask-sqlalchemy
# Update base.txt with new version
# Edit requirements/base.txt to update the version

# Or use pip-tools for more controlled updates
pip install pip-tools
pip-compile --upgrade-package flask-sqlalchemy requirements/base.in

Handling Security Vulnerabilities

Check for security vulnerabilities in your dependencies:

# Check for security vulnerabilities
pip install safety
safety check -r requirements/base.txt

If vulnerabilities are found, update the affected packages to secure versions.

CI/CD Pipeline

Integrate dependency management into your CI/CD pipeline:

# .github/workflows/python-test.yml
name: Python Tests

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

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements/testing.txt
    
    - name: Check for security vulnerabilities
      run: |
        pip install safety
        safety check -r requirements/base.txt
    
    - name: Run tests
      run: |
        pytest --cov=app

Deployment

When deploying to production, use the production requirements:

# On production server
git clone https://github.com/username/flask_project.git
cd flask_project
python -m venv venv
source venv/bin/activate
pip install -r requirements/production.txt

# Start the application with gunicorn
gunicorn wsgi:app

Development Workflow with requirements.txt

Here's a comprehensive workflow for managing dependencies in a Python project:

1. Project Initialization

  1. Create a virtual environment:
    python -m venv venv
    source venv/bin/activate  # On Windows: venv\Scripts\activate
  2. Create requirements structure:
    mkdir -p requirements
  3. Install initial dependencies:
    pip install flask flask-sqlalchemy
  4. Generate initial requirements:
    pip freeze > requirements/base.txt
  5. Create environment-specific files:
    echo "-r base.txt" > requirements/development.txt
    echo "-r base.txt" > requirements/production.txt
    echo "-r base.txt" > requirements/testing.txt
  6. Add additional environment-specific packages:
    pip install black pytest
    echo "black==21.8b0  # Code formatting" >> requirements/development.txt
    echo "pytest==6.2.5  # Testing framework" >> requirements/testing.txt

2. Daily Development Workflow

  1. Activate virtual environment:
    source venv/bin/activate  # On Windows: venv\Scripts\activate
  2. Install dependencies for your work:
    # For development
    pip install -r requirements/development.txt
    
    # For testing
    pip install -r requirements/testing.txt
  3. When adding a new dependency:
    # Install the package
    pip install new-package
    
    # Add to appropriate requirements file
    echo "new-package==1.0.0  # Purpose of this package" >> requirements/base.txt
  4. Before committing:
    # Check for outdated packages
    pip list --outdated
    
    # Check for security vulnerabilities
    safety check -r requirements/base.txt

3. Collaboration Workflow

  1. When pulling changes with new dependencies:
    git pull
    pip install -r requirements/development.txt
  2. When dependency conflicts arise:
    # Create a new virtual environment to test
    python -m venv test_env
    source test_env/bin/activate
    pip install -r requirements/development.txt
  3. If changes to dependencies are needed, discuss with team and update requirements files accordingly.

4. Deployment Workflow

  1. Prepare for deployment:
    # Ensure production requirements are up to date
    python -m pip install -r requirements/production.txt
    
    # Test with production settings
    python run.py --prod
  2. Deploy to production:
    # On production server
    pip install -r requirements/production.txt
  3. Monitor for dependency issues:
    # Set up alerts for security vulnerabilities
    # Integrate with security scanning tools

5. Maintenance Workflow

  1. Regular dependency updates:
    # Schedule regular reviews (e.g., monthly)
    pip list --outdated
    pip install --upgrade flask
    # Update version in requirements file
  2. Security patching:
    # Subscribe to security alerts
    # Promptly update vulnerable packages
  3. Removing unused dependencies:
    # Periodically review and remove unused packages
    # Update requirements files accordingly

Advanced Tools for Dependency Management

Beyond basic requirements.txt files, several advanced tools can enhance your dependency management:

1. pip-tools

We mentioned pip-tools earlier, but it deserves more explanation:

# Install pip-tools
pip install pip-tools

# Create a requirements.in file
flask
sqlalchemy
requests

# Compile to requirements.txt with pinned versions
pip-compile

# Install dependencies
pip-sync

Key Features:

2. Poetry

Poetry is a modern dependency management tool that replaces pip, virtualenv, and other tools:

# Install Poetry
pip install poetry

# Initialize a new project
poetry new myproject
cd myproject

# Add dependencies
poetry add flask sqlalchemy

# Add development dependencies
poetry add --dev pytest black

# Install dependencies
poetry install

# Export to requirements.txt
poetry export -f requirements.txt --output requirements.txt

Key Features:

3. Pipenv

Pipenv combines pip and virtualenv with a lockfile mechanism:

# Install Pipenv
pip install pipenv

# Initialize a project
mkdir myproject
cd myproject

# Install packages
pipenv install flask sqlalchemy

# Install development packages
pipenv install --dev pytest black

# Create a virtual environment and install dependencies
pipenv install

# Export to requirements.txt
pipenv lock -r > requirements.txt

Key Features:

4. conda

For scientific computing and data science, conda offers advanced dependency management:

# Create a conda environment with dependencies
conda create -n myenv python=3.9 numpy pandas matplotlib

# Export to environment.yml
conda env export > environment.yml

# Create environment from environment.yml
conda env create -f environment.yml

Key Features:

5. pip-audit

pip-audit is a tool for auditing Python dependencies for security vulnerabilities:

# Install pip-audit
pip install pip-audit

# Audit installed packages
pip-audit

# Audit requirements file
pip-audit -r requirements.txt

Key Features:

6. pip-review

pip-review helps you keep your packages up-to-date:

# Install pip-review
pip install pip-review

# Check for updates
pip-review

# Update all packages
pip-review --auto

# Update specific packages
pip-review --interactive

Key Features:

Comparison of Tools

Tool Best For Learning Curve Compatibility
requirements.txt Simple projects, compatibility Low Universal
pip-tools Medium projects, better dependency resolution Low-Medium Compatible with requirements.txt
Poetry Modern projects, library development Medium Can export to requirements.txt
Pipenv Application development Medium Can export to requirements.txt
conda Scientific computing, complex dependencies Medium-High Works alongside pip

Exercise: Requirements.txt Management for a Web Application

Let's apply what we've learned with a practical exercise:

Exercise: Create a Requirements Structure for a Web Application

Scenario: You're starting a new Flask web application that will have these features:

Tasks:

  1. Create a virtual environment for the project
  2. Create a directory structure with separate requirements files for different environments
  3. Populate the requirements files with appropriate packages
  4. Create a script to install dependencies for different environments
  5. Add security checking to ensure dependencies are up-to-date and secure

Step 1: Project Setup

# Create project directory
mkdir flask_project
cd flask_project

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Create directory structure
mkdir -p requirements app/templates app/static tests

Step 2: Create Requirements Files

Create these files in the requirements directory:

requirements/base.txt:

# Base requirements for Flask Web Application
# Python 3.9+

# Web Framework
flask==2.0.1

# Database
flask-sqlalchemy==2.5.1
sqlalchemy==1.4.25
alembic==1.7.3
flask-migrate==3.1.0

# Forms
flask-wtf==1.0.0
wtforms==2.3.3

# Authentication
flask-login==0.5.0
flask-bcrypt==0.7.1

# API
flask-restful==0.3.9
marshmallow==3.13.0

# Background Tasks
celery==5.1.2
redis==3.5.3

# Utilities
python-dotenv==0.19.0

requirements/development.txt:

# Development requirements
-r base.txt

# Debugging
flask-debugtoolbar==0.11.0

# Code Quality
black==21.8b0
flake8==3.9.2
isort==5.9.3

# Documentation
sphinx==4.2.0

# Development Server
watchdog==2.1.5

# REPL
ipython==7.27.0

requirements/testing.txt:

# Testing requirements
-r base.txt

# Testing
pytest==6.2.5
pytest-cov==2.12.1
pytest-flask==1.2.0
pytest-sugar==0.9.4

# Mocking
faker==8.13.2
factory-boy==3.2.0

requirements/production.txt:

# Production requirements
-r base.txt

# WSGI Server
gunicorn==20.1.0

# Performance
flask-caching==1.10.1

# Monitoring and Logging
sentry-sdk[flask]==1.3.1
prometheus-flask-exporter==0.18.2

Step 3: Create Installation Script

Create a script to help with dependency installation:

scripts/install_deps.sh:

#!/bin/bash
# Script to install dependencies for different environments

# Check if virtual environment is activated
if [[ "$VIRTUAL_ENV" == "" ]]; then
    echo "Virtual environment not activated. Please activate it first."
    echo "Run: source venv/bin/activate"
    exit 1
fi

# Process command line arguments
ENV=${1:-development}

case $ENV in
    development|dev)
        REQUIREMENTS="requirements/development.txt"
        ;;
    testing|test)
        REQUIREMENTS="requirements/testing.txt"
        ;;
    production|prod)
        REQUIREMENTS="requirements/production.txt"
        ;;
    *)
        echo "Unknown environment: $ENV"
        echo "Usage: $0 [development|testing|production]"
        exit 1
        ;;
esac

echo "Installing dependencies for $ENV environment..."
pip install -r $REQUIREMENTS

# Check for security vulnerabilities
echo "Checking for security vulnerabilities..."
pip install safety
safety check -r $REQUIREMENTS

echo "Done!"

Make the script executable:

chmod +x scripts/install_deps.sh

Step 4: Create a Project Initialization Script

scripts/setup_project.sh:

#!/bin/bash
# Script to set up a new project environment

# Create virtual environment if it doesn't exist
if [ ! -d "venv" ]; then
    echo "Creating virtual environment..."
    python -m venv venv
fi

# Activate virtual environment
source venv/bin/activate

# Install dependencies
./scripts/install_deps.sh development

# Initialize git repository if it doesn't exist
if [ ! -d ".git" ]; then
    echo "Initializing git repository..."
    git init
    
    # Create .gitignore
    echo "Creating .gitignore..."
    cat > .gitignore << EOL
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
ENV/
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Flask
instance/
.webassets-cache

# Celery
celerybeat-schedule
celerybeat.pid

# Testing
.coverage
htmlcov/
.pytest_cache/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Environment
.env
.env.local

# Logs
logs/
*.log
EOL
    
    # Initial commit
    git add .
    git commit -m "Initial project setup"
fi

echo "Project setup complete!"
echo "Activate the virtual environment with: source venv/bin/activate"

Make the script executable:

chmod +x scripts/setup_project.sh

Step 5: Create a Requirements Updating Script

scripts/update_deps.sh:

#!/bin/bash
# Script to update dependencies

# Check if virtual environment is activated
if [[ "$VIRTUAL_ENV" == "" ]]; then
    echo "Virtual environment not activated. Please activate it first."
    echo "Run: source venv/bin/activate"
    exit 1
fi

# Install pip-tools if not installed
pip install pip-tools

# Check outdated packages
echo "Checking for outdated packages..."
pip list --outdated

# Ask for confirmation
read -p "Do you want to update packages? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "Update canceled."
    exit 0
fi

# Create requirements.in files if they don't exist
if [ ! -f "requirements/base.in" ]; then
    echo "Creating requirements.in files from current requirements.txt files..."
    
    # Extract direct dependencies from requirements.txt
    grep -v "^-r" requirements/base.txt | grep -v "^#" | grep -v "^$" > requirements/base.in
    grep -v "^-r" requirements/development.txt | grep -v "^#" | grep -v "^$" > requirements/development.in
    echo "-r base.in" > requirements/development.in.tmp
    grep -v "^-r" requirements/development.in >> requirements/development.in.tmp
    mv requirements/development.in.tmp requirements/development.in
    
    # Same for testing and production
    # ... similar commands for testing.in and production.in ...
fi

# Update base requirements
echo "Updating base requirements..."
pip-compile --upgrade requirements/base.in

# Update environment-specific requirements
for env in development testing production; do
    echo "Updating $env requirements..."
    pip-compile --upgrade requirements/${env}.in
done

# Check for security vulnerabilities
echo "Checking for security vulnerabilities..."
pip install safety
safety check -r requirements/base.txt

echo "Dependencies updated successfully!"

Make the script executable:

chmod +x scripts/update_deps.sh

Step 6: Create a Basic Flask Application

app/__init__.py:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect

# Initialize extensions
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
csrf = CSRFProtect()

def create_app(config_object='app.config.DevelopmentConfig'):
    """Create and configure the Flask application."""
    app = Flask(__name__)
    app.config.from_object(config_object)
    
    # Initialize extensions
    db.init_app(app)
    migrate.init_app(app, db)
    login_manager.init_app(app)
    csrf.init_app(app)
    
    # Register blueprints
    from app.routes import main_bp
    app.register_blueprint(main_bp)
    
    return app

app/config.py:

import os
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

class Config:
    """Base configuration class."""
    SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-please-change')
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

class DevelopmentConfig(Config):
    """Development configuration."""
    DEBUG = True
    SQLALCHEMY_ECHO = True

class TestingConfig(Config):
    """Testing configuration."""
    TESTING = True
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
    WTF_CSRF_ENABLED = False

class ProductionConfig(Config):
    """Production configuration."""
    DEBUG = False
    TESTING = False

app/routes.py:

from flask import Blueprint, render_template

main_bp = Blueprint('main', __name__)

@main_bp.route('/')
def index():
    return render_template('index.html')

app/templates/index.html:


<html>
<head>
    <title>Flask App</title>
</head>
<body>
    <h1>Welcome to Flask App</h1>
    <p>This is a Flask application with proper dependency management.</p>
</body>
</html>

run.py:

from app import create_app

app = create_app()

if __name__ == '__main__':
    app.run()

Running the Application

# Set up the project
./scripts/setup_project.sh

# Activate virtual environment
source venv/bin/activate

# Run the application
python run.py

This exercise demonstrates:

By completing this exercise, you've created a robust dependency management system for a Python web application that follows best practices and can scale as the project grows.

Conclusion

We've explored requirements.txt files from basic usage to advanced techniques, and seen how they fit into the Python dependency management ecosystem. Let's recap the key takeaways:

Mastering requirements.txt and dependency management is a fundamental skill for Python developers. As you build more complex applications, these practices will help you maintain codebases that are easier to work with, share, and deploy.

Remember: "A chain is only as strong as its weakest link." In software, your dependencies are links in that chain. Proper dependency management helps ensure none of those links break unexpectedly.

Additional Resources