Skip to content

Commit

Permalink
🎨 Replace abstract handlers, customer handlers, drop 3.6 support, typ…
Browse files Browse the repository at this point in the history
…ing 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.
  • Loading branch information
jordaneremieff authored Feb 26, 2022
1 parent 88ae4d9 commit 7ee6846
Show file tree
Hide file tree
Showing 30 changed files with 1,083 additions and 998 deletions.
48 changes: 24 additions & 24 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.6', '3.7', '3.8', '3.9', '3.10']
python-version: ["3.7", "3.8", "3.9", "3.10"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@v2
name: Configure pip caching
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
pip install -U -r requirements.txt
- name: Run tests
run: |
scripts/test
- name: Run linters
run: |
scripts/lint
- name: Run codecov
run: codecov
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@v2
name: Configure pip caching
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
run: |
pip install -U -r requirements.txt
- name: Run tests
run: |
scripts/test
- name: Run linters
run: |
scripts/lint
- name: Run codecov
run: codecov
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
</a>
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/mangum.svg?style=flat-square">

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.
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.

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

## Features

- 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.
- 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).

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

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

## Requirements

Python 3.6+
Python 3.7+

## Installation

Expand All @@ -53,7 +53,7 @@ async def app(scope, receive, send):
handler = Mangum(app)
```

Or using a framework.
Or using a framework:

```python
from fastapi import FastAPI
Expand Down
2 changes: 1 addition & 1 deletion docs/asgi-frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ None of the framework details are important here. The routing decorator, request

```python
class Application(Protocol):
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
async def __call__(self, scope: Scope, receive: ASGIReceive, send: ASGISend) -> None:
...
```

Expand Down
8 changes: 4 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
</a>
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/mangum.svg?style=flat-square">

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.
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.

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

## Features

- 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.
- 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).

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

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

## Requirements

Python 3.6+
Python 3.7+

## Installation

Expand All @@ -53,7 +53,7 @@ async def app(scope, receive, send):
handler = Mangum(app)
```

Or using a framework.
Or using a framework:

```python
from fastapi import FastAPI
Expand Down
5 changes: 2 additions & 3 deletions mangum/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .types import Request, Response
from .adapter import Mangum # noqa: F401
from mangum.adapter import Mangum

__all__ = ["Mangum", "Request", "Response"]
__all__ = ["Mangum"]
104 changes: 65 additions & 39 deletions mangum/adapter.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,92 @@
from itertools import chain
import logging
from contextlib import ExitStack
from typing import List, Optional, Type
import warnings


from mangum.exceptions import ConfigurationError
from mangum.handlers.abstract_handler import AbstractHandler
from mangum.protocols import HTTPCycle, LifespanCycle
from mangum.types import ASGIApp, LambdaEvent, LambdaContext
from mangum.handlers import ALB, HTTPGateway, APIGateway, LambdaAtEdge
from mangum.exceptions import ConfigurationError
from mangum.types import (
ASGIApp,
LifespanMode,
LambdaConfig,
LambdaEvent,
LambdaContext,
LambdaHandler,
)


DEFAULT_TEXT_MIME_TYPES = [
"text/",
"application/json",
"application/javascript",
"application/xml",
"application/vnd.api+json",
]
logger = logging.getLogger("mangum")


logger = logging.getLogger("mangum")
HANDLERS: List[Type[LambdaHandler]] = [
ALB,
HTTPGateway,
APIGateway,
LambdaAtEdge,
]


class Mangum:
"""
Creates an adapter instance.
* **app** - An asynchronous callable that conforms to version 3.0 of the ASGI
specification. This will usually be an ASGI framework application instance.
* **lifespan** - A string to configure lifespan support. Choices are `auto`, `on`,
and `off`. Default is `auto`.
* **text_mime_types** - A list of MIME types to include with the defaults that
should not return a binary response in API Gateway.
* **api_gateway_base_path** - A string specifying the part of the url path after
which the server routing begins.
"""

def __init__(
self,
app: ASGIApp,
lifespan: str = "auto",
lifespan: LifespanMode = "auto",
api_gateway_base_path: str = "/",
custom_handlers: Optional[List[Type[LambdaHandler]]] = 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.api_gateway_base_path = api_gateway_base_path
self.api_gateway_base_path = api_gateway_base_path or "/"
self.config = LambdaConfig(api_gateway_base_path=self.api_gateway_base_path)

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

def __call__(self, event: LambdaEvent, context: LambdaContext) -> dict:
logger.debug("Event received.")
self.custom_handlers = custom_handlers or []

def infer(self, event: LambdaEvent, context: LambdaContext) -> LambdaHandler:
for handler_cls in chain(
self.custom_handlers,
HANDLERS,
):
handler = handler_cls.infer(
event,
context,
self.config,
)
if handler:
break
else:
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.)"
)

return handler

def __call__(self, event: LambdaEvent, context: LambdaContext) -> dict:
handler = self.infer(event, context)
with ExitStack() as stack:
if self.lifespan != "off":
if self.lifespan in ("auto", "on"):
lifespan_cycle = LifespanCycle(self.app, self.lifespan)
stack.enter_context(lifespan_cycle)

handler = AbstractHandler.from_trigger(
event, context, self.api_gateway_base_path
)
http_cycle = HTTPCycle(handler.request)
response = http_cycle(self.app, handler.body)
http_cycle = HTTPCycle(handler.scope, handler.body)
http_response = http_cycle(self.app)

return handler(http_response)

return handler.transform_response(response)
assert False, "unreachable" # pragma: no cover
6 changes: 6 additions & 0 deletions mangum/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from mangum.handlers.api_gateway import APIGateway, HTTPGateway
from mangum.handlers.alb import ALB
from mangum.handlers.lambda_at_edge import LambdaAtEdge


__all__ = ["APIGateway", "HTTPGateway", "ALB", "LambdaAtEdge"]
Loading

0 comments on commit 7ee6846

Please sign in to comment.