Skip to content

Commit 13a8c60

Browse files
committed
Amazon Lambda using AWS API Gateway proxy support
1 parent 9247f47 commit 13a8c60

File tree

17 files changed

+767
-121
lines changed

17 files changed

+767
-121
lines changed

docs/integrations.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,48 @@ Integrations
33

44
Openapi-core integrates with your popular libraries and frameworks. Each integration offers different levels of integration that help validate and unmarshal your request and response data.
55

6+
AWS Lambda function using Amazon API Gateway Proxy
7+
--------------------------------------------------
8+
9+
This section describes integration with `AWS Lambda <https://docs.aws.amazon.com/lambda/>`__ function using `Amazon API Gateway Proxy <hhttps://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html#apigateway-proxy>`__.
10+
11+
Low level
12+
~~~~~~~~~
13+
14+
You can use ``APIGatewayProxyEventOpenAPIRequest`` as an API Gateway proxy event (REST API) request factory:
15+
16+
.. code-block:: python
17+
18+
from openapi_core import unmarshal_request
19+
from openapi_core.contrib.aws import APIGatewayProxyEventOpenAPIRequest
20+
21+
openapi_request = APIGatewayProxyEventOpenAPIRequest(lambda_event)
22+
result = unmarshal_request(openapi_request, spec=spec)
23+
24+
You can use ``APIGatewayProxyEventOpenAPIResponse`` as an API Gateway proxy event (REST API) response factory:
25+
26+
.. code-block:: python
27+
28+
from openapi_core import unmarshal_response
29+
from openapi_core.contrib.aws import APIGatewayProxyEventOpenAPIResponse
30+
31+
openapi_response = APIGatewayProxyEventOpenAPIResponse(lambda_response)
32+
result = unmarshal_response(openapi_request, openapi_response, spec=spec)
33+
34+
If you use ``x-amazon-apigateway-any-method`` OpenAPI extension with ANY method that catches all HTTP methods, you want to define ``path_finder_cls`` to be ``AmazonAPIGatewayPathFinder``:
35+
36+
.. code-block:: python
37+
38+
from openapi_core.contrib.aws import AmazonAPIGatewayPathFinder
39+
40+
result = unmarshal_response(
41+
openapi_request,
42+
openapi_response,
43+
spec=spec,
44+
path_finder_cls=AmazonAPIGatewayPathFinder,
45+
)
46+
47+
648
Bottle
749
------
850

