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๋ก ์ ์ํ์ธ์:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- OpenAPI JSON: http://localhost:8000/openapi.json
๊ฒฝ๋ก ํ๋ผ๋ฏธํฐโ
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 ๊ธฐ์ด๋ฅผ ๋ง์คํฐํ๋ค๋ฉด, ๋ค์ ์ฃผ์ ๋ฅผ ํ์ตํด๋ณด์ธ์:
- REST API ๋ง๋ค๊ธฐ - ์์ ํ RESTful API ๊ตฌํํ๊ธฐ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค - SQLAlchemy๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐํ๊ธฐ
- ์ธ์ฆ - JWT, OAuth2 ๊ตฌํํ๊ธฐ
- ๋ฐฐํฌ - Docker, Kubernetes๋ก ํ๋ก๋์ ๋ฐฐํฌํ๊ธฐ