A function that waits for news
A Lambda handler is a function a cloud service calls for you when something happens, such as a photo landing in an S3 bucket. The news arrives as a dictionary called an event.
In this module you write the handler and test it on your own computer. Nothing is deployed anywhere, and the events are made up by hand.
By the end of this lesson
Note
I can write a handler that reads an event and returns a result.
Key words
| Word | What it means | Example |
|---|---|---|
| Handler | the function the service calls | handler(event, context) |
| Event | a dictionary describing what happened | |
| Record | one item in the event's list of news | |
| Fixture | a made-up event kept for testing | |
| Idempotent | safe to run twice with the same result |
The cells students fill in are left blank in this preview.
A handler takes an event and a context
A handler is an ordinary Python function with two parameters. event holds the news. context holds facts about the running function, such as how long it may run.
The function returns a result. Because it is only a function, you can call it yourself with a made-up event.
Not deployed anywhere
Note
No function is uploaded in this module. The event shapes here are the documented ones a function receives, not output from a stack of yours. You test by calling the function by hand.
Run: call a handler by hand
event = {
"Records": [
{
"eventSource": "aws:s3",
"eventName": "ObjectCreated:Put",
"s3": {"bucket": {"name": "shop-uploads"}, "object": {"key": "images/cat.png", "size": 1024}},
}
]
}
def handler(event, context):
first = event["Records"][0]
bucket = first["s3"]["bucket"]["name"]
key = first["s3"]["object"]["key"]
return {"stored": bucket + "/" + key}
print(handler(event, None))
# Change the key to images/dog.png and run it again.
An event holds a list of records
One event can carry several records, so a handler loops over Records. Each record says where it came from in eventSource.
An S3 record names a bucket and a key. An SQS record, from a queue, has a body. The body is a string, so a JSON body needs json.loads before you can read it.
Worked example: two kinds of record
| Field | S3 record | SQS record |
|---|---|---|
| eventSource | aws:s3 | aws:sqs |
| What happened | eventName, such as ObjectCreated:Put | a message in body |
| Where to look | s3.bucket.name and s3.object.key | body, a JSON string |
| A unique id | none to rely on | messageId |
The cells students fill in are left blank in this preview.
Run: read both kinds of record
import json
event = {"Records": [
{"eventSource": "aws:sqs", "messageId": "m-1", "body": "{\"order\": 1, \"item\": \"tea\"}"},
{"eventSource": "aws:s3", "eventName": "ObjectCreated:Put",
"s3": {"bucket": {"name": "shop-uploads"}, "object": {"key": "logs/web.log"}}},
]}
def handler(event, context):
seen = []
for record in event["Records"]:
if record["eventSource"] == "aws:sqs":
order = json.loads(record["body"])
seen.append("order " + str(order["order"]))
elif record["eventSource"] == "aws:s3":
seen.append("file " + record["s3"]["object"]["key"])
return seen
print(handler(event, None))
A handler may see the same news twice
A queue promises at-least-once delivery. The same message can reach your function twice, for example after a slow run.
A handler is idempotent when a second run changes nothing. Keep a note of what you finished, and skip any work already noted. A real function keeps that note in a table, as the pipeline lesson does. Here a set stands in for it.
A common mistake
Warning
Many people think each message arrives once. It does not: delivery is at least once. Make the handler safe to run twice, or one order is charged twice.
Run: skip what was already done
def handle(orders, done):
newly = []
for order in orders:
if order in done:
continue
done.add(order)
newly.append(order)
return newly
done = set()
print(handle([1, 2, 2], done))
print(handle([2, 3], done))
# Change the second list to [4, 1]. Predict the second line before you Run.