FastAPI/데이터베이스

ORM 적용 - GET 전체 조회 API / Python - Generator

hyunai 2026. 6. 14. 15:42
# connection.py

def get_db():
	# 세션 팩토리를 이용해서 세션 객체를 만들어준다.
    session = SessionFactory()
    try:
        yield session
    finally:
        session.close()
  • generator를 만들어준다.
  • FastAPI를 사용해서 데이버베이스에 접근할 수 있게 해준다.
  • request가 들어왔을 때 session이 생성이 돼서 yield 문으로 return이 된 후에 사용이 되다가 response 한 이후에 session을 close() 해서 삭제하는 식으로 FastAPI가 session을 관리해주게 된다.
# repository.py

from typing import List

from sqlalchemy import select
from sqlalchemy.orm import Session
from database.orm import ToDo

def get_todos(session: Session) -> List[ToDo]:
    return list(session.scalars(select(ToDo)))
  • repository 패턴
  • 함수를 통해서 데이터베이스를 조회하는 부분을 생성하고 그 함수를 main.py에서 사용
  • ToDo를 전체 select(전체 조회)해서 return

-> List[ToDo] 는 반환 타입 힌트

한 문장으로 표현하면:

"이 함수는 실행하고 나면 이런 타입의 값을 돌려줄 거야" 라고 알려주는 문법


기본 문법 구조

반환 타입 힌트 구조

def 함수이름(파라미터) -> 반환타입:
#                      ↑
#              이 화살표가 "반환값은 이거야" 라는 표시

파라미터 타입 힌트랑 비교해보기

타입 힌트 위치 비교

def get_todos(session: Session) -> List[ToDo]:
#             ↑                   ↑
#         파라미터 타입 힌트      반환값 타입 힌트
#         "session은 Session     "이 함수는 List[ToDo]를
#          타입으로 받을 거야"     돌려줄 거야"

str | None = None 이랑 같은 개념
파라미터에 타입 힌트를 붙이듯, 반환값에도 타입 힌트를 붙이는 것


List[ToDo] 가 뭔지

List[ToDo] 분해

List[ToDo]
↑    ↑
│    └── 리스트 안에 들어있는 요소 타입 (ToDo 객체)
└─────── 리스트 타입

타입 힌트 예시 비교

타입 힌트 의미
-> str 문자열 하나 반환
-> int 정수 하나 반환
-> List[str] 문자열들의 리스트 반환
-> List[ToDo] ToDo 객체들의 리스트 반환
-> None 반환값 없음

실제로 어떤 값이 나오냐면

반환값 시각화

def get_todos(session: Session) -> List[ToDo]:
    return list(session.scalars(select(ToDo)))

# 실제로 이런 값이 반환됨
[
    ToDo(id=1, contents="FastAPI 섹션 0 수강", is_done=True),
    ToDo(id=2, contents="FastAPI 섹션 1 수강", is_done=False),
    ToDo(id=3, contents="FastAPI 섹션 2 수강", is_done=False),
]
# ↑ ToDo 객체들이 담긴 List!

타입 힌트를 안 써도 동작은 한다

타입 힌트 유무 비교

# 타입 힌트 없는 버전 (동작은 같음)
def get_todos(session):
    return list(session.scalars(select(ToDo)))

# 타입 힌트 있는 버전
def get_todos(session: Session) -> List[ToDo]:
    return list(session.scalars(select(ToDo)))

그럼 왜 쓰냐면:

타입 힌트를 쓰는 이유

이유 설명
가독성 코드만 봐도 뭘 받고 뭘 반환하는지 바로 알 수 있음
IDE 자동완성 PyCharm이 타입을 알고 있어서 자동완성 도와줌
에러 조기 발견 잘못된 타입 쓰면 실행 전에 경고 띄워줌
FastAPI 필수 FastAPI는 타입 힌트 보고 자동으로 검증/직렬화 처리

전체 함수 한눈에 읽기

함수 전체 의미 시각화

