Skip to content
GitHub Discord

AWS Python uv Workspaces

Deploy Python Lambda functions from a uv workspace that uses package = true and a src/ layout.

SST traverses up from the handler path to find the nearest pyproject.toml.

├── sst.config.ts
├── pyproject.toml
├── handler.py
├── src
│ └── myapp
│ ├── __init__.py
│ └── utils.py
└── packages
├── api
│ ├── pyproject.toml
│ └── src/api
│ ├── __init__.py
│ └── handler.py
├── shared
│ ├── pyproject.toml
│ └── src/shared
│ ├── __init__.py
│ └── models.py
└── worker
├── pyproject.toml
└── src/worker
├── __init__.py
└── handler.py

With package = true, the package is importable by name instead of src.*.

pyproject.toml
[tool.hatch.build.targets.wheel]
packages = ["src/myapp"]

Then import it normally in your handler.

handler.py
from myapp import utils

Each workspace member can become its own function.

sst.config.ts
new sst.aws.Function("PackageHandler", {
handler: "packages/api/src/api/handler.lambda_handler",
runtime: "python3.11",
url: true,
});
sst.config.ts
const rootHandler = new sst.aws.Function("RootHandler", {
handler: "handler.lambda_handler",
runtime: "python3.11",
url: true,
});
const packageHandler = new sst.aws.Function("PackageHandler", {
handler: "packages/api/src/api/handler.lambda_handler",
runtime: "python3.11",
url: true,
});
const workspaceHandler = new sst.aws.Function("WorkspaceHandler", {
handler: "packages/worker/src/worker/handler.lambda_handler",
runtime: "python3.11",
url: true,
});
packages/api/src/api/handler.py
import json
from shared import models
def lambda_handler(event, context):
return {
"statusCode": 200,
"body": json.dumps(models.response("api package")),
}
packages/worker/src/worker/handler.py
import json
import arrow
from shared import models
def lambda_handler(event, context):
body = models.response("worker package")
body["timestamp"] = arrow.utcnow().isoformat()
return {
"statusCode": 200,
"body": json.dumps(body),
}

View the full example.