FastAPI #3 비동기 데이터베이스

FastAPI #3 비동기 데이터베이스 #

#2025-09-17


#1 main.py

#main.py
# FastAPI 엔드포인트 정의 이해
# FastAPI는 아래 두 가지 방식 중 하나로 엔드포인트를 정의
# ① 직접 app에 정의
# ② 모듈화한 라우터 파일을 include
from fastapi import FastAPI
from api.routers import task_a
from api.routers import done_a
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.openapi.docs import get_swagger_ui_html
import os
  • main.py
    • fastapi app 서버를 구성.
  • fastapi 프레임워크
    • 웹 요청이 들어오면 특정 함수로 연결해준다.
    • 연결 지점 = 엔드포인트.
    • ex) 누군가 브라우저에서 http://127.0.0.1:8001/hello를 호출하면, FastAPI는 이 요청을 보고 “아 이건 /hello 경로의 GET 요청이구나” 하고, 미리 등록해둔 hello() 함수를 실행한 뒤 그 반환값을 JSON으로 돌려준다.
  • fastapi 기본 구조
    • 먼저 app = FastAPI()로 애플리케이션 객체를 만들고
    • 그 뒤에 @app.get("/"), @app.get("/hello") 같은 데코레이터로 함수를 등록하기.
# app = FastAPI()
# FastAPI 앱(서버)의 기본 뼈대 생성
app = FastAPI(docs_url=None)
# 기본 /docs 비활성화, 개별 favicon 적용
@app.get("/")
async def root():
    return {"message": "Welcome to the FastAPI server!"}

@app.get("/hello")
async def hello():
    return {"message": "hello world!"}
  • 보통 FastAPI는 /docs 주소로 들어가면 자동으로 Swagger UI라는 API 설명서가 나오지만 여기서는 app = FastAPI(docs_url=None)라고 작성해 기본 /docs 경로를 막음
    • 나중에 직접 커스터마이징한 /docs 엔드포인트를 등록하려고.
    • 대신 / 경로에서는 단순히 “Welcome to the FastAPI server!“라는 메시지를 주고, /hello 경로에서는 “hello world!“라는 메시지를 줌.

#

# ① 경로 (/) 및 (/hello)에 대한 라우팅 추가 (라우터 파일 내에서 경로를 직접 정의)
# 별도의 라우팅이 없으면 GET /tasks, POST /tasks 등의 API가 동작하지 않음

# app.include_router(task.router)를 호출해야 task.py의 엔드포인트가 FastAPI 앱에 등록
# 여러 개의 라우트 모듈을 관리하기 쉽게 하기 위해 include_router()를 사용

# ② 라우터 등록 (FastAPI 앱에 실제로 등록, 모듈화한 라우터 파일을 include)
app.include_router(task_a.router) # main.py에서 api/routers/task.py의 라우트를 include_router()로 FastAPI 앱에 추가
app.include_router(done_a.router) # main.py에서 api/routers/done.py의 라우트를 include_router()로 FastAPI 앱에 추가
  • 라우터?
    • 엔드포인트들을 별도 파일로 나누어 관리할 수 있는 기능.
    • 할 일(Task)을 관리하는 API, 완료(Done)를 관리하는 API처럼 종류별로 나누면 프로젝트가 훨씬 깔끔해진다.
  • app.include_router(task_a.router), app.include_router(done_a.router)
    • task_a.py 안에 정의된 라우터들을 불러와서 fastapi 앱에 등록한다.
    • /tasks 같은 엔드포인트들이 main.py에 직접 쓰여 있지 않아도 라우터 파일이 include되면서 실제 서버에서 동작한다.

#

# static 경로 mount (필수!)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
STATIC_DIR = os.path.join(BASE_DIR, "static")  # 현재 main.py가 api/ 안에 있다고 가정
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")

