Skip to main content

FastAPI ์‹œ์ž‘ํ•˜๊ธฐ

FastAPI๋กœ ๋น ๋ฅด๊ณ  ํ˜„๋Œ€์ ์ธ ์›น API๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›Œ๋ด…์‹œ๋‹ค.

FastAPI๋ž€?โ€‹

FastAPI๋Š” Python์œผ๋กœ API๋ฅผ ๋น ๋ฅด๊ฒŒ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ํ˜„๋Œ€์ ์ธ ์›น ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ํƒ€์ž… ํžŒํŠธ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ๊ณผ ๋ฌธ์„œํ™”๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ํŠน์ง•โ€‹

  • ๋น ๋ฅธ ์„ฑ๋Šฅ - Node.js, Go์™€ ๋น„์Šทํ•œ ์ˆ˜์ค€์˜ ์†๋„
  • ์ž๋™ ๋ฌธ์„œํ™” - Swagger UI, ReDoc ์ž๋™ ์ƒ์„ฑ
  • ํƒ€์ž… ์•ˆ์ •์„ฑ - Python ํƒ€์ž… ํžŒํŠธ ํ™œ์šฉ
  • ๋น„๋™๊ธฐ ์ง€์› - async/await ์™„๋ฒฝ ์ง€์›
  • ์‰ฌ์šด ํ•™์Šต - ์ง๊ด€์ ์ด๊ณ  ๊ฐ„๋‹จํ•œ API

์™œ FastAPI์ธ๊ฐ€?โ€‹

# Flask (์ „ํ†ต์ )
@app.route('/items/<int:item_id>')
def read_item(item_id):
# ์ˆ˜๋™์œผ๋กœ ๊ฒ€์ฆ ํ•„์š”
if item_id < 0:
return {"error": "Invalid ID"}, 400
return {"item_id": item_id}

# FastAPI (ํ˜„๋Œ€์ )
@app.get("/items/{item_id}")
def read_item(item_id: int):
# ์ž๋™ ๊ฒ€์ฆ, ์ž๋™ ๋ฌธ์„œํ™”
return {"item_id": item_id}

์„ค์น˜ํ•˜๊ธฐโ€‹

# FastAPI ์„ค์น˜
pip install fastapi

# ASGI ์„œ๋ฒ„ (Uvicorn)
pip install "uvicorn[standard]"

# ํ•œ ๋ฒˆ์— ์„ค์น˜
pip install "fastapi[all]"

์ฒซ ๋ฒˆ์งธ APIโ€‹

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ด…์‹œ๋‹ค.

# main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}

@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}

์„œ๋ฒ„ ์‹คํ–‰โ€‹

# ๊ธฐ๋ณธ ์‹คํ–‰
uvicorn main:app --reload

# ํฌํŠธ ์ง€์ •
uvicorn main:app --reload --port 8080

# ํ˜ธ์ŠคํŠธ ์ง€์ • (์™ธ๋ถ€ ์ ‘์† ํ—ˆ์šฉ)
uvicorn main:app --reload --host 0.0.0.0

์ž๋™ ๋ฌธ์„œ ํ™•์ธโ€‹

์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•œ ํ›„ ๋‹ค์Œ URL๋กœ ์ ‘์†ํ•˜์„ธ์š”:

๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐโ€‹

URL ๊ฒฝ๋กœ์—์„œ ๊ฐ’์„ ์ถ”์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from fastapi import FastAPI

app = FastAPI()

# ๊ธฐ๋ณธ ๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}

# ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ
@app.get("/users/{user_id}/items/{item_id}")
def get_user_item(user_id: int, item_id: str):
return {"user_id": user_id, "item_id": item_id}

# Enum์œผ๋กœ ์ œํ•œ
from enum import Enum

class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"

@app.get("/models/{model_name}")
def get_model(model_name: ModelName):
if model_name == ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}

if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}

return {"model_name": model_name, "message": "Have some residuals"}

# ํŒŒ์ผ ๊ฒฝ๋กœ (ํŠน์ˆ˜ ์ผ€์ด์Šค)
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"file_path": file_path}

๊ฒฝ๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆโ€‹

from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(
item_id: int = Path(
..., # ํ•„์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ
title="Item ID",
description="์กฐํšŒํ•  ์•„์ดํ…œ์˜ ID",
ge=1, # greater than or equal (>=)
le=1000 # less than or equal (<=)
)
):
return {"item_id": item_id}

์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐโ€‹

