fastapi-aws 0.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_aws/__init__.py +7 -0
- fastapi_aws/__main__.py +342 -0
- fastapi_aws/_version.py +21 -0
- fastapi_aws/authorizers.py +182 -0
- fastapi_aws/integrations.py +306 -0
- fastapi_aws/models.py +57 -0
- fastapi_aws/route.py +191 -0
- fastapi_aws/router.py +45 -0
- fastapi_aws-0.0.7.dist-info/METADATA +332 -0
- fastapi_aws-0.0.7.dist-info/RECORD +12 -0
- fastapi_aws-0.0.7.dist-info/WHEEL +5 -0
- fastapi_aws-0.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-aws
|
|
3
|
+
Version: 0.0.7
|
|
4
|
+
Summary: AWS Integrations for FastAPI exported OpenAPI specifications
|
|
5
|
+
Home-page: https://github.com/bayinfosys/fastapi-aws
|
|
6
|
+
Author: Edward Grundy
|
|
7
|
+
Author-email: ed@bayis.co.uk
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: fastapi
|
|
14
|
+
Requires-Dist: httpx
|
|
15
|
+
Requires-Dist: uvicorn[standard]
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: requires-dist
|
|
23
|
+
Dynamic: requires-python
|
|
24
|
+
Dynamic: summary
|
|
25
|
+
|
|
26
|
+
# FastAPI AWS ApiGateway Integration
|
|
27
|
+
|
|
28
|
+
This extension enables routes to be defined in FastAPI with extra decorator parameters for AWS integrations.
|
|
29
|
+
|
|
30
|
+
The exported `private_openapi.json` will then contain integrations for:
|
|
31
|
+
+ `AWS Lambda function`
|
|
32
|
+
+ `AWS Step function`
|
|
33
|
+
+ `AWS S3 bucket`
|
|
34
|
+
|
|
35
|
+
Secured via:
|
|
36
|
+
+ `AWS Cognito User Pools`
|
|
37
|
+
+ `AWS Lambda Authorizer`
|
|
38
|
+
|
|
39
|
+
This specification can be uploaded to AWS APIGateway as a definition for a REST API (see below for automatation with terraform). CORS definitions for routes are also auto-generated.
|
|
40
|
+
|
|
41
|
+
This allows your AWS APIGateway REST API to be defined in python, reference pydantic models, integrated into your application code, etc.
|
|
42
|
+
|
|
43
|
+
A `public_openapi.json` is also produced which can be presented to swagger and other tools for use in documentation.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Endpoints
|
|
49
|
+
|
|
50
|
+
Import the `AWSAPIRouter` which uses the default `AWSAPIRoute` to define routes.
|
|
51
|
+
Simply use one of the following integration keywords to define the `openapi_extra` contents:
|
|
52
|
+
+ `aws_lambda_uri` trigger a lambda function
|
|
53
|
+
+ `aws_sfn_sync_arn` invoke a step-function synchronously
|
|
54
|
+
+ `aws_s3_bucket` s3 object access from apigw
|
|
55
|
+
+ `mock_response` fixed apigw responses (TBD)
|
|
56
|
+
+ `dynamodb` dynamodb queries via apigw endpoints (TBD)
|
|
57
|
+
|
|
58
|
+
The keyword-argument is used to define the format of the `x-amazon-apigateway-integration` added to the `openapi_extra` parameter.
|
|
59
|
+
|
|
60
|
+
**NOTE**: the default `FastAPI.router` should be replaced with a new `AWSAPIRouter` **before** adding any routes. If routes are added to the router, and the `app.include_router(aws_router)` is called, the `app.include_router` method will fail to pass the aws kwargs and cause the `AWSAPIRoute` to throw a `ValueError`. This is due to FastAPI sanitizing the kwargs to routes.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from fastapi import FastAPI, Depends
|
|
65
|
+
from pydantic import BaseModel
|
|
66
|
+
from fastapi.security import HTTPBearer
|
|
67
|
+
|
|
68
|
+
from fastapi_aws import AWSAPIRouter
|
|
69
|
+
|
|
70
|
+
# Instantiate the custom router
|
|
71
|
+
router = AWSAPIRouter()
|
|
72
|
+
|
|
73
|
+
# Create the FastAPI app and include the router
|
|
74
|
+
# NB: the router must be overridden **before** creating any routes
|
|
75
|
+
app = FastAPI()
|
|
76
|
+
app.router = router
|
|
77
|
+
# app.include_router(router) will cause the @router.get decorator to fail to add routes correctly
|
|
78
|
+
|
|
79
|
+
# Use the custom router to define routes so we can use the .get, .post, etc methods
|
|
80
|
+
@router.get(
|
|
81
|
+
"/user/{name}",
|
|
82
|
+
aws_lambda_uri="${user_function_arn}",
|
|
83
|
+
description="Get a user profile information",
|
|
84
|
+
summary="Username",
|
|
85
|
+
tags=["user"],
|
|
86
|
+
)
|
|
87
|
+
async def get_user_profile_information(name: str):
|
|
88
|
+
return {"status": "ok", "name": "hello, world"}
|
|
89
|
+
|
|
90
|
+
@router.post(
|
|
91
|
+
"/user/{name}",
|
|
92
|
+
aws_lambda_uri="${user_function_arn}",
|
|
93
|
+
description="Set a user profile name",
|
|
94
|
+
summary="Username",
|
|
95
|
+
tags=["user"],
|
|
96
|
+
)
|
|
97
|
+
async def set_user_profile(name: str):
|
|
98
|
+
return {"status": "ok"}
|
|
99
|
+
|
|
100
|
+
@router.post(
|
|
101
|
+
"/user-step-function",
|
|
102
|
+
aws_sfn_sync_arn="${step_function_arn}",
|
|
103
|
+
description="trigger a sync step function from this endpoint",
|
|
104
|
+
summary="operation",
|
|
105
|
+
tags=["user", "operation"]
|
|
106
|
+
)
|
|
107
|
+
async def user_post_data(user_info: UserInfoModel):
|
|
108
|
+
return "goodbye"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Authorization
|
|
112
|
+
|
|
113
|
+
`fastapi_aws.authorizers` exports a number of authorizers which can be added to endpoints for security.
|
|
114
|
+
|
|
115
|
+
These classes will also export the correct AWS integrations to build and associate authorizers with API Gateway endpoints.
|
|
116
|
+
|
|
117
|
+
+ `CognitoAuthorizer` enables AWS Cognito user pool authorization,
|
|
118
|
+
+ `LambdaAuthorizer` enables custom AWS Lambda function authorizers.
|
|
119
|
+
|
|
120
|
+
Both classes export the authorizers in the openapi spec, so an authorizer should **NOT** be provisioned in infrastructure.
|
|
121
|
+
|
|
122
|
+
To add custom header fields for the authorizers use the `header_names` parameter.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from fastapi import Security
|
|
126
|
+
from fastapi_aws import AWSAPIRouter, LambdaAuthorizer
|
|
127
|
+
|
|
128
|
+
bearer_auth = LambdaAuthorizer(
|
|
129
|
+
authorizer_name="${bearer_authorizer_name}",
|
|
130
|
+
aws_lambda_uri="${lambda_authorizer_uri}",
|
|
131
|
+
aws_iam_role_arn="${lambda_authorizer_iam_role_arn}",
|
|
132
|
+
header_names=["Authorization", "x-api-key"]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
router = AWSAPIRouter()
|
|
136
|
+
|
|
137
|
+
@router.get(
|
|
138
|
+
"/user/{name}",
|
|
139
|
+
aws_lambda_uri="${user_function_arn}",
|
|
140
|
+
description="Get a user profile information",
|
|
141
|
+
summary="Username",
|
|
142
|
+
tags=["user"],
|
|
143
|
+
)
|
|
144
|
+
async def get_user_profile_information(name: str, username=Security(bearer_auth)):
|
|
145
|
+
return {"status": "ok", "name": "hello, world"}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
In the above example, the `/user/{name}` endpoint will only succeed if the correct credentials are provided in either the `Authorization` or `x-api-key` header fields.
|
|
149
|
+
|
|
150
|
+
**NB**: the assignment `user=Security...` is required to ensure export to OpenAPI.
|
|
151
|
+
|
|
152
|
+
## Export OpenAPI specification
|
|
153
|
+
|
|
154
|
+
The module will create an OpenAPI spec from a routes file, and output two specifications:
|
|
155
|
+
1. A private OpenAPI spec with the (optional) CORS definitions and `x-amazon-apigateway-integration` definitions for consumption by AWS ApiGateway REST API, and
|
|
156
|
+
2. A public OpenAPI spec which can be presented for public consumption via swagger documentation etc.
|
|
157
|
+
|
|
158
|
+
NB: the python code for these endpoints will not be executed in anyway, they are purely descriptive.
|
|
159
|
+
|
|
160
|
+
Given a `my-routes.py`:
|
|
161
|
+
```python
|
|
162
|
+
from pydantic import BaseModel
|
|
163
|
+
from fastapi import Security
|
|
164
|
+
|
|
165
|
+
from fastapi_aws import AWSAPIRouter, LambdaAuthorizer
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class UserRequest(BaseModel):
|
|
169
|
+
username: str
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class UserResponse(BaseModel):
|
|
173
|
+
username: str
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class UserPublicResponse(BaseModel):
|
|
177
|
+
username: str
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
lambda_auth = LambdaAuthorizer(
|
|
181
|
+
authorizer_name="${lambda_authorizer_name}",
|
|
182
|
+
aws_lambda_uri="${lambda_authorizer_uri}",
|
|
183
|
+
aws_iam_role_arn="${lambda_authorizer_iam_role_arn}",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
router = AWSAPIRouter()
|
|
187
|
+
|
|
188
|
+
@router.post(
|
|
189
|
+
"/user",
|
|
190
|
+
description="post some user information",
|
|
191
|
+
response_model=UserResponse,
|
|
192
|
+
aws_lambda_uri="${polling_start_lambda_arn}",
|
|
193
|
+
aws_iam_arn="${polling_start_lambda_iam_role_arn}",
|
|
194
|
+
tags=["users"],
|
|
195
|
+
)
|
|
196
|
+
async def user(body: UserRequest, user=Security(lambda_auth)):
|
|
197
|
+
return UserResponse()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@router.get(
|
|
201
|
+
"/public/user",
|
|
202
|
+
description="retrieve public user information",
|
|
203
|
+
response_model=UserPublicResponse,
|
|
204
|
+
aws_lambda_uri="${user_public_data_lambda_arn}",
|
|
205
|
+
aws_iam_arn="${user_public_data_lambda_iam_role_arn}",
|
|
206
|
+
tags=["users"],
|
|
207
|
+
)
|
|
208
|
+
async def fetch_user_info():
|
|
209
|
+
return UserPublicResponse()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
These specifications are created from the module via:
|
|
214
|
+
```bash
|
|
215
|
+
fastapi_aws \
|
|
216
|
+
--title my-api \
|
|
217
|
+
--router my-routes.py \
|
|
218
|
+
--out-public ./api_public.json \
|
|
219
|
+
--out-private ./api_private.json \
|
|
220
|
+
--version 0.0.1
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
or via the docker image with:
|
|
224
|
+
```bash
|
|
225
|
+
docker run -it --rm \
|
|
226
|
+
-v $(shell pwd)/my-api:/app/routes:ro \
|
|
227
|
+
-v $(shell pwd)/terraform/rest-definition:/out \
|
|
228
|
+
fastapi_aws \
|
|
229
|
+
--title my-api \
|
|
230
|
+
--router routes.routes:router \
|
|
231
|
+
--out-public /out/api_public_definition.json \
|
|
232
|
+
--out-private /out/api_private_definition.json \
|
|
233
|
+
--version 0.0.1
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
## Terraform integration
|
|
238
|
+
|
|
239
|
+
The `openapi.json` can be used to define a `REST API` in `AWS ApiGateway`.
|
|
240
|
+
|
|
241
|
+
By setting the `aws_integration_uri` to a placeholder string compatible with terraform templates,
|
|
242
|
+
the exported `openapi.json` file can be loaded with `templatefile`.
|
|
243
|
+
The template can then substitute the placeholder string with the actual lambda function arns from the terraform resources.
|
|
244
|
+
|
|
245
|
+
```terraform
|
|
246
|
+
# Define a lambda function which we want to invoke from the API endpoint
|
|
247
|
+
resource "aws_lambda_function" "user_function" {
|
|
248
|
+
# ... Lambda configuration ...
|
|
249
|
+
function_name = "user-function"
|
|
250
|
+
# Other configurations
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# Create an IAM role for API Gateway to invoke Lambda functions
|
|
254
|
+
resource "aws_iam_role" "api_gateway_role" {
|
|
255
|
+
name = "api-gateway-lambda-invoke-role"
|
|
256
|
+
|
|
257
|
+
assume_role_policy = jsonencode({
|
|
258
|
+
"Version": "2012-10-17",
|
|
259
|
+
"Statement": [{
|
|
260
|
+
"Action": "sts:AssumeRole",
|
|
261
|
+
"Effect": "Allow",
|
|
262
|
+
"Principal": {
|
|
263
|
+
"Service": "apigateway.amazonaws.com"
|
|
264
|
+
}
|
|
265
|
+
}]
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Attach policy to allow invocation of Lambda functions
|
|
270
|
+
resource "aws_iam_role_policy" "api_gateway_policy" {
|
|
271
|
+
name = "api-gateway-lambda-invoke-policy"
|
|
272
|
+
role = aws_iam_role.api_gateway_role.id
|
|
273
|
+
|
|
274
|
+
policy = jsonencode({
|
|
275
|
+
"Version": "2012-10-17",
|
|
276
|
+
"Statement": [{
|
|
277
|
+
"Action": "lambda:InvokeFunction",
|
|
278
|
+
"Effect": "Allow",
|
|
279
|
+
"Resource": aws_lambda_function.user_function.arn
|
|
280
|
+
}]
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# Load the OpenAPI specification and replace placeholders
|
|
285
|
+
data "template_file" "openapi_spec" {
|
|
286
|
+
template = file("${path.module}/openapi.json")
|
|
287
|
+
|
|
288
|
+
vars = {
|
|
289
|
+
user_function_arn = aws_lambda_function.user_function.arn
|
|
290
|
+
lambda_invoke_iam_role_arn = aws_iam_role.api_gateway_role.arn
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
# Create the API Gateway REST API using the OpenAPI specification
|
|
295
|
+
resource "aws_api_gateway_rest_api" "api" {
|
|
296
|
+
name = "My API"
|
|
297
|
+
|
|
298
|
+
body = data.template_file.openapi_spec.rendered
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
# Deploy the API
|
|
302
|
+
resource "aws_api_gateway_deployment" "api_deployment" {
|
|
303
|
+
depends_on = [aws_api_gateway_rest_api.api]
|
|
304
|
+
rest_api_id = aws_api_gateway_rest_api.api.id
|
|
305
|
+
stage_name = "prod"
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Example
|
|
310
|
+
|
|
311
|
+
The following `Makefile` produces the openapi specs during a CICD process with:
|
|
312
|
+
|
|
313
|
+
```makefile
|
|
314
|
+
APP_NAME=Example-App
|
|
315
|
+
GIT_TAG=$(shell git describe --tags)
|
|
316
|
+
|
|
317
|
+
build/api-definition:
|
|
318
|
+
docker run -it --rm \
|
|
319
|
+
-v $(shell pwd)/package/src/api:/app/routes:ro \
|
|
320
|
+
-v $(shell pwd)/terraform/rest:/out \
|
|
321
|
+
fastapi_aws \
|
|
322
|
+
--title $(APP_NAME) \
|
|
323
|
+
--router routes.routes:router \
|
|
324
|
+
--out-public /out/api_public_definition.json \
|
|
325
|
+
--out-private /out/api_private_definition.json \
|
|
326
|
+
--version $(GIT_TAG)
|
|
327
|
+
|
|
328
|
+
build/infrastructure:
|
|
329
|
+
terraform init
|
|
330
|
+
terraform plan -out new.plan
|
|
331
|
+
terraform apply && rm new.plan
|
|
332
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
fastapi_aws/__init__.py,sha256=EAi65IfuiGJh9HE-Q-1uutBRmNKRnLzTkesdh9bs6s8,293
|
|
2
|
+
fastapi_aws/__main__.py,sha256=6eY2-rIqV69zPvNCOpS60PW0NGDoNQ8mktZ1koeZKzs,11785
|
|
3
|
+
fastapi_aws/_version.py,sha256=Fi0KXFzRbmtJaZ31ANCJStB858SeU6K67Widq0qwDIs,511
|
|
4
|
+
fastapi_aws/authorizers.py,sha256=zIFIB5P36bAPkMmzLplaFT-_sgEL4oBzs3dKjTdPIQ4,5874
|
|
5
|
+
fastapi_aws/integrations.py,sha256=b-zYoXOx-ep8AJJdGPdWLoGXErugtpLJzO-h-S2EIUE,10126
|
|
6
|
+
fastapi_aws/models.py,sha256=Ryy1OG2efHp31VQ9ucNw8Rwc2wXZS9VCjhFxYkhY2sk,1657
|
|
7
|
+
fastapi_aws/route.py,sha256=Jbx_E6Q5jp4IV4yjFlRxa3-0dIhWGtCstbK9ZyVouEI,8291
|
|
8
|
+
fastapi_aws/router.py,sha256=TuUkquFHLcZYqzwqjo_-docKCgtr1Rq6raipgfQf8pA,1538
|
|
9
|
+
fastapi_aws-0.0.7.dist-info/METADATA,sha256=LYMJzQHaALQrMEO7eqd_w79l8ScHOKsQTpB3Rw-pe2I,10288
|
|
10
|
+
fastapi_aws-0.0.7.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
11
|
+
fastapi_aws-0.0.7.dist-info/top_level.txt,sha256=920A6G6vs74mfgdQlmJ_wRgI55iup7aIdENDeSmL0M8,12
|
|
12
|
+
fastapi_aws-0.0.7.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fastapi_aws
|