Skip to main content

Ultra-lightweight AWS Lambda utilities for API Gateway routing and event handling.

Project description

modmex-lambda

Ultra-lightweight AWS Lambda utilities for API Gateway-first workloads.

modmex-lambda is a Lambda utility layer, not an ASGI framework. It focuses on API Gateway proxy events, fast routing, request binding, response serialization, middleware, dependency injection, event source wrappers, parsing, and structured logging with a small dependency footprint.

Install

pip install modmex-lambda

For local development:

poetry install --extras dev
poetry run pytest -q

API Gateway Resolvers

Choose the resolver that matches the API Gateway payload version used by your Lambda integration:

  • ApiGatewayRestResolver for REST API payload v1.
  • ApiGatewayHttpResolver for HTTP API payload v2 and Lambda Function URLs.
from modmex_lambda import ApiGatewayHttpResolver

app = ApiGatewayHttpResolver()


@app.get("/ping")
def ping():
    return {"message": "pong"}


handler = app.handler

The internal base resolver is intentionally not exported from the package root; application code should select REST or HTTP explicitly.

Routing

Routes are declared with decorators:

@app.get("/users/<user_id>")
def get_user(user_id: int):
    return {"user_id": user_id}


@app.post("/users", status_code=201)
def create_user():
    return {"id": 42}

Supported route decorators include get, post, put, patch, delete, options, and any.

Routers can also strip deployment prefixes:

app = ApiGatewayHttpResolver(strip_prefixes=["/prod"])

Request Binding

Use typing.Annotated with the public parameter markers:

  • Path()
  • Query()
  • Header()
  • Cookie()
  • Body()
from typing import Annotated

from modmex import BaseModel
from modmex_lambda import ApiGatewayHttpResolver, Request
from modmex_lambda.event_handler.params import Body, Header, Path, Query

app = ApiGatewayHttpResolver()


class CreateUserRequest(BaseModel):
    name: str
    age: int | None = None


@app.post("/users", status_code=201)
def create_user(
    payload: Annotated[CreateUserRequest, Body()],
    tenant_id: Annotated[str, Header(name="x-tenant-id")],
    request: Request,
):
    return {
        "id": 42,
        "tenant_id": tenant_id,
        "route": request.route,
        "payload": payload.model_dump(),
    }


@app.get("/users/<user_id>")
def get_user(
    user_id: Annotated[int, Path()],
    include_orders: Annotated[bool, Query()] = False,
):
    return {"user_id": user_id, "include_orders": include_orders}

For headers, simple scalar parameters can use Header(name="x-header-name"). Header models are also supported; field names are exposed as dash-case aliases.

class HeaderModel(BaseModel):
    x_tenant_id: str


@app.get("/me")
def me(headers: Annotated[HeaderModel, Header()]):
    return {"tenant": headers.x_tenant_id}

Responses

Route return values are converted to API Gateway proxy responses:

  • dict and list become JSON responses.
  • str becomes a text response.
  • bytes are base64 encoded.
  • None returns an empty response.
  • (body, status_code) sets the response status.
  • Response gives full control over status, headers, cookies, and content type.
from modmex_lambda import Response
from modmex_lambda.shared.cookies import Cookie


@app.get("/session")
def session():
    return Response(
        status_code=200,
        body={"ok": True},
        cookies=[Cookie("seen", "true", secure=True)],
    )

REST API responses use multiValueHeaders; HTTP API responses use the v2 headers and cookies shape.

Middleware

Middleware receives the resolver instance and a next_middleware callable. Global middleware can be registered with use or @app.middleware; route middleware can be attached per route.

from modmex_lambda import Response
from modmex_lambda.event_handler.middlewares import NextMiddleware


@app.middleware
def require_auth(app: ApiGatewayHttpResolver, next_middleware: NextMiddleware) -> Response:
    if app.current_event.headers.get("x-auth") != "ok":
        return Response(status_code=401, body={"message": "Unauthorized"})
    return next_middleware(app)

Middleware also wraps routing fallbacks, so 404 and 405 responses still flow through the middleware chain.

Dependency Injection

Depends supports nested dependency trees, request-aware dependencies, per-invocation caching, and overrides for tests.

from typing import Annotated

from modmex_lambda import Depends, Request
from modmex_lambda.event_handler.params import Path


class UserRepository:
    def __init__(self, *, tenant_id: str, token: str):
        self.tenant_id = tenant_id
        self.token = token

    def get_user(self, user_id: int) -> dict:
        # Replace this with a database or service call.
        return {"id": user_id, "tenant_id": self.tenant_id}


def get_token() -> str:
    return "token"


def get_tenant_id(request: Request) -> str:
    return request.headers["x-tenant-id"]