# favicon 직접 연결
@app.get("/favicon.ico")
async def favicon():
    return FileResponse(os.path.join(STATIC_DIR, "favicon.ico"))
  • 정적 파일(static files)은 fastapi에 연결해서 /static으로 접근.
  • favicon.ico
    • 웹 브라우저가 기본적으로 요청하는 아이콘 파일이기 때문에 @app.get("/favicon.ico”) 엔드포인트를 만들어서 직접 반환한다.
# Swagger UI 커스터마이징 - favicon 적용
from fastapi.openapi.docs import get_swagger_ui_html

@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
    return get_swagger_ui_html(
        openapi_url=app.openapi_url,
        title="My API Docs",
        swagger_favicon_url="/static/favicon.ico"
    )
  • swagger ui 커스터마이징
    • swagger_favicon_url="/static/favicon.ico”
    • 지정한 아이콘을 Swagger UI 화면에 반영.

#

#2

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.asyncio import AsyncAttrs

# ✅ MySQL 비동기 데이터베이스 URL
ASYNC_DB_URL = "mysql+aiomysql://manager:SqlDba-1@0.0.0.0:3379/demo?charset=utf8mb4"
  • FastAPI와 SQLAlchemy를 이용해서 비동기 방식으로 데이터베이스와 연결하기
  • 웹 애플리케이션이 데이터베이스와 소통하려면
    • “어디에 있는 DB에, 어떤 계정으로 접속할 것인지”를 정하고
    • 그 DB에 요청을 보냈다가 결과를 받는 과정을 반복한다.
    • 근데 단순히 한두 번 요청하는 게 아니라 수많은 요청을 동시에 처리해야 하므로 연결을 효율적으로 관리하는 체계가 필요함
  • ASYNC_DB_URL
    • 데이터베이스 접속 주소.

#

# ✅ 비동기 데이터베이스 엔진 생성
async_engine = create_async_engine(
    ASYNC_DB_URL,
    echo=True,
    future=True  # 최신 SQLAlchemy API 사용 시 권장
)

# ✅ 비동기 세션 팩토리 설정
AsyncSessionLocal = sessionmaker(
    bind=async_engine,
    class_=AsyncSession,
    expire_on_commit=False,
    autoflush=False,  # flush는 명시적으로
    autocommit=False  # commit은 명시적으로
)
  • async_engine
    • SQL 명령을 실행하는 데이터베이스 엔진
    • echo=True
      • 실행되는 SQL 쿼리가 콘솔에 그대로 찍힌다
    • future=True
      • SQLAlchemy의 최신 API 스타일을 쓰겠다.
    • 이 엔진을 통해 DB에 연결할 수 있다.
  • 세션(session)
    • DB에 연결해서 여러 쿼리를 실행하고 최종적으로 결과를 반영하거나 취소하는 과정 전체를 관리.
  • AsyncSessionLocal
    • 세션 팩토리 (세션을 필요할 때마다 새로 찍어내는 공장)

#

# ✅ 비동기 지원을 위한 SQLAlchemy Base 클래스
Base = declarative_base(cls=AsyncAttrs)
  • Base
    • SQLAlchemy에서 테이블 구조를 코드로 표현할 때 요 클래스를 쓴다고함.
  • declarative_base(cls=AsyncAttrs)
    • 비동기 처리를 지원하는 기능을 포함한 Base 클래스를 만들겠다
    • Task, Done 같은 모델들은 모두 이 Base를 상속받아 정의된다이제.
# ✅ 비동기 데이터베이스 세션을 반환하는 종속성 함수 (트랜잭션 처리 포함)
async def get_db():
    async with AsyncSessionLocal() as session:
        try:
            yield session  # 라우터에서 db 작업 수행
            await session.commit()  # ✅ 명시적 커밋
            print("[✅ COMMIT 완료]")
        except Exception as e:
            await session.rollback()  # ✅ 오류 발생 시 롤백
            print(f"[❌ ROLLBACK 발생]: {e}")
            raise
        finally:
            await session.close()
  • get_db()
    • router에서 db: AsyncSession = Depends(get_db)라고 쓰면
    • fastapi는 함수를 실행해서 세션을 꺼내고 작업이 끝나면 자동으로 커밋, 문제가 생기면 자동롤백, 끝나면 연결을 닫는다.

#