Python HTTP micro-framework for AWS Lambda.
Reason this release was yanked:
dsql_connection is broken - fixed in 0.11
Project description
Python HTTP micro-framework for AWS Lambda.
Lymo provides a Flask/Django-inspired API for building serverless web applications on AWS Lambda with minimal boilerplate. It handles routing, request/response abstraction, templating, and includes an optional Django-style ORM for PostgreSQL.
Installation
Basic installation:
pip install lymo
With optional dependencies:
# For Jinja2 templating support
pip install lymo[templates]
# For PostgreSQL database support
pip install lymo[db]
# For development
pip install lymo[dev]
# All extras
pip install lymo[templates,db,dev]
Requirements
Python 3.10 or higher
AWS Lambda environment with API Gateway v1 events
Optional: PostgreSQL (for database features)
Optional: Jinja2 (for templating)
Quick Start
Basic Lambda Handler
from lymo import App
from lymo.http_handler import http_handler
import routes # Your routes package
app = App(routes=routes)
def lambda_handler(event, context):
return http_handler(event, context, app)
Defining Routes
Create a routes package with modules containing route handlers:
# routes/users.py
from lymo.router import route
from lymo.http_responses import JsonResponse
@route("/users/{user_id}", "GET")
def get_user(request):
user_id = request.path_params["user_id"]
return JsonResponse(
request=request,
data={"user_id": user_id, "name": "John Doe"}
)
@route("/users/", "POST")
def create_user(request):
# Access request body
data = request.body
return JsonResponse(
request=request,
data={"message": "User created"},
status_code=201
)
Edge Caching
Add cache-control headers for GET requests:
from lymo.router import route
from lymo.http_responses import JsonResponse
@route("/quotes/", "GET", cache_ttl=300) # 5 minute cache
def get_quotes(request):
return JsonResponse(request=request, data={"quotes": []})
Note: cache_ttl only applies to safe methods (GET, HEAD, OPTIONS) and is ignored for state changing operations (POST, PUT, PATCH, DELETE).
Core Concepts
Application Object
The App class is the central configuration object:
from lymo import App
app = App(
routes=routes, # Required: routes package
template_dir="templates", # Optional: Jinja2 template directory
template_globals={"site": "My"}, # Optional: global template variables
template_extensions=[], # Optional: Jinja2 extensions
resources={"db": db_conn}, # Optional: shared resources (DB, etc.)
logger=logger # Optional: Python logger
)
Request Object
The HttpRequest object provides access to Lambda event data:
request.method - HTTP method (GET, POST, etc.)
request.path - Request path
request.headers - Request headers dict
request.queryparams - Query string parameters dict
request.body - Request body
request.path_params - URL path parameters from route pattern
request.template_env - Jinja2 environment (if configured)
request.resources - Shared resources dict from App
Response Types
HttpResponse
Plain text or HTML response:
from lymo.http_responses import HttpResponse
return HttpResponse(
request=request,
body="<h1>Hello World</h1>",
status_code=200,
headers={"Custom-Header": "value"}
)
JsonResponse
Automatic JSON serialization with support for datetime, UUID, Decimal, and Pydantic models:
from lymo.http_responses import JsonResponse
return JsonResponse(
request=request,
data={"message": "Success", "timestamp": datetime.now()},
status_code=200
)
TemplateResponse
Render Jinja2 templates:
from lymo.http_responses import TemplateResponse
return TemplateResponse(
request=request,
template="users/detail.html",
context={"user": user},
status_code=200
)
The request object is automatically added to the template context.
Routing
Routes are defined using the @route decorator and automatically discovered when the app initializes.
Path Parameters
Extract parameters from URLs using {param} syntax:
@route("/posts/{post_id}/comments/{comment_id}", "GET")
def get_comment(request):
post_id = request.path_params["post_id"]
comment_id = request.path_params["comment_id"]
return JsonResponse(request=request, data={...})
Route Organization
Organize routes in a package structure:
routes/
__init__.py
users.py
posts.py
comments.py
All modules in the routes package are automatically imported and their @route decorators registered.
Database Features
Lymo includes an optional Django-style ORM for PostgreSQL using Pydantic and psycopg.
Model Definition
from lymo.db import Model
from datetime import datetime
class User(Model):
id: int
email: str
name: str
created_at: datetime
updated_at: datetime
Table names are automatically derived from the class name (User → user, BlogPost → blog_post).
Database Connection
Pass database connections explicitly using .using(conn):
import psycopg
# In your Lambda handler setup
conn = psycopg.connect("postgresql://...")
app = App(
routes=routes,
resources={"db": conn}
)
# In your route handler
@route("/users/{user_id}", "GET")
def get_user(request):
db = request.resources["db"]
user = User.objects.using(db).find(id=user_id).get()
return JsonResponse(request=request, data=user.model_dump())
AWS DSQL Support
Lymo includes built-in support for AWS DSQL (Aurora DSQL) connections with IAM authentication:
from lymo.dsql import create_dsql_connection
# Create DSQL connection with IAM auth
conn = create_dsql_connection(
cluster_identifier="my-cluster",
region="us-east-1",
cluster_user="admin", # Optional, defaults to "admin"
schema="public", # Optional, defaults to "public"
expires_in=3600 # Optional, token TTL in seconds
)
app = App(
routes=routes,
resources={"db": conn}
)
The DSQL connection helper:
Automatically generates IAM authentication tokens via boto3
Configures SSL/TLS with the correct settings for DSQL
Sets the PostgreSQL search_path to your specified schema
Supports psycopg 3.x with optimized SSL negotiation for newer versions
Note: The connection token expires after expires_in seconds (default: 3600). For long-running Lambda containers, consider implementing connection refresh logic or using shorter expiration times.
Query API
Basic Queries
# Get all records
users = User.objects.using(conn).list()
# Get single record (raises ValueError if not found or multiple found)
user = User.objects.using(conn).find(id=123).get()
# Count records
count = User.objects.using(conn).count()
Filtering
# Equality filters
active_users = User.objects.using(conn).find(status="active").list()
# Multiple filters (AND)
users = User.objects.using(conn).find(status="active", verified=True).list()
Chainable Methods
# Order by
users = (
User.objects.using(conn)
.find(status="active")
.order_by("created_at", "DESC")
.list()
)
# Limit and offset
users = User.objects.using(conn).limit(10, offset=20).list()
# Pagination
users = User.objects.using(conn).paginate(page=2, per_page=50).list()
Date Filtering
from datetime import datetime, date
# Inclusive range (BETWEEN)
users = User.objects.using(conn).date_range(
"created_at",
date_from=datetime(2024, 1, 1),
date_to=datetime(2024, 12, 31)
).list()
# Exclusive range (>= AND <)
users = User.objects.using(conn).date_between(
"created_at",
date_from=datetime(2024, 1, 1),
date_to=datetime(2024, 1, 31)
).list()
# Exact date match
today_users = User.objects.using(conn).date("created_at", date.today()).list()
Generic Range Filtering
# Works with any type (numeric, string, UUID, etc.)
users = User.objects.using(conn).between("age", 18, 65).list()
JSON Output
# Return JSON strings instead of model instances
users_json = User.objects.using(conn).find(status="active").list(json=True)
user_json = User.objects.using(conn).find(id=123).get(json=True)
Async Support
All query methods have async equivalents:
import psycopg
async with await psycopg.AsyncConnection.connect("postgresql://...") as conn:
users = await User.objects.using(conn).alist()
user = await User.objects.using(conn).find(id=123).aget()
count = await User.objects.using(conn).acount()
Templating
When template_dir is provided, Lymo configures Jinja2 with:
Auto-escaping for HTML/XML
Custom filters: urlencode, base64
Optional custom globals and extensions
Example:
app = App(
routes=routes,
template_dir="templates",
template_globals={"site_name": "My Site"},
template_extensions=["jinja2.ext.do"]
)
In templates:
<!DOCTYPE html>
<html>
<head>
<title>{{ site_name }}</title>
</head>
<body>
<h1>{{ user.name }}</h1>
<a href="/profile?user={{ user.id|urlencode }}">Profile</a>
<img src="data:image/png;base64,{{ image_data|base64 }}">
</body>
</html>
Architecture
Request Flow
AWS Lambda receives API Gateway v1 event
http_handler() creates HttpRequest wrapper
Router matches path and HTTP method to handler via regex
Route handler executes with request object
Response object (HttpResponse/JsonResponse/TemplateResponse) is returned
Lambda returns response dict to API Gateway
Path Processing
API Gateway base paths are automatically stripped:
Resource: /api/{proxy+}
Path: /api/users/123
Processed: /users/123
Error Handling
Uncaught exceptions return 500 responses with error details and traceback (if logger is configured, errors are logged).
Data Serialization
The custom JSON serializer handles:
datetime and date → ISO format strings
uuid.UUID → string representation
Decimal → float
Pydantic models → model_dump() output
Other objects → str() representation
Development
Setup
# Clone repository
git clone https://github.com/newadventures/lymo.git
cd lymo
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install with dev dependencies
pip install -e ".[dev,db,templates]"
Linting
ruff check src/ tests/
Building
# Build distribution
python -m build
# Check package
twine check dist/*
License
See LICENSE file for details.
Links
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lymo-0.10.tar.gz.
File metadata
- Download URL: lymo-0.10.tar.gz
- Upload date:
- Size: 18.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dadb77424e42685afed2aa0243b274548c46513f68ad151d557905fd50f99c8e
|
|
| MD5 |
86a4d49b6593d6494ee61536b1173ff2
|
|
| BLAKE2b-256 |
d9a30a7256e72415d7ba60e0d39dca54844a50456375f7e1e15174cd20cb3fa0
|
Provenance
The following attestation bundles were made for lymo-0.10.tar.gz:
Publisher:
publish-to-pypi.yml on newadventures/lymo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lymo-0.10.tar.gz -
Subject digest:
dadb77424e42685afed2aa0243b274548c46513f68ad151d557905fd50f99c8e - Sigstore transparency entry: 702042375
- Sigstore integration time:
-
Permalink:
newadventures/lymo@a16fe169fbca3cc43b1f649c03ee7543205e94c0 -
Branch / Tag:
refs/tags/0.10 - Owner: https://github.com/newadventures
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@a16fe169fbca3cc43b1f649c03ee7543205e94c0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file lymo-0.10-py3-none-any.whl.
File metadata
- Download URL: lymo-0.10-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
12a167f55fdda206cbdf720680c50f47dbccd9494888afab5406f9328b05ceec
|
|
| MD5 |
5a6cd74579eb88d6bc14102c42aa60ab
|
|
| BLAKE2b-256 |
b5c57ea5662b095f331d01bd5641739f9c8ccdf1be9f8bd4e64eabeedd571461
|
Provenance
The following attestation bundles were made for lymo-0.10-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on newadventures/lymo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lymo-0.10-py3-none-any.whl -
Subject digest:
12a167f55fdda206cbdf720680c50f47dbccd9494888afab5406f9328b05ceec - Sigstore transparency entry: 702042376
- Sigstore integration time:
-
Permalink:
newadventures/lymo@a16fe169fbca3cc43b1f649c03ee7543205e94c0 -
Branch / Tag:
refs/tags/0.10 - Owner: https://github.com/newadventures
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@a16fe169fbca3cc43b1f649c03ee7543205e94c0 -
Trigger Event:
release
-
Statement type: