Skip to content

Commit dcd14cb

Browse files
committed
AWS API Gateway with Amazon Lambda integrations support
1 parent f6dd268 commit dcd14cb

19 files changed

+1042
-167
lines changed

Diff for: docs/integrations.rst

+53
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,59 @@ 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+
Amazon API Gateway
7+
------------------
8+
9+
This section describes integration with `Amazon API Gateway <https://aws.amazon.com/api-gateway/>`__.
10+
11+
It is useful for:
12+
* `AWS Lambda integrations <https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html>`__ where Lambda functions handle events from API Gateway (Amazon API Gateway event format version 1.0 and 2.0).
13+
* `AWS Lambda function URLs <https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html>`__ where Lambda functions handle events from dedicated HTTP(S) endpoint (Amazon API Gateway event format version 2.0).
14+
15+
Low level
16+
~~~~~~~~~
17+
18+
You can use ``APIGatewayEventV2OpenAPIRequest`` as an API Gateway event (format version 2.0) request factory:
19+
20+
.. code-block:: python
21+
22+
from openapi_core import unmarshal_request
23+
from openapi_core.contrib.aws import APIGatewayEventV2OpenAPIRequest
24+
25+
openapi_request = APIGatewayEventV2OpenAPIRequest(event)
26+
result = unmarshal_request(openapi_request, spec=spec)
27+
28+
If you use format version 1.0, then import and use ``APIGatewayEventOpenAPIRequest`` as an API Gateway event (format version 1.0) request factory.
29+
30+
You can use ``APIGatewayEventV2ResponseOpenAPIResponse`` as an API Gateway event (format version 2.0) response factory:
31+
32+
.. code-block:: python
33+
34+
from openapi_core import unmarshal_response
35+
from openapi_core.contrib.aws import APIGatewayEventV2ResponseOpenAPIResponse
36+
37+
openapi_response = APIGatewayEventV2ResponseOpenAPIResponse(response)
38+
result = unmarshal_response(openapi_request, openapi_response, spec=spec)
39+
40+
If you use format version 1.0, then import and use ``APIGatewayEventResponseOpenAPIResponse`` as an API Gateway event (format version 1.0) response factory.
41+
42+
ANY method
43+
~~~~~~~~~~
44+
45+
API Gateway have special ``ANY`` method that catches all HTTP methods. It's specified as `x-amazon-apigateway-any-method <https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-any-method.html>`__ OpenAPI extension. If you use the extension, you want to define ``path_finder_cls`` to be ``APIGatewayPathFinder``:
46+
47+
.. code-block:: python
48+
49+
from openapi_core.contrib.aws import APIGatewayPathFinder
50+
51+
result = unmarshal_response(
52+
openapi_request,
53+
openapi_response,
54+
spec=spec,
55+
path_finder_cls=APIGatewayPathFinder,
56+
)
57+
58+
659
Bottle
760
------
861

