Web, API, Python, and AWS Development Reference
Table of Contents
1. Web
1.1. WSGI vs ASGI
WSGI (Web Server Gateway Interface) is the traditional synchronous standard for Python web applications, while ASGI (Asynchronous Server Gateway Interface) is the modern async-capable successor.
# WSGI application (synchronous)
def wsgi_app(environ, start_response):
status = '200 OK'
headers = [('Content-Type', 'text/plain')]
start_response(status, headers)
return [b'Hello from WSGI']
# ASGI application (asynchronous)
async def asgi_app(scope, receive, send):
await send({
'type': 'http.response.start',
'status': 200,
'headers': [[b'content-type', b'text/plain']],
})
await send({
'type': 'http.response.body',
'body': b'Hello from ASGI',
})
1.2. Django Deployment Patterns
The most common production setup uses Gunicorn with Nginx as a reverse proxy.
# settings.py for production
ALLOWED_HOSTS = ['example.com', 'www.example.com']
STATIC_ROOT = '/var/www/static/'
MEDIA_ROOT = '/var/www/media/'
# Database connection pooling
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'CONN_MAX_AGE': 600,
'OPTIONS': {'connect_timeout': 10},
}
}
1.3. FastAPI for Modern APIs
FastAPI provides automatic API documentation, type validation, and async support out of the box.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_available: bool = True
@app.post("/items/")
async def create_item(item: Item):
return {"item": item, "status": "created"}
@app.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id not in item_db:
raise HTTPException(status_code=404, detail="Item not found")
return item_db[item_id]
2. API
2.1. GraphQL with Python
Python has two major implementations: Strawberry (modern, type-hint based) and Graphene (mature, class-based).
# Strawberry example (modern approach)
import strawberry
from typing import List
@strawberry.type
class Book:
title: str
author: str
year: int
@strawberry.type
class Query:
@strawberry.field
def books(self) -> List[Book]:
return [
Book(title="1984", author="George Orwell", year=1949),
Book(title="Brave New World", author="Aldous Huxley", year=1932),
]
schema = strawberry.Schema(query=Query)
https://github.com/prisma/graphql-config/blob/master/specification.md https://graphene-python.org/
2.2. REST Best Practices
from fastapi import FastAPI, Query
from typing import Optional
app = FastAPI()
@app.get("/api/v1/users")
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(50, ge=1, le=100),
filter_role: Optional[str] = None
):
offset = (page - 1) * page_size
users = get_users(offset, page_size, filter_role)
return {
"data": users,
"pagination": {
"page": page,
"page_size": page_size,
"total": get_total_users(filter_role)
}
}
3. Python
3.1. AWS SDK (boto3) Patterns
boto3 1.37.38 (locally installed) confirms the resource/client split shown below: =bucket.upload_file(Filename, Key, ExtraArgs=None, ...)= accepts =Metadata= as a valid =ExtraArgs= key, and =botocore.exceptions.ClientError= with =.response['Error']['Code']= is the documented error-handling pattern. No live AWS calls made.
import boto3
from botocore.exceptions import ClientError
# High-level resource API
s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket')
# Upload file with metadata
bucket.upload_file(
'/tmp/data.csv',
'uploads/data.csv',
ExtraArgs={'Metadata': {'uploaded-by': 'pipeline'}}
)
# Low-level client API for fine-grained control
s3_client = boto3.client('s3')
try:
response = s3_client.get_object(Bucket='my-bucket', Key='data.csv')
data = response['Body'].read()
except ClientError as e:
if e.response['Error']['Code'] == 'NoSuchKey':
print("File not found")
3.2. Lambda with Python
import json
import os
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])
def lambda_handler(event, context):
try:
# Parse input
body = json.loads(event['body'])
item_id = body['id']
# Process
response = table.get_item(Key={'id': item_id})
# Return success
return {
'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(response.get('Item', {}))
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
4. AWS
4.1. Terraform for AWS Infrastructure
=runtime = "python3.11"= names a real AWS Lambda runtime identifier, still supported today (AWS's Lambda runtimes doc lists =python3.11= with a Jun 30, 2027 deprecation date) and already available well before this note's 2024-01-01 date -- Lambda's Python 3.12 runtime had already shipped by Nov 2023. Not anachronistic.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
resource "aws_lambda_function" "api_handler" {
filename = "lambda.zip"
function_name = "api-handler"
role = aws_iam_role.lambda_role.arn
handler = "main.lambda_handler"
runtime = "python3.11"
timeout = 30
memory_size = 512
environment {
variables = {
TABLE_NAME = aws_dynamodb_table.main.name
}
}
}
4.2. Fargate Deployment Patterns
# Task definition for Python API
{
"family": "python-api",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "api",
"image": "123456789.dkr.ecr.us-east-1.amazonaws.com/python-api:latest",
"portMappings": [{"containerPort": 8000, "protocol": "tcp"}],
"environment": [
{"name": "DATABASE_URL", "value": "postgresql://..."}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/python-api",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
4.3. Cost Optimization
=put_bucket_lifecycle_configuration= rejects the rule key =Id= with a client-side =ParamValidationError= (confirmed locally with boto3 1.37.38, no AWS call made) -- the S3 service model names this field =ID=, not =Id=. This has been the modeled name since the lifecycle-configuration API shipped, so the bug was present in the note's own 2024-01-01 context, not a later API change. Corrected below.
#+BEGIN_SRC python import boto3
s3_client = boto3.client('s3')
lifecycle_policy = { 'Rules': [ { 'ID': 'Archive old logs', 'Status': 'Enabled', 'Prefix': 'logs/', 'Transitions': [ {'Days': 30, 'StorageClass': 'STANDARD_IA'}, {'Days': 90, 'StorageClass': 'GLACIER'} ], 'Expiration': {'Days': 365} } ] }
s3_client.put_bucket_lifecycle_configuration( Bucket='my-bucket', LifecycleConfiguration=lifecycle_policy )
5. Related
- Django on AWS Fargate – a Django deployment on Fargate, worked through
- Terraform AWS Patterns – the Terraform side of the same AWS setup
- Serverless Architecture – the Lambda alternative to containers