├── .gitignore ├── Dockerfile ├── Dockerfile.aws.lambda ├── LICENSE ├── README.md ├── app ├── __init__.py ├── app.py ├── handlers │ ├── __init__.py │ ├── exception_handler.py │ └── http_exception_handler.py ├── middlewares │ ├── __init__.py │ ├── correlation_id_middleware.py │ └── logging_middleware.py ├── monitoring │ ├── __init__.py │ └── logging_config.py └── routes │ ├── __init__.py │ └── helloworld_router.py ├── documentation └── deployment │ └── awsconsole │ ├── aws_console.md │ └── images │ ├── api_gtw_1.jpg │ ├── api_gtw_10.jpg │ ├── api_gtw_11.jpg │ ├── api_gtw_2.jpg │ ├── api_gtw_3.jpg │ ├── api_gtw_5.jpg │ ├── api_gtw_6.JPG │ ├── api_gtw_7.jpg │ ├── api_gtw_8.jpg │ ├── api_gtw_9.jpg │ ├── config_lambda_1.jpg │ ├── config_lambda_2.jpg │ ├── config_lambda_3.JPG │ ├── config_lambda_4.jpg │ ├── config_lambda_5.jpg │ ├── config_lambda_6.JPG │ ├── create_ecr_1.jpg │ ├── create_ecr_2.JPG │ ├── create_ecr_3.jpg │ ├── push_ecr_1.JPG │ ├── test_1.jpg │ └── test_2.JPG └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | COPY ./app ./app 3 | COPY ./requirements.txt ./requirements.txt 4 | RUN pip install -r ./requirements.txt 5 | ENV HOST="0.0.0.0" 6 | ENV PORT=5000 7 | ENTRYPOINT uvicorn app.app:app --host ${HOST} --port ${PORT} 8 | -------------------------------------------------------------------------------- /Dockerfile.aws.lambda: -------------------------------------------------------------------------------- 1 | FROM public.ecr.aws/lambda/python:3.8 2 | COPY ./app ./app 3 | COPY ./requirements.txt ./requirements.txt 4 | RUN pip install -r ./requirements.txt 5 | CMD ["app.app.handler"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Archived 2 | 3 | This repository has been archived and will no longer be supported. 4 | 5 | ### Python + FastAPI + Mangum + AWS Lambda Container 6 | 7 | A demo project to test the AWS Lambda container support with Python FastAPI framework. The purpose of the project is to show how to develop a REST API with FastAPI, how to build & test it locally and how to deploy it on AWS using serverless services (AWS ECR, AWS Lambda & AWS API Gateway). 8 | 9 | ### Prerequisites 10 | 11 | - Docker CLI 12 | - Python 3.8 13 | 14 | ### Install dependencies 15 | 16 | A requirements file declare all dependencies (Mangum, FastAPI, Uvicorn, ...). Use the following command to install the required dependencies (For Python 3.8.5) 17 | 18 | ``` 19 | pip install -r ./requirements.txt 20 | ``` 21 | 22 | TIP : Before installing required dependencies, do not forget to create a virtual environment using your favorite tool (Conda, ...). 23 | 24 | ### Run locally 25 | 26 | You can either use the following command : 27 | 28 | ``` 29 | python -m app.app 30 | ``` 31 | 32 | Or deploy on uvicorn : 33 | 34 | ``` 35 | uvicorn app.app:app --reload --host 0.0.0.0 --port 5000 36 | ``` 37 | 38 | You can test the application by using the following command : 39 | 40 | ``` 41 | curl http://localhost:5000/hello/ 42 | ``` 43 | 44 | ### Build the 'regular' container 45 | 46 | This command builds a container which will run a Uvicorn server and deploy the ASGI app on it : 47 | 48 | ``` 49 | docker build -t hello-world-uvicorn . 50 | ``` 51 | 52 | ### Run the container 53 | 54 | The command starts the container : 55 | 56 | ``` 57 | docker run -p 5000:5000 hello-world-uvicorn:latest 58 | ``` 59 | 60 | You can make a test with this command : 61 | 62 | ``` 63 | curl http://localhost:5000/hello/ 64 | ``` 65 | 66 | ### Build the container for AWS Lambda 67 | 68 | Now we can build the container for AWS Lambda which will use the Mangum handler. We use another Dockerfile which will use a base image provided by AWS : 69 | 70 | ``` 71 | docker build -t hello-world-lambda . -f Dockerfile.aws.lambda 72 | ``` 73 | 74 | ### Run the AWS Lambda container for local test 75 | 76 | Let's start the container to test the lambda locally : 77 | 78 | ``` 79 | docker run -p 9000:8080 hello-world-lambda:latest 80 | ``` 81 | 82 | ### Test the Lambda 83 | 84 | We send the input event that the lambda would receive from the API Gateway with the following command : 85 | 86 | ``` 87 | curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{ 88 | "resource": "/hello", 89 | "path": "/hello/", 90 | "httpMethod": "GET", 91 | "headers": { 92 | "Accept": "*/*", 93 | "Accept-Encoding": "gzip, deflate", 94 | "cache-control": "no-cache", 95 | "CloudFront-Forwarded-Proto": "https", 96 | "CloudFront-Is-Desktop-Viewer": "true", 97 | "CloudFront-Is-Mobile-Viewer": "false", 98 | "CloudFront-Is-SmartTV-Viewer": "false", 99 | "CloudFront-Is-Tablet-Viewer": "false", 100 | "CloudFront-Viewer-Country": "US", 101 | "Content-Type": "application/json", 102 | "headerName": "headerValue", 103 | "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", 104 | "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", 105 | "User-Agent": "PostmanRuntime/2.4.5", 106 | "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", 107 | "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", 108 | "X-Forwarded-For": "54.240.196.186, 54.182.214.83", 109 | "X-Forwarded-Port": "443", 110 | "X-Forwarded-Proto": "https" 111 | }, 112 | "multiValueHeaders":{ 113 | "Accept":[ 114 | "*/*" 115 | ], 116 | "Accept-Encoding":[ 117 | "gzip, deflate" 118 | ], 119 | "cache-control":[ 120 | "no-cache" 121 | ], 122 | "CloudFront-Forwarded-Proto":[ 123 | "https" 124 | ], 125 | "CloudFront-Is-Desktop-Viewer":[ 126 | "true" 127 | ], 128 | "CloudFront-Is-Mobile-Viewer":[ 129 | "false" 130 | ], 131 | "CloudFront-Is-SmartTV-Viewer":[ 132 | "false" 133 | ], 134 | "CloudFront-Is-Tablet-Viewer":[ 135 | "false" 136 | ], 137 | "CloudFront-Viewer-Country":[ 138 | "US" 139 | ], 140 | "":[ 141 | "" 142 | ], 143 | "Content-Type":[ 144 | "application/json" 145 | ], 146 | "headerName":[ 147 | "headerValue" 148 | ], 149 | "Host":[ 150 | "gy415nuibc.execute-api.us-east-1.amazonaws.com" 151 | ], 152 | "Postman-Token":[ 153 | "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" 154 | ], 155 | "User-Agent":[ 156 | "PostmanRuntime/2.4.5" 157 | ], 158 | "Via":[ 159 | "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" 160 | ], 161 | "X-Amz-Cf-Id":[ 162 | "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" 163 | ], 164 | "X-Forwarded-For":[ 165 | "54.240.196.186, 54.182.214.83" 166 | ], 167 | "X-Forwarded-Port":[ 168 | "443" 169 | ], 170 | "X-Forwarded-Proto":[ 171 | "https" 172 | ] 173 | }, 174 | "queryStringParameters": { 175 | }, 176 | "multiValueQueryStringParameters":{ 177 | }, 178 | "pathParameters": { 179 | }, 180 | "stageVariables": { 181 | "stageVariableName": "stageVariableValue" 182 | }, 183 | "requestContext": { 184 | "accountId": "12345678912", 185 | "resourceId": "roq9wj", 186 | "stage": "testStage", 187 | "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", 188 | "identity": { 189 | "cognitoIdentityPoolId": null, 190 | "accountId": null, 191 | "cognitoIdentityId": null, 192 | "caller": null, 193 | "apiKey": null, 194 | "sourceIp": "192.168.196.186", 195 | "cognitoAuthenticationType": null, 196 | "cognitoAuthenticationProvider": null, 197 | "userArn": null, 198 | "userAgent": "PostmanRuntime/2.4.5", 199 | "user": null 200 | }, 201 | "resourcePath": "/hello/", 202 | "httpMethod": "GET", 203 | "apiId": "gy415nuibc" 204 | }, 205 | "body": "{}", 206 | "isBase64Encoded": false 207 | }' 208 | ``` 209 | 210 | See the server logs : 211 | ``` 212 | (fastapi-lambda-container) gbdevw@gbdevw-dev:~/python-fastapi-aws-lambda-container$ docker run -p 9000:8080 hello-world:latest 213 | time="2020-12-28T15:31:13.892" level=info msg="exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)" 214 | time="2020-12-28T15:31:18.345" level=info msg="extensionsDisabledByLayer(/opt/disable-extensions-jwigqn8j) -> stat /opt/disable-extensions-jwigqn8j: no such file or directory" 215 | time="2020-12-28T15:31:18.345" level=warning msg="Cannot list external agents" error="open /opt/extensions: no such file or directory" 216 | START RequestId: b7cd3c8d-6c70-4194-b5bd-6a0b8e766b1f Version: $LATEST 217 | {"timestamp": "2020-12-28T15:31:18.650259", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "Waiting for application startup."} 218 | {"timestamp": "2020-12-28T15:31:18.650726", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "LifespanCycleState.STARTUP: 'lifespan.startup.complete' event received from application."} 219 | {"timestamp": "2020-12-28T15:31:18.650863", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "Application startup complete."} 220 | {"timestamp": "2020-12-28T15:31:18.651481", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "api-request", "message": "Request", "uuid": "bd8ec07d-2b70-436d-8157-1d802be13240", "method": "GET", "url": "https://gy415nuibc.execute-api.us-east-1.amazonaws.com/hello/"} 221 | {"timestamp": "2020-12-28T15:31:18.652105", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "api-response", "message": "Response sent", "uuid": "bd8ec07d-2b70-436d-8157-1d802be13240", "code": 200} 222 | {"timestamp": "2020-12-28T15:31:18.652506", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "HTTPCycleState.REQUEST: 'http.response.start' event received from application."} 223 | {"timestamp": "2020-12-28T15:31:18.652641", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "HTTPCycleState.RESPONSE: 'http.response.body' event received from application."} 224 | {"timestamp": "2020-12-28T15:31:18.652750", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "HTTPCycleState.RESPONSE: 'http.response.body' event received from application."} 225 | {"timestamp": "2020-12-28T15:31:18.652996", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "HTTPCycleState.RESPONSE: 'http.response.body' event received from application."} 226 | {"timestamp": "2020-12-28T15:31:18.653305", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "Waiting for application shutdown."} 227 | {"timestamp": "2020-12-28T15:31:18.653449", "level": "INFO", "service": "Helloworld", "instance": "84cc35a9-965e-4ac9-9f45-6554341c0dc2", "type": "internal", "message": "LifespanCycleState.SHUTDOWN: 'lifespan.shutdown.complete' event received from application."} 228 | END RequestId: b7cd3c8d-6c70-4194-b5bd-6a0b8e766b1f 229 | REPORT RequestId: b7cd3c8d-6c70-4194-b5bd-6a0b8e766b1f Init Duration: 0.42 ms Duration: 308.38 ms Billed Duration: 400 ms Memory Size: 3008 MB Max Memory Used: 3008 MB 230 | ``` 231 | 232 | And the lambda output : 233 | 234 | ```json 235 | {"isBase64Encoded": false, "statusCode": 200, "headers": {"content-length": "25", "content-type": "application/json", "x-correlation-id": "e6ccda71-c841-40de-8208-aff40a2b155b"}, "body": "{\"message\":\"Hello World\"}"} 236 | ``` 237 | 238 | ### Deploy the application on AWS 239 | 240 | [Step by step guide to deploy the application on AWS using console](./documentation/deployment/awsconsole/aws_console.md) 241 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/app/__init__.py -------------------------------------------------------------------------------- /app/app.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | import uvicorn 3 | 4 | from fastapi import FastAPI, HTTPException 5 | from mangum import Mangum 6 | 7 | from app.routes import helloworld_router 8 | from app.monitoring import logging_config 9 | from app.middlewares.correlation_id_middleware import CorrelationIdMiddleware 10 | from app.middlewares.logging_middleware import LoggingMiddleware 11 | from app.handlers.exception_handler import exception_handler 12 | from app.handlers.http_exception_handler import http_exception_handler 13 | 14 | ############################################################################### 15 | # Application object # 16 | ############################################################################### 17 | 18 | app = FastAPI() 19 | 20 | ############################################################################### 21 | # Logging configuration # 22 | ############################################################################### 23 | 24 | logging_config.configure_logging(level='DEBUG', service='Helloworld', instance=str(uuid.uuid4())) 25 | 26 | ############################################################################### 27 | # Error handlers configuration # 28 | ############################################################################### 29 | 30 | app.add_exception_handler(Exception, exception_handler) 31 | app.add_exception_handler(HTTPException, http_exception_handler) 32 | 33 | ############################################################################### 34 | # Middlewares configuration # 35 | ############################################################################### 36 | 37 | # Tip : middleware order : CorrelationIdMiddleware > LoggingMiddleware -> reverse order 38 | app.add_middleware(LoggingMiddleware) 39 | app.add_middleware(CorrelationIdMiddleware) 40 | 41 | ############################################################################### 42 | # Routers configuration # 43 | ############################################################################### 44 | 45 | app.include_router(helloworld_router.router, prefix='/hello', tags=['hello']) 46 | 47 | ############################################################################### 48 | # Handler for AWS Lambda # 49 | ############################################################################### 50 | 51 | handler = Mangum(app) 52 | 53 | ############################################################################### 54 | # Run the self contained application # 55 | ############################################################################### 56 | 57 | if __name__ == "__main__": 58 | uvicorn.run(app, host="0.0.0.0", port=5000) -------------------------------------------------------------------------------- /app/handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/app/handlers/__init__.py -------------------------------------------------------------------------------- /app/handlers/exception_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from fastapi import Request, HTTPException 4 | from fastapi.responses import JSONResponse 5 | 6 | logger = logging.getLogger() 7 | 8 | async def exception_handler (request: Request, exc: Exception): 9 | logger.exception(exc, extra={'uuid':request.state.correlation_id, 'type':'app-error'}) 10 | return JSONResponse( 11 | status_code=500, 12 | content={"uuid": request.state.correlation_id, "message": "An internal error has occured"} 13 | ) -------------------------------------------------------------------------------- /app/handlers/http_exception_handler.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from fastapi import Request, HTTPException 4 | from fastapi.responses import JSONResponse 5 | 6 | logger = logging.getLogger() 7 | 8 | async def http_exception_handler (request: Request, exc: HTTPException): 9 | logger.exception(exc, extra={'uuid':request.state.correlation_id, 'type':'api-error'}) 10 | return JSONResponse( 11 | status_code=exc.status_code, 12 | content={"uuid": request.state.correlation_id, "message": exc.detail} 13 | ) 14 | -------------------------------------------------------------------------------- /app/middlewares/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/app/middlewares/__init__.py -------------------------------------------------------------------------------- /app/middlewares/correlation_id_middleware.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from starlette.middleware.base import BaseHTTPMiddleware 4 | 5 | class CorrelationIdMiddleware (BaseHTTPMiddleware): 6 | 7 | def __init__(self, app): 8 | super().__init__(app) 9 | 10 | # Add or use the provided correlation ID (request header : x-correlation-id) 11 | async def dispatch (self, request, call_next): 12 | 13 | # Add or reuse correlation id 14 | request.state.correlation_id = request.headers.get("x-correlation-id", str(uuid.uuid4())) 15 | 16 | # Next middleware 17 | response = await call_next(request) 18 | 19 | # Add correlation id header to response 20 | response.headers["x-correlation-id"] = request.state.correlation_id 21 | 22 | # Return response 23 | return response -------------------------------------------------------------------------------- /app/middlewares/logging_middleware.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import uuid 3 | 4 | from starlette.middleware.base import BaseHTTPMiddleware 5 | 6 | class LoggingMiddleware (BaseHTTPMiddleware): 7 | 8 | def __init__(self, app): 9 | super().__init__(app) 10 | self.logger = logging.getLogger() 11 | 12 | async def dispatch (self, request, call_next): 13 | 14 | # Log the request 15 | req_uuid = request.state.correlation_id 16 | self.logger.info("Request", extra={'uuid':req_uuid, 'type':'api-request', 'method':str(request.method).upper(), 'url':str(request.url)}) 17 | 18 | # Next middleware 19 | response = await call_next(request) 20 | 21 | # Log the response 22 | self.logger.info("Response sent", extra={'uuid':req_uuid, 'type':'api-response', 'code':response.status_code}) 23 | 24 | # Return response 25 | return response -------------------------------------------------------------------------------- /app/monitoring/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/app/monitoring/__init__.py -------------------------------------------------------------------------------- /app/monitoring/logging_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | import logging 4 | import json 5 | 6 | from json import JSONEncoder 7 | from pythonjsonlogger import jsonlogger 8 | from datetime import datetime 9 | from logging.config import dictConfig 10 | 11 | # Custom JSON encoder which enforce standard ISO 8601 format, UUID format 12 | class ModelJsonEncoder(JSONEncoder): 13 | def default(self, o): 14 | if isinstance(o, UUID): 15 | return str(o) 16 | if isinstance(o, datetime): 17 | return o.isoformat() 18 | return json.JSONEncoder.default(self, o) 19 | 20 | class LogFilter(logging.Filter): 21 | 22 | def __init__(self, service=None, instance=None): 23 | self.service = service 24 | self.instance = instance 25 | 26 | def filter(self, record): 27 | record.service = self.service 28 | record.instance = self.instance 29 | return True 30 | 31 | class JsonLogFormatter(jsonlogger.JsonFormatter): 32 | 33 | def add_fields(self, log_record, record, message_dict): 34 | 35 | super().add_fields(log_record, record, message_dict) 36 | 37 | # Add timestamp field with default : now 38 | if not log_record.get('timestamp'): 39 | now = datetime.utcnow().isoformat() 40 | log_record['timestamp'] = now 41 | 42 | # Add level field 43 | if log_record.get('level'): 44 | log_record['level'] = log_record['level'].upper() 45 | else: 46 | log_record['level'] = record.levelname 47 | 48 | # Add type field for internal logs 49 | if not log_record.get('type'): 50 | log_record['type'] = 'internal' 51 | 52 | # Configure Logging 53 | def configure_logging(level='DEBUG', service=None, instance=None): 54 | dictConfig({ 55 | 'version': 1, 56 | 'formatters': {'default': { 57 | '()': JsonLogFormatter, 58 | 'format': '%(timestamp)s %(level)s %(service)s %(instance)s %(type)s %(message)s', 59 | 'json_encoder': ModelJsonEncoder 60 | }}, 61 | 'filters': {'default': { 62 | '()': LogFilter, 63 | 'service': service, 64 | 'instance': instance 65 | }}, 66 | 'handlers': {'default_handler': { 67 | 'class': 'logging.StreamHandler', 68 | 'stream': 'ext://sys.stdout', 69 | 'filters': ['default'], 70 | 'formatter': 'default' 71 | }}, 72 | 'root': { 73 | 'level': level, 74 | 'handlers': ['default_handler'] 75 | } 76 | }) -------------------------------------------------------------------------------- /app/routes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/app/routes/__init__.py -------------------------------------------------------------------------------- /app/routes/helloworld_router.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, HTTPException 2 | 3 | router = APIRouter() 4 | 5 | @router.get("/") 6 | async def hello(): 7 | return {"message": "Hello World"} -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/aws_console.md: -------------------------------------------------------------------------------- 1 | ### Deploy the application on AWS using the console 2 | 3 | This guide will show how to deploy the containerized application on AWS using the console. 4 | 5 | Three services from AWS will be used : 6 | 7 | - Amazon Elastic Container Registry to host the container that will be run by AWS Lambda 8 | - Amazon Lambda to run the application 9 | - Amazon API Gateway as the front door for the API running on AWS Lambda 10 | 11 | ### Prerequisites 12 | 13 | Clone the project. Once at the root of the cloned project, perform all these steps from the readme : 14 | 15 | 1. Install dependencies 16 | 2. Build the container for AWS Lambda 17 | 3. Run the AWS Lambda container for local test 18 | 4. Test the Lambda 19 | 5. Once you are done testing, stop the application (CTRL+C) 20 | 21 | If everything went well, you now have a local image of the container that can run on AWS Lambda. You can verify by using the following command : 22 | 23 | ``` 24 | docker image ls 25 | ``` 26 | 27 | The output should contain a entry like this : 28 | 29 | ``` 30 | gbdevw:~/environment $ docker image ls 31 | REPOSITORY TAG IMAGE ID CREATED SIZE 32 | hello-world-lambda latest a7567220a919 12 minutes ago 733MB 33 | ``` 34 | 35 | For the next steps, the guide assumes the image is named 'hello-world-lambda:latest'. 36 | 37 | ### Create a private ECR repository 38 | 39 | The first major step is to create a AWS ECR repsitory and push the container image on it. The following images show step by step how to create the ECR repository : 40 | 41 | ![](images/create_ecr_1.jpg) 42 | 43 | ![](images/create_ecr_2.JPG) 44 | 45 | ![](images/create_ecr_3.jpg) 46 | 47 | The guide will use the following URI to tag the container images : '766641682971.dkr.ecr.eu-central-1.amazonaws.com/python-fastapi-lambda' 48 | 49 | ### Push the container on ECR 50 | 51 | Next, the container image you built as prerequisite will be pushed on the ECR repository you just created. The following steps will be performed : 52 | 53 | 1. Tag the container image accordingly in order to be able to push it on the ECR repository 54 | 2. Authenticate your Docker CLI to AWS ECR 55 | 3. Push the container image on your ECR repository 56 | 57 | At first, use the following command to retag the container image you previously built : 58 | 59 | ``` 60 | docker tag hello-world-lambda:latest 766641682971.dkr.ecr.eu-central-1.amazonaws.com/python-fastapi-lambda:latest 61 | ``` 62 | 63 | You can verify the results using the following command : 64 | 65 | ``` 66 | docker image ls 67 | ``` 68 | 69 | The output should contain these two entries : 70 | 71 | ``` 72 | (python-fastapi) gbdevw:~/environment/python-fastapi-aws-lambda-container (main) $ docker image ls 73 | REPOSITORY TAG IMAGE ID CREATED SIZE 74 | 766641682971.dkr.ecr.eu-central-1.amazonaws.com/python-fastapi-lambda latest a7567220a919 About an hour ago 733MB 75 | hello-world-lambda latest a7567220a919 About an hour ago 733MB 76 | ``` 77 | 78 | Then, use the following command to authenticate your docker CLI to AWS ECR : 79 | 80 | ``` 81 | aws ecr get-login-password --region eu-central-1 | docker login --username AWS --password-stdin 766641682971.dkr.ecr.eu-central-1.amazonaws.com/python-fastapi-lambda 82 | ``` 83 | 84 | Once you are logged in, push the image to AWS ECR using this command : 85 | 86 | ``` 87 | docker push 766641682971.dkr.ecr.eu-central-1.amazonaws.com/python-fastapi-lambda:latest 88 | ``` 89 | 90 | The container image is now available in your ECR repository 91 | 92 | ![](images/push_ecr_1.JPG) 93 | 94 | ### Configure AWS Lambda to run your container 95 | 96 | The second major step is to configure AWS lambda to run the container. The following images show this step by step : 97 | 98 | ![](images/config_lambda_1.jpg) 99 | 100 | ![](images/config_lambda_2.jpg) 101 | 102 | ![](images/config_lambda_3.JPG) 103 | 104 | ![](images/config_lambda_4.jpg) 105 | 106 | ![](images/config_lambda_5.jpg) 107 | 108 | ![](images/config_lambda_6.JPG) 109 | 110 | ### Configure AWS API Gateway as the front door for your API running on AWS Lambda 111 | 112 | The last major step is to configure AWS API Gateway as the front door for your API. The application defines only one resource (/hello) with a GET method. The following images show how to configure the API Gateway step by step The followng images show this step by step : 113 | 114 | ![](images/api_gtw_1.jpg) 115 | 116 | ![](images/api_gtw_2.jpg) 117 | 118 | ![](images/api_gtw_3.jpg) 119 | 120 | ![](images/api_gtw_5.jpg) 121 | 122 | ![](images/api_gtw_6.JPG) 123 | 124 | ![](images/api_gtw_7.jpg) 125 | 126 | ![](images/api_gtw_8.jpg) 127 | 128 | ![](images/api_gtw_9.jpg) 129 | 130 | ![](images/api_gtw_10.jpg) 131 | 132 | ![](images/api_gtw_11.jpg) 133 | 134 | ### Test the application 135 | 136 | You can make a request to your API using the following command (do not forget to replace the hostname) : 137 | 138 | ``` 139 | curl https://8jjzn56u5h.execute-api.eu-central-1.amazonaws.com/dev/hello/ 140 | ``` 141 | 142 | Or use your browser : 143 | 144 | ![](images/test_1.jpg) 145 | 146 | See the logs from Cloudwatch : 147 | 148 | ![](images/test_2.JPG) 149 | 150 | ### Clean up 151 | 152 | To clean up resources : 153 | 154 | 1. Delete the API Gateway 155 | 2. Delete the Lambda function 156 | 3. Delete the ECR repository 157 | -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_1.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_10.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_11.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_2.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_3.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_5.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_6.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_6.JPG -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_7.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_8.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/api_gtw_9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/api_gtw_9.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/config_lambda_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/config_lambda_1.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/config_lambda_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/config_lambda_2.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/config_lambda_3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/config_lambda_3.JPG -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/config_lambda_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/config_lambda_4.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/config_lambda_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/config_lambda_5.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/config_lambda_6.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/config_lambda_6.JPG -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/create_ecr_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/create_ecr_1.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/create_ecr_2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/create_ecr_2.JPG -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/create_ecr_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/create_ecr_3.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/push_ecr_1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/push_ecr_1.JPG -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/test_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/test_1.jpg -------------------------------------------------------------------------------- /documentation/deployment/awsconsole/images/test_2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbdevw/python-fastapi-aws-lambda-container/5db802206b8527d5d7f82cf53e088d524c1e79bf/documentation/deployment/awsconsole/images/test_2.JPG -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2020.12.5 2 | click==7.1.2 3 | fastapi==0.63.0 4 | h11==0.11.0 5 | httptools==0.1.1 6 | mangum==0.10.0 7 | pydantic==1.7.3 8 | python-dotenv==0.15.0 9 | python-json-logger==2.0.1 10 | PyYAML==5.3.1 11 | starlette==0.13.6 12 | typing-extensions==3.7.4.3 13 | uvicorn==0.13.2 14 | uvloop==0.14.0 15 | watchgod==0.6 16 | websockets==8.1 17 | --------------------------------------------------------------------------------