Diff for: openapi_core/contrib/aws/__init__.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""OpenAPI core contrib aws module"""
2+
from openapi_core.contrib.aws.decorators import (
3+
APIGatewayEventV2OpenAPIHandleDecorator,
4+
)
5+
from openapi_core.contrib.aws.finders import APIGatewayPathFinder
6+
from openapi_core.contrib.aws.requests import APIGatewayEventOpenAPIRequest
7+
from openapi_core.contrib.aws.requests import APIGatewayEventV2OpenAPIRequest
8+
from openapi_core.contrib.aws.responses import (
9+
APIGatewayEventResponseOpenAPIResponse,
10+
)
11+
from openapi_core.contrib.aws.responses import (
12+
APIGatewayEventV2ResponseOpenAPIResponse,
13+
)
14+
15+
__all__ = [
16+
"APIGatewayEventOpenAPIRequest",
17+
"APIGatewayEventResponseOpenAPIResponse",
18+
"APIGatewayEventV2OpenAPIHandleDecorator",
19+
"APIGatewayEventV2OpenAPIRequest",
20+
"APIGatewayEventV2ResponseOpenAPIResponse",
21+
"APIGatewayPathFinder",
22+
]

Diff for: openapi_core/contrib/aws/datatypes.py

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from typing import Dict
2+
from typing import List
3+
from typing import Optional
4+
5+
from pydantic import Field
6+
from pydantic.dataclasses import dataclass
7+
8+
9+
class APIGatewayEventConfig:
10+
extra = "allow"
11+
12+
13+
@dataclass(config=APIGatewayEventConfig, frozen=True)
14+
class APIGatewayEvent:
15+
"""AWS API Gateway event"""
16+
17+
headers: Dict[str, str]
18+
19+
path: str
20+
httpMethod: str
21+
resource: str
22+
23+
queryStringParameters: Optional[Dict[str, str]] = None
24+
isBase64Encoded: Optional[bool] = None
25+
body: Optional[str] = None
26+
pathParameters: Optional[Dict[str, str]] = None
27+
stageVariables: Optional[Dict[str, str]] = None
28+
29+
multiValueHeaders: Optional[Dict[str, List[str]]] = None
30+
version: Optional[str] = "1.0"
31+
multiValueQueryStringParameters: Optional[Dict[str, List[str]]] = None
32+
33+
34+
@dataclass(config=APIGatewayEventConfig, frozen=True)
35+
class APIGatewayEventV2:
36+
"""AWS API Gateway event v2"""
37+
38+
headers: Dict[str, str]
39+
40+
version: str
41+
routeKey: str
42+
rawPath: str
43+
rawQueryString: str
44+
45+
queryStringParameters: Optional[Dict[str, str]] = None
46+
isBase64Encoded: Optional[bool] = None
47+
body: Optional[str] = None
48+
pathParameters: Optional[Dict[str, str]] = None
49+
stageVariables: Optional[Dict[str, str]] = None
50+
51+
cookies: Optional[List[str]] = None
52+
53+
54+
@dataclass(config=APIGatewayEventConfig, frozen=True)
55+
class APIGatewayEventResponse:
56+
"""AWS API Gateway event response"""
57+
58+
body: str
59+
isBase64Encoded: bool
60+
statusCode: int
61+
headers: Dict[str, str]
62+
multiValueHeaders: Dict[str, List[str]]
63+
64+
65+
@dataclass(config=APIGatewayEventConfig, frozen=True)
66+
class APIGatewayEventV2Response:
67+
"""AWS API Gateway event v2 response"""
68+
69+
body: str
70+
isBase64Encoded: bool = False
71+
statusCode: int = 200
72+
headers: Dict[str, str] = Field(
73+
default_factory=lambda: {"content-type": "application/json"}
74+
)

Diff for: openapi_core/contrib/aws/finders.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from openapi_core.templating.paths.finders import APICallPathFinder
2+
from openapi_core.templating.paths.iterators import (
3+
CatchAllMethodOperationsIterator,
4+
)
5+
6+
7+
class APIGatewayPathFinder(APICallPathFinder):
8+
operations_iterator = CatchAllMethodOperationsIterator(
9+
"any",
10+
"x-amazon-apigateway-any-method",
11+
)
12+
13+
14+
class APIGatewayIntegrationPathFinder(APICallPathFinder):
15+
operations_iterator = CatchAllMethodOperationsIterator(
16+
"any",
17+
"x-amazon-apigateway-any-method",
18+
)

Diff for: openapi_core/contrib/aws/requests.py

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
from typing import Dict
2+
from typing import Optional
3+
4+
from werkzeug.datastructures import Headers
5+
from werkzeug.datastructures import ImmutableMultiDict
6+
7+
from openapi_core.contrib.aws.datatypes import APIGatewayEvent
8+
from openapi_core.contrib.aws.datatypes import APIGatewayEventV2
9+
from openapi_core.contrib.aws.types import APIGatewayEventPayload
10+
from openapi_core.datatypes import RequestParameters
11+
12+
13+
class APIGatewayEventOpenAPIRequest:
14+
"""
15+
Converts an API Gateway event payload to an OpenAPI request.
16+
17+
Designed to be used with API Gateway REST API specification exports for
18+
integrations that use event v1 payload. Uses API Gateway event v1 httpMethod
19+
and path data. Requires APIGatewayPathFinder to resolve ANY methods.
20+
"""
21+
22+
def __init__(self, payload: APIGatewayEventPayload):
23+
self.event = APIGatewayEvent(**payload)
24+
25+
self.parameters = RequestParameters(
26+
path=self.path_params,
27+
query=ImmutableMultiDict(self.query_params),
28+
header=Headers(self.event.headers),
29+
cookie=ImmutableMultiDict(),
30+
)
31+
32+
@property
33+
def path_params(self) -> Dict[str, str]:
34+
params = self.event.pathParameters
35+
if params is None:
36+
return {}
37+
return params
38+
39+
@property
40+
def query_params(self) -> Dict[str, str]:
41+
params = self.event.queryStringParameters
42+
if params is None:
43+
return {}
44+
return params
45+
46+
@property
47+
def proto(self) -> str:
48+
return self.event.headers.get("X-Forwarded-Proto", "https")
49+
50+
@property
51+
def host(self) -> str:
52+
return self.event.headers["Host"]
53+
54+
@property
55+
def host_url(self) -> str:
56+
return "://".join([self.proto, self.host])
57+
58+
@property
59+
def path(self) -> str:
60+
return self.event.path
61+
62+
@property
63+
def method(self) -> str:
64+
return self.event.httpMethod.lower()
65+
66+
@property
67+
def body(self) -> Optional[str]:
68+
return self.event.body
69+
70+
@property
71+
def mimetype(self) -> str:
72+
return self.event.headers.get("Content-Type", "")
73+
74+
75+
class APIGatewayEventV2OpenAPIRequest:
76+
"""
77+
Converts an API Gateway event v2 payload to an OpenAPI request.
78+
79+
Designed to be used with API Gateway HTTP API specification exports for
80+
integrations that use event v2 payload. Uses API Gateway event v2 routeKey
81+
and rawPath data. Requires APIGatewayPathFinder to resolve ANY methods.
82+
83+
.. note::
84+
API Gateway HTTP APIs don't support request validation
85+
"""
86+
87+
def __init__(self, payload: APIGatewayEventPayload):
88+
self.event = APIGatewayEventV2(**payload)
89+
90+
self.parameters = RequestParameters(
91+
path=self.path_params,
92+
query=ImmutableMultiDict(self.query_params),
93+
header=Headers(self.event.headers),
94+
cookie=ImmutableMultiDict(),
95+
)
96+
97+
@property
98+
def path_params(self) -> Dict[str, str]:
99+
if self.event.pathParameters is None:
100+
return {}
101+
return self.event.pathParameters
102+
103+
@property
104+
def query_params(self) -> Dict[str, str]:
105+
if self.event.queryStringParameters is None:
106+
return {}
107+
return self.event.queryStringParameters
108+
109+
@property
110+
def proto(self) -> str:
111+
return self.event.headers.get("x-forwarded-proto", "https")
112+
113+
@property
114+
def host(self) -> str:
115+
return self.event.headers["host"]
116+
117+
@property
118+
def host_url(self) -> str:
119+
return "://".join([self.proto, self.host])
120+
121+
@property
122+
def path(self) -> str:
123+
return self.event.rawPath
124+
125+
@property
126+
def method(self) -> str:
127+
return self.event.routeKey.split(" ")[0].lower()
128+
129+
@property
130+
def body(self) -> Optional[str]:
131+
return self.event.body
132+
133+
@property
134+
def mimetype(self) -> str:
135+
return self.event.headers.get("content-type", "")
136+
137+
138+
class APIGatewayEventV2HTTPOpenAPIRequest(APIGatewayEventV2OpenAPIRequest):
139+
"""
140+
Converts an API Gateway event v2 payload to an OpenAPI request.
141+
142+
Uses http integration path and method data.
143+
"""
144+
145+
@property
146+
def path(self) -> str:
147+
return self.event.http.path
148+
149+
@property
150+
def method(self) -> str:
151+
return self.event.http.method.lower()

0 commit comments

Comments
 (0)