본 시리즈는 '박응용' 님의 '점프 투 FastAPI'를 바탕으로 학습 및 실습한 내용을 정리한 것입니다.
구현 및 파인튜닝한 모델을 사용한 웹 서비스 구현을 위해 FastAPI의 학습 필요성을 느껴 학습 과정을 정리합니다. 내용의 정확성이나 이론적인 부분은 당연히 원본 페이지를 참조하시는 게 좋고, 본 시리즈에서는 구현 도중 발생하는 문제 등을 해결하는 과정을 함께 기록하여 '처음부터 끝까지 따라 할 수 있는' 시리즈를 만드는 것을 목표로 합니다(물론 제1목표는 학습 내용 기록입니다).
이번 프로젝트는 질문과 답변을 작성하는 게시판을 구현하는 것을 목표로 한다. 본격적인 구현에 앞서 미리 프로젝트의 전체 구조를 설계해 두는 것이 좋다. 대략적인 구조는 다음과 같다.
├── main.py
├── database.py
├── models.py
├── domain
│ ├── answer
│ ├── question
│ └── user
└── frontend
각 구조는 다음과 같은 역할을 한다.
질문 답변 게시판의 구현을 위해서는 데이터를 저장 및 조회하는 기능을 구현해야 하며, 따라서 데이터베이스를 사용해야 한다. 일반적으로는 SQL을 이용해 데이터베이스를 다루지만, ORM(Object Relational Mapping)을 이용하면 파이썬 문법만으로도 데이터베이스를 다룰 수 있다. 따라서 파이썬 ORM 중 가장 많이 사용되는 SQLAlchemy를 이용해 데이터베이스를 다루도록 한다(내 경우 간단한 SQL은 구현 가능하지만 일단 튜토리얼대로 구현해보도록 한다).
다음 명령어를 통해 SQLAlchemy 라이브러리를 설치한다.
(practice_1) C:\workspace\fastapi_practice\practice_1> pip install sqlalchemy
FastAPI에 ORM을 적용하기 위해 데이터베이스 설정을 해야 한다. 메인 디렉토리에 database.py 파일을 생성한 후 다음 코드를 입력한다.
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./practice_1.db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
각 코드의 역할은 다음과 같다.
질문과 답변 모델에 필요한 속성(attribute)은 각각 다음과 같을 것이다.
[질문 모델 속성]
| 속성명 | 설명 |
|---|---|
| id | 질문 데이터의 고유 번호(Primary key) |
| subject | 질문의 제목 |
| content | 질문의 내용 |
| create_date | 질문 작성 일시 |
[답변 모델 속성]
| 속성명 | 설명 |
|---|---|
| id | 답변 데이터의 고유 번호(Primary key) |
| question_id | 질문 데이터의 고유 번호(Foreign key) |
| content | 답변의 내용 |
| create_date | 답변 작성 일시 |
상기에서 구상한 속성을 바탕으로 모델을 정의해 보자. 모델 속성을 정의하기 위한 models.py 파일을 생성하고 질문 모델인 Question 클래스를 선언한다.
from sqlalchemy import Column, Integer, String, Text, DateTime
from database import Base
class Question(Base):
__tablename__ = "Question"
id = Column(Integer, primary_key=True)
subject = Column(String, nullable=False)
content = Column(Text, nullable=False)
create_date = Column(DateTime, nullable=False)
모델 클래스는 database.py에서 정의한 Base 클래스를 상속하여 만들어야 한다. __tablename__은 모델에 의해 관리되는 테이블의 이름이며, 각 속성은 Column으로 생성한다. 이때 Column의 첫 번째 인자는 해당 속성의 데이터 타입을 의미하며, 추가로 입력되는 속성은 다음과 같다.
마찬가지의 방법으로 Answer 클래스도 생성한다.
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Question(Base):
__tablename__ = "Question"
id = Column(Integer, primary_key=True)
subject = Column(String, nullable=False)
content = Column(Text, nullable=False)
create_date = Column(DateTime, nullable=False)
class Answer(Base):
__tablename__ = "Answer"
id = Column(Integer, primary_key=True)
content = Column(String, nullable=False)
create_date = Column(DateTime, nullable=False)
question_id = Column(Integer, ForeignKey("question.id"))
question = relationship("Question", backref="answers")
다른 속성은 앞서 질문 모델과 같으므로 넘어가고, 여기서 중요한 것은 question_id와 question 속성이다.
모델을 구상하고 생성했으니, 이를 이용해 데이터베이스 테이블을 생성해 보자. 이를 위해 SQLAlchemy의 alembic을 이용한다. alembic은 SQLAlchemy로 작성한 모델을 기반으로 데이터베이스를 관리할 수 있도록 도와주는 도구이다.
# alembic 설치
(practice_1) C:\workspace\fastapi_practice\practice_1> pip install alembic
# 초기화
(practice_1) C:\workspace\fastapi_practice\practice_1> alembic init migrations
설치 및 초기화를 완료하면 practice_1 디렉토리 아래에 migrations라는 디렉토리와 alembic.ini 파일이 생성된다. migrations 디렉토리는 alembic 도구 사용 시 생성되는 리비전 파일들을 저장하는 디렉토리이고, alembic.ini 파일은 alembic의 환경 설정 파일이다.
리비전 파일이란 alembic을 이용해 테이블을 생성 혹은 변경할 때마다 생성되는 작업 파일을 말한다.
이제 alembic.ini 파일을 열어 다음과 같이 수정한다.
...
sqlalchemy.url = sqlite:///./myapi.db
...
migrations 폴더의 env.py 파일도 다음과 같이 수정한다.
...
import models
...
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = models.Base.metadata
...
터미널에서 다음 명령을 수행해 리비전 파일을 생성한다.
(practice_1) C:\workspace\fastapi_practice\practice_1> alembic revision --autogenerate
그러면 다음과 같은 메시지와 함께 migrations\versions 디렉토리에 리비전 파일이 생성된 것을 확인할 수 있다.
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'Question'
INFO [alembic.autogenerate.compare] Detected added table 'Answer'
Generating C:\workspace\fastapi_practice\practice_1\migrations\versions\a2bf9f6ae936_.py ... done
이렇게 생성된 리비전 파일을 실행한다.
(practice_1) C:\workspace\fastapi_practice\practice_1> alembic upgrade head
그러면 다음과 같은 메시지와 함께 practice_1 디렉토리에 practice_1.db라는 파일이 생성된 것을 확인할 수 있다. 바로 이 파일이 SQLite의 데이터베이스 파일이다.
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> a2bf9f6ae936, empty message
생성한 데이터베이스에 정말로 Question과 Asnwer 테이블이 생성되었는지 확인해보자 DB Brower for SQLite를 설치해 확인해보면 다음과 같이 Answer와 Question 테이블이 생성된 것을 확인할 수 있다.