openapi_core/contrib/aws/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""OpenAPI core contrib aws module"""
2+
from openapi_core.contrib.aws.finders import AmazonAPIGatewayPathFinder
3+
from openapi_core.contrib.aws.requests import (
4+
APIGatewayProxyEventOpenAPIRequest,
5+
)
6+
from openapi_core.contrib.aws.responses import (
7+
APIGatewayProxyEventOpenAPIResponse,
8+
)
9+
10+
__all__ = [
11+
"AmazonAPIGatewayPathFinder",
12+
"APIGatewayProxyEventOpenAPIRequest",
13+
"APIGatewayProxyEventOpenAPIResponse",
14+
]

openapi_core/contrib/aws/datatypes.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from typing import List
2+
from typing import Optional
3+
4+
from pydantic.dataclasses import dataclass
5+
6+
7+
class LambdaConfig:
8+
extra = "allow"
9+
10+
11+
@dataclass(config=LambdaConfig, kw_only=True)
12+
class BaseAPIGatewayProxyEvent:
13+
path: str
14+
httpMethod: str
15+
headers: dict
16+
queryStringParameters: Optional[dict] = None
17+
isBase64Encoded: Optional[bool] = None
18+
body: Optional[str] = None
19+
pathParameters: Optional[dict] = None
20+
stageVariables: Optional[dict] = None
21+
22+
23+
@dataclass(config=LambdaConfig, kw_only=True)
24+
class APIGatewayProxyEvent(BaseAPIGatewayProxyEvent):
25+
"""AWS API Gateway proxy v1 event"""
26+
27+
resource: str
28+
multiValueHeaders: dict
29+
version: Optional[str] = "1.0"
30+
multiValueQueryStringParameters: Optional[dict] = None
31+
32+
33+
@dataclass(config=LambdaConfig, kw_only=True)
34+
class APIGatewayProxyV2Event(BaseAPIGatewayProxyEvent):
35+
"""AWS API Gateway proxy v2 event"""
36+
37+
version: str
38+
routeKey: str
39+
rawPath: dict
40+
rawQueryString: str
41+
cookies: Optional[List[str]] = None
42+
43+
44+
@dataclass(config=LambdaConfig, kw_only=True)
45+
class APIGatewayProxyEventResponse:
46+
"""AWS API Gateway proxy v1 event response"""
47+
48+
isBase64Encoded: bool
49+
statusCode: str
50+
headers: dict
51+
multiValueHeaders: dict
52+
body: str
53+
54+
55+
@dataclass(config=LambdaConfig, kw_only=True)
56+
class APIGatewayProxyV2EventResponse:
57+
"""AWS API Gateway proxy v2 event response"""
58+
59+
isBase64Encoded: bool = False
60+
statusCode: str = 200

openapi_core/contrib/aws/finders.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from openapi_core.templating.paths.finders import APICallPathFinder
2+
from openapi_core.templating.paths.iterators import AnyMethodOperationsIterator
3+
4+
5+
class AmazonAPIGatewayPathFinder(APICallPathFinder):
6+
operations_iterator = AnyMethodOperationsIterator(
7+
any_method="x-amazon-apigateway-any-method",
8+
)

openapi_core/contrib/aws/requests.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from typing import Optional
2+
3+
from werkzeug.datastructures import Headers
4+
from werkzeug.datastructures import ImmutableMultiDict
5+
6+
from openapi_core.contrib.aws.datatypes import APIGatewayProxyEvent
7+
from openapi_core.contrib.aws.typing import LambdaEvent
8+
from openapi_core.datatypes import RequestParameters
9+
10+
11+
class APIGatewayProxyEventOpenAPIRequest:
12+
def __init__(self, lambda_event: LambdaEvent):
13+
self.event = APIGatewayProxyEvent(**lambda_event)
14+
15+
self.parameters = RequestParameters(
16+
query=ImmutableMultiDict(self.event.queryStringParameters),
17+
header=Headers(self.event.headers),
18+
cookie=ImmutableMultiDict(),
19+
)
20+
21+
@property
22+
def host_url(self) -> str:
23+
proto = self.event.headers["X-Forwarded-Proto"]
24+
host = self.event.headers["Host"]
25+
return "://".join([proto, host])
26+
27+
@property
28+
def path(self) -> str:
29+
return self.event.resource
30+
31+
@property
32+
def method(self) -> str:
33+
return self.event.httpMethod.lower()
34+
35+
@property
36+
def body(self) -> Optional[str]:
37+
return self.event.body
38+
39+
@property
40+
def mimetype(self) -> str:
41+
return self.event.headers.get("Content-Type", "")

openapi_core/contrib/aws/responses.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from werkzeug.datastructures import Headers
2+
3+
from openapi_core.contrib.aws.datatypes import APIGatewayProxyEventResponse
4+
from openapi_core.contrib.aws.typing import LambdaResponse
5+
6+
7+
class APIGatewayProxyEventOpenAPIResponse:
8+
"""Lambda response
9+
10+
Documentation: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html
11+
"""
12+
13+
def __init__(self, lambda_response: LambdaResponse):
14+
self.response = APIGatewayProxyEventResponse(**lambda_response)
15+
16+
@property
17+
def data(self) -> str:
18+
return self.response.body
19+
20+
@property
21+
def status_code(self) -> int:
22+
return self.response.statusCode
23+
24+
@property
25+
def headers(self) -> Headers:
26+
return self.response.headers
27+
28+
@property
29+
def mimetype(self) -> str:
30+
content_type = self.response.headers.get("Content-Type", "")
31+
assert isinstance(content_type, str)
32+
return content_type

openapi_core/contrib/aws/typing.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from typing import Any
2+
from typing import Mapping
3+
4+
LambdaEvent = Mapping[str, Any]
5+
LambdaResponse = Mapping[str, Any]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from openapi_core.templating.paths.finders import APICallPathFinder
2+
from openapi_core.templating.paths.finders import WebhookPathFinder
3+
4+
__all__ = [
5+
"APICallPathFinder",
6+
"WebhookPathFinder",
7+
]

0 commit comments

Comments
 (0)