0% found this document useful (0 votes)
4 views26 pages

Python Mastery Part14 WebDevelopment

Part 14 of the Python Programming Mastery Guide focuses on web development using Python, covering frameworks like Flask, Django, and FastAPI. It includes topics such as REST API design, authentication, database connections, and deployment strategies. The section aims to equip readers with practical skills through real-world projects and exercises.

Uploaded by

abdul deejah
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views26 pages

Python Mastery Part14 WebDevelopment

Part 14 of the Python Programming Mastery Guide focuses on web development using Python, covering frameworks like Flask, Django, and FastAPI. It includes topics such as REST API design, authentication, database connections, and deployment strategies. The section aims to equip readers with practical skills through real-world projects and exercises.

Uploaded by

abdul deejah
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

PYTHON PROGRAMMING

MASTERY GUIDE
From Zero to Professional Developer

PART 14
Web Development

Flask • Django • FastAPI • REST APIs


Authentication • Databases • Deployment
PART 14: WEB DEVELOPMENT
Web development is one of the most in-demand Python skills. Whether you are building an internal
business tool, a public-facing web application, or a REST API that powers a mobile app, Python's web
frameworks make it fast, clean, and enjoyable. Three frameworks dominate the Python web landscape:
Flask for simplicity, Django for completeness, and FastAPI for modern async APIs.

In this part you will build real applications with all three frameworks, understand REST API design,
implement authentication, connect to databases, and learn the basics of deploying your applications to
production.

🎯 What You Will Learn in Part 14

14.1 Web Development Fundamentals — HTTP, request/response, REST


14.2 Flask — lightweight micro-framework, routing, templates, forms
14.3 Flask REST API — JSON endpoints, Blueprint organisation
14.4 Django — full-featured framework, ORM, admin, templates
14.5 FastAPI — modern async API framework with automatic docs
14.6 REST API Design Principles — endpoints, methods, status codes
14.7 Authentication — sessions, JWT tokens, password hashing
14.8 Deployment — environment variables, gunicorn, Docker basics, Heroku
14.9 Real-World Projects
14.10 Exercises, Knowledge Check, Common Mistakes, Professional Tips

14.1 Web Development Fundamentals

The HTTP Request-Response Cycle


Every web interaction follows the same pattern: a client (browser or app) sends an HTTP request to a
server, the server processes it and sends back an HTTP response. Understanding this cycle is
fundamental to web development.

# HTTP request/response
# HTTP Request structure
# ─────────────────────────────────────────────────────────
# Method: GET, POST, PUT, PATCH, DELETE
# URL: [Link]
# Headers: Content-Type: application/json
# Authorization: Bearer eyJ...
# Body: {"name": "Alice", "email": "alice@[Link]"}

# HTTP Response structure


# ─────────────────────────────────────────────────────────
# Status: 200 OK
# Headers: Content-Type: application/json
# Body: {"id": 42, "name": "Alice", "email": "alice@[Link]"}

HTTP Methods and Status Codes


Method Purpose Example URL Example Body
GET Retrieve a resource GET /users/42 (none)
POST Create a new resource POST /users {"name":"Alice"}
PUT Replace an entire resource PUT /users/42 {"name":"Alice","email":"..."}
PATCH Partially update a resource PATCH /users/42 {"name":"Alicia"}
DELETE Delete a resource DELETE /users/42 (none)

Status Code Meaning When to Use


200 OK Success GET, PUT, PATCH succeeded
201 Created Resource created POST succeeded, new resource created
204 No Content Success, no body DELETE succeeded
400 Bad Request Client sent invalid data Validation errors, malformed JSON
401 Unauthorized Not authenticated Missing or invalid token
403 Forbidden Authenticated but not User lacks required role
permitted
404 Not Found Resource does not exist ID not found in database
409 Conflict Resource already exists Duplicate email on registration
422 Unprocessable Validation error (FastAPI) Field type mismatch
Entity
500 Internal Server Server bug Unhandled exception in your code
Error

14.2 Flask — The Micro-Framework


Flask is a lightweight, unopinionated web framework. It gives you the essentials (routing, request
handling, templating) and gets out of the way. It is perfect for APIs, microservices, small-to-medium
web apps, and learning web development concepts.

Installation and Hello World


# Flask hello world
pip install flask

# File: [Link]
from flask import Flask

app = Flask(__name__)

@[Link]('/')
def index():
return 'Hello, World!'

@[Link]('/greet/<name>')
def greet(name):
return f'Hello, {name}!'

if __name__ == '__main__':
[Link](debug=True) # debug=True enables auto-reload and error pages