def get_todos(session: Session) -> List[ToDo]:
     ↑         ↑         ↑           ↑
  함수이름   파라미터   Session      ToDo 객체들의
                       타입으로     리스트를
                       받아         반환

💡 핵심 요약:
-> = "이 함수가 반환하는 값의 타입은"
List[ToDo] = "ToDo 객체들이 담긴 리스트"
타입 힌트는 안 써도 동작하지만, 쓰면 코드 이해와 자동완성에 훨씬 도움이 돼요.

 

# main.py

from typing import List

from database.connection import get_db
from database.orm import ToDo
from database.repository import get_todos
from fastapi import  FastAPI, Body, HTTPException, Depends
from sqlalchemy.orm import Session

# status_code만 적어주면 =200을 안 적어도 자동으로 200으로 매핑된다.
@app.get("/todos", status_code=200)
def get_todos_handler(
    order: str | None = None,
    session: Session = Depends(get_db),
):
    # 타입 힌트
    todos: List[ToDo] = get_todos(session=session)

    if order and order == "DESC":
        return todos[::-1]
    return todos

swagger ui에서 확인해보기

제너레이터란?

한 문장으로 표현하면:

"중간에 잠깐 멈췄다가, 나중에 다시 이어서 실행할 수 있는 함수"


일반 함수 vs 제너레이터 함수 비교

일반 함수

def normal_func():
    print("시작")
    return "결과"   # ← 여기서 완전히 끝남, 다시 돌아올 수 없음
    print("이건 실행 안 됨")

제너레이터 함수

def generator_func():
    print("시작")
    yield "결과"    # ← 여기서 잠깐 멈추고 값을 넘겨줌
    print("나중에 다시 여기서 이어서 실행됨")

yield가 핵심. return은 끝내는 거고, yield는 잠깐 멈추는 것


제너레이터 동작 흐름

yield 동작 시각화

generator_func() 호출
        ↓
"시작" 출력
        ↓
yield "결과"  →→→→→→→  호출한 쪽이 "결과"를 받음
   ⏸ 멈춤!                    (여기서 다른 작업 가능)
        ↓                           ↓
   (나중에 next() 호출)  ←←←←←←  작업 끝
        ↓
"나중에 다시 여기서 이어서 실행됨" 출력
        ↓
함수 완전 종료

직접 눈으로 확인해보기

제너레이터 직접 실행

def generator_func():
    print("1. 시작")
    yield "잠깐 멈춤"
    print("3. 다시 실행")

gen = generator_func()   # 아직 실행 안 됨!

print("next() 호출 전")
value = next(gen)        # 여기서 실행 시작 → yield에서 멈춤
print(f"2. yield가 넘겨준 값: {value}")
next(gen)                # yield 이후부터 다시 실행

# 출력 순서:
# next() 호출 전
# 1. 시작
# 2. yield가 넘겨준 값: 잠깐 멈춤
# 3. 다시 실행

next()가 뭔지는 밑에서 다시 다뤄보자.


이제 get_db()를 이해해보기

get_db 제너레이터

def get_db():
    session = SessionFactory()  # 1. DB 세션 생성
    try:
        yield session           # 2. 세션을 넘겨주고 잠깐 멈춤
    finally:
        session.close()         # 3. API 처리 끝나면 여기서 세션 닫음

FastAPI에서 get_db 동작 흐름

GET /todos 요청 들어옴
        ↓
get_db() 실행 시작
        ↓
session = SessionFactory()  → DB 연결 생성
        ↓
yield session  →→→→→→→→→→  get_todos_handler에게 session 전달
   ⏸ 멈춤!                          ↓
                              todos 조회 로직 실행
                                     ↓
                              응답 반환
        ↓                           ↓
finally 실행  ←←←←←←←←←←  요청 처리 완료
        ↓
session.close()  → DB 연결 닫음

Depends(get_db)가 하는 역할

Depends 동작

