from sqlalchemy import Boolean, Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class ToDo(Base):
__tablename__ = "todo"
id = Column(Integer, primary_key=True, index=True)
contents = Column(String(256), nullable=False)
is_done = Column(Boolean, nullable=False)
# 객체를 출력하기 위한 메소드
def __repr__(self):
return f"ToDo(id={self.id}, contents={self.contents}, is_done={self.is_done})"
declarative_base?
한 문장으로 표현하면:
"ORM 모델 클래스들의 부모 클래스를 만들어주는 공장"
왜 필요한지 먼저 이해하기
ORM의 역할은 Python 클래스 ↔ DB 테이블 을 연결하는 것
ORM 개념 시각화
Python 세계 DB 세계
────────────────── ──────────────────
class ToDo: ↔ TABLE todo
id ↔ id
contents ↔ contents
is_done ↔ is_done
근데 Python 클래스가 "나는 DB 테이블이야" 라는 걸 SQLAlchemy한테 알려주려면 특정 부모 클래스를 상속받아야 된다. 그 부모 클래스를 만들어주는 게 declarative_base()
코드로 보기
declarative_base 사용 흐름
from sqlalchemy.orm import declarative_base
# 1. Base 클래스 생성 (공장에서 틀을 만드는 것)
Base = declarative_base()
# 2. Base를 상속받아서 ORM 모델 정의
class ToDo(Base): # ← Base 상속!
__tablename__ = "todo" # ← 어떤 테이블과 연결할지
id = Column(Integer, primary_key=True)
contents = Column(String(256))
is_done = Column(Boolean)
# 3. Base가 알고 있는 모든 모델로 테이블 생성
Base.metadata.create_all(bind=engine)
Base가 하는 역할 3가지
Base의 역할
| 역할 | 설명 | 예시 |
| 테이블 매핑 | 클래스와 DB 테이블 연결 | __tablename__ = "todo" |
| 컬럼 인식 | Column()을 DB 컬럼으로 인식 | id = Column(Integer) |
| 테이블 관리 | 연결된 모든 테이블 생성/삭제 | Base.metadata.create_all() |
Base 없이 상속 안 하면?
Base 상속 안 할 때 차이
# 그냥 클래스 (SQLAlchemy가 모름)
class ToDo:
id = 1
contents = "할일"
# Base 상속 (SQLAlchemy가 DB 테이블로 인식)
class ToDo(Base):
__tablename__ = "todo"
id = Column(Integer, primary_key=True)
metadata가 뭔지도 같이 이해하기
Base.metadata 구조
Base
└── metadata ← Base에 연결된 모든 테이블 정보 저장소
├── ToDo 테이블 정보
├── User 테이블 정보 (나중에 추가하면)
└── Post 테이블 정보 (나중에 추가하면)
Base.metadata.create_all() → 저장된 모든 테이블 한번에 생성
Base.metadata.drop_all() → 저장된 모든 테이블 한번에 삭제
실무에서 어떻게 쓰이냐면
실무 프로젝트 구조
# database/orm.py
Base = declarative_base() # 여기서 한 번만 만들고
class ToDo(Base): # 모든 모델이 같은 Base 상속
__tablename__ = "todo"
...
class User(Base): # 이것도 같은 Base
__tablename__ = "user"
...
# 나중에 한방에 모든 테이블 생성
Base.metadata.create_all(bind=engine) # ToDo, User 둘 다 생성
핵심 요약:
Base = declarative_base() 는 한 번만 만들고,
모든 ORM 모델은 이 Base를 상속받아서 "나는 DB 테이블이야"를 선언하는 것.
ORM 모델링
모델링을 위해 콘솔창을 켜준다.
from database.connection import SessionFactory
- Session을 생성하기 위해 SessionFactory를 import 해준다.
session = SessionFactory()
- session 객체 하나 생성
from sqlalchemy import select
- select 함수 import
from database.orm import ToDo
- ToDo 클래스를 import
session.scalars(select(ToDo))
======================================================
2026-06-09 22:48:18,134 INFO sqlalchemy.engine.Engine SELECT DATABASE()
2026-06-09 22:48:18,135 INFO sqlalchemy.engine.Engine [raw sql] {}
2026-06-09 22:48:18,145 INFO sqlalchemy.engine.Engine SELECT @@sql_mode
2026-06-09 22:48:18,145 INFO sqlalchemy.engine.Engine [raw sql] {}
2026-06-09 22:48:18,149 INFO sqlalchemy.engine.Engine SELECT @@lower_case_table_names
2026-06-09 22:48:18,149 INFO sqlalchemy.engine.Engine [raw sql] {}
2026-06-09 22:48:18,156 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2026-06-09 22:48:18,163 INFO sqlalchemy.engine.Engine SELECT todo.id, todo.contents, todo.is_done
FROM todo
2026-06-09 22:48:18,163 INFO sqlalchemy.engine.Engine [generated in 0.00046s] {}
<sqlalchemy.engine.result.ScalarResult object at 0x000001B9710DD020>
- 이런식으로 그냥 scalars(select(ToDo))를 해주게 되면 쿼리가 날아가는 것은 보이지만 마지막 줄에 ScalarResult 라는 object로 들어가 있어 보기가 힘들다.
list(session.scalars(select(ToDo)))
=====================================================
2026-06-09 22:50:35,977 INFO sqlalchemy.engine.Engine SELECT todo.id, todo.contents, todo.is_done
FROM todo
2026-06-09 22:50:35,977 INFO sqlalchemy.engine.Engine [cached since 137.8s ago] {}
[ToDo(id=1, contents=FastAPI Section 0, is_done=True), ToDo(id=2, contents=FastAPI Section 0, is_done=True), ToDo(id=3, contents=FastAPI Section 1, is_done=True), ToDo(id=4, contents=FastAPI Section 2, is_done=False)]
- 지금처럼 list에 scalar 객체를 담아서 보내면 id 1~4번에 해당하는 데이터 결과들이 출력되는 것을 확인할 수 있다.
todos = list(session.scalars(select(ToDo)))
=====================================================
2026-06-09 22:53:33,363 INFO sqlalchemy.engine.Engine SELECT todo.id, todo.contents, todo.is_done
FROM todo
2026-06-09 22:53:33,363 INFO sqlalchemy.engine.Engine [cached since 315.2s ago] {}
todos
=====================================================
[ToDo(id=1, contents=FastAPI Section 0, is_done=True), ToDo(id=2, contents=FastAPI Section 0, is_done=True), ToDo(id=3, contents=FastAPI Section 1, is_done=True), ToDo(id=4, contents=FastAPI Section 2, is_done=False)]
for todo in todos:
print(todo)
======================================================
ToDo(id=1, contents=FastAPI Section 0, is_done=True)
ToDo(id=2, contents=FastAPI Section 0, is_done=True)
ToDo(id=3, contents=FastAPI Section 1, is_done=True)
ToDo(id=4, contents=FastAPI Section 2, is_done=False)
- 이렇게 변수에 할당을 해서 todos를 쳐보면 똑같이 나오게 된다.
- 그리고 반복문을 돌려서 todos를 실제로 출력을 하게 되면 이렇게 todo가 정상적으로 파이썬 안에서 사용이 가능한 것을 확인할 수 있다.
'FastAPI > 데이터베이스' 카테고리의 다른 글
| ORM 적용 - HTTP Response 처리 (1) | 2026.06.14 |
|---|---|
| ORM 적용 - GET 전체 조회 API / Python - Generator (0) | 2026.06.14 |
| 데이터베이스 연결 (0) | 2026.06.04 |
| DDL - Create Table (0) | 2026.06.04 |
| MySQL 컨테이너 실행 (0) | 2026.05.19 |