โป FastAPI Tutorial์ ๊ณต๋ถํ๋ฉฐ ์ดํดํ๋๋ก ์์ฑํ ๋ด์ฉ์ผ๋ก, ์ค์ ๋ด์ฉ๊ณผ ๋ค๋ฅด๊ฑฐ๋ ํ๋ฆฐ ๋ถ๋ถ์ด ์์ ์ ์์ต๋๋ค. ๋๊ธ๋ก ์๋ ค์ฃผ์๋ฉด ์ฆ์ ๋ฐ์ํ๊ฒ ์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค.
https://fastapi.tiangolo.com/ko/tutorial/
[์์ต์ - ์ฌ์ฉ์ ์๋ด์ - ๋์ ๋ถ - FastAPI
์์ต์ - ์ฌ์ฉ์ ์๋ด์ - ๋์ ๋ถ ์ด ์์ต์๋ FastAPI์ ๋๋ถ๋ถ์ ๊ธฐ๋ฅ์ ๋จ๊ณ๋ณ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. ๊ฐ ์น์ ์ ์ด์ ์น์ ์ ๊ธฐ๋ฐํด์ ์ ์ง์ ์ผ๋ก ๋ง๋ค์ด ์ก์ง๋ง, ์ฃผ์ ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ
fastapi.tiangolo.com](https://fastapi.tiangolo.com/ko/tutorial/)
1. Body - ์ฌ๋ฌ ๊ฐ์ ๋งค๊ฐ๋ณ์
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(
item_id: Annotated[int, Path(title="the ID of the item to get", ge=0, le=1000)],
q: Union[str, None] = None,
item: Union[Item, None] = None
):
result = {"item_id": item_id}
if q:
result.update({"q": q})
if item:
result.update({"item": item})
return result
Body - ๋จ์ผ ๋ณ์
@app.put("/body/{item_id}")
async def update_body(
item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
importance: Annotated[int, Body()]
⇒ ์ด๋ฌ๋ฉด ๋ฐ์ดํฐ ๋ชฉ๋ก-๊ทธ๋ฌ๋๊น response body์ ํฌํจ๋จ- ์ด๋ฌ์ง ์์ผ๋ฉด Query ๋งค๊ฐ๋ณ์๋ก ํ๋จ๋๊ณ ๋ฐ์ดํฐ ๋ฐ๋๋ก ๋ค์ด๊ฐ์ง ์์. ์๋ฅผ ์คํํ๋ฉด ์๋์ฒ๋ผ ๋์จ๋ค.
- query ๋งค๊ฐ๋ณ์๋ ์ฃผ์์ฐฝ์ ํ์๋๊ณ , body์ ํฌํจ๋๋ ์น๊ตฌ๋ค์ ํ์๋์ง ์๋๋ค.
- ๋ณด์์์ ์๋ฏธ๊ฐ ํฐ ๊ฒ ๊ฐ๋ค.
{ "item_id": 3, "item": { "name": "hell", "description": "biohazard", "price": 0, "tax": 0 }, "user": { "username": "mia", "full_name": "edan" }, "importance": 10 }
๋ค์ค ๋ณ์
@app.put("/body/{item_id}")
async def update_body(
item_id: int, item: Item, user: User, importance: Annotated[int, Body()], q: Union[str, None] = None
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
- ์์ ๊ฐ์ด ์ผ๋ฐ ์ฟผ๋ฆฌ ๋งค๊ฐ๋ณ์์ ํจ๊ป ์ฐ๋ ๊ฒ๋ ๋น์ฐํ ๊ฐ๋ฅํจ
๋ฑ ํ๋๋ง body์ ํฌํจ์ํค๊ณ ์ ํ ๋
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
Body(embed=True)
๋ฅผ ๋ถ์ด๋ฉด ์ผ์ชฝ์ด ์ค๋ฅธ์ชฝ์ฒ๋ผ ๋ณํจ
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
}
2. Body - Field
⇒ pydantic model ์์์ ์์ฑ์ ์ถ๊ฐํ ๋ = field
from typing import Union
from fastapi import Body, FastAPI
from pydantic import BaseModel, Field
from typing_extensions import Annotated
app = FastAPI()
class Item(BaseModel):
name: str
description: Union[str, None] = Field(
default=None, title="The description of the item", max_length=300
)
price: float = Field(gt=0, description="The price must be greater than zero")
tax: Union[float, None] = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
3. Body - Nested models
from typing import List, Set, Union
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
tags: Set[str] = set()
images: Union[List[Image], None] = None
class Offer(BaseModel):
name: str
description: Union[str, None] = None
price: float
items: List[Item]
@app.post("/offers/")
async def create_offer(offer: Offer):
return offer
- ์ฐ์์ ์ผ๋ก ์ ์ํ ํด๋์ค๋ฅผ ์๋ฃํ๋ง๋ฅ ์ฌ์ฉํ๋ ์์
- ๋จ์ ๋ฆฌ์คํธ๋ก ๊ตฌ์ฑ๋ body๋ ์ฌ์ฉ ๊ฐ๋ฅ
from typing import List
from fastapi import FastAPI
from pydantic import BaseModel, HttpUrl
app = FastAPI()
class Image(BaseModel):
url: HttpUrl
name: str
@app.post("/images/multiple/")
async def create_multiple_images(images: List[Image]):
return images
- dictionary์ ๊ฒฝ์ฐ key์ value์ ์๋ฃํ์ ๋ฐ๋ก.
@app.post("/index-weights/")
async def create_index_weights(weights: Dict[int, float]):
return weights
4.Request ์์ ๋ฐ์ดํฐ ์ ์ธ
⇒ Pydantic model์ ๊ธฐ๋ฐ์ผ๋ก ํ ํด๋์ค์์๋ config๋ผ๋ ์๋ธ ํด๋์ค์ schema_extra๋ผ๋ ๊ฑฐ ์ฐ๋ฉด ๋๋ค.
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: Union[float, None] = None
class Config:
schema_extra = {
"example": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results
# ๊ธฐ๋ณธ ์์๋ฐ์ดํฐ๊ฐ request body์ ๋ฏธ๋ฆฌ ์์. JSON
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2
}
Field
์๋ ๋ฐ๋ก example์ ๋ฃ์ด์ฃผ๋ ๋ฐฉ๋ฒ๋ ์๋ค.
Body๋ก Annotatedํ ๋ ์์ ๋ฃ๊ธฐ
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Annotated[
Item,
Body(
example={
"name": "Foo",
"description": "A very nice item",
"price": 24.3,
"tax": 3.3
}
)
]
):
results = {"item_id": item_id, "item": item}
return results
- ์ฌ๋ฌ ๊ฐ์ example ๋ฃ๊ธฐ?
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Annotated[
Item,
Body(
examples={
"normal": {
"summary": "A normal example",
"description": "A **normal** item works correctly.",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted data",
"description": "FastAPI can convert price `strings` to actual `numbers` automatically",
"value": {
"name": "Bar",
"price": "35.4", # string to number automatically
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "thirty five point four", # float์ธ๋ฐ string
},
},
},
),
],
):
results = {"item_id": item_id, "item": item}
return results
5. Cookie ๋งค๊ฐ๋ณ์
from typing import Union
from fastapi import Cookie, FastAPI
from typing_extensions import Annotated
app = FastAPI()
@app.get("/items/")
async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None):
return {"ads_id": ads_id}
6. Header ๋งค๊ฐ๋ณ์
- ๋ฉํ๋ฐ์ดํฐ์ ๋ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์์.
from typing import Union
from fastapi import FastAPI, Header
app = FastAPI()
@app.get("/items/")
async def read_items(user_agent: Union[str, None] = Header(default=None)):
return {"User-Agent": user_agent}
→ ๊ธฐ๋ณธ์ ์ผ๋ก path, query, cookie์ ๊ฐ์ ๊ธฐ๋ฅ
- ์ถ๊ฐ ๊ธฐ๋ฅ : ์ธ๋๋ฐ(_)๋ฅผ ํ์ดํ(-)์ผ๋ก ์๋ ๋ณํ HTTP ํ์ค ํค๋ ๊ท๊ฒฉ
- ์ค๋ณต์ ์ผ๋ก header๋ฅผ ์ ์กํ๋ ๊ฒฝ์ฐ ํ์ด์ฌ ๋ฆฌ์คํธ๋ก ์ ์ฅ
7. ์๋ต ๋ชจ๋ธ - ๋ฐํํ
1. ์ผ๋ฐ ํ์ด์ฌ docstring ๋ฌธ๋ฒ๋๋ก ํจ์์ ๋ฐํํ์ ํ๊ธฐํ๊ธฐ
@app.post("/items/")
async def create_item(item: Item) -> Item: # return type = Item object
return item
@app.get("/items/")
async def read_items() -> List[Item]: # return type = list of Item object
return [
Item(name="Portal Gun", price=42.0),
Item(name="Plumbus", price=32.0),
]
2. response_model
parameter ์ฌ์ฉํ๊ธฐ
์ ์ฌ์ ํ์ด์ฌ ์ค๋ฅ๋ฅผ ๋ง๊ธฐ ์ํด์ ์ธ ์ ์์. FastAPI์์ ์ถ๋ ฅ ๋ฐ์ดํฐ๋ฅผ response model์ ์๋ฃํ์ผ๋ก ๋ณํ ๋ฐ ํํฐ๋ง
@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
return item
@app.get("/items/", response_model=List[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
- ๋ฐ๋ผ์ ์ ๋ ฅ๋ฐ์ ๋ฐ์ดํฐ ์ค ์ผ๋ถ๋ฅผ ํํฐ๋งํด์ ์ถ๋ ฅ์ผ๋ก ๋๋ฆฌ๊ณ ์ถ์ผ๋ฉด ๊ทธ๊ฒ๋ ๊ฐ๋ฅ!
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
@app.post("/user/", response_model=UserOut)
async def create_user(user: UserIn) -> Any:
return user
- ์ด ๊ฒฝ์ฐ ์
๋ ฅ ๋ฐ์ดํฐ์์ password๋ฅผ ํํฐ๋งํ
UserOut
์ผ๋ก ์๋ ๋ณํ - python ์ผ๋ฐ ํจ์ ๋ฐํ์๋ค๊ฐ ์ฐ๋ฉด ๋์ด ๋ฌ๋ผ์ ์๋๋ค๊ณ ์ค๋ฅ๋จ.
from typing import Union
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr
app = FastAPI()
class BaseUser(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
class UserIn(BaseUser): # BaseUser ์์!
password: str
# username: str
# email: EmailStr
# full_name: Union[str, None] = None
@app.post("/user/")
async def create_user(user: UserIn) -> BaseUser:
return user
⇒ ์ด ๊ฒฝ์ฐ BaseUser์ UserIn์ด ๊ฐ์ง ์ธ์คํด์ค๊ฐ ํ๋๋ ์๋๋ฐ?
- ์์ ๋ฐ์์ผ๋๊น UserIn์ BaseUser๊ฐ ๊ฐ์ง ๊ฒ์ด ์์๋์ด์์. ๋ฐ๋ผ์ ์์๋ฐ์ ๊ฒ๋ค์ ํํฐ์ ์ ๊ฑธ๋ฆฌ๊ณ ์๋ password๋ง ๊ฑธ๋ฆผ.
- ๊ฒฐ๊ณผ
# request body
{
"username": "dain",
"email": "dmelli0505@gmail.com",
"full_name": "dain kang",
"password": "123456789"
}
# response body
{
"username": "dain",
"email": "dmelli0505@gmail.com",
"full_name": "dain kang"
}
3. ์๋ต ์ง์ ๋ฐํ
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/portal")
async def get_potal(teleport: bool = False) -> Response:
if teleport:
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
return JSONResponse(content={"message": "Here's your interdimenseional portal."})
๋ ํ์ response๋ฅผ ๋ฐํํ๋ ๊ฒ๋ ok.
- ๊ทธ๋ฌ๋ ๋ฐํํ์ด A or B๋ ์๋จ.
@app.get("/portal")
async def get_portal(teleport: bool = False) -> Union[Response, dict]:
if teleport:
return RedirectResponse(url="https://www.youtube.com/watch?v=dQw4w9WgXcQ")
return {"message": "Here's your interdimensional portal."}
- ์ด ๊ฒฝ์ฐ Response or Dictionary ๋ฐํ? ๋ช ์์ ์ด์ง ์์ผ๋ฏ๋ก ๋ถ๊ฐ๋ฅ
@app.get("/portal/", response_model=None)
ํ๋ฉด ๋นํ์ฑํ
4. ์๋ต ๋ชจ๋ธ ๋งค๊ฐ๋ณ์
⇒ ์๋ต๋ชจ๋ธ๋ ๊ธฐ๋ณธ๊ฐ์ ๊ฐ์ง ์ ์์.
class Item(BaseModel):
name: str
description: Union[str, None] = None
price: float
tax: float = 10.5
tags: List[str] = []
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
- ์ด๋ ๊ฒ ์ฐ๋ฉด ๊ธฐ๋ณธ๊ฐ ์๋ ๊ฒ๋ค ๋นผ๊ณ ๋๋จธ์ง๋ง response๋ก ๋ค์ด๊ฐ.
{ name: "temp", price: 2134 }
- ๊ทธ๋ฌ๋ ๋ฏธ๋ฆฌ ๋ฐ์ดํฐ์ ๊ธฐ๋ณธ๊ฐ๊ณผ ๊ฐ์ ๊ฐ์ด ๋ค์ด์๋ ๊ฒฝ์ฐ์๋ ํ๋ฒํ๊ฒ ์ ๋ถ response๋ก ๋ค์ด๊ฐ.
items = [
{
name: "temp",
description: None,
price: 2134,
tax: 10.5,
tags: []
},
...
]
# temp.com/items/0 -> response
{
name: "temp",
description: None,
price: 2134,
tax: 10.5,
tags: []
}
8. ์ ๋ ฅ / ์ถ๋ ฅ / DB ๋ชจ๋ธ
- ์ ๋ ฅ ๋ชจ๋ธ = ๋น๋ฐ๋ฒํธ ํ์
- ์ถ๋ ฅ ๋ชจ๋ธ = ๋น๋ฐ๋ฒํธ ํฌํจํ๋ฉด ์๋จ
- DB ๋ชจ๋ธ = ํด์ ๋น๋ฐ๋ฒํธ๊ฐ ํ์ํ ์ ์์
class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Union[str, None] = None
class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
class UserInDB(BaseModel):
username: str
hashed_password: str
email: EmailStr
full_name: Union[str, None] = None
→ ์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ๋ ๋ถ๋ถ
def fake_password_hasher(raw_password: str):
return "supersecret" + raw_password
def fake_save_user(user_in: UserIn):
hashed_password = fake_password_hasher(user_in.password)
user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password)
print("User saved! ...really?")
return user_in_db
- return type
UserInDB
**user_in.dict()
๋ Pydantic model์ method์ธ๋ฐ, model data์ ๋ํ dictionary๋ฅผ ์ ๊ณตํ๋ค.
**user_in.dict() ⇒ {
"username": "dump",
"password": "1234",
"email": "dump@example.com",
"full_name": "dump dumpio"
}
- ๊ทธ๋ฆฌ๊ณ ๊ฒน์น๋ key์๋ง unwrapping ํ๋ฏ๋ก
UserInDB
์์๋ username, email, full_name๋ง ๋จ๊ฒ ๋๋ค. ๊ฑฐ๊ธฐ์hashed_password
๋ฅผ ๋ํด์ ๊ตฌ์ฑํ ๊ฒ.
@app.post("/user/", response_model=UserOut)
async def create_user(user_in: UserIn):
user_saved = fake_save_user(user_in=user_in)
return user_saved
- ๊ฒฐ๊ณผ
# Request body
{
"username": "dump",
"password": "1234",
"email": "dump@example.com",
"full_name": "dump dumpio"
}
# Response body
{
"username": "dump",
"email": "dump@example.com",
"full_name": "dump dumpio"
}
์ด์ค ์ ์ธ ์ง์ํ๊ธฐ
class UserBase(BaseModel):
username: str
email: EmailStr
full_name: Union[str, None] = None
class UserIn(UserBase):
password: str
class UserOut(UserBase):
pass
class UserInDB(UserBase):
hashed_password: str
์์๋ฐ์์ ๊ธธ์ด ์ค์ด๊ธฐ
๊ธฐํ response model
- ๋ฆฌํด ๋ชจ๋ธ์ด ๋ ์ค ํ๋์ผ ๋ ⇒ Union
@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem]) # Union์ผ๋ก
async def read_item(item_id: str):
return items[item_id]
- ๋ฆฌํด ๋ชจ๋ธ์
List
๋ก ์ฌ์ฉ๋ ๊ฐ๋ฅ
@app.get("/items/", response_model=List[Item])
async def read_items():
return items
- field / attribute ์ด๋ฆ์ ์ ๋ชจ๋ฅผ ๋ Dictionary ํํ๋ก๋ response๋ฅผ ๋ฐํ ๊ฐ๋ฅํ๋ค.
from typing import Dict
from fastapi import FastAPI
app = FastAPI()
@app.get("/keyword-weights/", response_model=Dict[str, float])
async def read_keyword_weights():
return {"foo": 2.3, "bar": 3.4}
Dict[str, float]
์ด๋ฏ๋ก key = string, value = float ํ์ธ ๋์ ๋๋ฆฌ๊ฐ return ๋ ์์ ์์ ๋ช ์
9. ์๋ต ์ํ ์ฝ๋
@app.get()
@app.post()
@app.put()
@app.delete()
์ ๊ฐ์ ๋์์ ์ํํ์ ๋ 404 error์ ๊ฐ์ด ์๋ต ์ํ ์ฝ๋๋ฅผ ๋ฐ์๋ณผ ์ ์๋๋ฐ, ์ด๋ฅผ status_code
๋งค๊ฐ๋ณ์๋ก ์ง์ ํ ์ ์๋ค.
from fastapi import FastAPI
app = FastAPI()
@app.post('/items/', status_code=201)
async def create_item(name:str):
return {"name": name}
HTTP ์ํ์ฝ๋
- 1xx : ์ ๋ณด. ์ง์ ์ ์ผ๋ก ์ฌ์ฉ๋์ง ์์ผ๋ฉฐ body๋ฅผ ๊ฐ์ง ์ ์์
- 2xx : ์ฑ๊ณต์ ์ธ ์๋ต.
- 200 : ์ ์ฒด ์ฑ๊ณต
- 201 : ์์ฑ๋จ - DB์ ์๋ก์ด ๋ ์ฝ๋ ์์ฑ ํ ์ฌ์ฉ
- 204 : ๋ด์ฉ ์์ - ํด๋ผ์ด์ธํธ์ ๋ฐํํ ๊ฒ ์์. body๋ฅผ ๊ฐ์ง ์ ์์.
- 3xx : ๋ฆฌ๋ค์ด๋ ์
์ฉ - body๊ฐ ์์ ์๋ ์๊ณ , ์์ ์๋ ์์.
- 304 : ์์ ๋์ง ์์ - body ์์
- 4xx : ํด๋ผ์ด์ธํธ ์ค๋ฅ
- 404 : ์ฐพ์ ์ ์์
- 400 : ์ผ๋ฐ ํด๋ผ์ด์ธํธ ์ค๋ฅ
- 5xx : ์๋ฒ ์ค๋ฅ
FastAPI์์ ํธ์ ๋ณ์ ์ฌ์ฉ
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
return {"name": name}
'๐ฅ Web > โก Back-end | FastAPI' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[FastAPI] Tutorial(1) (0) | 2023.03.29 |
---|