@app.get("/todos")
def get_todos_handler(
    session: Session = Depends(get_db),  
#                      ↑
#   "get_db 실행해서 yield한 값을 session에 넣어줘"
):
    todos = get_todos(session=session)  # session 바로 사용 가능

FastAPI가 Depends를 보고 자동으로 해줘요:

Depends가 자동으로 처리하는 것

순서  동작
요청 시작 get_db() 실행 → session 생성
핸들러 실행 session을 파라미터로 주입
요청 완료 finally 블록 실행 → session.close()

Depends도 밑에서 더 알아보자


왜 일반 함수 대신 제너레이터를 쓰냐면

만약 일반 함수로 했다면?

# 일반 함수로 하면 세션을 닫을 타이밍을 알 수 없음
def get_db():
    session = SessionFactory()
    return session
    # 언제 session.close() 해야 하지...?

# 제너레이터로 하면 요청 전후를 하나의 함수로 관리
def get_db():
    session = SessionFactory()
    try:
        yield session       # 요청 처리 중
    finally:
        session.close()     # 요청 끝나면 자동 정리

💡 핵심 요약:
yield 앞 = 요청 전 준비 (세션 생성)
yield = 핸들러에게 세션 전달
yield 뒤 = 요청 후 정리 (세션 닫기)
이 3단계를 하나의 함수로 깔끔하게 관리할 수 있어서 제너레이터를 써요.


next()란?

next()는 Python 내장 함수

"제너레이터한테 다음 단계로 진행해" 라고 명령하는 함수


제너레이터는 기본적으로 가만히 있는다

제너레이터는 호출해도 바로 실행 안 됨

def generator_func():
    print("1. 시작")
    yield "잠깐 멈춤"
    print("3. 다시 실행")

gen = generator_func()  # 실행 X, 그냥 제너레이터 객체만 생성
print(gen)              # <generator object generator_func at 0x...>

제너레이터는 누군가 "실행해" 라고 명령해야 움직인다.
그 명령이 바로 next()


next() 호출할 때마다 어떻게 되는지

next() 단계별 동작

gen = generator_func()

# 1번째 next() → yield까지 실행하고 멈춤
value = next(gen)
# "1. 시작" 출력
# yield에서 멈추고 "잠깐 멈춤" 반환

# 2번째 next() → yield 이후부터 끝까지 실행
next(gen)
# "3. 다시 실행" 출력
# 함수 끝

# 3번째 next() → 더 이상 실행할 게 없음
next(gen)
# StopIteration 에러 발생!

시각화로 보기

next() 호출 흐름

gen = generator_func()
           │
           │  next(gen) 1번째 호출
           ▼
    print("1. 시작")         ← 실행
    yield "잠깐 멈춤"         ← 여기서 멈추고 "잠깐 멈춤" 반환
           ⏸
           │  next(gen) 2번째 호출
           ▼
    print("3. 다시 실행")     ← 실행
    함수 끝                   ← StopIteration

next()가 내장 함수인 이유

Python 주요 내장 함수들

함수 역할
print() 출력
len() 길이 반환
range() 숫자 범위 생성
next() 이터레이터/제너레이터 다음 값 꺼냄
list() 리스트로 변환

next()는 import 없이 바로 쓸 수 있어요.


사실 for문이 next()를 자동으로 호출

for문 = next() 자동 반복

def countdown():
    yield 3
    yield 2
    yield 1

# 직접 next() 호출
gen = countdown()
next(gen)  # 3
next(gen)  # 2
next(gen)  # 1

# for문이 내부적으로 next()를 자동 호출
for num in countdown():
    print(num)
# 3
# 2
# 1

💡 핵심 요약:
next() = 제너레이터한테 "다음 yield까지 실행해" 명령
for문 = next()를 자동으로 반복 호출하는 것
get_db()에서는 FastAPI가 내부적으로 next()를 대신 호출.


Depends란?

한 문장으로 표현하면:

"이 함수를 실행하기 전에, 먼저 저 함수를 실행해서 결과를 가져다줘" 라고 FastAPI한테 시키는 도구


