# response.py
from typing import List
from pydantic import BaseModel
# BaseModel을 상속받는 스키마 클래스
class ToDoSchema(BaseModel):
id: int
contents : str
is_done: bool
# SQL alchemy를 읽어줄 수 있도록 하기 위한 옵션
# Pydantic에 정의한 ORM 모드라는 걸 사용 가능
# 따라서 ToDoSchema에 SQL alchemy ORM 객체를 주면 Pydantic이 이 ORM 객체를 해석해서 맞게 변경을 해준다.
class Config:
from_attributes = True
# 실제로 response에 활용을 할 클래스
class ListToDoResponse(BaseModel):
todos: List[ToDoSchema]
- 지금 ToDoSchema에 컬럼과 이미 ORM에 생성한 컬럼과 동일하다.
- 근데 굳이 왜 response를 한번 더 분리하고 정의하냐?
- 아직은 컬럼이 단순하고 이 구조가 단순하지만 응답의 컬럼 간의 연산이 있거나 이 객체를 뭔가 중첩된 구조로 반환을 한다거나 contents 값을 제외하고 return을 하고싶다 식의 다양한 usecase가 있기 때문에 response 객체를 분리를 해놓게 되면 훨씬 유연하게 코드 변경을 할 수가 있다.
class Config: from_attributes = True 이해하기
왜 이게 필요한지 - 문제부터 이해하기
FastAPI에서 데이터는 이렇게 흐른다.
데이터 흐름
MySQL DB
↓
SQLAlchemy ORM 객체 (ToDo) ← Python 객체
↓
Pydantic 스키마 (ToDoSchema) ← 응답용 객체
↓
JSON 응답
여기서 중간에 "SQLAlchemy 객체 → Pydantic 스키마" 변환할 때 문제가 생긴다.
왜 변환이 안 되냐면
Pydantic은 기본적으로 딕셔너리만 읽을 수 있다.
Pydantic이 읽을 수 있는 것 vs 없는 것
# Pydantic이 기본으로 읽을 수 있는 것 (딕셔너리)
todo_dict = {
"id": 1,
"contents": "FastAPI 공부",
"is_done": False
}
ToDoSchema(**todo_dict) # 문제 없음
# Pydantic이 기본으로 읽을 수 없는 것 (SQLAlchemy 객체)
todo_orm = session.scalar(select(ToDo)) # SQLAlchemy 객체
ToDoSchema(???) # 어떻게 넣어야 하지?
SQLAlchemy 객체가 뭔지 시각화
todo_orm 객체 내부
─────────────────────────────
todo_orm.id = 1
todo_orm.contents = "FastAPI 공부"
todo_orm.is_done = False
+ 그 외 SQLAlchemy 내부 정보 수십 가지
─────────────────────────────
Pydantic: "이게 뭔지 모르겠어, 읽는 방법을 몰라"
from_attributes = True 가 하는 일
from_attributes 설정 효과
from_attributes = False (기본값)
────────────────────────────────
Pydantic: "딕셔너리만 읽을 수 있어"
SQLAlchemy 객체 → 변환 불가
from_attributes = True (설정 후)
────────────────────────────────
Pydantic: "객체의 속성(attribute)도 읽을 수 있어"
SQLAlchemy 객체의 .id, .contents, .is_done → 변환 가능
쉽게 말하면:
"Pydantic한테 딕셔너리 말고 객체 속성도 읽는 법을 가르쳐주는 것"
실제로 어떻게 동작하냐면
from_attributes 동작 과정
# SQLAlchemy ORM 객체
todo_orm = session.scalar(select(ToDo))
# todo_orm.id = 1
# todo_orm.contents = "FastAPI 공부"
# todo_orm.is_done = False
# from_attributes = True 설정 덕분에
# Pydantic이 객체의 속성을 하나씩 읽어서 변환함
result = ToDoSchema.model_validate(todo_orm)
# 내부적으로 이렇게 동작하는 것과 같음
# ToDoSchema(
# id = todo_orm.id, ← 속성 읽기
# contents = todo_orm.contents, ← 속성 읽기
# is_done = todo_orm.is_done ← 속성 읽기
# )
print(result)
# ToDoSchema(id=1, contents='FastAPI 공부', is_done=False)
class Config 는 왜 클래스 안에 또 클래스가 있냐면
Config 클래스 역할
class ToDoSchema(BaseModel):
# 실제 데이터 필드
id: int
contents: str
is_done: bool
# ToDoSchema의 동작 방식을 설정하는 곳
class Config:
from_attributes = True # ORM 객체도 읽을 수 있게 허용
Config 클래스 비유
| 비유 | 설명 |
| ToDoSchema | 자동차 |
| class Config | 자동차 설정 메뉴 |
| from_attributes = True | "오프로드 모드 켜기" |
자동차 자체는 ToDoSchema고, Config는 그 자동차의 동작 방식을 바꾸는 설정판
전체 흐름 한눈에 보기
전체 변환 흐름
DB에서 데이터 조회
↓
SQLAlchemy ORM 객체 (ToDo)
todo.id = 1
todo.contents = "FastAPI 공부"
todo.is_done = False
↓
model_validate(todo) ← from_attributes = True 덕분에 가능
↓
Pydantic 스키마 (ToDoSchema)
id = 1
contents = "FastAPI 공부"
is_done = False
↓
JSON 응답
{
"id": 1,
"contents": "FastAPI 공부",
"is_done": false
}
💡 핵심 요약:
Pydantic은 기본적으로 딕셔너리만 읽을 수 있다.
from_attributes = True 설정을 하면 SQLAlchemy ORM 객체도 읽을 수 있게 된다.
class Config는 Pydantic 모델의 동작 방식을 설정하는 공간.
콘솔로 확인해보기
# 생성한 ToDoSchema import
from schema.response import ToDoSchema
# database의 orm에서 ToDo import
from database.orm import ToDo
# 임의의 ToDo를 하나 생성
# 이 때 생성된 객체는 데이터베이스에 저장되거나 이런 건 아니고 말 그대로 Python 객체
todo = ToDo(id=100, contents="test", is_done=True)
# ToDoSchema 객체 생성
ToDoSchema.model_validate(todo)
=====================================================
ToDoSchema(id=100, contents='test', is_done=True)
별도 작업을 해주지 않아도 ORM 모드를 True로 주게 된다면 Pydantic Schema로 쉽게 변환을 할 수 있다.
# main.py
# status_code만 적어주면 =200을 안 적어도 자동으로 200으로 매핑된다.
@app.get("/todos", status_code=200)
def get_todos_handler(
order: str | None = None,
session: Session = Depends(get_db),
# 타입 힌트
) -> ListToDoResponse:
# 타입 힌트
todos: List[ToDo] = get_todos(session=session)
if order and order == "DESC":
return ListToDoResponse(
# todos: List[ToDoSchema] response.py에 이렇게 리스트 형식으로 작성해놨기 때문에 동일하게 []로 작성
todos=[ToDoSchema.model_validate(todo) for todo in todos[::-1]]
)
return ListToDoResponse(
todos=[ToDoSchema.model_validate(todo) for todo in todos]
)
- 이전 글까지만 해도 ORM 객체를 그대로 return을 해주어도 그대로 JSON 형태로 변환이 돼서 잘 응답을 해주긴했다.
- 하지만 일반적으로는 데이터를 직접 return하지 않고 데이터가 표현되는 부분을 분리를 해서 처리를 한다.
리스트 컴프리헨션?
한 문장으로 표현하면:
"리스트의 각 요소를 하나씩 꺼내서 가공한 뒤, 새로운 리스트로 만드는 문법"
일반 for문이랑 비교해보기
일반 for문 vs 리스트 컴프리헨션
# 일반 for문 방식
result = []
for todo in todos:
result.append(ToDoSchema.model_validate(todo))
# 리스트 컴프리헨션 방식 (위랑 완전히 같은 동작)
result = [ToDoSchema.model_validate(todo) for todo in todos]
둘이 완전히 똑같이 동작. 리스트 컴프리헨션이 그냥 더 짧게 쓰는 방법
문법 구조 분해
리스트 컴프리헨션 구조
[ToDoSchema.model_validate(todo) for todo in todos]
↑ ↑ ↑ ↑ ↑
각 요소에 할 작업 for 하나씩 in 원본 리스트
(todo를 Schema로 변환) 꺼낼
변수명
자연어로 읽으면:
"todos에서 todo를 하나씩 꺼내서, model_validate(todo)를 실행한 결과로 새 리스트를 만들어"
단계별로 동작 과정 보기
동작 과정 시각화
todos (SQLAlchemy 객체 리스트)
[
ToDo(id=1, contents="섹션 0 수강", is_done=True),
ToDo(id=2, contents="섹션 1 수강", is_done=False),
ToDo(id=3, contents="섹션 2 수강", is_done=False),
]
↓ for todo in todos (하나씩 꺼냄)
↓ ToDoSchema.model_validate(todo) (변환)
결과 리스트
[
ToDoSchema(id=1, contents="섹션 0 수강", is_done=True),
ToDoSchema(id=2, contents="섹션 1 수강", is_done=False),
ToDoSchema(id=3, contents="섹션 2 수강", is_done=False),
]
더 쉬운 예시로 이해하기
숫자로 이해하는 리스트 컴프리헨션
# 원본 리스트
numbers = [1, 2, 3, 4, 5]
# 각 숫자에 2를 곱한 새 리스트 만들기
doubled = [n * 2 for n in numbers]
print(doubled) # [2, 4, 6, 8, 10]
# 문자열 리스트도 가능
names = ["alice", "bob", "charlie"]
upper_names = [name.upper() for name in names]
print(upper_names) # ["ALICE", "BOB", "CHARLIE"]
지금 코드에 대입해서 읽기
실제 코드 자연어로 읽기
todos=[ToDoSchema.model_validate(todo) for todo in todos]
# ↑ ↑ ↑ ↑
# ToDoSchema로 변환한 결과를 todo를 하나씩 todos에서
# 새 리스트에 담아 꺼내서 꺼내
"todos 리스트에서 todo를 하나씩 꺼내서,
ToDoSchema.model_validate(todo)로 변환한 결과들을
새로운 리스트로 만들어서 todos에 담아"
전체 맥락에서 보기
main.py 전체 흐름
return ListToDoResponse(
todos=[ToDoSchema.model_validate(todo) for todo in todos]
# ↑ SQLAlchemy 객체들을 Pydantic 스키마로 변환한 리스트
)
# 최종적으로 이런 JSON이 반환됨
# {
# "todos": [
# {"id": 1, "contents": "섹션 0 수강", "is_done": true},
# {"id": 2, "contents": "섹션 1 수강", "is_done": false},
# {"id": 3, "contents": "섹션 2 수강", "is_done": false}
# ]
# }
💡 핵심 요약:
[A for B in C] = "C에서 B를 하나씩 꺼내서 A를 실행한 결과로 새 리스트 만들기"
일반 for문을 한 줄로 압축한 문법
swagger ui로 확인

'FastAPI > 데이터베이스' 카테고리의 다른 글
| 파이참 - Refactoring (0) | 2026.06.16 |
|---|---|
| ORM 적용 - GET 단일 조회 API (0) | 2026.06.16 |
| ORM 적용 - GET 전체 조회 API / Python - Generator (0) | 2026.06.14 |
| ORM 모델링 (1) | 2026.06.09 |
| 데이터베이스 연결 (0) | 2026.06.04 |