A Guide to Starting a FastAPI + Poetry + Serverless Project
Nimish Verma
Posted on October 20, 2021
(My first DEV.to article)
Hello, in this article I will go over setting up a basic FastAPI app using Poetry, and deploying/packaging it using Serverless. I will also go over Serverless Lift, and use it to generate a DynamoDB instance.
If you are already aware of the technologies mentioned above, please skip to the next section.
Section 1 - Introduction to the Technologies.
FastAPI
FastAPI is a Python-based web framework based on ASGI (Starlette) that is used to make APIs, mostly. As the name suggests, it is much faster than Django and Flask, and comes with a few features (as compared to the star-studded Django) such as pydantic typing, and OpenAPI documentation. I have written an article on FastAPI over here.
Python Frameworks and REST API. | by Nimish Verma | The Startup | Medium | The Startup
Nimish Verma γ» γ»
Medium
Poetry
Poetry is a package manager for Python. For people with background in Javascript, can think of it as a npm manager. Just like package.json
(poetry.toml
) and package-lock.json
(poetry.lock
), Poetry maintains dependency tree, virtual environments, and also comes with a CLI.
Using Poetry is not mandatory, I personally am new to it too. Poetry allows us to manage config dependencies and resolve dependency issues which normally occur in old/unmaintained third party libraries that results in conflicted dependencies. Not only that, it allows a better reproduction of the environment and publishing to Pypi.
To install Poetry CLI run curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
for osx, linux; and for windows run (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python -
in Powershell. More instructions here
Serverless
Serverless is gaining a lot of popularity as we are transitioning to a microservices architecture. Serverless comes with a CLI and a dashboard that helps you monitor your serverless functions on a variety of different cloud providers. It provides a higher level layer to monitor and deploy these functions easily.
I have been using Serverless for a bunch of different side projects and it comes with various plugins to make deployment easier for cloud infrastructures.
To install Serverless CLI, download using curl -o- -L https://slss.io/install | bash
or npm install -g serverless
Section 2 - Starting a FastAPI project with Poetry
After having installed Poetry, let us initialize a poetry project.
poetry new my-project # change project name to whatever you want
This creates a python package with a README, tests directory, and a couple of poetry files. Next we install fastapi using
poetry add fastapi uvicorn[standard]
These two are required in order to start a FastAPI project and run the ASGI Starlette server.
In my-project/my-project
we create our app.py, a pretty basic one for the sake of this project. We write two endpoints for now as follows-
from fastapi import FastAPI
import os
stage = os.environ.get('STAGE', 'dev')
app = FastAPI()
@app.get("/")
def index():
return {"Hello": "World"}
@app.get("/users/{user_id}")
def read_item(user_id: int):
return {"user_id": user_id}
To run this project, normally we would run using
uvicorn my-project.app:app
Step 3 - Serverless Packaging and Deployment
Now we make a serverless.yml
in the root folder my-project
service: My-Project
package:
individually: true
provider:
name: aws
profile: ${opt:aws-profile, "default"}
region: "us-west-2"
stage: ${opt:stage, "dev"}
runtime: python3.8
plugins:
- serverless-offline
- serverless-python-requirements
custom:
pythonRequirements:
dockerizePip: true
usePoetry: true
functions:
app:
handler: my-project.app.handler #I will explain why we have handler here
environment:
STAGE: ${self:provider.stage}
events:
- http:
method: get
path: /
- http:
method: any
path: /{proxy+}
This is pretty easy to understand, we use the standard python packaging plugin for serverless called serverless-python-requirements
. It packages the python project that contains a requirements.txt
or a poetry/pipenv package. We point our handler to the app.py
.
Right now the app file does not export any handler, so you might be wondering why did I not use app.app
instead. This is because we have to wrap our ASGI app to adapt AWS Lambda and API Gateway. For that we use Mangum.
First we install mangum using
poetry add mangum
Next we wrap our app into mangum using the following addition to app.py
from mangum import Mangum
handler = Mangum(app)
This makes our ASGI app (FastAPI) support API Gateway for HTTP, REST, and WebSockets.
Before proceeding to next step we just have to initialize our npm project and install serverless-python-requirements
to it, since serverless is a node package.
In the project root run
npm init --yes
npm install serverless-python-requiremnts
Step 4 - Using Serverless Lift [πBONUSπ]
Serverless Lift provides another abstraction layer over the AWS SDK to initialize and deploy services like storage, webhooks, static pages, databases, queues and more! We are gonna install it using
serverless plugin install -n serverless-lift
and add it to our serverless.yml
which should look like
plugins:
- serverless-offline
- serverless-python-requirements
- serverless-lift
Next, we are gonna construct a DynamoDB table (single table) and use it to access user information in our database.
With the following code in serverless.yml
we will have the following handled automatically:
β Deploy, if not already exists, a DynamoDB table with generic PK and SK, and up to 20 configurable secondary composite indices called GSIs with generic PKs and SKs for each of them.
β Stream setup with OLD_IMAGES and NEW_IMAGES
β Other small setup configs such as cost set to pay per req
and TTL enabled.
β Automatically assigned necessary permissions to your lambda functions in this yaml file.
β Variable name injection for table name and stream name
constructs:
myTable:
type: database/dynamodb-single-table
functions:
app:
handler: my-project.app.handler
environment:
STAGE: ${self:provider.stage}
TABLE_NAME: ${construct:myTable.tableName}
events:
- http:
method: get
path: /
- http:
method: any
path: /{proxy+}
Now in our app.py
we can access this table name and don't have to worry about assigning the lambda an IAM role.
For the users endpoint we can do the following (make sure you populate your DB first obviously, but this is just for the sake of an example):
@app.get("/users/user_id")
def read_item(user_id: int):
table_name = os.environ.get('TABLE_NAME', '')
table = boto3.resource("dynamodb", region_name='us-west-2').Table(table_name)
response = table.get_item(
Key={
'PK': user_id
}
)
return {"user_obj": response['Item']}
To make this run just install boto3
poetry add boto3
Et voila!
Step 5 - Testing and Deploying
To test this locally we run
serverless offline --stage dev --noPrependStageInUrl
If you dont include the --noPrependStageInUrl
flag, it will run your server at localhost:3000/dev/{proxy}+
. If you to run it like that, make sure you include root_path='dev'
parameter in app=FastAPI()
to see the docs
We see that it runs locally, and also shows us the docs. To deploy this we use
serverless deploy --stage dev #or prod
And serverless will deploy it in our AWS profile, as long as you have the initial serverless config setup.
Known issue of serverless-python-requirements is that it will throw a Poetry not found error when you try to deploy or package the sls project. To fix that please go node_modules/serverless-python-requirements/lib/poetry.js
and replace the res
at line 17 with
const res = spawnSync(
'poetry',
[
'export',
'--without-hashes',
'-f',
'requirements.txt',
'-o',
'requirements.txt',
'--with-credentials',
],
{
cwd: this.servicePath,
shell: true // <- we added this
}
);
This will prevent that issue. Kudos to this issue creator
"Error: poetry not found! Install it according to the poetry docs." on Windows 10 #609
Hi,
I'm on Windows10. Serverless environment:
Your Environment Information --------------------------- Operating System: win32 Node Version: 14.15.4 Framework Version: 2.42.0 Plugin Version: 5.1.2 SDK Version: 4.2.2 Components Version: 3.10.0
Version of plugin:
5.1.1
I'm using poetry and according to the documentation, this should work fine. From the doc:
If you include a pyproject.toml and have poetry installed instead of a requirements.txt this will use poetry export --without-hashes -f requirements.txt -o requirements.txt --with-credentials to generate them.
But I ran into this error when I tried to deploy:
PS > serverless deploy Serverless: Generating requirements.txt from pyproject.toml...
Error ---------------------------------------------------
Error: poetry not found! Install it according to the poetry docs. at ServerlessPythonRequirements.pyprojectTomlToRequirements (C:<path replaced>\node_modules\serverless-python-requirements\lib\poetry.js:34:13)
After some research I found this comment: https://stackoverflow.com/a/54515183/5759828 suggesting to use {shell:true}. As part of my testing I found that the output of spawnSync (line 17 in poetry.js) is:
error: Error: spawnSync poetry ENOENT
I then added shell:true to poetry.js like this:
const res = spawnSync(
'poetry',
[
'export',
'--without-hashes',
'-f',
'requirements.txt',
'-o',
'requirements.txt',
'--with-credentials',
],
{
cwd: this.servicePath,
shell: true <--- added this
}
);
and now it works fine.
ππππ
If you read it till here, thank you for reading the article. Make sure you share and like this article. If you think there is a fault or if I missed something, please reach out.
The code for this is hosted on Github in case anyone is interested.
NimishVerma / ServerlessFastapiPoetry
Could not have thought of a better name, lol
References
Posted on October 20, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.