왜 필요한지 - 문제부터 이해하기

Depends 없이 쓴다면?

@app.get("/todos")
def get_todos_handler(order: str | None = None):
    # 매번 여기서 직접 세션을 만들고
    session = SessionFactory()
    try:
        todos = get_todos(session=session)
        return todos
    finally:
        session.close()  # 매번 여기서 직접 닫고

@app.post("/todos")
def create_todo_handler():
    # 여기서도 또 똑같이 반복...
    session = SessionFactory()
    try:
        ...
    finally:
        session.close()  # 또 반복...

@app.delete("/todos/{todo_id}")
def delete_todo_handler():
    # 또 반복...
    session = SessionFactory()
    try:
        ...
    finally:
        session.close()  # 또또 반복...

모든 핸들러마다 똑같은 코드가 반복


Depends가 이걸 어떻게 해결하냐면

Depends 사용 후

# 세션 관련 코드는 여기 한 곳에만!
def get_db():
    session = SessionFactory()
    try:
        yield session
    finally:
        session.close()

# 각 핸들러는 그냥 받아서 쓰기만 하면 됨
@app.get("/todos")
def get_todos_handler(session: Session = Depends(get_db)):
    return get_todos(session=session)

@app.post("/todos")
def create_todo_handler(session: Session = Depends(get_db)):
    ...  # session 그냥 쓰면 됨

@app.delete("/todos/{todo_id}")
def delete_todo_handler(session: Session = Depends(get_db)):
    ...  # session 그냥 쓰면 됨

Depends 동작 흐름

Depends 동작 시각화

GET /todos 요청
       ↓
FastAPI가 get_todos_handler 실행 전에
Depends(get_db) 발견!
       ↓
"get_db 먼저 실행해야겠다"
       ↓
get_db() 실행 → session 생성 → yield session
       ↓
session을 get_todos_handler에 자동으로 주입
       ↓
get_todos_handler 실행 (session 사용)
       ↓
응답 반환
       ↓
get_db()의 finally 실행 → session.close()

자연어로 읽어보기

코드를 자연어로 해석

def get_todos_handler(
    order: str | None = None,      # 쿼리스트링에서 order 받아와
    session: Session = Depends(get_db),  
#            ↑                ↑
#        이 타입으로      get_db 실행한 결과를
#        받을 거야        여기에 넣어줘
):

Depends는 세션 말고도 다양하게 쓰인다

Depends 활용 예시들

# 1. 로그인 체크
def get_current_user(token: str):
    # 토큰 검증 로직
    return user

@app.get("/my-profile")
def my_profile(user = Depends(get_current_user)):
    return user  # 로그인한 유저 정보 바로 사용

# 2. 권한 체크
def check_admin(user = Depends(get_current_user)):
    if not user.is_admin:
        raise HTTPException(403)
    return user

@app.delete("/users/{user_id}")
def delete_user(admin = Depends(check_admin)):
    ...  # 관리자만 여기 들어올 수 있음

핵심 정리

Depends 한눈에 정리

항목  설명
역할 핸들러 실행 전에 먼저 특정 함수 실행
장점 반복 코드 제거, 재사용 가능
주입 타이밍 핸들러 실행 직전 자동으로 주입
주로 쓰는 곳 DB 세션, 로그인 체크, 권한 체크

💡 핵심 요약:
Depends(get_db) = "get_db 먼저 실행해서 결과를 session에 넣어줘"
FastAPI가 알아서 실행 순서를 관리해주기 때문에
개발자는 그냥 session 받아서 쓰기만 하면 된다.


 

'FastAPI > 데이터베이스' 카테고리의 다른 글

ORM 적용 - GET 단일 조회 API  (0) 2026.06.16
ORM 적용 - HTTP Response 처리  (1) 2026.06.14
ORM 모델링  (1) 2026.06.09
데이터베이스 연결  (0) 2026.06.04
DDL - Create Table  (0) 2026.06.04