URL์˜ ์ฟผ๋ฆฌ ์ŠคํŠธ๋ง์—์„œ ๊ฐ’์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from fastapi import FastAPI
from typing import Optional

app = FastAPI()

# ๊ธฐ๋ณธ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ
@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}

# ์„ ํƒ์  ํŒŒ๋ผ๋ฏธํ„ฐ
@app.get("/items/{item_id}")
def read_item(item_id: str, q: Optional[str] = None):
if q:
return {"item_id": item_id, "q": q}
return {"item_id": item_id}

# ์—ฌ๋Ÿฌ ๊ฐ’ ๋ฐ›๊ธฐ
@app.get("/items/")
def read_items(q: Optional[list[str]] = None):
return {"q": q}
# ์š”์ฒญ: /items/?q=foo&q=bar
# ์‘๋‹ต: {"q": ["foo", "bar"]}

# ๋ถˆ๋ฆฐ ํŒŒ๋ผ๋ฏธํ„ฐ
@app.get("/items/{item_id}")
def read_item(item_id: str, short: bool = False):
item = {"item_id": item_id}
if not short:
item.update(
{"description": "This is an amazing item"}
)
return item

์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆโ€‹

from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/items/")
def read_items(
q: Optional[str] = Query(
None,
min_length=3,
max_length=50,
regex="^fixedquery$",
title="Query string",
description="์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์˜ˆ์‹œ"
),
page: int = Query(1, ge=1),
size: int = Query(10, ge=1, le=100)
):
results = {"page": page, "size": size}
if q:
results.update({"q": q})
return results

Request Bodyโ€‹

POST, PUT ์š”์ฒญ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Pydantic ๋ชจ๋ธโ€‹

from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional

app = FastAPI()

# ๋ฐ์ดํ„ฐ ๋ชจ๋ธ ์ •์˜
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None

@app.post("/items/")
def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict

# ๊ฒ€์ฆ ์ถ”๊ฐ€
class ItemWithValidation(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
description: Optional[str] = Field(None, max_length=500)
price: float = Field(..., gt=0, description="๊ฐ€๊ฒฉ์€ 0๋ณด๋‹ค ์ปค์•ผ ํ•ฉ๋‹ˆ๋‹ค")
tax: Optional[float] = Field(None, ge=0, le=100)

@app.post("/validated-items/")
def create_validated_item(item: ItemWithValidation):
return item

์ค‘์ฒฉ๋œ ๋ชจ๋ธโ€‹

from pydantic import BaseModel, HttpUrl
from typing import Optional, List, Set

class Image(BaseModel):
url: HttpUrl
name: str

class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: Set[str] = set()
images: Optional[List[Image]] = None

@app.post("/items/")
def create_item(item: Item):
return item

# ์š”์ฒญ ์˜ˆ์‹œ:
# {
# "name": "Foo",
# "price": 45.2,
# "tags": ["electronics", "computers"],
# "images": [
# {"url": "http://example.com/img1.jpg", "name": "Image 1"},
# {"url": "http://example.com/img2.jpg", "name": "Image 2"}
# ]
# }

Body + Path + Query ํŒŒ๋ผ๋ฏธํ„ฐโ€‹

@app.put("/items/{item_id}")
def update_item(
item_id: int, # Path ํŒŒ๋ผ๋ฏธํ„ฐ
item: Item, # Body
q: Optional[str] = None # Query ํŒŒ๋ผ๋ฏธํ„ฐ
):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result

์‘๋‹ต ๋ชจ๋ธโ€‹

์‘๋‹ต์˜ ํ˜•์‹์„ ์ •์˜ํ•˜๊ณ  ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from pydantic import BaseModel, EmailStr
from typing import Optional

class UserIn(BaseModel):
username: str
password: str
email: EmailStr
full_name: Optional[str] = None

class UserOut(BaseModel):
username: str
email: EmailStr
full_name: Optional[str] = None

@app.post("/users/", response_model=UserOut)
def create_user(user: UserIn):
# ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์‘๋‹ต์—์„œ ์ œ์™ธ๋จ
return user

# ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ ์ง€์ •
from fastapi import status

@app.post("/users/", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def create_user(user: UserIn):
return user

# ๋ฆฌ์ŠคํŠธ ์‘๋‹ต
@app.get("/users/", response_model=List[UserOut])
def read_users():
return [
{"username": "john", "email": "john@example.com"},
{"username": "jane", "email": "jane@example.com"}
]

์—๋Ÿฌ ์ฒ˜๋ฆฌโ€‹

์ ์ ˆํ•œ ์—๋Ÿฌ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from fastapi import FastAPI, HTTPException, status

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}

@app.get("/items/{item_id}")
def read_item(item_id: str):
if item_id not in items:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Item not found",
headers={"X-Error": "There goes my error"}
)
return {"item": items[item_id]}

