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 REQUESTLambda
Web
debugging Lambdas locally is painful
debugging Lambdas locally is painful
Slow feedback.
Hard to debug.
pytest
NO HTTP
serverless
NO PDB
localstack
CODE REFRESH TAKES ~2 MINUTES
... same goes for the real thing really
... just like it happens on every other web service I'm developing
asddef 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
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
Dispatch Strategies
Environment Variable Injection
Custom Entrypoint for complex usecases
Non-HTTP Invocation
you still need to sell the whole serverless thing to the team
you still need to sell the whole serverless thing to the team
asdWeb
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
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
Reduce cold start by 1 second
Fueled by your favourite ASGI framework
No rewrite needed. Your ASGI app is portable!
Dev/Staging on Lambda but prod on EC2
Early stage development on lambda
Private environment for QAs
Thank You.