FastAPI/데이터베이스

ORM 적용 - POST API

hyunai 2026. 6. 24. 15:11
# request.py
from pydantic import BaseModel


class CreateToDoRequest(BaseModel):
    contents: str
    is_done: bool


# orm.py
from schema.request import CreateToDoRequest

    # 요청 받은 requestBody를 ORM 객체로 변환해주는 클래스 method
    @classmethod
    def create(cls, request : CreateToDoRequest) -> "ToDo":
        return cls(
            contents=request.contents,
            is_done=request.is_done,
        )
  • classmethod는 request를 그대로 전달을 받는다.
  • return에 cls에 맞는 데이터로 매핑을 해주면 되는데 앞서 id 값은 데이터베이스에 의해 자동으로 결정하도록 했기 때문에 별도로 지정해주지 않는다.
  • Pydantic을 통해 전달 받은 Pydantic으로 request를 받아서 이제 ORM 객체를 생성해주게 된다.
  • 여기까지는 데이터베이스에 실제로 저장을 한 것은 아니다.

@classmethod 와 cls


파이썬 메서드 종류부터 이해하기

메서드 3종류 비교

종류  선언 방식 첫 번째 파라미터 접근 가능한 것
일반 메서드 그냥 def self (인스턴스) 인스턴스 속성
클래스 메서드 @classmethod cls (클래스) 클래스 자체
정적 메서드 @staticmethod 없음 둘 다 접근 불가

self 랑 cls 차이부터 이해하기

self vs cls 비교

class ToDo:
    # 일반 메서드 → self = 만들어진 객체 자신
    def get_contents(self):
        return self.contents  # 객체의 속성에 접근
    
    # 클래스 메서드 → cls = ToDo 클래스 자체
    @classmethod
    def create(cls, request):
        return cls(...)  # ToDo(...) 와 완전히 같은 말

self vs cls 시각화

self = 만들어진 붕어빵 (인스턴스)
cls  = 붕어빵 틀 (클래스 자체)

ToDo 클래스 (붕어빵 틀)
    ↓ ToDo() 호출
todo 인스턴스 (붕어빵)
    ↓
todo.contents, todo.is_done 접근 가능

@classmethod 가 뭔지

classmethod 없을 때 vs 있을 때

# classmethod 없이 객체 생성
todo = ToDo(
    contents=request.contents,
    is_done=request.is_done,
)

# classmethod로 객체 생성
todo = ToDo.create(request=request)

둘 다 결과는 같다. 차이는 "생성 로직을 클래스 안에 캡슐화했냐"


cls(...) 가 왜 ToDo(...) 랑 같냐면

cls 동작 원리

class ToDo(Base):
    @classmethod
    def create(cls, request: CreateToDoRequest) -> "ToDo":
        #        ↑
        #    cls = ToDo 클래스 자체를 가리킴
        
        return cls(              # = ToDo() 랑 완전히 같음
            contents=request.contents,
            is_done=request.is_done,
        )

# 실제로 이렇게 호출하면
todo = ToDo.create(request=request)

# 내부적으로 이렇게 동작하는 것과 같음
todo = ToDo(
    contents=request.contents,
    is_done=request.is_done,
)

왜 굳이 @classmethod로 만드냐면

classmethod 장점

# 밖에서 직접 생성하는 방식
@app.post("/todos")
def create_todo_handler(request: CreateToDoRequest):
    # 핸들러가 ToDo 생성 방법을 알아야 함
    todo = ToDo(
        contents=request.contents,
        is_done=request.is_done,
    )
    # 만약 나중에 필드가 추가되면?
    # 모든 핸들러를 다 찾아서 수정해야 함

# classmethod로 만드는 방식
@app.post("/todos")
def create_todo_handler(request: CreateToDoRequest):
    # 핸들러는 그냥 create()만 호출하면 됨
    todo = ToDo.create(request=request)
    # 나중에 필드 추가되면 create()만 수정하면 됨

classmethod 쓰는 이유

