Get Your Lambda...
Damian Wysocki

Mirumee Commerce Lab
For over 15 years, we have been shaping the future of technology through open source, transparency, and
a sustainable approach to product development.
A. Who We Are

-
We combine technology, design, and data.
-
We build global
e-commerce platforms using a modern stack. -
Open source at our core.
-
Headless architecture for ambitious brands.
A. What We Do

Django
Django
Lambda




HTTP REQUEST
Lambda
Web
The Developer’s Dilemma
debugging Lambdas locally is painful
The Developer’s Dilemma
debugging Lambdas locally is painful
Slow feedback.
Hard to debug.
Traditional Lambda Workflow
pytest
- easy
- fast
- deterministic
NO HTTP
serverless
- somewhat involved
- lack of Python 3.12 support
- exposes the lambda over HTTP
NO PDB
localstack
- mimics the real thing closely
- supports API GW v1
(in the community version)
CODE REFRESH TAKES ~2 MINUTES
... same goes for the real thing really
I just want the service to reload on save...
... just like it happens on every other web service I'm developing

asd
def http_handler(event, context):
breakpoint()
return {
"statusCode": 200,
"body": "Hello World"
}
Minimal setup
[tool.smyth]
host = "0.0.0.0"
port = 8080
[tool.smyth.handlers.http_handler]
handler_path = "handler.http_handler"
url_path = "/api/{path:path}"
> python -m smyth
❯ uv run python -m smyth
Uvicorn:Worker[4664] [12:41:39] INFO Will watch for changes in these directories: ['/Users/pkucmus/Development/drops']
Uvicorn:Worker[4664] INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
Uvicorn:Worker[4664] INFO Started reloader process [4664] using StatReload
Uvicorn:Worker[4666] [12:41:39] INFO Started server process [4666]
Uvicorn:Worker[4666] INFO Waiting for application startup.
Smyth:SpawnProcess-1 INFO Started process http_handler:0
Uvicorn:Worker[4666] INFO Application startup complete.
Uvicorn:Worker[4664] [12:41:46] WARNING StatReload detected changes in 'handler.py'. Reloading...
Uvicorn:Worker[4666] [12:41:46] INFO Shutting down
Uvicorn:Worker[4666] INFO Waiting for application shutdown.
Smyth:SpawnProcess-1 INFO Stopping process http_handler:0
Smyth:http_handler:0 [12:41:46] DEBUG Received message: type='smyth.stop' event=None context=None
Smyth:http_handler:0 DEBUG Stopping process
Uvicorn:Worker[4666] INFO Application shutdown complete.
Uvicorn:Worker[4666] INFO Finished server process [4666]
Uvicorn:Worker[4682] [12:41:47] INFO Started server process [4682]
Uvicorn:Worker[4682] INFO Waiting for application startup.
Smyth:SpawnProcess-2 INFO Started process http_handler:0
Uvicorn:Worker[4682] INFO Application startup complete.
Reload
COUNTER = 0
def http_handler(event, context):
global COUNTER
COUNTER += 1
return {
"statusCode": 200,
"body": f"Hello World: {COUNTER}"
}
the module is imported once, per a Smyth Process lifetime
Cold start simualtion
[tool.smyth]
host = "0.0.0.0"
port = 8080
[tool.smyth.handlers.http_handler]
handler_path = "handler.http_handler"
url_path = "/api/{path:path}"
concurrency = 2
❯ uv run python -m smyth
Uvicorn:Worker[4851] [12:53:54] INFO Will watch for changes in these directories: ['/Users/pkucmus/Development/drops']
Uvicorn:Worker[4851] INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
Uvicorn:Worker[4851] INFO Started reloader process [4851] using StatReload
Uvicorn:Worker[4854] [12:53:54] INFO Started server process [4854]
Uvicorn:Worker[4854] INFO Waiting for application startup.
Smyth:SpawnProcess-1 INFO Started process http_handler:0
Smyth:SpawnProcess-1 INFO Started process http_handler:1
Uvicorn:Worker[4854] INFO Application startup complete.
Concurrency
HTTP REQUEST
EVENT

Lambda
Web
The real thing
HTTP REQUEST
EVENT
Lambda
Web
Smyth