# Run: python [Link]


# Visit: [Link]
# Visit: [Link]

Routing and URL Variables


# Flask routing
from flask import Flask, request, jsonify, abort

app = Flask(__name__)

# Static route
@[Link]('/about')
def about():
return '<h1>About Us</h1>'

# URL variable — capture part of the URL


@[Link]('/users/<int:user_id>')
def get_user(user_id):
return jsonify({'user_id': user_id, 'name': 'Alice'})

# Multiple HTTP methods on one route


@[Link]('/users', methods=['GET', 'POST'])
def users():
if [Link] == 'GET':
return jsonify({'users': [{'id':1,'name':'Alice'}]})
elif [Link] == 'POST':
data = request.get_json()
return jsonify({'message': 'Created', 'data': data}), 201

# Query parameters: /search?q=python&page=2


@[Link]('/search')
def search():
query = [Link]('q', '') # default ''
page = [Link]('page', 1, type=int) # convert to int
return jsonify({'query': query, 'page': page})

# URL converters: <int:id>, <float:price>, <path:filename>, <string:name>

Request and Response Objects


# Request and response
from flask import Flask, request, jsonify, make_response

app = Flask(__name__)

@[Link]('/data', methods=['POST'])
def receive_data():
# Get JSON body
data = request.get_json()
if not data:
return jsonify({'error': 'No JSON body provided'}), 400

# Get form data


# name = [Link]('name')

# Get headers
auth_header = [Link]('Authorization', '')
user_agent = [Link]('User-Agent')

# Get cookies
session_id = [Link]('session_id')

# Build custom response with headers


response = make_response(jsonify({'received': data}), 200)
[Link]['X-Custom-Header'] = 'my-value'
response.set_cookie('visit_count', '1', max_age=3600)
return response

# Error handlers
@[Link](404)
def not_found(error):
return jsonify({'error': 'Resource not found'}), 404

@[Link](500)
def server_error(error):
return jsonify({'error': 'Internal server error'}), 500

Flask Templates with Jinja2


# Jinja2 templates
# templates/[Link]
# <!DOCTYPE html>
# <html>
# <body>
# <h1>Hello, {{ name }}!</h1>
# {% if students %}
# <ul>
# {% for s in students %}
# <li>{{ [Link] }} — Grade: {{ [Link] }}</li>
# {% endfor %}
# </ul>
# {% else %}
# <p>No students found.</p>
# {% endif %}
# </body>
# </html>

from flask import Flask, render_template

app = Flask(__name__)

@[Link]('/students')
def students():
data = [
{'name': 'Alice', 'grade': 'A'},
{'name': 'Bob', 'grade': 'B'},
]
return render_template('[Link]', name='Teacher', students=data)

Flask Blueprints — Organising Large Apps


# Flask Blueprints
# Blueprints split a large Flask app into modular components

# File structure:
# myapp/
# ├── [Link] — application factory
# ├── [Link] — configuration
# └── routes/
# ├── __init__.py
# ├── [Link] — user routes
# └── [Link] — product routes

# routes/[Link]
from flask import Blueprint, jsonify

users_bp = Blueprint('users', __name__, url_prefix='/api/users')

@users_bp.route('/', methods=['GET'])
def list_users():
return jsonify([{'id': 1, 'name': 'Alice'}])

@users_bp.route('/<int:user_id>', methods=['GET'])
def get_user(user_id):
return jsonify({'id': user_id, 'name': 'Alice'})

# [Link] — application factory


from flask import Flask
from [Link] import users_bp
from [Link] import products_bp

def create_app(config=None):
app = Flask(__name__)
[Link].from_object(config or '[Link]')
app.register_blueprint(users_bp)
app.register_blueprint(products_bp)
return app

if __name__ == '__main__':
app = create_app()
[Link](debug=True)

14.3 Building a Flask REST API


A REST API is a web service that uses HTTP methods and JSON to allow clients (mobile apps,
frontend JavaScript, other services) to interact with your data. Let us build a complete CRUD (Create,
Read, Update, Delete) API.

# Complete Flask CRUD API


# File: [Link] — A complete CRUD REST API with Flask
from flask import Flask, jsonify, request, abort
from functools import wraps
import uuid

app = Flask(__name__)

# In-memory database (replace with real DB in production)