이유  설명
캡슐화 생성 로직이 ToDo 클래스 안에 모여있음
유지보수 생성 방법 바뀌면 create()만 수정하면 됨
가독성 ToDo.create(request) 가 ToDo(contents=..., is_done=...) 보다 읽기 쉬움

반환 타입 -> "ToDo" 에서 왜 따옴표가 붙냐면

따옴표 있는 타입 힌트

@classmethod
def create(cls, request: CreateToDoRequest) -> "ToDo":
#                                              ↑
#                                         따옴표로 감싼 이유:
#                                         자기 자신 클래스를 타입 힌트로
#                                         쓸 때 따옴표 필요

# 왜냐면 ToDo 클래스가 아직 완전히 정의되기 전에
# 자기 자신을 참조하려고 하면 에러가 나기 때문
# 따옴표로 감싸면 "나중에 확인해줘" 라는 의미

전체 흐름 한눈에 보기

create() 전체 동작 흐름

POST /todos 요청
{
  "contents": "FastAPI 공부",
  "is_done": false
}
        ↓
create_todo_handler(request=CreateToDoRequest)
        ↓
ToDo.create(request=request) 호출
        ↓
cls = ToDo 클래스 자체
cls(contents="FastAPI 공부", is_done=False)
        ↓
ToDo 인스턴스 생성
todo.contents = "FastAPI 공부"
todo.is_done  = False
        ↓
session에 저장

💡 핵심 요약:
@classmethod = 인스턴스 없이 클래스 이름으로 바로 호출할 수 있는 메서드
cls = 클래스 자체 (self는 인스턴스, cls는 클래스)
cls(...) = ToDo(...) 랑 완전히 같은 말
쓰는 이유 = 생성 로직을 클래스 안에 모아서 유지보수를 쉽게 하기 위해


# main.py

@app.post("/todos", status_code=201)
# FastAPI가 알아서 reqeustBody를 CreateToDoRequest에 넣어서 처리를 해준다.
def create_todo_handler(request: CreateToDoRequest):
    todo: ToDo=ToDo.create(request=request)
  • 변환된 ORM 객체를 통해서 DB에 실제로 저장해달라는 요청을 한다.
# repository.py

# todo를 ORM 객체로 전달을 받도록 설정해주고 return을 해준다. 
def create_todo(session: Session, todo:ToDo) -> ToDo:
    # 생성한 ORM 객체를 Session Object에 먼저 추가를 해준다.
    session.add(instance=todo)
    # db save
    # 이때 DB상의 ToDo id 값은 할당이 되어 저장이 된다.
    session.commit()
    # db를 다시 read ->이때 지금 메모리 상의 todo_id 결정
    session.refresh(instance=todo)
    return todo
# request.py
from pydantic import BaseModel

class CreateToDoRequest(BaseModel):
	# id 값은 DB에서 자동으로 올려주기 때문에 id 없애기
    contents: str
    is_done: bool


# main.py

@app.post("/todos", status_code=201)
# FastAPI가 알아서 reqeustBody를 CreateToDoRequest에 넣어서 처리를 해준다.
def create_todo_handler(
        request: CreateToDoRequest,
        session: Session = Depends(get_db)
) -> ToDoSchema:
    todo: ToDo=ToDo.create(request=request) # id=None
    todo: ToDo=create_todo(session=session, todo=todo) # id=int
    return ToDoSchema.model_validate(todo)
  • 첫번째 todo는 아직 id 값이 none이다.
  • 두번째 todo에서는 create_todo에서 정해진 id 값이 들어간 todo
  • session을 통해서 todo에 todo가 실제로 DB에 저장이 되고 리프레쉬된 두번째 todo가 이 변수에 다시 할당이 되게 된다.

swagger에서 확인

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

ORM 적용 - DELETE API  (0) 2026.06.26
ORM 적용 - PATCH API  (0) 2026.06.25
파이참 - Refactoring  (0) 2026.06.16
ORM 적용 - GET 단일 조회 API  (0) 2026.06.16
ORM 적용 - HTTP Response 처리  (1) 2026.06.14