Lambda
Web
import boto3
lambda_client = boto3.client(
"lambda",
endpoint_url="http://localhost:8080"
)
def order_handler(event, context):
lambda_client.invoke(
FunctionName="email_handler",
InvocationType="Event", # or RequestResponse
Payload=b'{"to": "hello@mirumee.com", "subject": "Order made"}',
)
return {"statusCode": 200, "body": f"Orders requests: {COUNT}"}
def email_handler(event, context):
print(event)
return {"statusCode": 200, "body": f"Products requests: {COUNT}"}
add it to the TOML
Lambda invoke endpoint
from smyth.types import EventData, RunnerProcessProtocol, SmythHandler
async def generate_api_gw_v1_event_data(
request: Request,
smyth_handler: SmythHandler,
process: RunnerProcessProtocol
) -> EventData:
source_ip = None
if request.client:
source_ip = request.client.host
return {
"resource": request.url.path,
"path": request.url.path,
"httpMethod": request.method,
"headers": dict(request.headers),
"queryStringParameters": dict(request.query_params),
"pathParameters": {}, # You may need to populate this based on your routing
"stageVariables": None,
"requestContext": {
"resourceId": "offlineContext_resourceId",
"resourcePath": request.url.path,
"httpMethod": request.method,
"extendedRequestId": "offlineContext_extendedRequestId",
"requestTime": "21/Nov/2020:20:13:27 +0000",
"path": request.url.path,
"accountId": "offlineContext_accountId",
"protocol": request.url.scheme,
"stage": "dev",
"domainPrefix": "offlineContext_domainPrefix",
"requestTimeEpoch": int(request.timestamp().timestamp() * 1000),
"requestId": "offlineContext_requestId",
"identity": {
...
},
"domainName": "offlineContext_domainName",
"apiId": "offlineContext_apiId"
},
"body": (await request.body()).decode("utf-8"),
"isBase64Encoded": False
}
[tool.smyth]
host = "0.0.0.0"
port = 8080
[tool.smyth.handlers.http_handler]
handler_path = "handler.http_handler"
url_path = "/api/{path:path}"
event_data_function_path = "handler.generate_api_gw_v1_event_data"
Event generation
There's more...
Dispatch Strategies
Environment Variable Injection
Custom Entrypoint for complex usecases
Non-HTTP Invocation
There's another
problem
you still need to sell the whole serverless thing to the team
There's another
problem
you still need to sell the whole serverless thing to the team
asd

Web
ASGI

The gist of it
import asyncio
from lynara import Lynara, APIGatewayProxyEventV2Interface
from fastapi import FastAPI
app = FastAPI()
lynara = Lynara(app=app)
def lambda_handler(event, context):
return asyncio.run(
lynara.run(event, context, APIGatewayProxyEventV2Interface)
)
Setup
import asyncio
from lynara import LifespanMode, Lynara, APIGatewayProxyEventV2Interface
from fastapi import FastAPI
app = FastAPI()
lynara = Lynara(app=app, lifespan_mode=LifespanMode.AUTO)
def lambda_handler(event, context):
return asyncio.run(
lynara.run(event, context, APIGatewayProxyEventV2Interface)
)
Lifespan support
import asyncio
from lynara import (
Lynara,
APIGatewayProxyEventV1Interface,
APIGatewayProxyEventV2Interface
)
from fastapi import FastAPI
app = FastAPI()
lynara = Lynara(app=app)
# AWS API Gateway Proxy V2 or Lambda function URL
def http_handler(event, context):
return asyncio.run(
lynara.run(event, context, APIGatewayProxyEventV2Interface)
)
# AWS API Gateway Proxy V1
def http_handler(event, context):
return asyncio.run(
lynara.run(event, context, APIGatewayProxyEventV1Interface)
)
Interface support
Sure:
if you are stubborn enough and can translate an event into a ASGI request you could use FastAPI to handle DynamoDB, SQS and other events
Custom Interfaces

Benchmarks
Cold start performance
def lambda_handler_pure(event, context):
return {"data": "Hello It's me Damian!"}
def lambda_handler_pure(event, context):
return {"data": "Hello It's me Damian!"}
0.49 sec of cold start
from fastapi import FastAPI
def lambda_handler_pure(event, context):
return {"data": "Hello It's me Damian!"}
from fastapi import FastAPI
def lambda_handler_pure(event, context):
return {"data": "Hello It's me Damian!"}
1.39 sec of cold start
Lambda Layers
Lambda Layers
Reduce cold start by 1 second



serverless
Fueled by your favourite ASGI framework
Great
power
Great
power
With
comes great responsibility

















No rewrite needed. Your ASGI app is portable!
It's your choice...
Dev/Staging on Lambda but prod on EC2
Early stage development on lambda
Private environment for QAs
Thank You.
deck
By Damian Wysocki
deck
- 102