26
loading...
This website collects cookies to deliver better user experience
mkdir fastapi_learn && cd fastapi_learn
app
directory and enter it. Create an an empty file __init__.py
and main.py
.
├── app
├── __init__.py
└── main.py
# Python 2:
$ virtualenv env
# Python 3
$ python3 -m venv env
$ source env/bin/activate
(env) $
$ pip install fastapi
$ pip install "uvicorn[standard]"
touch main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"Hello": "World"}
/
using get
operation and define a path operation function root
which is a python function that is called by FastAPI whenever it receives a request to the URL /
using a GET operation.. For a person coming from flask, the name path operations seemed very strange to me but this term is synonymous to routes ICYMI.$ uvicorn main:app --reload
main
: the file main.py
(the Python "module").app
: the object created inside of main.py
with the line app = FastAPI()
.--reload
: make the server restart after code changes. Only do this for development.
If everything is okay one should get the following output
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28720]
INFO: Started server process [28722]
INFO: Waiting for application startup.
INFO: Application startup complete.
{"Hello": "World"}
GET
requests in the path /
the goal however is to create a simple CRUD
API. A CRUD app is a specific type of software application that consists of four basic operations; Create, Read, Update, Delete. We start by creating our dummy database which is a simple list of dictionaries to store posts.#dummy in memory posts
posts = [{
"title": "title of post 1",
"content": "content of post 1",
"id": 1,
"published": True,
"rating": 8
},
{
"title": "title of post 2",
"content": "content of post 2",
"id": 2,
"published": True,
"rating": 6.5
}
]
The title
The content
The ID
The published flag
The rating
We then create a new path operation to view all posts
# get endpoint to get all posts
@app.get("/posts")
def get_posts():
return {"data":f"{posts}"}
request body
is data sent by the client to your API. A response body
is the data your API sends to the client. We leverage pydantic
and use python type annotations
put simply,We define how data(request and response body) should be in pure, canonical python and validate it with pydantic
. BaseModel
and use standard Python types for all the attributes:from typing import Optional
from pydantic import BaseModel
...
# request body pydantic model
class Post(BaseModel):
title: str
content: str
published: bool = True
rating: Optional[int] = None
...
# post endpoint to add a post
@app.post("/posts",status_code=status.HTTP_201_CREATED)
def create_posts(post:Post):
last_id = int(posts[-1]['id'])
payload = post.dict()
payload["id"] = last_id+1
posts.append(payload)
return {"data":f"{payload}"}
# get endpoint to get a post by id as a path parameter
@app.get("/posts/{post_id}")
def get_post(post_id: int, response: Response):
post = find_post(post_id)
if not post:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=f"post with id: {post_id} was not found")
#response.status_code = status.HTTP_404_NOT_FOUND
#return {"message": }
return {"data":post}
# get endpoint to get the latest post
@app.get("/posts/latest")
def get_latest():
post = posts[len(posts)-1]
return {"data":f"{post}"}
# put endpoint to update a post
@app.put("/posts/{post_id}")
def update_post(post_id:int,post:Post):
if not find_post(post_id):
raise(HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail=f"post with id: {post_id} was not found"))
else:
#update the post
for key,value in post.dict().items():
#print(f"{key} : {value}")
posts[post_id-1][key] = value
return{"data":f"{posts[post_id-1]}","message":f"Successfully updated post wihth id {post_id}"}
# post end point to delete a post
@app.delete("/posts/{post_id}",status_code=status.HTTP_204_NO_CONTENT)
def delete_post(post_id:int):
post = find_post(post_id)
if not post:
raise(HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail=f"post with id: {post_id} was not found"))
else:
# delete the post
posts.pop(post_id-1)
return Response(status_code=status.HTTP_204_NO_CONTENT)
main.py
looks like this at this point:from fastapi import FastAPI,Response,status,HTTPException
from fastapi.params import Body
from typing import Optional
from pydantic import BaseModel
# aplication instance
app = FastAPI()
# request body pydantic model
class Post(BaseModel):
title: str
content: str
published: bool = True
rating: Optional[int] = None
#dummy in memory posts
posts = [{
"title": "title of post 1",
"content": "content of post 1",
"id": 1,
"published": True,
"rating": 8
},
{
"title": "title of post 2",
"content": "content of post 2",
"id": 2,
"published": True,
"rating": 6.5
}
]
def find_post(id):
for i,post in enumerate(posts):
if post["id"] == id:
return post
#path operations - synonymous to routes
#root
@app.get("/")
def root():
message = "Hello am learning FastAPI!!"
return{"message":f"{message}"}
# get endpoint to get all posts
@app.get("/posts")
def get_posts():
return {"data":f"{posts}"}
# post endpoint to add a post
@app.post("/posts",status_code=status.HTTP_201_CREATED)
def create_posts(post:Post):
last_id = int(posts[-1]['id'])
#print(last_id)
payload = post.dict()
payload["id"] = last_id+1
#print(payload)
posts.append(payload)
return {"data":f"{payload}"}
# get endpoint to get the latest post
@app.get("/posts/latest")
def get_latest():
post = posts[len(posts)-1]
return {"data":f"{post}"}
# get endpoint to get a post by id as a path parameter
@app.get("/posts/{post_id}")
def get_post(post_id: int, response: Response):
post = find_post(post_id)
if not post:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail=f"post with id: {post_id} was not found")
#response.status_code = status.HTTP_404_NOT_FOUND
#return {"message": }
return {"data":post}
# post end point to delete a post
@app.delete("/posts/{post_id}",status_code=status.HTTP_204_NO_CONTENT)
def delete_post(post_id:int):
post = find_post(post_id)
if not post:
raise(HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail=f"post with id: {post_id} was not found"))
else:
# delete the post
posts.pop(post_id-1)
return Response(status_code=status.HTTP_204_NO_CONTENT)
# put endpoint to update a post
@app.put("/posts/{post_id}")
def update_post(post_id:int,post:Post):
if not find_post(post_id):
raise(HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail=f"post with id: {post_id} was not found"))
else:
#update the post
for key,value in post.dict().items():
#print(f"{key} : {value}")
posts[post_id-1][key] = value
return{"data":f"{posts[post_id-1]}","message":f"Successfully updated post wihth id {post_id}"}
requirements.txt
file by running:$ pip freeze > requirements.txt
Docker
is a tool that makes it easier to create, deploy, and run applications using containers
.With Docker you can easily deploy a web application along with its dependencies, environment variables, and configuration settings - everything you need to recreate your environment quickly and efficiently.docker container
is a collection of dependencies and code organised as software that enables applications to run quickly and efficiently in a range of computing environments.docker image
, on the other hand, is a blueprint that specifies how to run an application. In order for Docker to build images automatically, a set of instructions must be stored in a special file known as a Dockerfile
.$ sudo apt-get remove docker docker-engine docker.io containerd runc
apt
package index and install packages to allow apt to use a repository over HTTPSsudo apt-get install ca-certificates curl gnupg lsb-release
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
apt
package and install the latest version of Docker Engine
and containerd
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io
docker
command as root
this is the default behaviour to change this add your username to the docker group:$ sudo usermod -aG docker ${USER}
Docker
can build images automatically by reading the instructions from a Dockerfile
. A Dockerfile
is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.A Dockerfile
adheres to a specific format and set of instructions which you can find at Dockerfile reference.Dockerfile
at the root of our working directory, and add the following instructions to it :FROM python:3.6
WORKDIR /fastapi
COPY requirements.txt /fastapi
RUN pip install -r requirements.txt
COPY ./app /fastapi/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
FROM
specifies the parent image Python:3.6
from which you are buildingWORKDIR
sets the current working directory to /fastapi for any RUN
, CMD
,COPY
and ADD
instructions that follow it in the Dockerfile
.COPY
Copies the file with the requirements to the /fastapi directory.CMD
specifies what command to run within the container.The main purpose of a CMD
is to provide defaults for an executing container.In this case it runs the uvicorn server.There can only be one CMD
instruction in a Dockerfile
. If you list more than one CMD
then only the last CMD
will take effect.RUN
actually runs a command and commits the result; CMD
does not execute anything at build time, but specifies the intended command for the image..
├── app
│ ├── __init__.py
│ └── main.py
├── Dockerfile
├── requirements.txt
└── env
$ docker build -t fastapi_learn .
$ docker run -d -p 8000:80 fastapi_learn
{"message": "Hello am learning FastAPI!!"}