22
loading...
This website collects cookies to deliver better user experience
docker-compose up
from the root of the project to spin up everything and let's get coding! The first thing we'll do is creating some crud utilities for reading from our database. Well create two functions:get_question
to fetch a specific question based on an ID;list_questions
to list all the existing pollscrud.py
file in your poll app and write the following code in it:# crud.py
from sqlalchemy import select
from sqlalchemy.orm import Session
from polls import models
from polls import schemas
def get_question(db: Session, question_id: int):
question_query = select(models.Question).where(models.Question.id == question_id)
question = db.execute(question_query).one().Question
return question
def list_questions(db: Session):
question_query = select(models.Question)
questions = db.execute(question_query).scalars().all()
return questions
django-orm
. The querying syntax looks a lot like SQL, and that's what I like it so much: if you'realready comfortable working with SQL, learning to use SQLAlchemy is a breeze! select(models.Question).where(models.Question.id == question_id)
is equivalent to the following SQL statement:SELECT * FROM polls_question WHERE polls_question.id = 1
SELECT *FROM polls_question as questionLEFTJOIN polls_choice as choice on choice.question_id = question.id
schemas.py
file to your poll app, and create the following schemas :# app/polls/schemas.py
import datetimefrom typing import List
import pydantic
class Choice(pydantic.BaseModel):
choice_text: str
votes: int
class Config:
orm_mode = True
class Question(pydantic.BaseModel):
id: int
question_text: str
pub_date: datetime.datetime
class Config:
orm_mode = True
class ReadQuestionChoices(ReadQuestion):
id: int
question_text: str
pub_date: datetime.datetime
choices: List[BaseChoice]classConfig: orm_mode = True
Question
schema and the Question
model, are very similar. This is not ideal, as this can lead to a bit of code duplication. orm_mode
to true means that pydantic will know to access the attributes with the object.attribute
syntax rather than dict['key']
. And when accessing the choices
attribute, SQLAlchemy will take care of making the necessary joins, which will offer us a nice nested structure where each question is associated with an array of related choices.ReadQuestion
and ReadQuestionChoices
have most of their attributes in common, which is not very DRY. Pydantic support inheriting from existing schemas though, which means we can re-write it like that:import datetime
from typing import List
import pydantic
class BaseChoice(pydantic.BaseModel):
choice_text: str
votes: int
class Config:
orm_mode = True
class BaseQuestion(pydantic.BaseModel):
question_text: str
pub_date: datetime.datetime
class Config:
orm_mode = True
# We only include the id when reading
# That way we can reuse base question
# When creating our write schemas
class ReadQuestion(BaseQuestion):
id: int
class ReadQuestionChoices(ReadQuestion):
choices: List[BaseChoice]
/polls/
;/polls/{id}
GET /polls/{id}
endpoint to fetch the data they need. polls/endpoints.py
create those two path operations :# polls/endpoints.py
from typing import List
from . import schemas
from . import crud
@router.get("/", response_model=List[schemas.ReadQuestion])
async def index():
return crud.list_questions(db)
@router.get("/{id}/", response_model=schemas.ReadQuestionChoices)
async def question_detail(id: int):
try:
return crud.get_question(db, id)
except NoResultFound:
raise HTTPException(
status_code=404,
detail="Token does not exist",
)
router.get
call : the response_model
. This response model will be applied to the value returned by the path operation, before sending the response to the client. We're using the List
type from the standard typing
package to specify that we returns several questions in the index.question_detail
path operation, we specify the {id} in the router url, and FastAPI is smart enough to automatically pass it as the first parameter to our path operations !Session
objects to communicate with the database. We're passing them a variable called db
here, but it's not defined anywhere yet ! self.request
object available in Class Based view is a dependency injection pattern ! Hope this clears at up !SessionLocal
we created in database.py
we created in part 2# polls/endpoints.py
# Database session as a dependency
def get_db():
db = database.SessionLocal()
try:
yield db
finally:
db.close()
Depends
function providing from FastAPI:# polls/endpoints.py
...
from fastapi import Depends
...
@router.get("/", response_model=List[schemas.ReadQuestion])
async def index(db=Depends(get_db)):
return crud.list_questions(db)
@router.get("/{id}/", response_model=schemas.ReadQuestionChoices)
async def question_detail(id: int, db=Depends(get_db)):
try:
return crud.get_question(db, id)
except NoResultFound:
raise HTTPException(
status_code=404,
detail="Token does not exist",
)
localhost/docs
. Create a few questions using PGAdmin or a psql client to create a few questions and choices, and try it out ! Everyting should be working smootlhy, and Swagger should display some information about the reponse form that matches our pydantic schemas.CORSMiddleware
in main.py
:# main.py
...
from fastapi.middleware.cors import CORSMiddleware
origins = ["http://localhost:3000"]
...
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
...