Skip to content

Commit 7ee6846

Browse files
🎨 Replace abstract handlers, customer handlers, drop 3.6 support, typing additions and fixes, refactoring. (#242)
* 🎨 Replace abstract handlers and introduce provisional support for custom handlers, various type annotation fixes and additions, remove dataclasses, update README and requirements. * 💥 Drop support for Python 3.6.
1 parent 88ae4d9 commit 7ee6846

30 files changed

+1083
-998
lines changed

.github/workflows/test.yml

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,28 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
14+
python-version: ["3.7", "3.8", "3.9", "3.10"]
1515
steps:
16-
- uses: actions/checkout@v2
17-
- name: Set up Python ${{ matrix.python-version }}
18-
uses: actions/setup-python@v2
19-
with:
20-
python-version: ${{ matrix.python-version }}
21-
- uses: actions/cache@v2
22-
name: Configure pip caching
23-
with:
24-
path: ~/.cache/pip
25-
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
26-
restore-keys: |
27-
${{ runner.os }}-pip-
28-
- name: Install dependencies
29-
run: |
30-
pip install -U -r requirements.txt
31-
- name: Run tests
32-
run: |
33-
scripts/test
34-
- name: Run linters
35-
run: |
36-
scripts/lint
37-
- name: Run codecov
38-
run: codecov
16+
- uses: actions/checkout@v2
17+
- name: Set up Python ${{ matrix.python-version }}
18+
uses: actions/setup-python@v2
19+
with:
20+
python-version: ${{ matrix.python-version }}
21+
- uses: actions/cache@v2
22+
name: Configure pip caching
23+
with:
24+
path: ~/.cache/pip
25+
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
26+
restore-keys: |
27+
${{ runner.os }}-pip-
28+
- name: Install dependencies
29+
run: |
30+
pip install -U -r requirements.txt
31+
- name: Run tests
32+
run: |
33+
scripts/test
34+
- name: Run linters
35+
run: |
36+
scripts/lint
37+
- name: Run codecov
38+
run: codecov

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
</a>
99
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/mangum.svg?style=flat-square">
1010

11-
Mangum is an adapter for using [ASGI](https://asgi.readthedocs.io/en/latest/) applications with AWS Lambda & API Gateway. It is intended to provide an easy-to-use, configurable wrapper for any ASGI application deployed in an AWS Lambda function to handle API Gateway requests and responses.
11+
Mangum is an adapter for running [ASGI](https://asgi.readthedocs.io/en/latest/) applications in AWS Lambda to handle API Gateway, ALB, and Lambda@Edge events.
1212

1313
***Documentation***: https://mangum.io/
1414

1515
## Features
1616

17-
- API Gateway support for [HTTP](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) APIs.
17+
- Event handlers for API Gateway [HTTP](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) APIs, [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html), and [CloudFront Lambda@Edge](https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html).
1818

1919
- Compatibility with ASGI application frameworks, such as [Starlette](https://www.starlette.io/), [FastAPI](https://fastapi.tiangolo.com/), and [Quart](https://pgjones.gitlab.io/quart/).
2020

@@ -26,7 +26,7 @@ Mangum is an adapter for using [ASGI](https://asgi.readthedocs.io/en/latest/) ap
2626

2727
## Requirements
2828

29-
Python 3.6+
29+
Python 3.7+
3030

3131
## Installation
3232

@@ -53,7 +53,7 @@ async def app(scope, receive, send):
5353
handler = Mangum(app)
5454
```
5555

56-
Or using a framework.
56+
Or using a framework:
5757

5858
```python
5959
from fastapi import FastAPI

docs/asgi-frameworks.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ None of the framework details are important here. The routing decorator, request
3030

3131
```python
3232
class Application(Protocol):
33-
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
33+
async def __call__(self, scope: Scope, receive: ASGIReceive, send: ASGISend) -> None:
3434
...
3535
```
3636

docs/index.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
</a>
99
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/mangum.svg?style=flat-square">
1010

11-
Mangum is an adapter for using [ASGI](https://asgi.readthedocs.io/en/latest/) applications with AWS Lambda & API Gateway. It is intended to provide an easy-to-use, configurable wrapper for any ASGI application deployed in an AWS Lambda function to handle API Gateway requests and responses.
11+
Mangum is an adapter for running [ASGI](https://asgi.readthedocs.io/en/latest/) applications in AWS Lambda to handle API Gateway, ALB, and Lambda@Edge events.
1212

1313
***Documentation***: https://mangum.io/
1414

1515
## Features
1616

17-
- API Gateway support for [HTTP](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) APIs.
17+
- Event handlers for API Gateway [HTTP](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) APIs, [Application Load Balancer](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html), and [CloudFront Lambda@Edge](https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html).
1818

1919
- Compatibility with ASGI application frameworks, such as [Starlette](https://www.starlette.io/), [FastAPI](https://fastapi.tiangolo.com/), and [Quart](https://pgjones.gitlab.io/quart/).
2020

@@ -26,7 +26,7 @@ Mangum is an adapter for using [ASGI](https://asgi.readthedocs.io/en/latest/) ap
2626

2727
## Requirements
2828

29-
Python 3.6+
29+
Python 3.7+
3030

3131
## Installation
3232

@@ -53,7 +53,7 @@ async def app(scope, receive, send):
5353
handler = Mangum(app)
5454
```
5555

56-
Or using a framework.
56+
Or using a framework:
5757

5858
```python
5959
from fastapi import FastAPI

mangum/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from .types import Request, Response
2-
from .adapter import Mangum # noqa: F401
1+
from mangum.adapter import Mangum
32

4-
__all__ = ["Mangum", "Request", "Response"]
3+
__all__ = ["Mangum"]

mangum/adapter.py

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,92 @@
1+
from itertools import chain
12
import logging
23
from contextlib import ExitStack
4+
from typing import List, Optional, Type
5+
import warnings
36

4-
5-
from mangum.exceptions import ConfigurationError
6-
from mangum.handlers.abstract_handler import AbstractHandler
77
from mangum.protocols import HTTPCycle, LifespanCycle
8-
from mangum.types import ASGIApp, LambdaEvent, LambdaContext
8+
from mangum.handlers import ALB, HTTPGateway, APIGateway, LambdaAtEdge
9+
from mangum.exceptions import ConfigurationError
10+
from mangum.types import (
11+
ASGIApp,
12+
LifespanMode,
13+
LambdaConfig,
14+
LambdaEvent,
15+
LambdaContext,
16+
LambdaHandler,
17+
)
918

1019

11-
DEFAULT_TEXT_MIME_TYPES = [
12-
"text/",
13-
"application/json",
14-
"application/javascript",
15-
"application/xml",
16-
"application/vnd.api+json",
17-
]
20+
logger = logging.getLogger("mangum")
1821

1922

20-
logger = logging.getLogger("mangum")
23+
HANDLERS: List[Type[LambdaHandler]] = [
24+
ALB,
25+
HTTPGateway,
26+
APIGateway,
27+
LambdaAtEdge,
28+
]
2129

2230

2331
class Mangum:
24-
"""
25-
Creates an adapter instance.
26-
27-
* **app** - An asynchronous callable that conforms to version 3.0 of the ASGI
28-
specification. This will usually be an ASGI framework application instance.
29-
* **lifespan** - A string to configure lifespan support. Choices are `auto`, `on`,
30-
and `off`. Default is `auto`.
31-
* **text_mime_types** - A list of MIME types to include with the defaults that
32-
should not return a binary response in API Gateway.
33-
* **api_gateway_base_path** - A string specifying the part of the url path after
34-
which the server routing begins.
35-
"""
36-
3732
def __init__(
3833
self,
3934
app: ASGIApp,
40-
lifespan: str = "auto",
35+
lifespan: LifespanMode = "auto",
4136
api_gateway_base_path: str = "/",
37+
custom_handlers: Optional[List[Type[LambdaHandler]]] = None,
4238
) -> None:
39+
if lifespan not in ("auto", "on", "off"):
40+
raise ConfigurationError(
41+
"Invalid argument supplied for `lifespan`. Choices are: auto|on|off"
42+
)
43+
4344
self.app = app
4445
self.lifespan = lifespan
45-
self.api_gateway_base_path = api_gateway_base_path
46+
self.api_gateway_base_path = api_gateway_base_path or "/"
47+
self.config = LambdaConfig(api_gateway_base_path=self.api_gateway_base_path)
4648

47-
if self.lifespan not in ("auto", "on", "off"):
48-
raise ConfigurationError(
49-
"Invalid argument supplied for `lifespan`. Choices are: auto|on|off"
49+
if custom_handlers is not None:
50+
warnings.warn( # pragma: no cover
51+
"Support for custom event handlers is currently provisional and may "
52+
"drastically change (or be removed entirely) in the future.",
53+
FutureWarning,
5054
)
5155

52-
def __call__(self, event: LambdaEvent, context: LambdaContext) -> dict:
53-
logger.debug("Event received.")
56+
self.custom_handlers = custom_handlers or []
57+
58+
def infer(self, event: LambdaEvent, context: LambdaContext) -> LambdaHandler:
59+
for handler_cls in chain(
60+
self.custom_handlers,
61+
HANDLERS,
62+
):
63+
handler = handler_cls.infer(
64+
event,
65+
context,
66+
self.config,
67+
)
68+
if handler:
69+
break
70+
else:
71+
raise RuntimeError( # pragma: no cover
72+
"The adapter was unable to infer a handler to use for the event. This "
73+
"is likely related to how the Lambda function was invoked. (Are you "
74+
"testing locally? Make sure the request payload is valid for a "
75+
"supported handler.)"
76+
)
77+
78+
return handler
5479

80+
def __call__(self, event: LambdaEvent, context: LambdaContext) -> dict:
81+
handler = self.infer(event, context)
5582
with ExitStack() as stack:
56-
if self.lifespan != "off":
83+
if self.lifespan in ("auto", "on"):
5784
lifespan_cycle = LifespanCycle(self.app, self.lifespan)
5885
stack.enter_context(lifespan_cycle)
5986

60-
handler = AbstractHandler.from_trigger(
61-
event, context, self.api_gateway_base_path
62-
)
63-
http_cycle = HTTPCycle(handler.request)
64-
response = http_cycle(self.app, handler.body)
87+
http_cycle = HTTPCycle(handler.scope, handler.body)
88+
http_response = http_cycle(self.app)
89+
90+
return handler(http_response)
6591

66-
return handler.transform_response(response)
92+
assert False, "unreachable" # pragma: no cover

mangum/handlers/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from mangum.handlers.api_gateway import APIGateway, HTTPGateway
2+
from mangum.handlers.alb import ALB
3+
from mangum.handlers.lambda_at_edge import LambdaAtEdge
4+
5+
6+
__all__ = ["APIGateway", "HTTPGateway", "ALB", "LambdaAtEdge"]

0 commit comments

Comments
 (0)