Files
personal_site/functions/python/data-processor/mangum/adapter.py
T

85 lines
3.1 KiB
Python

from __future__ import annotations
import asyncio
import logging
from contextlib import ExitStack
from itertools import chain
from typing import Any
from mangum.exceptions import ConfigurationError
from mangum.handlers import ALB, APIGateway, HTTPGateway, LambdaAtEdge
from mangum.protocols import HTTPCycle, LifespanCycle
from mangum.types import ASGI, LambdaConfig, LambdaContext, LambdaEvent, LambdaHandler, LifespanMode
logger = logging.getLogger("mangum")
HANDLERS: list[type[LambdaHandler]] = [ALB, HTTPGateway, APIGateway, LambdaAtEdge]
DEFAULT_TEXT_MIME_TYPES: list[str] = [
"text/",
"application/json",
"application/javascript",
"application/xml",
"application/vnd.api+json",
"application/vnd.oai.openapi",
]
class Mangum:
def __init__(
self,
app: ASGI,
lifespan: LifespanMode = "auto",
api_gateway_base_path: str = "/",
custom_handlers: list[type[LambdaHandler]] | None = None,
text_mime_types: list[str] | None = None,
exclude_headers: list[str] | None = None,
) -> None:
if lifespan not in ("auto", "on", "off"):
raise ConfigurationError("Invalid argument supplied for `lifespan`. Choices are: auto|on|off")
self.app = app
self.lifespan = lifespan
self.custom_handlers = custom_handlers or []
exclude_headers = exclude_headers or []
self.config = LambdaConfig(
api_gateway_base_path=api_gateway_base_path or "/",
text_mime_types=text_mime_types or [*DEFAULT_TEXT_MIME_TYPES],
exclude_headers=[header.lower() for header in exclude_headers],
)
self._setup_event_loop()
def infer(self, event: LambdaEvent, context: LambdaContext) -> LambdaHandler:
for handler_cls in chain(self.custom_handlers, HANDLERS):
if handler_cls.infer(event, context, self.config):
return handler_cls(event, context, self.config)
raise RuntimeError( # pragma: no cover
"The adapter was unable to infer a handler to use for the event. This "
"is likely related to how the Lambda function was invoked. (Are you "
"testing locally? Make sure the request payload is valid for a "
"supported handler.)"
)
def _setup_event_loop(self) -> None:
try:
asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
def __call__(self, event: LambdaEvent, context: LambdaContext) -> dict[str, Any]:
handler = self.infer(event, context)
scope = handler.scope
with ExitStack() as stack:
if self.lifespan in ("auto", "on"):
lifespan_cycle = LifespanCycle(self.app, self.lifespan)
stack.enter_context(lifespan_cycle)
scope.update({"state": lifespan_cycle.lifespan_state.copy()})
http_cycle = HTTPCycle(scope, handler.body)
http_response = http_cycle(self.app)
return handler(http_response)
assert False, "unreachable" # pragma: no cover