# ์ปค์Šคํ…€ ์˜ˆ์™ธ
class ItemNotFoundException(Exception):
def __init__(self, item_id: str):
self.item_id = item_id

@app.exception_handler(ItemNotFoundException)
def item_not_found_handler(request, exc: ItemNotFoundException):
return JSONResponse(
status_code=404,
content={"message": f"Item {exc.item_id} not found"}
)

@app.get("/items/{item_id}")
def read_item(item_id: str):
if item_id not in items:
raise ItemNotFoundException(item_id=item_id)
return {"item": items[item_id]}

์˜์กด์„ฑ ์ฃผ์ž…โ€‹

์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ๊ด€์‹ฌ์‚ฌ๋ฅผ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

from fastapi import Depends, FastAPI, HTTPException
from typing import Optional

app = FastAPI()

# ๊ฐ„๋‹จํ•œ ์˜์กด์„ฑ
def common_parameters(q: Optional[str] = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
def read_items(commons: dict = Depends(common_parameters)):
return commons

@app.get("/users/")
def read_users(commons: dict = Depends(common_parameters)):
return commons

# ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ์˜์กด์„ฑ
class CommonQueryParams:
def __init__(self, q: Optional[str] = None, skip: int = 0, limit: int = 100):
self.q = q
self.skip = skip
self.limit = limit

@app.get("/items/")
def read_items(commons: CommonQueryParams = Depends()):
return commons

์ธ์ฆ ์˜์กด์„ฑโ€‹

from fastapi import Depends, HTTPException, Header
from typing import Optional

def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
return x_token

def verify_key(x_key: str = Header(...)):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key

@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]

์‹ค์ „ ์˜ˆ์ œโ€‹

์˜ˆ์ œ 1: ๊ฐ„๋‹จํ•œ Todo APIโ€‹

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional, List

app = FastAPI(title="Todo API", version="1.0.0")

# ๋ฐ์ดํ„ฐ ๋ชจ๋ธ
class TodoItem(BaseModel):
id: Optional[int] = None
title: str
description: Optional[str] = None
completed: bool = False

class TodoUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
completed: Optional[bool] = None

# ์ž„์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
todos: List[TodoItem] = []
todo_counter = 1

# ์ „์ฒด ๋ชฉ๋ก ์กฐํšŒ
@app.get("/todos", response_model=List[TodoItem])
def get_todos(skip: int = 0, limit: int = 10):
return todos[skip : skip + limit]

# ํŠน์ • ํ•ญ๋ชฉ ์กฐํšŒ
@app.get("/todos/{todo_id}", response_model=TodoItem)
def get_todo(todo_id: int):
for todo in todos:
if todo.id == todo_id:
return todo
raise HTTPException(status_code=404, detail="Todo not found")

# ์ƒˆ ํ•ญ๋ชฉ ์ƒ์„ฑ
@app.post("/todos", response_model=TodoItem, status_code=201)
def create_todo(todo: TodoItem):
global todo_counter
todo.id = todo_counter
todo_counter += 1
todos.append(todo)
return todo

# ํ•ญ๋ชฉ ์ˆ˜์ •
@app.put("/todos/{todo_id}", response_model=TodoItem)
def update_todo(todo_id: int, todo_update: TodoUpdate):
for todo in todos:
if todo.id == todo_id:
if todo_update.title is not None:
todo.title = todo_update.title
if todo_update.description is not None:
todo.description = todo_update.description
if todo_update.completed is not None:
todo.completed = todo_update.completed
return todo
raise HTTPException(status_code=404, detail="Todo not found")

# ํ•ญ๋ชฉ ์‚ญ์ œ
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int):
for i, todo in enumerate(todos):
if todo.id == todo_id:
todos.pop(i)
return
raise HTTPException(status_code=404, detail="Todo not found")

# ํ†ต๊ณ„
@app.get("/todos/stats/summary")
def get_stats():
total = len(todos)
completed = sum(1 for todo in todos if todo.completed)
pending = total - completed
return {
"total": total,
"completed": completed,
"pending": pending
}

์˜ˆ์ œ 2: ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ APIโ€‹

from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List
import hashlib

app = FastAPI()

