FastAPI MySQL을 연동한 웹서버 구축 개요
Python 진영 중 현대적인 웹 프레임워크 FastAPI와 대중적인 관계형 데이터베이스인 MySQL을 연동하는 간단한 웹 서버를 구축한다. 이 과정에서 편의를 위해 Docker를 사용하며, 관계형 데이터베이스를 객체 지향적인 방식으로 다룰 수 있게 도와주는 ORM 라이브러리 SQLAlchemy를 함께 사용한다.
참고 사항
- 윈도우 10 환경에서 작업
- C:\...\fastapi-mysql>로 시작하는 모든 명령어는 cmd에서 프로젝트 루트 디렉토리인 fastapi-mysql에 위치한 상태에서 실행함
- 작업시 venv 가상환경을 사용하였지만 사용하지 않은 것처럼 작성함
- 디렉토리에 venv 디렉토리, mysql/data의 하위 파일, .gitnore파일은 생략함
1. Python3.10.5 설치 및 pip 업그레이드
1.1 Python3.10.5 버전 설치 및 환경변수 설정 생략
1.2 (선택) venv 가상환경 설치
C:\...\fastapi-mysql> python -m venv venv
C:\...\fastapi-mysql> cd venv/Scripts
C:\...\fastapi-mysql> activate.bat
# 가상환경 상태
(venv) C:\...\fastapi-mysql>
만약 가상환경을 설치하였으면 작업시 cmd에서 좌측 (venv) 처럼 가상환경 상태인지 항상 확인해야 한다.
1.3 아래 명령어를 통해 pip 최신 버전 업그레이드
C:\...\fastapi-mysql> pip install --upgrade pip
여기에서 pip 버전은 22.1.2이다.
2. FastAPI 설치
2. 1 pip를 사용하여 fastapi 패키지 다운로드
C:\\...\\fastapi-mysql> **pip install fastapi[all]**
위 설치는 웹서버를 실행하는 Starlette를 포함한다.
2. 2 main.py 작성
app 디렉토리에 main.py 파일을 생성하고 아래 코드를 입력한다.
# FastAPI는 Starlette를 직접 상속하며 Starlette의 모든 기능을 사용할 수 있다.
from fastapi import FastAPI
# FastAPI 인스턴스 생성
app = FastAPI()
# 루트 엔드포인트 생성
@app.get("/")
async def root():
return {"message": "Hello FastAPI"}
2. 3 현재 디렉토리 구조
📦fastapi-mysql (원하는 이름으로 설정 여기서는 fastapi-mysql)
┗ 📂app
┗ 📜main.py
2. 4 실행 테스트
cmd에 아래 명령어를 입력하여 올바르게 실행되었는지 확인할 수 있다.
C:\\...\\fastapi-mysql> **uvicorn app.main:app --reload**
# asgi구현체인 uvicorn을 통해 FastAPI를 실행한다.
# :앞은 main.py 파일 디렉토리 경로이다.
# :뒤는 main.py에서 정의한 FastAPI 인스턴스 변수명이다.
# --reload는 개발환경에서만 사용해야하며 파일이 수정되었을때 자동으로 서버를 재실행한다.
웹브라우저에서 http://127.0.0.1:8000/에 접속하면
{"message":"Hello FastAPI"} 라는 Json 데이터를 확인할 수 있다.
3. FastAPI와 MySQL을 Docker로 실행하기
3. 1 필요한 디렉토리 및 파일 생성
pip를 사용하여 requirements 파일 생성한다. 이 파일은 도커 이미지를 컨테이너로 실행할 때 개발 시 사용한 패키지정보를 담고 있다. 이 파일은 새 환경에서 pip만 설치되어있으면 다시 모든 패키지를 다운받을 수 있다.
# requirements.txt 파일 생성
C:\\...\\fastapi-mysql> pip freeze > requirements.txt
3. 2 디렉토리와 필요한 파일 생성하기
📦fastapi-mysql
┣ 📂app
┃ ┗ 📜main.py
┣ 📂 mysql (새 디렉토리)
┃ ┣ 📂data (새 디렉토리)
┃ ┗ 📂conf.d (새 디렉토리)
┃ ┗ 📜my.cnf (새 파일)
┣ 📜.env (새 파일)
┣ 📜docker-compose.yml (새 파일)
┣ 📜dockerfile (새 파일)
┗ 📜requirements.txt (새 파일)
3. 3 .env 파일(dotenv) 작성
mysql 설정 정보는 코드로 직접 입력하는 것이 아니라 .env 파일을 통해 관리해야 한다.
MYSQL_HOST={공인 ip주소 또는 localhost}
MYSQL_PORT= {포트 번호, 기본 3306}
MYSQL_ROOT_PASSWORD={루트 계정 비밀번호}
MYSQL_DATABASE={데이터베이스 이름}
MYSQL_USER={유저 아이디}
MYSQL_PASSWORD={자신의 비밀번호}
# 예시
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_ROOT_PASSWORD=1234
MYSQL_DATABASE=fastapi_mysql
MYSQL_USER=walden
MYSQL_PASSWORD=1234
3. 4 my.cnf 파일 작성
컨테이너의 my.cnf 설정 파일을 변경하려면 이 파일을 변경하면 된다.
여기서는 MySQL의 문자 설정을 utf8mb4로 설정
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
3. 5 Dockerfile 작성
Dockerfile은 베이스 이미지를 가져와 새로운 이미지를 생성할 수 있도록 한다. 여기서는 python 이미지를 가져와 requirements.txt를 통해 FastAPI와 같이 필요한 패키지를 설치하고 개발한 소스를 복사한 이미지를 생성한다.
그리고 작성한 소스를 복사하고 컨테이너가 생성될 경우 입력될 명령어를 가장 하단 CMD에 정의한다.
# 원하는 이미지로 변경 가능
FROM bitnami/python:3.10.5
RUN pip install --upgrade pip
WORKDIR /fastapi-mysql
COPY ./requirements.txt /fastapi-mysql/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /fastapi-mysql/requirements.txt
COPY ./app /fastapi-mysql/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
3. 6 Docker-compose.yml 작성
Docker compose는 단일 명령으로 다중 컨테이너 앱을 실행할 수 있도록 도와주는 도구이다. 여기서는 도커 허브에서 MySQL 이미지를 가져와 환경설정을 하고 이전에 작성한 Dockerfile을 실행하도록 한다.
Dockerfile은 Dockerfile만으로도 실행 가능한 상태이지만 Docker compose에서 여러 개의 Dockerfile과 여러 개의 도커 허브의 이미지를 함께 실행, 종료 등 관리를 할 수 있다.
version: "3.8"
services:
db:
image: mysql:8.0.29
container_name: mysql8029
ports:
- 3306:3306
volumes:
- ./mysql/conf.d:/etc/mysql/conf.d
- ./mysql/data:/var/lib/mysql
env_file: .env
environment:
TZ: Asia/Seoul
restart: always
server:
build:
context: .
dockerfile: ./Dockerfile
container_name: FastAPI
ports:
- "8000:8000"
restart: always
아래 명령어를 통해 도커 컴포즈에 정의한 컨테이너를 관리할 수 있다.
# 도커 컴포즈에 정의한 이미지를 실행
C:\\...\\fastapi-mysql> docker-compose up
# 종료
C:\\...\\fastapi-mysql> docker-compose down
# 현재 프로세스 확인
C:\\...\\fastapi-mysql> docker-compose ps
3. 7 Docker volume이란
볼륨은 Docker 컨테이너에서 생성하고 사용하는 데이터를 유지하기 위한 기본 메커니즘입니다. - 도커 공식 문서 -
여기에서는 도커 볼륨을 docker-compose.yml 파일에 db → volume 섹션에서 정의하였다. 콜론 좌측은 docker-compose를 실행하는 호스트의 디렉토리 경로를 의미하고 콜론 우측은 컨테이너 내부의 디렉토리 경로를 의미한다.
위에서 생성한 mysql/data 디렉토리는 mysql 데이터 저장 경로인 var/lib/mysql과 연동되는 볼륨을 생성하고 이후 해당 mysql 컨테이너는 해당 디렉토리를 참조하여 이전에 쌓인 데이터가 유지된다.
4. SQLAlchemy로 FastAPI와 MySQL 연동
SQLAlchemy를 통해 Docker로 생성한 MySQL에 연결하고 서버 실행 시 Python 코드로 작성한 클래스를 참조하여 MySQL 테이블을 생성하도록 구현한다.
4. 1 SQLAlchemy, pymysql설치
C:\\...\\fastapi-mysql> pip install sqlalchemy
C:\\...\\fastapi-mysql> pip install pymysql
# 도커에서 다시 패키지 설치 시 누락되지 않도록 하기
C:\\...\\fastapi-mysql> pip freeze > requirements.txt
4. 2 필요한 디렉토리 및 파일 생성
데이터베이스 연동과 SQLAlchemy로 정의한 클래스를 토대로 테이블 생성 등을 위한 파일들이다. 추가로 디렉토리 구조가 생기면서 다른 경로의 패키지를 가져오기 위해 __init__.py 파일을 디렉토리마다 생성한다.
📦fastapi-mysql
┣ 📂app
┃ ┣ 📂core (새 디렉토리)
┃ ┃ ┣ 📜__init__.py (새 파일)
┃ ┃ ┗ 📜config.py (새 파일)
┃ ┣ 📂db (새 디렉토리)
┃ ┃ ┣ 📂models (새 디렉토리)
┃ ┃ ┃ ┣ 📜__init__.py (새 파일)
┃ ┃ ┃ ┣ 📜comments.py (새 파일)
┃ ┃ ┃ ┗ 📜posts.py (새 파일)
┃ ┃ ┣ 📜__init__.py (새 파일)
┃ ┃ ┣ 📜base.py (새 파일)
┃ ┃ ┣ 📜base_class.py (새 파일)
┃ ┃ ┗ 📜session.py (새 파일)
┃ ┗ 📜main.py
┣ 📂mysql
┃ ┣ 📂conf.d
┃ ┗ 📜my.cnf
┣ 📜.env
┣ 📜docker-compose.yml
┣ 📜dockerfile
┗ 📜requirements.txt
4. 3 config.py 파일 작성하기
pydantic의 BaseSettings를 상속한 Settings 클래스는 .env에 정의한 데이터베이스 환경 정보를 가져와 변수에 저장한다. @lru_cache()를 통해 Settings 클래스의 데이터를 캐싱하고 최초 클래스 인스턴스 생성 이후로 캐싱된 인스턴스를 반환한다.
from pydantic import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
MYSQL_HOST: str
MYSQL_PORT: int
MYSQL_ROOT_PASSWORD: str
MYSQL_DATABASE: str
MYSQL_USER: str
MYSQL_PASSWORD: str
class Config:
env_file = ".env"
@lru_cache()
def get_setting():
return Settings()
4. 4 session.py 파일 작성하기
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ..core.config import get_setting
settings = get_setting()
SQLALCHEMY_DATABASE_URL = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(
settings.MYSQL_USER,
settings.MYSQL_PASSWORD,
settings.MYSQL_HOST,
settings.MYSQL_PORT,
settings.MYSQL_DATABASE,
)
db_engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=db_engine)
4. 5 Base_class.py 파일 작성하기
MySQL table과 매핑되는 Python 클래스들의 슈퍼 클래스를 작성한다.
from typing import Any
from sqlalchemy.ext.declarative import as_declarative, declared_attr
@as_declarative()
class Base:
id: Any
__name__: str
#to generate tablename from classname
@declared_attr
def __tablename__(cls) -> str:
return cls.__name__.lower()
4. 6 데이터베이스 테이블과 대응되는 모델 클래스 작성
한 개의 포스트에 다수의 코멘트가 저장될 수 있다.
comments.py 작성하기
from sqlalchemy import Column, ForeignKey,Integer, String
from ..base_class import Base
class Comment(Base):
id = Column(Integer,primary_key=True)
description = Column(String(255),nullable=True)
post_id = Column(Integer, ForeignKey("post.id"))
posts.py 작성하기
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from ..base_class import Base
class Post(Base):
id = Column(Integer,primary_key = True)
title = Column(String(255),nullable= False)
decription = Column(String(255),nullable= False)
comment = relationship("Comment")
4. 7 base.py 파일 작성
다수의 데이터 모델을 한곳에서 연동할 수 있도록 한다.
from .base_class import Base
from .models.comments import Comment
from .models.posts import Post
4. 8 main.py에 테이블 생성 로직 추가하기
from fastapi import FastAPI
from db.session import db_engine
from db.base import Base
def create_tables():
Base.metadata.create_all(bind=db_engine)
def get_application():
app = FastAPI()
create_tables()
return app
app = get_application()
...
4. 9 실행
다시 docker-compose를 통해 실행하면 MySQL과 연동되어 posts와 comments 테이블이 생성되는 것을 확인할 수 있다.