Modern Python Engineering
Chapitre 15
15 - Cloud Python
> **Duree :** 3 semaines > **Objectif :** Maitriser le developpement cloud avec Python : AWS, GCP, Docker, Kubernetes, serverless.
Cours 15 : Cloud Python
1. AWS SDK (boto3)
1.1 S3
import boto3
from pathlib import Path
s3 = boto3.client("s3")
BUCKET = "my-bucket"
def upload_file(local_path: str, s3_key: str) -> bool:
try:
s3.upload_file(local_path, BUCKET, s3_key)
return True
except Exception as e:
print(f"Upload error: {e}")
return False
def download_file(s3_key: str, local_path: str):
s3.download_file(BUCKET, s3_key, local_path)
def list_files(prefix: str = "") -> list:
response = s3.list_objects_v2(Bucket=BUCKET, Prefix=prefix)
return [obj["Key"] for obj in response.get("Contents", [])]
def generate_presigned_url(s3_key: str, expiration=3600):
return s3.generate_presigned_url(
"get_object",
Params={"Bucket": BUCKET, "Key": s3_key},
ExpiresIn=expiration,
)
1.2 SQS
sqs = boto3.client("sqs")
queue_url = "https://sqs.eu-west-3.amazonaws.com/123456789/my-queue"
# Send
sqs.send_message(
QueueUrl=queue_url,
MessageBody='{"key": "value"}',
DelaySeconds=0,
)
# Receive
messages = sqs.receive_message(
QueueUrl=queue_url,
MaxNumberOfMessages=10,
WaitTimeSeconds=20, # Long polling
)
# Delete
if messages.get("Messages"):
for msg in messages["Messages"]:
sqs.delete_message(
QueueUrl=queue_url,
ReceiptHandle=msg["ReceiptHandle"],
)
1.3 DynamoDB
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Users")
# CRUD
table.put_item(Item={"id": "1", "name": "Alice", "email": "alice@example.com"})
response = table.get_item(Key={"id": "1"})
item = response.get("Item")
table.update_item(
Key={"id": "1"},
UpdateExpression="SET #n = :name",
ExpressionAttributeNames={"#n": "name"},
ExpressionAttributeValues={":name": "Alice Updated"},
)
table.delete_item(Key={"id": "1"})
# Query
response = table.query(
KeyConditionExpression=boto3.dynamodb.conditions.Key("id").eq("1")
)
1.4 Lambda invocation
lambda_client = boto3.client("lambda")
response = lambda_client.invoke(
FunctionName="my-function",
InvocationType="RequestResponse", # ou "Event" pour async
Payload=json.dumps({"key": "value"}),
)
result = json.loads(response["Payload"].read())
2. AWS Lambda et Powertools
2.1 Handler basique
import json
def handler(event, context):
return {
"statusCode": 200,
"body": json.dumps({"message": "Hello from Lambda!"}),
"headers": {"Content-Type": "application/json"},
}
2.2 Lambda Powertools
from aws_lambda_powertools import Logger, Tracer, Metrics
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.utilities.typing import LambdaContext
logger = Logger()
tracer = Tracer()
metrics = Metrics()
app = APIGatewayRestResolver()
@app.get("/users/{id}")
@tracer.capture_method
def get_user(id: str):
logger.info(f"Getting user {id}")
metrics.add_metric(name="UserRetrieved", unit="Count", value=1)
return {"id": id, "name": "Alice"}
@logger.inject_lambda_context
@tracer.capture_lambda_handler
@metrics.log_metrics(capture_cold_start=True)
def handler(event: dict, context: LambdaContext):
return app.resolve(event, context)
3. AWS SAM
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Globals:
Function:
Runtime: python3.12
Timeout: 30
Tracing: Active
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: prod
MyFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.handler
Events:
ApiEvent:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /{proxy+}
Method: ANY
sam build
sam deploy --guided
sam logs -n MyFunction --tail
4. AWS CDK
from aws_cdk import (
App, Stack, Duration,
aws_lambda as lambda_,
aws_s3 as s3,
aws_dynamodb as dynamodb,
aws_sqs as sqs,
)
class DataPipelineStack(Stack):
def __init__(self, app: App, id: str):
super().__init__(app, id)
bucket = s3.Bucket(self, "DataBucket",
versioned=True,
removal_policy=RemovalPolicy.DESTROY,
lifecycle_rules=[s3.LifecycleRule(expiration=Duration.days(90))],
)
table = dynamodb.Table(self, "MetadataTable",
partition_key=dynamodb.Attribute(name="id", type=dynamodb.AttributeType.STRING),
billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST,
)
queue = sqs.Queue(self, "TaskQueue",
visibility_timeout=Duration.seconds(300),
)
fn = lambda_.Function(self, "Processor",
runtime=lambda_.Runtime.PYTHON_3_12,
handler="processor.handler",
code=lambda_.Code.from_asset("src"),
timeout=Duration.minutes(5),
environment={
"BUCKET_NAME": bucket.bucket_name,
"TABLE_NAME": table.table_name,
"QUEUE_URL": queue.queue_url,
},
)
bucket.grant_read_write(fn)
table.grant_read_write_data(fn)
queue.grant_send_messages(fn)
app = App()
DataPipelineStack(app, "DataPipelineStack")
app.synth()
cdk deploy
cdk destroy
cdk diff
5. Google Cloud
5.1 GCS
from google.cloud import storage
client = storage.Client()
# Upload
bucket = client.bucket("my-bucket")
blob = bucket.blob("data/file.csv")
blob.upload_from_filename("local_file.csv")
# Download
blob.download_to_filename("downloaded.csv")
# List
blobs = client.list_blobs("my-bucket", prefix="data/")
for blob in blobs:
print(blob.name)
# Generate signed URL
url = blob.generate_signed_url(
version="v4",
expiration=3600,
method="GET",
)
5.2 Pub/Sub
from google.cloud import pubsub_v1
project_id = "my-project"
# Publish
publisher = pubsub_v1.PublisherClient()
topic = publisher.topic_path(project_id, "my-topic")
future = publisher.publish(topic, b"Message data", source="python")
future.result()
# Subscribe
subscriber = pubsub_v1.SubscriberClient()
subscription = subscriber.subscription_path(project_id, "my-subscription")
def callback(message):
print(f"Received: {message.data}")
message.ack()
streaming_pull = subscriber.subscribe(subscription, callback=callback)
streaming_pull.result()
5.3 Cloud Run
# main.py
from fastapi import FastAPI
import os
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello from Cloud Run!", "revision": os.environ.get("K_REVISION", "local")}
@app.get("/health")
async def health():
return {"status": "healthy"}
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ src/
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080"]
gcloud builds submit --tag gcr.io/PROJECT/my-app
gcloud run deploy my-app --image gcr.io/PROJECT/my-app --platform managed
6. Kubernetes
6.1 Python client
from kubernetes import client, config, watch
# Load config
config.load_kube_config()
# Core API
v1 = client.CoreV1Api()
pods = v1.list_pod_for_all_namespaces()
for pod in pods.items:
print(f"{pod.metadata.name} - {pod.status.phase}")
# Deploy pod
pod = client.V1Pod(
metadata=client.V1ObjectMeta(name="my-pod"),
spec=client.V1PodSpec(
containers=[client.V1Container(
name="app",
image="python:3.12-slim",
command=["python", "-c", "print('hello')"],
)],
restart_policy="Never",
),
)
v1.create_namespaced_pod(namespace="default", body=pod)
# Watch events
w = watch.Watch()
for event in w.stream(v1.list_namespaced_pod, namespace="default"):
print(f"{event['type']}: {event['object'].metadata.name}")
if event['object'].status.phase == 'Running':
w.stop()
7. Serverless
7.1 Chalice
from chalice import Chalice
app = Chalice(app_name="my-api")
app.debug = True
@app.route("/users/{id}")
def get_user(id):
return {"id": id, "name": "Alice"}
@app.route("/users", methods=["POST"])
def create_user():
body = app.current_request.json_body
return {"created": body["name"]}
@app.schedule("rate(1 hour)")
def periodic_task(event):
print("Running scheduled task...")
8. Terraform CDK
import cdktf
from constructs import Construct
from imports.aws import AwsProvider, S3Bucket, DynamodbTable, LambdaFunction
class MyStack(cdktf.TerraformStack):
def __init__(self, scope: Construct, id: str):
super().__init__(scope, id)
AwsProvider(self, "AWS", region="eu-west-3")
S3Bucket(self, "Data", bucket="my-unique-bucket-123")
DynamodbTable(self, "Users",
name="Users",
hash_key="id",
attribute=[{"name": "id", "type": "S"}],
billing_mode="PAY_PER_REQUEST",
)
app = cdktf.App()
MyStack(app, "my-stack")
app.synth()
cdktf init --template python
cdktf get
cdktf deploy
Diagramme en cours de génération...