Skip to content

Commit 8436ce6

Browse files
georgebvkhamaileon
authored andcommitted
✨ Added text_mime_types argument (Kludex#277)
* added vscode files to gitignore * added text_mime_types argument * updated text mime type definition and propagation * copy default text mime types inside mangum * api gateway test * moved instance attributes to config * added tests for custom text mime types
1 parent 4f92b06 commit 8436ce6

File tree

13 files changed

+260
-32
lines changed

13 files changed

+260
-32
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,7 @@ venv.bak/
105105

106106
# IDE Settings
107107
.idea/
108+
.vscode
109+
.devcontainer
108110

109111
.DS_Store

docs/adapter.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ handler = Mangum(
77
app,
88
lifespan="auto",
99
api_gateway_base_path=None,
10+
custom_handlers=None,
11+
text_mime_types=None,
1012
)
1113
```
1214

mangum/adapter.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@
2626
LambdaAtEdge,
2727
]
2828

29+
DEFAULT_TEXT_MIME_TYPES: List[str] = [
30+
"text/",
31+
"application/json",
32+
"application/javascript",
33+
"application/xml",
34+
"application/vnd.api+json",
35+
"application/vnd.oai.openapi",
36+
]
37+
2938

3039
class Mangum:
3140
def __init__(
@@ -34,6 +43,7 @@ def __init__(
3443
lifespan: LifespanMode = "auto",
3544
api_gateway_base_path: str = "/",
3645
custom_handlers: Optional[List[Type[LambdaHandler]]] = None,
46+
text_mime_types: Optional[List[str]] = None,
3747
) -> None:
3848
if lifespan not in ("auto", "on", "off"):
3949
raise ConfigurationError(
@@ -42,24 +52,22 @@ def __init__(
4252

4353
self.app = app
4454
self.lifespan = lifespan
45-
self.api_gateway_base_path = api_gateway_base_path or "/"
46-
self.config = LambdaConfig(api_gateway_base_path=self.api_gateway_base_path)
4755
self.custom_handlers = custom_handlers or []
56+
self.config = LambdaConfig(
57+
api_gateway_base_path=api_gateway_base_path or "/",
58+
text_mime_types=text_mime_types or [*DEFAULT_TEXT_MIME_TYPES],
59+
)
4860

4961
def infer(self, event: LambdaEvent, context: LambdaContext) -> LambdaHandler:
5062
for handler_cls in chain(self.custom_handlers, HANDLERS):
5163
if handler_cls.infer(event, context, self.config):
52-
handler = handler_cls(event, context, self.config)
53-
break
54-
else:
55-
raise RuntimeError( # pragma: no cover
56-
"The adapter was unable to infer a handler to use for the event. This "
57-
"is likely related to how the Lambda function was invoked. (Are you "
58-
"testing locally? Make sure the request payload is valid for a "
59-
"supported handler.)"
60-
)
61-
62-
return handler
64+
return handler_cls(event, context, self.config)
65+
raise RuntimeError( # pragma: no cover
66+
"The adapter was unable to infer a handler to use for the event. This "
67+
"is likely related to how the Lambda function was invoked. (Are you "
68+
"testing locally? Make sure the request payload is valid for a "
69+
"supported handler.)"
70+
)
6371

6472
def __call__(self, event: LambdaEvent, context: LambdaContext) -> dict:
6573
handler = self.infer(event, context)

mangum/handlers/alb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def __call__(self, response: Response) -> dict:
153153

154154
finalized_headers = case_mutated_headers(multi_value_headers)
155155
finalized_body, is_base64_encoded = handle_base64_response_body(
156-
response["body"], finalized_headers
156+
response["body"], finalized_headers, self.config["text_mime_types"]
157157
)
158158

159159
out = {

mangum/handlers/api_gateway.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def __call__(self, response: Response) -> dict:
115115
response["headers"]
116116
)
117117
finalized_body, is_base64_encoded = handle_base64_response_body(
118-
response["body"], finalized_headers
118+
response["body"], finalized_headers, self.config["text_mime_types"]
119119
)
120120

121121
return {
@@ -204,7 +204,7 @@ def __call__(self, response: Response) -> dict:
204204
finalized_headers["content-type"] = "application/json"
205205

206206
finalized_body, is_base64_encoded = handle_base64_response_body(
207-
response["body"], finalized_headers
207+
response["body"], finalized_headers, self.config["text_mime_types"]
208208
)
209209
response_out = {
210210
"statusCode": response["status"],
@@ -221,7 +221,7 @@ def __call__(self, response: Response) -> dict:
221221
response["headers"]
222222
)
223223
finalized_body, is_base64_encoded = handle_base64_response_body(
224-
response["body"], finalized_headers
224+
response["body"], finalized_headers, self.config["text_mime_types"]
225225
)
226226
return {
227227
"statusCode": response["status"],

mangum/handlers/lambda_at_edge.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def scope(self) -> Scope:
7979
def __call__(self, response: Response) -> dict:
8080
multi_value_headers, _ = handle_multi_value_headers(response["headers"])
8181
response_body, is_base64_encoded = handle_base64_response_body(
82-
response["body"], multi_value_headers
82+
response["body"], multi_value_headers, self.config["text_mime_types"]
8383
)
8484
finalized_headers: Dict[str, List[Dict[str, str]]] = {
8585
key.decode().lower(): [{"key": key.decode().lower(), "value": val.decode()}]

mangum/handlers/utils.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,6 @@
55
from mangum.types import Headers
66

77

8-
DEFAULT_TEXT_MIME_TYPES = [
9-
"text/",
10-
"application/json",
11-
"application/javascript",
12-
"application/xml",
13-
"application/vnd.api+json",
14-
"application/vnd.oai.openapi",
15-
]
16-
17-
188
def maybe_encode_body(body: Union[str, bytes], *, is_base64: bool) -> bytes:
199
body = body or b""
2010
if is_base64:
@@ -71,12 +61,14 @@ def handle_multi_value_headers(
7161

7262

7363
def handle_base64_response_body(
74-
body: bytes, headers: Dict[str, str]
64+
body: bytes,
65+
headers: Dict[str, str],
66+
text_mime_types: List[str],
7567
) -> Tuple[str, bool]:
7668
is_base64_encoded = False
7769
output_body = ""
7870
if body != b"":
79-
for text_mime_type in DEFAULT_TEXT_MIME_TYPES:
71+
for text_mime_type in text_mime_types:
8072
if text_mime_type in headers.get("content-type", ""):
8173
try:
8274
output_body = body.decode()

mangum/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ class Response(TypedDict):
116116

117117
class LambdaConfig(TypedDict):
118118
api_gateway_base_path: str
119+
text_mime_types: List[str]
119120

120121

121122
class LambdaHandler(Protocol):

tests/handlers/test_alb.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,3 +331,44 @@ async def app(scope, receive, send):
331331
"headers": {"content-type": content_type.decode()},
332332
"body": res_body,
333333
}
334+
335+
336+
def test_aws_alb_response_extra_mime_types():
337+
content_type = b"application/x-yaml"
338+
utf_res_body = "name: 'John Doe'"
339+
raw_res_body = utf_res_body.encode()
340+
b64_res_body = "bmFtZTogJ0pvaG4gRG9lJw=="
341+
342+
async def app(scope, receive, send):
343+
await send(
344+
{
345+
"type": "http.response.start",
346+
"status": 200,
347+
"headers": [[b"content-type", content_type]],
348+
}
349+
)
350+
await send({"type": "http.response.body", "body": raw_res_body})
351+
352+
event = get_mock_aws_alb_event("GET", "/test", {}, None, None, False, False)
353+
354+
# Test default behavior
355+
handler = Mangum(app, lifespan="off")
356+
response = handler(event, {})
357+
assert content_type.decode() not in handler.config["text_mime_types"]
358+
assert response == {
359+
"statusCode": 200,
360+
"isBase64Encoded": True,
361+
"headers": {"content-type": content_type.decode()},
362+
"body": b64_res_body,
363+
}
364+
365+
# Test with modified text mime types
366+
handler = Mangum(app, lifespan="off")
367+
handler.config["text_mime_types"].append(content_type.decode())
368+
response = handler(event, {})
369+
assert response == {
370+
"statusCode": 200,
371+
"isBase64Encoded": False,
372+
"headers": {"content-type": content_type.decode()},
373+
"body": utf_res_body,
374+
}

tests/handlers/test_api_gateway.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,3 +358,46 @@ async def app(scope, receive, send):
358358
"multiValueHeaders": {},
359359
"body": res_body,
360360
}
361+
362+
363+
def test_aws_api_gateway_response_extra_mime_types():
364+
content_type = b"application/x-yaml"
365+
utf_res_body = "name: 'John Doe'"
366+
raw_res_body = utf_res_body.encode()
367+
b64_res_body = "bmFtZTogJ0pvaG4gRG9lJw=="
368+
369+
async def app(scope, receive, send):
370+
await send(
371+
{
372+
"type": "http.response.start",
373+
"status": 200,
374+
"headers": [[b"content-type", content_type]],
375+
}
376+
)
377+
await send({"type": "http.response.body", "body": raw_res_body})
378+
379+
event = get_mock_aws_api_gateway_event("POST", "/test", {}, None, False)
380+
381+
# Test default behavior
382+
handler = Mangum(app, lifespan="off")
383+
response = handler(event, {})
384+
assert content_type.decode() not in handler.config["text_mime_types"]
385+
assert response == {
386+
"statusCode": 200,
387+
"isBase64Encoded": True,
388+
"headers": {"content-type": content_type.decode()},
389+
"multiValueHeaders": {},
390+
"body": b64_res_body,
391+
}
392+
393+
# Test with modified text mime types
394+
handler = Mangum(app, lifespan="off")
395+
handler.config["text_mime_types"].append(content_type.decode())
396+
response = handler(event, {})
397+
assert response == {
398+
"statusCode": 200,
399+
"isBase64Encoded": False,
400+
"headers": {"content-type": content_type.decode()},
401+
"multiValueHeaders": {},
402+
"body": utf_res_body,
403+
}

0 commit comments

Comments
 (0)