# ๋ชจ๋ธ
class UserBase(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
email: EmailStr
full_name: Optional[str] = None

class UserCreate(UserBase):
password: str = Field(..., min_length=8)

class UserUpdate(BaseModel):
email: Optional[EmailStr] = None
full_name: Optional[str] = None
password: Optional[str] = None

class User(UserBase):
id: int
is_active: bool = True

# ์ž„์‹œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
users_db: List[dict] = []
user_counter = 1

# ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹ฑ (์‹ค์ œ๋กœ๋Š” bcrypt ์‚ฌ์šฉ ๊ถŒ์žฅ)
def hash_password(password: str) -> str:
return hashlib.sha256(password.encode()).hexdigest()

# ์‚ฌ์šฉ์ž ์ƒ์„ฑ
@app.post("/users/", response_model=User, status_code=201)
def create_user(user: UserCreate):
global user_counter

# ์ค‘๋ณต ํ™•์ธ
if any(u["username"] == user.username for u in users_db):
raise HTTPException(status_code=400, detail="Username already exists")

if any(u["email"] == user.email for u in users_db):
raise HTTPException(status_code=400, detail="Email already exists")

# ์‚ฌ์šฉ์ž ์ƒ์„ฑ
user_dict = user.dict()
user_dict["id"] = user_counter
user_dict["is_active"] = True
user_dict["password"] = hash_password(user.password)

users_db.append(user_dict)
user_counter += 1

# ๋น„๋ฐ€๋ฒˆํ˜ธ ์ œ์™ธํ•˜๊ณ  ๋ฐ˜ํ™˜
return {k: v for k, v in user_dict.items() if k != "password"}

# ์‚ฌ์šฉ์ž ๋ชฉ๋ก
@app.get("/users/", response_model=List[User])
def list_users(skip: int = 0, limit: int = 10):
return [
{k: v for k, v in user.items() if k != "password"}
for user in users_db[skip : skip + limit]
]

# ์‚ฌ์šฉ์ž ์กฐํšŒ
@app.get("/users/{user_id}", response_model=User)
def get_user(user_id: int):
for user in users_db:
if user["id"] == user_id:
return {k: v for k, v in user.items() if k != "password"}
raise HTTPException(status_code=404, detail="User not found")

# ์‚ฌ์šฉ์ž ์ˆ˜์ •
@app.patch("/users/{user_id}", response_model=User)
def update_user(user_id: int, user_update: UserUpdate):
for user in users_db:
if user["id"] == user_id:
if user_update.email is not None:
user["email"] = user_update.email
if user_update.full_name is not None:
user["full_name"] = user_update.full_name
if user_update.password is not None:
user["password"] = hash_password(user_update.password)
return {k: v for k, v in user.items() if k != "password"}
raise HTTPException(status_code=404, detail="User not found")

# ์‚ฌ์šฉ์ž ์‚ญ์ œ
@app.delete("/users/{user_id}", status_code=204)
def delete_user(user_id: int):
for i, user in enumerate(users_db):
if user["id"] == user_id:
users_db.pop(i)
return
raise HTTPException(status_code=404, detail="User not found")

์˜ˆ์ œ 3: ํŒŒ์ผ ์—…๋กœ๋“œโ€‹

from fastapi import FastAPI, File, UploadFile, HTTPException
from typing import List
import shutil
from pathlib import Path

app = FastAPI()

# ์—…๋กœ๋“œ ๋””๋ ‰ํ† ๋ฆฌ
UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)

# ๋‹จ์ผ ํŒŒ์ผ ์—…๋กœ๋“œ
@app.post("/upload/")
async def upload_file(file: UploadFile = File(...)):
try:
# ํŒŒ์ผ ์ €์žฅ
file_path = UPLOAD_DIR / file.filename
with file_path.open("wb") as buffer:
shutil.copyfileobj(file.file, buffer)

