## Create and Run in Venv or on server port ```bash poetry init && poetry shell && poetry add 'fastapi[all]' ``` After Code is setup and ready to run: ```bash # runs the app in the main.py file with the --reload option so that # changes when the file is saved are hot loaded into the running API service # on the port where the API is listening # assumes there is a python package # . My_Repo_Name # ├─── app/ # │ ├─── __init__.py # │ └─── main.py # │ └─── app() uvicorn app.main:app --reload ``` ## Create a FastAPI ```python from fastapi import FastAPI app = FastAPI() ``` ## Documentation Auto generated documentation available on the API's port by default and is automatically constructed! http://127.0.0.1:8000/docs ## Path definitions for API End Points <https://calmcode.io/til/fastapi-two-decorators.html> > Let's consider a very basic FastAPI app that has a health endpoint. ```python from fastapi import FastAPI app = FastAPI() @app.get("/health") def get_health(): return {"status": "alive"} ``` > This `/health` endpoint has one job: to return a response with status 200. It can serve as a signal that the service is healthy and this is typically used by cloud providers as a indication that a service might need a restart. > > Some services however, don't use `/health` but `/healthz` instead. So how might you implement this? You could add another route. ```python from fastapi import FastAPI app = FastAPI() @app.get("/health") def get_health(): return {"status": "alive"} @app.get("/healthz") def get_health(): return {"status": "alive"} ``` > But there's a simpler way, you can also just do this: ```python from fastapi import FastAPI app = FastAPI() @app.get("/health") @app.get("/healthz") def get_health(): return {"status": "alive"} ``` > You can totally stack decorators in Python, and FastAPI will just consider it another route that needs to be added. > > So you only need a single line of code. Neat! ### ROOT ```python # This is a path operation / or also called a route @app.get('/') # this actually converts this function into an API end point that the user can access # │ └──── The path operation, this is what happens when the users goes to the root, it path was `/login` then the user would only get here if they went to our site and `/login` # └───────── The GET HTTP method is used to retrieve data from a server async def root(): return {"message": "Hello from my API"} # fastapi automatically converts to JSON ``` Without Comments: ```python @app.get('/') async def root(): return {"message": "Hello from my API"} ``` ### POST ```python from fastapi.params import Body from pydantic import BaseModel @app.post('/posts') # V1 def create_posts(payLoad: dict = Body(...)): # Take in the body (data) of the POST request and do stuff with it print(payLoad) return {"new_post": f"{payLoad['title'] =} {payLoad['content'] =}"} #===========================================================================# @app.post('/posts') # V2 def create_posts(new_post: Post): # Take in the body (data) of the POST request and do stuff with it print(new_post) return {"new_post": f"{new_post.title =} {new_post.content =}"} ``` ### GET ```python @app.get('/posts') # in this instance this data gets returned to the user at 127.0.0.1:8000/posts # Due to granularity for the paths in the decorators if there are duplicates then the first one that matches is chosen def get_posts(): return {"data": "this is your posts"} ``` Get Without Comments: ```python @app.get('/posts') def get_posts(): return {"data": "this is your posts"} ``` ### PUT ```python @app.put("/posts/{id}") def update_post(id: int, post: Post): index = find_index_post(id) if index == None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} does not exist") post_dict = post.dict() post_dict['id'] = id my_posts[index] = post_dict print(post) return {"data": post_dict} ``` ### DELETE ```python @app.delete("/posts/{id}", status_code=status.HTTP_204_NO_CONTENT) def delete_post(id: int): index = find_index_post(id) if index == None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} does not exist") my_posts.pop(index) return Response(status_code=status.HTTP_204_NO_CONTENT) ``` ## Order matters If your API function has a path parameter then your other more specific paths need to come before it so that it doesnt think part of the path is the expected input parameter ```python @app.get("/posts/latest") def get_latest_post(): post = my_posts[len(my_posts) - 1] return {"detail": post} @app.get("/posts/{id}") def get_post(id: int): post = find_post(id) return {"post_detail": post} ``` ## Authentication ![[2022-01-11-10-25-47.png]] ## Test Client ```python from fastapi import FastAPI from fastapi.testclient import TestClient from app.main import app import pytest client = TestClient(app) def test_root(): result = client.get('/') print(result.json().get('message')) assert result.json().get('message') == 'Hello World' assert result.status_code == 200 # return {"msg": "Hello World"} def test_create_user(): result = client.post("/users/", json={"email": "[email protected]","password": "password123"}) # print(result.json().get('message')) print(result.json()) assert result.json().get('email') == "[email protected]" assert result.status_code == 201 ``` ## HTTP Status Codes ### 204 Cant delete something already deleted or doesnt exist and therefore cannot delete. No data returned ```python @app.delete("/posts/{id}", status_code=status.HTTP_204_NO_CONTENT) def delete_post(id: int): index = find_index_post(id) if index == None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} does not exist") my_posts.pop(index) return Response(status_code=status.HTTP_204_NO_CONTENT) ``` ### 404 ```python from fastapi import FastAPI, Response, status, HTTPException app = FastAPI() @app.get("/posts/{id}") def get_post(id: int, response: Response): post = find_post(id) if not post: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"post with id: {id} was not found") return {"post_detail": post} ``` ## Router ```markdown . └───app │ database.py │ utils.py │ __main__.py │ └───routers post.py user.py ``` using routers you can break apart your API code into separate modules ### In the submodules ```python from fastapi import APIRouter router = APIRouter() # change the @app decorator from `@app` to `@router` ``` ### In the main file ```python from .routers import post, user app.include_router(post.router) ``` this is like a [[header-file]] where all the code gets dumped into that location. So this is a way of separating out the code to separate files cleanly. ### Prefixes Adding a prefix to the router object makes it so you can keep the endpoint path short and sweet. passing this: ```python router = APIRouter ( prefix="/posts" ) ``` makes it so you can take this code: ```python @app.get('/posts') def get_posts(): ... @app.get("/posts/latest") def get_latest_post(): ... @app.get("/posts/{id}") def get_post(): ... ``` And turn it into this: ```python @app.get('') def get_posts(): ... @app.get("/latest") def get_latest_post(): ... @app.get("/{id}") def get_post(): ... ``` ### Documenation Tags Specifying tags in the router object makes the documentation for granularly defined and easier to read. ```python router = APIRouter( prefix='/posts', tags='posts' ) ``` results in something like this: ![[2022-01-11-10-22-42.png]]