├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── example_app ├── __init__.py ├── api │ ├── __init__.py │ └── api_v1 │ │ ├── __init__.py │ │ ├── api.py │ │ └── endpoints │ │ ├── __init__.py │ │ └── example.py ├── core │ ├── __init__.py │ ├── config.py │ └── models │ │ ├── input.py │ │ └── output.py └── main.py ├── requirements.txt ├── scripts └── example.ipynb ├── setup.py ├── template.yml ├── tests ├── __init__.py ├── test_example_endpoint.py └── test_ping.py └── tree.txt /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .python-version 3 | 4 | /.vscode/ 5 | /.pytest_cache/ 6 | /.cache/ 7 | 8 | *.code-workspace 9 | packaged.yaml 10 | /.aws-sam/ 11 | 12 | 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | pip-wheel-metadata/ 36 | share/python-wheels/ 37 | *.egg-info/ 38 | .installed.cfg 39 | *.egg 40 | MANIFEST 41 | 42 | # PyInstaller 43 | # Usually these files are written by a python script from a template 44 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 45 | *.manifest 46 | *.spec 47 | 48 | # Installer logs 49 | pip-log.txt 50 | pip-delete-this-directory.txt 51 | 52 | # Unit test / coverage reports 53 | htmlcov/ 54 | .tox/ 55 | .nox/ 56 | .coverage 57 | .coverage.* 58 | .cache 59 | nosetests.xml 60 | coverage.xml 61 | *.cover 62 | *.py,cover 63 | .hypothesis/ 64 | .pytest_cache/ 65 | 66 | # Translations 67 | *.mo 68 | *.pot 69 | 70 | # Django stuff: 71 | *.log 72 | local_settings.py 73 | db.sqlite3 74 | db.sqlite3-journal 75 | 76 | # Flask stuff: 77 | instance/ 78 | .webassets-cache 79 | 80 | # Scrapy stuff: 81 | .scrapy 82 | 83 | # Sphinx documentation 84 | docs/_build/ 85 | 86 | # PyBuilder 87 | target/ 88 | 89 | # Jupyter Notebook 90 | .ipynb_checkpoints 91 | 92 | # IPython 93 | profile_default/ 94 | ipython_config.py 95 | 96 | # pyenv 97 | .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 107 | __pypackages__/ 108 | 109 | # Celery stuff 110 | celerybeat-schedule 111 | celerybeat.pid 112 | 113 | # SageMath parsed files 114 | *.sage.py 115 | 116 | # Environments 117 | .env 118 | .venv 119 | env/ 120 | venv/ 121 | ENV/ 122 | env.bak/ 123 | venv.bak/ 124 | 125 | # Spyder project settings 126 | .spyderproject 127 | .spyproject 128 | 129 | # Rope project settings 130 | .ropeproject 131 | 132 | # mkdocs documentation 133 | /site 134 | 135 | # mypy 136 | .mypy_cache/ 137 | .dmypy.json 138 | dmypy.json 139 | 140 | # Pyre type checker 141 | .pyre/ 142 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: check-yaml 6 | args: [--unsafe] 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | - id: check-ast 10 | - repo: https://github.com/psf/black 11 | rev: 19.3b0 12 | hooks: 13 | - id: black 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | cache: pip 3 | python: 4 | - '3.7' 5 | install: 6 | - pip install awscli 7 | - pip install aws-sam-cli 8 | jobs: 9 | include: 10 | - stage: test 11 | script: 12 | - pip install pytest 13 | - pip install -e . 14 | - pytest . -v 15 | - stage: deploy 16 | script: 17 | - sam validate 18 | - sam build --debug 19 | - sam package --s3-bucket my-travis-deployment-bucket --output-template-file out.yml --region eu-west-1 20 | - sam deploy --template-file out.yml --stack-name example-stack-name --region eu-west-1 --no-fail-on-empty-changeset --capabilities CAPABILITY_IAM 21 | skip_cleanup: true 22 | if: branch = master 23 | notifications: 24 | email: 25 | on_failure: always 26 | env: 27 | global: 28 | - AWS_DEFAULT_REGION=eu-west-1 29 | - secure: your-encrypted-aws-access-key-id 30 | - secure: your-encrypted-aws-secret-access-key 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7 2 | 3 | RUN pip install fastapi uvicorn mangum pydantic 4 | 5 | EXPOSE 8080 6 | 7 | COPY ./example_app /example_app 8 | 9 | CMD ["uvicorn", "example_app.main:app", "--host", "0.0.0.0", "--port", "8080"] 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 You 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Disclaimer 2 | I won't answer issues or emails regarding the project anymore. 3 | 4 | The project is old and not maintained anymore. I'm not sure if it still works this way, but maybe it can still serve as a starting point for your exploration. 5 | 6 | # fastapi-aws-lambda-example 7 | 8 | for a detailed guide on how to deploy a fastapi application with AWS API Gateway and AWS Lambda check [this](https://iwpnd.pw/articles/2020-01/deploy-fastapi-to-aws-lambda) 9 | -------------------------------------------------------------------------------- /example_app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwpnd/fastapi-aws-lambda-example/c68aef5bff12df42070be41c33b7e1d9d63344be/example_app/__init__.py -------------------------------------------------------------------------------- /example_app/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwpnd/fastapi-aws-lambda-example/c68aef5bff12df42070be41c33b7e1d9d63344be/example_app/api/__init__.py -------------------------------------------------------------------------------- /example_app/api/api_v1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwpnd/fastapi-aws-lambda-example/c68aef5bff12df42070be41c33b7e1d9d63344be/example_app/api/api_v1/__init__.py -------------------------------------------------------------------------------- /example_app/api/api_v1/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | from .endpoints.example import router as example_router 4 | 5 | 6 | router = APIRouter() 7 | router.include_router(example_router) 8 | -------------------------------------------------------------------------------- /example_app/api/api_v1/endpoints/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwpnd/fastapi-aws-lambda-example/c68aef5bff12df42070be41c33b7e1d9d63344be/example_app/api/api_v1/endpoints/__init__.py -------------------------------------------------------------------------------- /example_app/api/api_v1/endpoints/example.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | from example_app.core.models.output import OutputExample 4 | from example_app.core.models.input import InputExample 5 | 6 | router = APIRouter() 7 | 8 | 9 | @router.get("/example", tags=["example get"]) 10 | def example_get(): 11 | """ 12 | Say hej! 13 | 14 | This will greet you properly 15 | 16 | And this path operation will: 17 | * return "hej!" 18 | """ 19 | return {"msg": "Hej!"} 20 | 21 | 22 | @router.post("/example", response_model=OutputExample, tags=["example post"]) 23 | def example_endpoint(inputs: InputExample): 24 | """ 25 | Multiply two values 26 | 27 | This will multiply two inputs. 28 | 29 | And this path operation will: 30 | * return a*b 31 | """ 32 | return {"a": inputs.a, "b": inputs.b, "result": inputs.a * inputs.b} 33 | -------------------------------------------------------------------------------- /example_app/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwpnd/fastapi-aws-lambda-example/c68aef5bff12df42070be41c33b7e1d9d63344be/example_app/core/__init__.py -------------------------------------------------------------------------------- /example_app/core/config.py: -------------------------------------------------------------------------------- 1 | from starlette.datastructures import CommaSeparatedStrings 2 | import os 3 | 4 | ALLOWED_HOSTS = CommaSeparatedStrings(os.getenv("ALLOWED_HOSTS", "")) 5 | API_V1_STR = "/api/v1" 6 | PROJECT_NAME = "FastAPI-AWS-Lambda-Example-API" 7 | -------------------------------------------------------------------------------- /example_app/core/models/input.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | 4 | class InputExample(BaseModel): 5 | a: int = Field(..., title="Input value a") 6 | b: int = Field(..., title="Input value b") 7 | -------------------------------------------------------------------------------- /example_app/core/models/output.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | 4 | class OutputExample(BaseModel): 5 | a: int = Field(..., title="Input value a") 6 | b: int = Field(..., title="Input value b") 7 | result: int = Field(..., title="Result of a * b") 8 | -------------------------------------------------------------------------------- /example_app/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from mangum import Mangum 3 | 4 | from example_app.api.api_v1.api import router as api_router 5 | from example_app.core.config import API_V1_STR, PROJECT_NAME 6 | 7 | app = FastAPI( 8 | title=PROJECT_NAME, 9 | # if not custom domain 10 | # openapi_prefix="/prod" 11 | ) 12 | 13 | 14 | app.include_router(api_router, prefix=API_V1_STR) 15 | 16 | 17 | @app.get("/ping") 18 | def pong(): 19 | """ 20 | Sanity check. 21 | 22 | This will let the user know that the service is operational. 23 | 24 | And this path operation will: 25 | * show a lifesign 26 | 27 | """ 28 | return {"ping": "pong!"} 29 | 30 | 31 | handler = Mangum(app, enable_lifespan=False) 32 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi>=0.42.0 2 | mangum>=0.7.0 3 | pytest 4 | requests 5 | -------------------------------------------------------------------------------- /scripts/example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 3, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import requests" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 8, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "text/plain": [ 20 | "{'ping': 'pong!'}" 21 | ] 22 | }, 23 | "execution_count": 8, 24 | "metadata": {}, 25 | "output_type": "execute_result" 26 | } 27 | ], 28 | "source": [ 29 | "url = \"http://127.0.0.1:8000/ping\"\n", 30 | "response = requests.get(url)\n", 31 | "response.json()" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 15, 37 | "metadata": {}, 38 | "outputs": [ 39 | { 40 | "data": { 41 | "text/plain": [ 42 | "{'a': 5, 'b': 5, 'result': 25}" 43 | ] 44 | }, 45 | "execution_count": 15, 46 | "metadata": {}, 47 | "output_type": "execute_result" 48 | } 49 | ], 50 | "source": [ 51 | "url = \"http://127.0.0.1:8000/api/v1/example\"\n", 52 | "a = 5\n", 53 | "b = 5\n", 54 | "payload = {\"a\": a, \"b\": b}\n", 55 | "response = requests.post(url, json=payload)\n", 56 | "response.json()" 57 | ] 58 | } 59 | ], 60 | "metadata": { 61 | "kernelspec": { 62 | "display_name": "Python 3", 63 | "language": "python", 64 | "name": "python3" 65 | }, 66 | "language_info": { 67 | "codemirror_mode": { 68 | "name": "ipython", 69 | "version": 3 70 | }, 71 | "file_extension": ".py", 72 | "mimetype": "text/x-python", 73 | "name": "python", 74 | "nbconvert_exporter": "python", 75 | "pygments_lexer": "ipython3", 76 | "version": "3.7.5" 77 | } 78 | }, 79 | "nbformat": 4, 80 | "nbformat_minor": 4 81 | } 82 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | packages = [] 4 | with open("requirements.txt", "r") as f: 5 | requirements = f.read().splitlines() 6 | 7 | 8 | setup( 9 | name="example_app", 10 | version="0.1.0", 11 | description="example api to be deployed to aws lambda", 12 | url="http://github.com/iwpnd/fastapi-aws-lambda-example", 13 | author="probably you", 14 | author_email="probably@you.pw", 15 | license="MIT", 16 | include_package_data=True, 17 | install_requires=requirements, 18 | packages=packages, 19 | zip_safe=False, 20 | classifiers=[ 21 | "Programming Language :: Python :: 3", 22 | "Intended Audience :: Developers", 23 | ], 24 | ) 25 | -------------------------------------------------------------------------------- /template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | fastAPI aws lambda example 5 | Resources: 6 | FastapiExampleLambda: 7 | Type: AWS::Serverless::Function 8 | Properties: 9 | Events: 10 | ApiEvent: 11 | Properties: 12 | RestApiId: 13 | Ref: FastapiExampleGateway 14 | Path: /{proxy+} 15 | Method: ANY 16 | Type: Api 17 | FunctionName: fastapi-lambda-example 18 | CodeUri: ./ 19 | Handler: example_app.main.handler 20 | Runtime: python3.7 21 | Timeout: 300 # timeout of your lambda function 22 | MemorySize: 128 # memory size of your lambda function 23 | Description: fastAPI aws lambda example 24 | # other options, see -> 25 | # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy-globals.html#sam-specification-template-anatomy-globals-supported-resources-and-properties 26 | Role: !Sub arn:aws:iam::${AWS::AccountId}:role/fastapilambdarole 27 | 28 | FastapiExampleGateway: 29 | Type: AWS::Serverless::Api 30 | Properties: 31 | StageName: prod 32 | OpenApiVersion: '3.0.0' 33 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwpnd/fastapi-aws-lambda-example/c68aef5bff12df42070be41c33b7e1d9d63344be/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_example_endpoint.py: -------------------------------------------------------------------------------- 1 | from starlette.testclient import TestClient 2 | from example_app.main import app 3 | from example_app.core.config import API_V1_STR 4 | import json 5 | 6 | client = TestClient(app) 7 | 8 | 9 | def is_json(myjson): 10 | try: 11 | json_object = json.loads(myjson) 12 | except ValueError as e: 13 | return False 14 | return True 15 | 16 | 17 | def test_example_endpoint_availability(): 18 | response = client.get(API_V1_STR + "/example") 19 | assert response.status_code == 200 20 | 21 | 22 | def test_example_route_valid_json(): 23 | response = client.get(API_V1_STR + "/example") 24 | assert is_json(response.content) 25 | 26 | 27 | def test_example_endpoint_post(): 28 | payload = {"a": 4, "b": 6} 29 | response = client.post(API_V1_STR + "/example", json=payload) 30 | assert response.status_code == 200 31 | assert all([k in response.json() for k in ["a", "b", "result"]]) 32 | assert response.json()["result"] == 24 33 | -------------------------------------------------------------------------------- /tests/test_ping.py: -------------------------------------------------------------------------------- 1 | from starlette.testclient import TestClient 2 | from example_app.main import app 3 | 4 | client = TestClient(app) 5 | 6 | 7 | def test_ping(): 8 | response = client.get("/ping") 9 | assert response.status_code == 200 10 | -------------------------------------------------------------------------------- /tree.txt: -------------------------------------------------------------------------------- 1 | . 2 | ├── Dockerfile 3 | ├── LICENSE 4 | ├── README.md 5 | ├── app 6 | │   ├── __init__.py 7 | │   ├── api 8 | │   │   ├── __init__.py 9 | │   │   └── api_v1 10 | │   │   ├── __init__.py 11 | │   │   ├── api.py 12 | │   │   └── endpoints 13 | │   │   ├── __init__.py 14 | │   │   └── endpoint.py 15 | │   ├── core 16 | │   │   ├── __init__.py 17 | │   │   ├── config.py 18 | │   │   └── models 19 | │   └── main.py 20 | ├── pyproject.toml 21 | ├── requirements.txt 22 | ├── scripts 23 | ├── template.yml 24 | ├── tests 25 | │   └── __init__.py 26 | └── tree.txt 27 | 28 | 8 directories, 17 files 29 | --------------------------------------------------------------------------------