def get_user_repository(
    tenant_id: Annotated[str, Depends(get_tenant_id)],
    token: Annotated[str, Depends(get_token)],
) -> UserRepository:
    return UserRepository(tenant_id=tenant_id, token=token)


@app.get("/users/<user_id>")
def get_user(
    user_id: Annotated[int, Path()],
    repository: Annotated[UserRepository, Depends(get_user_repository)],
):
    return repository.get_user(user_id)

Disable dependency caching when a dependency must run every time:

def next_counter() -> int:
    ...


@app.get("/counter")
def counter(value: Annotated[int, Depends(next_counter, use_cache=False)]):
    return {"value": value}

For tests, set app.dependency_overrides:

app.dependency_overrides[get_token] = lambda: "test-token"

Exception Handling

Built-in error responses:

  • request validation errors return 400.
  • NotFoundError returns 404.
  • MethodNotAllowedError returns 405.
  • UnauthorizedError returns 401.
  • ForbiddenError returns 403.

Custom handlers can be registered per exception type. The most specific handler wins.

from modmex_lambda import Response


class DomainError(Exception):
    pass


@app.exception_handler(DomainError)
def on_domain_error(exc: DomainError):
    return Response(status_code=409, body={"message": str(exc)})

If a custom exception handler raises, the resolver falls back to the default error response when one exists.

CORS

Pass CORSConfig to the resolver to add CORS headers and automatic preflight behavior.

from modmex_lambda import ApiGatewayHttpResolver
from modmex_lambda.event_handler.cors import CORSConfig

app = ApiGatewayHttpResolver(
    cors=CORSConfig(
        allow_origin="https://app.example",
        allow_headers=["X-Tenant-Id"],
        allow_credentials=True,
    ),
)

Parser

from modmex_lambda.parser import event_parser, parse

parsed = parse(event={"name": "Ada"}, model=MyModel)


@event_parser(model=MyModel)
def lambda_handler(event: MyModel, context):
    ...

Event Source Data Classes

Current scoped data classes include:

  • APIGatewayProxyEvent and APIGatewayProxyEventV2
  • APIGatewayRestEvent and APIGatewayHttpEvent aliases
  • APIGatewayAuthorizerEvent
  • APIGatewayWebSocketEvent
  • Cognito User Pool trigger wrappers
from modmex_lambda.data_classes import APIGatewayHttpEvent
from modmex_lambda.event_sources import event_source


@event_source(data_class=APIGatewayHttpEvent)
def lambda_handler(event: APIGatewayHttpEvent, context):
    return {"path": event.path}

Validation

Modmex is the default validation and coercion engine. Pydantic is not required for normal operation.

Logging

from modmex_lambda import Logger

logger = Logger(service="users")


def lambda_handler(event, context):
    logger.append_keys(tenant_id="mx")
    logger.info("request received")

The logger emits structured JSON and can extract Lambda request IDs and API Gateway correlation IDs.

Benchmarks

The benchmark suite lives in .benchmark/api_gateway_benchmark.py.

It covers cold imports, app setup, route registration, API Gateway v1/v2 invocation, event_parser, event_source, and logger hot paths.

poetry run python .benchmark/api_gateway_benchmark.py

More details are in .benchmark/README.md.

Limitations

  • Event source scope is intentionally focused on API Gateway and Cognito.
  • OpenAPI/Swagger generation is not implemented.
  • Async resolver pipelines are not implemented yet.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

modmex_lambda-0.1.0.tar.gz (74.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

modmex_lambda-0.1.0-py3-none-any.whl (55.9 kB view details)

Uploaded Python 3

File details

Details for the file modmex_lambda-0.1.0.tar.gz.

File metadata

  • Download URL: modmex_lambda-0.1.0.tar.gz
  • Upload date:
  • Size: 74.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for modmex_lambda-0.1.0.tar.gz
Algorithm Hash digest
SHA256 51772bd12494b2962d6a01137d911c0b013179782e7f3ad1d67d1b1efc6bc1fa
MD5 85e42a467a6c1e69ed1d94c445df336c
BLAKE2b-256 3c491cc857d20365f0024064a4d71beafb3acc7e7e0fe6b5626d6673571c2825

See more details on using hashes here.

Provenance

The following attestation bundles were made for modmex_lambda-0.1.0.tar.gz:

Publisher: release.yml on modmex/modmex-lambda

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file modmex_lambda-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: modmex_lambda-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 55.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for modmex_lambda-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 db61998adc316f2c4f7762fe7a0d5fb43d15f132512d9f0e0573702cd23f8bcb
MD5 6f3495f32116dbd6ab0813ada2dbd11f
BLAKE2b-256 cedf21555c648094531eea1358013c73820440c409037d50b1b266ed2632a7fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for modmex_lambda-0.1.0-py3-none-any.whl:

Publisher: release.yml on modmex/modmex-lambda

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page