return {
"filename": file.filename,
"content_type": file.content_type,
"size": file_path.stat().st_size
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
finally:
file.file.close()

# ์—ฌ๋Ÿฌ ํŒŒ์ผ ์—…๋กœ๋“œ
@app.post("/upload-multiple/")
async def upload_multiple_files(files: List[UploadFile] = File(...)):
uploaded_files = []

for file in files:
try:
file_path = UPLOAD_DIR / file.filename
with file_path.open("wb") as buffer:
shutil.copyfileobj(file.file, buffer)

uploaded_files.append({
"filename": file.filename,
"size": file_path.stat().st_size
})
finally:
file.file.close()

return {"uploaded_files": uploaded_files}

# ํŒŒ์ผ ๋ชฉ๋ก
@app.get("/files/")
def list_files():
files = []
for file_path in UPLOAD_DIR.iterdir():
if file_path.is_file():
files.append({
"filename": file_path.name,
"size": file_path.stat().st_size
})
return {"files": files}

์˜ˆ์ œ 4: ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…โ€‹

from fastapi import BackgroundTasks, FastAPI
import time

app = FastAPI()

def write_log(message: str):
"""์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…"""
time.sleep(5)
with open("log.txt", "a") as log:
log.write(f"{message}\n")

@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
# ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰
background_tasks.add_task(write_log, f"Notification sent to {email}")

# ์ฆ‰์‹œ ์‘๋‹ต
return {"message": "Notification sent in background"}

์ž์ฃผ ๋ฌป๋Š” ์งˆ๋ฌธโ€‹

Q1. FastAPI vs Flask, ์–ด๋–ค ๊ฒƒ์„ ์„ ํƒํ•ด์•ผ ํ•˜๋‚˜์š”?โ€‹

A: ๋‹ค์Œ ๊ธฐ์ค€์œผ๋กœ ์„ ํƒํ•˜์„ธ์š”:

FastAPI ์„ ํƒ:

  • ์ƒˆ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘
  • API ์œ„์ฃผ์˜ ๊ฐœ๋ฐœ
  • ํƒ€์ž… ์•ˆ์ •์„ฑ ํ•„์š”
  • ์ž๋™ ๋ฌธ์„œํ™” ํ•„์š”
  • ๊ณ ์„ฑ๋Šฅ ํ•„์š”

Flask ์„ ํƒ:

  • ๊ธฐ์กด ํ”„๋กœ์ ํŠธ ์œ ์ง€๋ณด์ˆ˜
  • ๊ฐ„๋‹จํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜
  • ํ’๋ถ€ํ•œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ƒํƒœ๊ณ„ ํ•„์š”

Q2. async/await๋ฅผ ๊ผญ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋‚˜์š”?โ€‹

A: ์„ ํƒ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค. ์ผ๋ฐ˜ ํ•จ์ˆ˜๋„ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

# ๋™๊ธฐ (CPU ์ง‘์•ฝ์  ์ž‘์—…์— ์ ํ•ฉ)
@app.get("/sync")
def sync_endpoint():
return {"message": "Sync"}

# ๋น„๋™๊ธฐ (I/O ์ง‘์•ฝ์  ์ž‘์—…์— ์ ํ•ฉ)
@app.get("/async")
async def async_endpoint():
return {"message": "Async"}

Q3. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋Š” ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐํ•˜๋‚˜์š”?โ€‹

A: SQLAlchemy๋‚˜ Tortoise ORM์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# SQLAlchemy ์˜ˆ์‹œ
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

@app.get("/users/")
def read_users(db: Session = Depends(get_db)):
users = db.query(User).all()
return users

Q4. CORS๋Š” ์–ด๋–ป๊ฒŒ ์„ค์ •ํ•˜๋‚˜์š”?โ€‹

A: CORSMiddleware๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # ํ”„๋กœ๋•์…˜์—์„œ๋Š” ํŠน์ • ๋„๋ฉ”์ธ๋งŒ
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

Q5. ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•˜๋‚˜์š”?โ€‹

A: pydantic์˜ BaseSettings๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

from pydantic import BaseSettings

class Settings(BaseSettings):
app_name: str = "My API"
database_url: str
secret_key: str

class Config:
env_file = ".env"

settings = Settings()

๋‹ค์Œ ๋‹จ๊ณ„โ€‹

FastAPI ๊ธฐ์ดˆ๋ฅผ ๋งˆ์Šคํ„ฐํ–ˆ๋‹ค๋ฉด, ๋‹ค์Œ ์ฃผ์ œ๋ฅผ ํ•™์Šตํ•ด๋ณด์„ธ์š”:

  1. REST API ๋งŒ๋“ค๊ธฐ - ์™„์ „ํ•œ RESTful API ๊ตฌํ˜„ํ•˜๊ธฐ
  2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค - SQLAlchemy๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐํ•˜๊ธฐ
  3. ์ธ์ฆ - JWT, OAuth2 ๊ตฌํ˜„ํ•˜๊ธฐ
  4. ๋ฐฐํฌ - Docker, Kubernetes๋กœ ํ”„๋กœ๋•์…˜ ๋ฐฐํฌํ•˜๊ธฐ

์ฐธ๊ณ  ์ž๋ฃŒโ€‹