users_db = {
'1': {'id':'1','name':'Alice
Johnson','email':'alice@[Link]','role':'admin'},
'2': {'id':'2','name':'Bob Smith', 'email':'bob@[Link]',
'role':'user'},
}

# ── Simple API key authentication ─────────────────────────


VALID_API_KEY = 'secret-api-key-123'

def require_api_key(f):
@wraps(f)
def decorated(*args, **kwargs):
key = [Link]('X-API-Key', '')
if key != VALID_API_KEY:
return jsonify({'error': 'Invalid or missing API key'}), 401
return f(*args, **kwargs)
return decorated

# ── GET all users ─────────────────────────────────────────


@[Link]('/api/users', methods=['GET'])
@require_api_key
def list_users():
page = [Link]('page', 1, type=int)
per_page = [Link]('limit', 10, type=int)
role = [Link]('role', None)

users = list(users_db.values())
if role:
users = [u for u in users if u['role'] == role]

start = (page - 1) * per_page


end = start + per_page
return jsonify({
'users': users[start:end],
'total': len(users),
'page': page,
'per_page': per_page
})

# ── GET single user ───────────────────────────────────────


@[Link]('/api/users/<user_id>', methods=['GET'])
@require_api_key
def get_user(user_id):
user = users_db.get(user_id)
if not user:
return jsonify({'error': f'User {user_id} not found'}), 404
return jsonify(user)

# ── POST create user ──────────────────────────────────────


@[Link]('/api/users', methods=['POST'])
@require_api_key
def create_user():
data = request.get_json()
if not data:
return jsonify({'error': 'JSON body required'}), 400

required = ['name', 'email']


missing = [f for f in required if f not in data]
if missing:
return jsonify({'error': f'Missing fields: {missing}'}), 400

if any(u['email'] == data['email'] for u in users_db.values()):


return jsonify({'error': 'Email already exists'}), 409

uid = str(uuid.uuid4())[:8]
user = {'id': uid, 'name': data['name'],
'email': data['email'], 'role': [Link]('role','user')}
users_db[uid] = user
return jsonify(user), 201

# ── PUT update user ───────────────────────────────────────


@[Link]('/api/users/<user_id>', methods=['PUT'])
@require_api_key
def update_user(user_id):
if user_id not in users_db:
return jsonify({'error': 'User not found'}), 404
data = request.get_json() or {}
users_db[user_id].update({
'name': [Link]('name', users_db[user_id]['name']),
'email': [Link]('email', users_db[user_id]['email']),
'role': [Link]('role', users_db[user_id]['role'])
})
return jsonify(users_db[user_id])

# ── DELETE user ───────────────────────────────────────────


@[Link]('/api/users/<user_id>', methods=['DELETE'])
@require_api_key
def delete_user(user_id):
if user_id not in users_db:
return jsonify({'error': 'User not found'}), 404
del users_db[user_id]
return '', 204

if __name__ == '__main__':
[Link](debug=True)

14.4 Django — The Full-Stack Framework


Django is a 'batteries included' web framework that provides everything you need to build a full web
application: an ORM for database access, an admin panel, authentication, forms, templates, and
security features. It follows the Model-View-Template (MVT) pattern and enforces a clear project
structure.

Django vs Flask
Feature Flask Django
Philosophy Micro — minimal, you choose Full-stack — everything included
everything
ORM External (SQLAlchemy) Built-in (powerful)
Admin Panel External Built-in, auto-generated
Authentication External (Flask-Login) Built-in
Forms External (WTForms) Built-in
Learning Curve Lower — start simple Higher — more to learn upfront
Best For APIs, microservices, simple Full web apps, content sites, complex projects
apps
Used By Pinterest API, LinkedIn Instagram, Disqus, National Geographic

Creating a Django Project


# Django project setup
# Install Django
pip install django djangorestframework

# Create a new project


django-admin startproject mysite
cd mysite

# Create an app within the project


python [Link] startapp blog

# Project structure:
# mysite/
# ├── [Link] — command-line utility
# ├── mysite/
# │ ├── [Link] — all project settings
# │ ├── [Link] — root URL configuration
# │ └── [Link] — WSGI deployment entry point
# └── blog/
# ├── [Link] — database models
# ├── [Link] — request handlers
# ├── [Link] — app URL patterns
# ├── [Link] — admin panel config
# └── templates/ — HTML templates

# Run the development server


python [Link] runserver
# Visit [Link]

Django Models — The ORM


# Django models
# blog/[Link]
from [Link] import models
from [Link] import User

class Category([Link]):
name = [Link](max_length=100, unique=True)
slug = [Link](unique=True)

class Meta:
verbose_name_plural = 'categories'
ordering = ['name']

def __str__(self):
return [Link]

class Post([Link]):
STATUS_CHOICES = [('draft','Draft'), ('published','Published')]

title = [Link](max_length=250)
slug = [Link](unique_for_date='publish')
author = [Link](User, on_delete=[Link],
related_name='blog_posts')
body = [Link]()
publish = [Link](auto_now_add=True)
created = [Link](auto_now_add=True)
updated = [Link](auto_now=True)
status = [Link](max_length=10, choices=STATUS_CHOICES,
default='draft')
category = [Link](Category, on_delete=models.SET_NULL,
null=True, blank=True)
tags = [Link]('Tag', blank=True)

class Meta:
ordering = ['-publish']

def __str__(self):
return [Link]

class Tag([Link]):
name = [Link](max_length=50, unique=True)
def __str__(self): return [Link]

# Create and apply migrations


# python [Link] makemigrations
# python [Link] migrate

Django Views and URLs


# Django views and URLs
# blog/[Link]
from [Link] import render, get_object_or_404
from [Link] import JsonResponse
from .models import Post
def post_list(request):
posts =
[Link](status='published').select_related('author')
return render(request, 'blog/[Link]', {'posts': posts})

def post_detail(request, pk):


post = get_object_or_404(Post, pk=pk, status='published')
return render(request, 'blog/[Link]', {'post': post})

# ── Django ORM queries ────────────────────────────────────


# [Link]() — all records
# [Link](status='draft') — filter
# [Link](author=user) — exclude
# [Link](pk=1) — single (raises if not found)
# [Link].order_by('-publish') — sort descending
# [Link]('title','author') — specific fields
# [Link]() — count
# [Link]() / .last() — first/last
# [Link](title__contains='Python') — field lookups

# blog/[Link]
from [Link] import path
from . import views

urlpatterns = [
path('', views.post_list, name='post_list'),
path('post/<int:pk>/', views.post_detail, name='post_detail'),
]

# mysite/[Link]
from [Link] import admin
from [Link] import path, include

urlpatterns = [
path('admin/', [Link]),
path('blog/', include('[Link]')),
]

Django Admin
# Django admin
# blog/[Link] — register models with the admin panel
from [Link] import admin
from .models import Post, Category, Tag

@[Link](Post)
class PostAdmin([Link]):
list_display = ['title', 'author', 'status', 'publish']
list_filter = ['status', 'created', 'publish', 'author']
search_fields = ['title', 'body']
prepopulated_fields = {'slug': ('title',)}
raw_id_fields = ['author']
date_hierarchy= 'publish'
ordering = ['status', '-publish']

[Link](Category)
[Link](Tag)

# Create a superuser to log into the admin panel


# python [Link] createsuperuser
# Visit [Link]

14.5 FastAPI — Modern Async API Framework


FastAPI is a modern, high-performance web framework for building APIs with Python 3.9+. It is built on
Starlette and Pydantic, uses Python type hints extensively for validation, and automatically generates
interactive API documentation. It is increasingly preferred for new API projects in industry.

FastAPI Key Features


Feature Description
Automatic docs Generates Swagger UI (/docs) and ReDoc (/redoc) automatically from type
hints
Type validation Pydantic models validate request bodies and response schemas
automatically
Async support Built on asyncio — handles thousands of concurrent requests natively
Dependency injection Clean, testable injection of database sessions, auth, configuration
Performance Comparable to NodeJS and Go — much faster than Flask for I/O-bound
tasks
Standards-based Fully compatible with OpenAPI and JSON Schema standards

# FastAPI complete CRUD API


pip install fastapi uvicorn[standard] pydantic

# File: [Link]
from fastapi import FastAPI, HTTPException, Depends, status
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List
from datetime import datetime

app = FastAPI(
title='User Management API',
description='A complete CRUD API for managing users',
version='1.0.0'
)

# ── Pydantic models — define shape of request/response ───


class UserCreate(BaseModel):
name: str = Field(..., min_length=2, max_length=100)
email: str = Field(...)
role: str = Field('user', pattern='^(admin|user|moderator)
$')

class UserResponse(BaseModel):
id: int
name: str
email: str
role: str
created_at: datetime

class Config:
from_attributes = True # allows creating from ORM objects

# In-memory store
fake_db: dict = {}
counter = 0

# ── GET all users ─────────────────────────────────────────


@[Link]('/users', response_model=List[UserResponse])
async def list_users(skip: int = 0, limit: int = 10):
users = list(fake_db.values())
return users[skip : skip + limit]

# ── GET single user ───────────────────────────────────────


@[Link]('/users/{user_id}', response_model=UserResponse)
async def get_user(user_id: int):
if user_id not in fake_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f'User {user_id} not found'
)
return fake_db[user_id]

# ── POST create user ──────────────────────────────────────


@[Link]('/users', response_model=UserResponse,
status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate): # body auto-validated by
Pydantic
global counter
if any(u['email'] == [Link] for u in fake_db.values()):
raise HTTPException(status_code=409, detail='Email already
exists')
counter += 1
record = {**[Link](), 'id': counter, 'created_at': [Link]()}
fake_db[counter] = record
return record

# ── DELETE user ───────────────────────────────────────────


@[Link]('/users/{user_id}', status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(user_id: int):
if user_id not in fake_db:
raise HTTPException(status_code=404, detail='User not found')
del fake_db[user_id]

# Run: uvicorn main:app --reload


# Docs: [Link]

FastAPI Dependency Injection


# FastAPI dependency injection
from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Optional

# Dependencies are functions injected via Depends()

# Auth dependency
async def get_current_user(x_api_key: str = Header(...)):
if x_api_key != 'valid-key-123':
raise HTTPException(status_code=401, detail='Invalid API key')
return {'username': 'alice', 'role': 'admin'}

# Admin-only dependency
async def require_admin(user=Depends(get_current_user)):
if user['role'] != 'admin':
raise HTTPException(status_code=403, detail='Admin only')
return user

@[Link]('/admin/stats')
async def admin_stats(user=Depends(require_admin)):
return {'total_users': len(fake_db), 'requested_by': user['username']}

# Database session dependency (using SQLAlchemy)


from [Link] import Session

def get_db():
db = SessionLocal()
try:
yield db
finally:
[Link]()

@[Link]('/db-users')
async def db_users(db: Session = Depends(get_db)):
return [Link](UserModel).all()
14.6 REST API Design Principles
A well-designed REST API is intuitive to use, consistent, and self-documenting. These principles are
what separate a professional API from a confusing one.

Principle Good Bad


Use nouns for resources /users, /products, /orders /getUsers, /createProduct, /deleteOrder
Use HTTP methods GET /users, POST /users, POST /users/delete, GET /createUser
correctly DELETE /users/1
Plural resource names /users, /posts, /comments /user, /post, /comment
Nest for relationships /users/42/orders, /getUserOrders?user_id=42
/posts/5/comments
Use query params for GET /users? GET /admin-users-active
filtering role=admin&active=true
Return consistent JSON Always return the same structure {data:...} sometimes, [...] other times
Version your API /api/v1/users, /api/v2/users (no versioning — breaking changes)
Use pagination GET /users?page=2&limit=20 Returning all 10M records at once

# REST API design patterns


# Well-designed API URL patterns:

GET /api/v1/users — list all users (paginated)


GET /api/v1/users/42 — get user with id 42
POST /api/v1/users — create a new user
PUT /api/v1/users/42 — replace user 42 entirely
PATCH /api/v1/users/42 — partially update user 42
DELETE /api/v1/users/42 — delete user 42

GET /api/v1/users/42/orders — all orders for user 42


POST /api/v1/users/42/orders — create order for user 42
GET /api/v1/users/42/orders/7 — order 7 belonging to user 42

GET /api/v1/products?category=electronics&sort=price&order=asc&page=2

# Standard response envelope


{
'status': 'success',
'data': {...},
'meta': {'page': 2, 'total': 150, 'per_page': 20}
}

# Error response
{
'status': 'error',
'code': 'VALIDATION_ERROR',
'message': 'Email is required',
'details': [{'field': 'email', 'error': 'This field is required'}]
}

14.7 Authentication — JWT Tokens


Authentication verifies who you are. Authorisation verifies what you are allowed to do. JSON Web
Tokens (JWT) are the industry standard for stateless API authentication — the client stores the token
and sends it with every request.

# JWT authentication
pip install PyJWT bcrypt

# [Link] — JWT authentication utilities


import jwt
import bcrypt
from datetime import datetime, timedelta, timezone
from functools import wraps
from flask import request, jsonify

SECRET_KEY = 'your-secret-key-change-in-production'
ALGORITHM = 'HS256'

def hash_password(password: str) -> str:


'''Hash a password using bcrypt.'''
salt = [Link](rounds=12)
hashed = [Link]([Link]('utf-8'), salt)
return [Link]('utf-8')

def verify_password(password: str, hashed: str) -> bool:


'''Verify a password against its bcrypt hash.'''
return [Link]([Link]('utf-8'), [Link]('utf-
8'))

def create_token(user_id: int, role: str, expires_hours: int = 24) -> str:
'''Create a JWT access token.'''
payload = {
'sub': str(user_id),
'role': role,
'iat': [Link]([Link]),
'exp': [Link]([Link]) +
timedelta(hours=expires_hours)
}
return [Link](payload, SECRET_KEY, algorithm=ALGORITHM)

def decode_token(token: str) -> dict:


'''Decode and validate a JWT token.'''
try:
return [Link](token, SECRET_KEY, algorithms=[ALGORITHM])
except [Link]:
raise ValueError('Token has expired')
except [Link]:
raise ValueError('Invalid token')

def require_auth(f):
'''Decorator to protect routes with JWT authentication.'''
@wraps(f)
def decorated(*args, **kwargs):
auth_header = [Link]('Authorization', '')
if not auth_header.startswith('Bearer '):
return jsonify({'error': 'Missing bearer token'}), 401
token = auth_header.split(' ')[1]
try:
payload = decode_token(token)
request.user_id = int(payload['sub'])
request.user_role = payload['role']
except ValueError as e:
return jsonify({'error': str(e)}), 401
return f(*args, **kwargs)
return decorated

# Flask routes using auth


from flask import Flask
app = Flask(__name__)

@[Link]('/auth/register', methods=['POST'])
def register():
data = request.get_json()
# Validate, save user with hashed password...
hashed_pw = hash_password(data['password'])
# save to DB: {'email': data['email'], 'password_hash': hashed_pw}
return jsonify({'message': 'Registered successfully'}), 201

@[Link]('/auth/login', methods=['POST'])
def login():
data = request.get_json()
# Look up user in DB, verify password...
user = find_user_by_email(data['email']) # your DB query
if not user or not verify_password(data['password'],
user['password_hash']):
return jsonify({'error': 'Invalid credentials'}), 401
token = create_token(user['id'], user['role'])
return jsonify({'token': token, 'expires_in': '24h'})

@[Link]('/profile')
@require_auth
def profile():
return jsonify({'user_id': request.user_id, 'role':
request.user_role})

14.8 Deployment
Deploying a web application means making it accessible on the internet. We cover the essentials:
environment variables for configuration, Gunicorn as a production WSGI server, Docker for
containerisation, and the basics of deploying to a cloud platform.

Environment Variables and Configuration


# Environment variables
# NEVER hardcode secrets in your code!
# Use environment variables instead

# .env file (never commit this to Git!)


# SECRET_KEY=your-super-secret-key-here
# DATABASE_URL=postgresql://user:password@localhost:5432/mydb
# DEBUG=False
# ALLOWED_HOSTS=[Link],[Link]

# [Link]
import os
from dotenv import load_dotenv # pip install python-dotenv

load_dotenv() # loads .env file into environment

class Config:
SECRET_KEY = [Link]('SECRET_KEY', 'dev-only-insecure-key')
DATABASE_URL = [Link]('DATABASE_URL', 'sqlite:///[Link]')
DEBUG = [Link]('DEBUG', 'False').lower() == 'true'

class Development(Config):
DEBUG = True

class Production(Config):
DEBUG = False
# Add production-specific settings

Running with Gunicorn


# Gunicorn
# Gunicorn is the production WSGI server for Flask/Django
pip install gunicorn

# Flask — run 4 worker processes


gunicorn --workers 4 --bind [Link]:8000 'app:create_app()'

# Django
gunicorn --workers 4 --bind [Link]:8000 [Link]:application

# FastAPI — uses uvicorn instead of gunicorn


uvicorn main:app --host [Link] --port 8000 --workers 4

# Procfile (for Heroku / Railway)


# web: gunicorn --workers 4 app:create_app()

Docker Basics
# Dockerfile and docker-compose
# Dockerfile — defines how to build the container image
FROM python:3.12-slim

# Set working directory


WORKDIR /app

# Install dependencies first (cached layer)


COPY [Link] .
RUN pip install --no-cache-dir -r [Link]

# Copy source code


COPY . .

# Set environment variables


ENV DEBUG=False
ENV PORT=8000

# Expose port
EXPOSE 8000

# Run the application


CMD ["gunicorn", "--workers", "4", "--bind", "[Link]:8000",
"app:create_app()"]

# Build and run:


# docker build -t myapp:latest .
# docker run -p 8000:8000 --env-file .env myapp:latest

# [Link] — run app + database together


version: '3.8'
services:
web:
build: .
ports:
- '8000:8000'
env_file:
- .env
depends_on:
- db
db:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: password

14.9 Real-World Project: Task Management REST API


# Task Manager API
# A complete REST API with FastAPI + JWT auth + Pydantic
from fastapi import FastAPI, HTTPException, Depends, status
from [Link] import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, Field
from typing import List, Optional
from datetime import datetime, timezone
import jwt, uuid

app = FastAPI(title='Task Manager API', version='1.0.0')


security = HTTPBearer()
SECRET = 'my-jwt-secret'

# ── Pydantic schemas ──────────────────────────────────────


class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: str = Field('', max_length=1000)
priority: str = Field('medium', pattern='^(low|medium|high)$')
due_date: Optional[datetime] = None

class TaskUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
priority: Optional[str] = None
completed: Optional[bool] = None
due_date: Optional[datetime] = None

class TaskResponse(BaseModel):
id: str
title: str
description: str
priority: str
completed: bool
created_at: datetime
due_date: Optional[datetime]
owner_id: str
# ── In-memory store ───────────────────────────────────────
tasks_db: dict = {}
users_db: dict = {'user1': {'id': 'user1', 'name': 'Alice'}}

# ── Auth dependency ───────────────────────────────────────


async def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security)
):
try:
payload = [Link]([Link], SECRET,
algorithms=['HS256'])
user_id = [Link]('sub')
if user_id not in users_db:
raise HTTPException(status_code=401, detail='Invalid user')
return users_db[user_id]
except [Link]:
raise HTTPException(status_code=401, detail='Invalid token')

# ── Routes ────────────────────────────────────────────────
@[Link]('/tasks', response_model=List[TaskResponse])
async def list_tasks(
completed: Optional[bool] = None,
priority: Optional[str] = None,
user=Depends(get_current_user)
):
tasks = [t for t in tasks_db.values() if t['owner_id'] == user['id']]
if completed is not None:
tasks = [t for t in tasks if t['completed'] == completed]
if priority:
tasks = [t for t in tasks if t['priority'] == priority]
return tasks

@[Link]('/tasks', response_model=TaskResponse,
status_code=status.HTTP_201_CREATED)
async def create_task(task: TaskCreate, user=Depends(get_current_user)):
task_id = str(uuid.uuid4())
record = {
'id': task_id,
'title': [Link],
'description': [Link],
'priority': [Link],
'completed': False,
'created_at': [Link]([Link]),
'due_date': task.due_date,
'owner_id': user['id']
}
tasks_db[task_id] = record
return record

@[Link]('/tasks/{task_id}', response_model=TaskResponse)
async def update_task(
task_id: str, update: TaskUpdate, user=Depends(get_current_user)
):
task = tasks_db.get(task_id)
if not task:
raise HTTPException(status_code=404, detail='Task not found')
if task['owner_id'] != user['id']:
raise HTTPException(status_code=403, detail='Not your task')
for field, value in [Link](exclude_none=True).items():
task[field] = value
return task

@[Link]('/tasks/{task_id}', status_code=204)
async def delete_task(task_id: str, user=Depends(get_current_user)):
task = tasks_db.get(task_id)
if not task or task['owner_id'] != user['id']:
raise HTTPException(status_code=404, detail='Task not found')
del tasks_db[task_id]

14.10 Exercises and Projects

Exercise Set A: Flask


1. Build a Flask bookstore API with CRUD endpoints for books (title, author, price, isbn, stock).
Add pagination to the list endpoint and filtering by author.
2. Add Jinja2 templates to your bookstore — a homepage listing all books, a detail page for each
book, and a simple HTML form to add new books.
3. Add API key authentication to all write endpoints (POST, PUT, DELETE) using a decorator.

Exercise Set B: FastAPI


4. Build a notes API with FastAPI. Notes have: title, content, tags (list), created_at, updated_at.
Implement full CRUD with Pydantic validation.
5. Add JWT authentication to your notes API. Users can only see and modify their own notes.
6. Add a /notes/search?q=keyword endpoint that searches note titles and content.

Exercise Set C: Django


7. Create a Django project for a blog. Create a Post model with title, body, author, tags, status
(draft/published), and created_at. Register it in the admin with filtering and search.
8. Add class-based views (ListView, DetailView) and URLs. Create basic templates to display the
blog list and individual posts.

Project: Complete REST API with Authentication


Build a complete project management REST API:
9. Models: User, Project, Task, Comment. Tasks belong to Projects; Comments belong to Tasks.
10. Auth: register, login (returns JWT), logout (token blacklist), refresh token.
11. CRUD: full CRUD for Projects and Tasks. Users can only access their own projects.
12. Roles: admin users can see all projects; regular users only see their own.
13. Pagination, filtering, sorting on list endpoints.
14. Input validation with clear error messages.
15. Rate limiting: max 100 requests per minute per IP.
16. Full Swagger/OpenAPI documentation (FastAPI generates this automatically).

14.11 Knowledge Check

# Question
1 What are the five main HTTP methods and when is each used?
2 What status code should a POST that creates a resource return?
3 What is the difference between Flask and Django?
4 What is a Blueprint in Flask and why is it useful?
5 What does Django's ORM allow you to do?
6 What makes FastAPI different from Flask for API development?
7 What is a Pydantic model and what does it do automatically?
8 What is the difference between Authentication and Authorisation?
9 Why should passwords never be stored in plaintext?
10 What is a JWT and what three parts does it contain?
11 Why should secrets never be hardcoded in source code?
12 What does Gunicorn do and why is it used instead of Flask's dev server?

14.12 Common Mistakes

Mistake Problem Fix


Hardcoding secrets API keys, passwords in source Use environment variables and python-dotenv
code committed to Git
Running dev server in Flask dev server is single- Use Gunicorn or uvicorn in production
production threaded, insecure
No input validation SQL injection, crashes from Validate all input with Pydantic or WTForms
bad input
Returning 200 for errors Client cannot tell if request Use correct status codes: 400, 401, 404, 500
succeeded
Storing plain passwords Catastrophic if database is Always hash with bcrypt or argon2
breached
No pagination on list Returns all records — DB Always paginate: ?page=1&limit=20
endpoints overload at scale
Ignoring CORS in Browser blocks cross-origin Add Flask-CORS or Django CORS headers
browser apps requests
Mixing business logic in Views become unmaintainable Move logic to service layer or models
views

14.13 Professional Tips

🏆 Industry Best Practices for Web Development

1. API FIRST: Design your API endpoints and request/response schemas before
writing any code. A well-designed API is far easier to implement than a
poorly designed one refactored later.

2. USE FASTAPI FOR NEW APIs: FastAPI's automatic validation, documentation, and
async support make it the best choice for new API projects in 2024+.

3. NEVER TRUST CLIENT INPUT: Validate EVERYTHING from the client — types,
lengths, formats, ranges. Pydantic makes this effortless in FastAPI.

4. USE HTTPS EVERYWHERE: Never deploy an API without HTTPS. Most cloud
platforms
provide free TLS certificates. HTTP leaks tokens and credentials.

5. IMPLEMENT RATE LIMITING: Protect your API from abuse and DDoS with rate
limiting. Flask-Limiter and FastAPI SlowAPI make this easy.

6. SEPARATE CONCERNS: Use the service layer pattern — keep route handlers thin.
Routes validate input and return responses; services contain business logic.

7. LOG EVERYTHING: Log all requests, responses, and errors with structured
logging. Include a request_id in every log line for traceability.

8. WRITE API TESTS: Use pytest + requests or FastAPI's TestClient. Test every
endpoint including error cases. Untested APIs break silently.
14.14 Part 14 Summary

📚 What You Learned in Part 14

✓ HTTP methods: GET (read), POST (create), PUT/PATCH (update), DELETE (delete)
✓ HTTP status codes: 200/201/204 for success; 400/401/403/404/409/500 for errors
✓ Flask: lightweight, routing with @[Link], Jinja2 templates, Blueprints
✓ Flask REST API: jsonify(), request.get_json(), proper status codes, CRUD
✓ Django: models (ORM), migrations, views, URLs, admin panel auto-generation
✓ Django ORM: filter(), exclude(), get(), order_by(), values(), annotations
✓ FastAPI: Pydantic validation, automatic docs at /docs, async routes, Depends()
✓ REST API design: nouns not verbs, HTTP methods semantically, versioning, pagination
✓ JWT authentication: create_token(), decode_token(), require_auth decorator
✓ bcrypt for password hashing — never store plaintext passwords
✓ Environment variables via python-dotenv for secrets management
✓ Gunicorn for Flask/Django production; uvicorn for FastAPI production
✓ Docker: Dockerfile, docker-compose for containerised deployments

➡️ Coming Up in Part 15: Automation and Scripting

In Part 15 we teach Python to automate the world:

• Automation fundamentals — when and what to automate


• Web scraping with BeautifulSoup — extracting data from HTML
• requests library — making HTTP requests programmatically
• Selenium — browser automation (click, fill forms, screenshot)
• File and folder automation — batch operations
• Scheduling — running tasks at intervals
• Email and notification automation
• Real-world automation projects

— End of Part 14 —
Python Programming Mastery Guide | Part 14: Web Development

You might also like