Python 간단 예제 분석

Elena·2026년 3월 18일
post-thumbnail
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.orm import declarative_base, sessionmaker, Session
from pydantic import BaseModel

# ---------------------------------------------------------
# 1. DB 설정 (application.yml + DataSource 빈 등록 역할)
# ---------------------------------------------------------
# mysql+pymysql 은 "MySQL(마리아DB) 언어를 쓸 거고, pymysql 번역기를 쓸 거야!" 라는 뜻
SQLALCHEMY_DATABASE_URL = "mysql+pymysql://my_id:my_password@127.0.0.1:3306/weather_db"

# oracle+oracledb 은 "오라클 언어와 oracledb 번역기를 쓸 거야!" 라는 뜻
# SQLALCHEMY_DATABASE_URL = "oracle+oracledb://my_id:my_password@127.0.0.1:1521/?service_name=my_service"

engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()


# ---------------------------------------------------------
# 2. Entity 모델 정의 (JPA의 @Entity 클래스 역할)
# ---------------------------------------------------------
class WeatherEntity(Base):
    __tablename__ = "weather_data"  # 실제 DB 테이블 이름

    id = Column(Integer, primary_key=True, index=True)
    region = Column(String, index=True)
    rainfall = Column(Integer)
    is_heavy_rain = Column(Boolean)

# 서버가 켜질 때 테이블이 없으면 자동으로 만들어줌 (ddl-auto: update 와 비슷함)
Base.metadata.create_all(bind=engine)


# ---------------------------------------------------------
# 3. DTO 및 FastAPI 설정
# ---------------------------------------------------------
app = FastAPI()

# 응답용 DTO
class WeatherResponse(BaseModel):
    region: str
    rainfall: int
    is_heavy_rain: bool

    class Config:
        # 핵심: JPA Entity를 Pydantic DTO로 알아서 변환해주는 마법의 옵션!
        from_attributes = True 

# 매 요청마다 DB 세션을 열고 닫아주는 함수
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


# ---------------------------------------------------------
# 4. API 엔드포인트 (@GetMapping 역할)
# ---------------------------------------------------------
@app.get("/api/weather", response_model=list[WeatherResponse])
def get_weather_data(db: Session = Depends(get_db)):
    
    # JpaRepository의 findAll()과 완벽히 똑같음
    # SELECT * FROM weather_data; 쿼리가 실행됨
    weather_list = db.query(WeatherEntity).all()
    
    return weather_list

1. Fast API란?

-> 파이썬의 최신 웹 프레임워크

파이썬 백엔드 생태계에는 크게 '3대장' 프레임워크가 있다. 자바 생태계에 빗대서 표현한다면?

1.프레임 워크

1. Django (장고)

  • "파이썬계의 순정 Spring"
  • 특징: 가장 전통적이고 무거움. ORM, 관리자 페이지, 인증 등 웹 개발에 필요한 모든 기능이 '풀세트'로 들어있음

  • 어울리는 곳: 규모가 크고 복잡한 시스템을 처음부터 끝까지 다 만들어야 할 때

2. Flask (플라스크)

  • "파이썬계의 Spark/Javalin"
  • 특징: 아주 가볍고 최소한의 기능만 제공. 필요한 라이브러리(DB 연결 등)는 개발자가 직접 골라서 조립해야 함

  • 어울리는 곳: 아주 단순한 API 서버나 마이크로서비스를 만들 때

3. FastAPI (패스트에이피아이)

  • "파이썬계의 Spring Boot (API 특화버전)"
  • 특징: 최근 가장 가파르게 성장하는 대세. 이름처럼 속도가 엄청나게 빠르고(Node.js나 Go 언어에 필적함), 코드를 짜면 API 문서(Swagger)를 자동으로 만들어주는 장점. 요즘 새로 시작하는 프로젝트들은 대부분 FastAPI를 선택하는 추세

2. Depends

Depends는 스프링의 @Autowired(의존성 주입)와 인터셉터(Interceptor) 역할을 동시에 수행하는 FastAPI의 핵심 무기

  • 작동 순서:
  1. 클라이언트가 /api/weather를 호출
  2. FastAPI가 매개변수를 봄 "어? Depends(get_db)가 있네?"
  3. API 로직을 실행하기 전에, get_db() 함수를 먼저 실행해서 db 객체를 만들어옴
  4. 그 db 객체를 get_weather_data 함수 안에 넣어줌 (의존성 주입)

get_db()에서는 왜 return이 아니라 yield를 쓸까? (라이프사이클 관리)
-> 자바의 try-with-resources나 @Transactional이 끝날 때 커넥션을 반환하는 것과 같은 원리

  • get_db()에서 yield db를 만나면, 잠시 멈추고 db 통로를 API 함수로 빌려줌
  • API 함수가 데이터 조회를 마치고 클라이언트에게 응답(return)을 성공적으로 보냄
  • 그 직후, 다시 get_db()로 돌아와서 finally: db.close()를 실행해 DB 통로를 아주 안전하게 닫음

Depends는 단순히 객체를 주입하는 것뿐만 아니라, "이 API에 들어오기 전에 자격 검증을 해라"라는 수문장 역할도 함

from fastapi import Depends, HTTPException

# 1. 수문장 함수 (토큰 검사기)
def verify_token(token: str):
    if token != "super-secret":
        # 토큰이 틀리면 API 로직으로 넘어가지도 않고 여기서 400 에러를 튕겨냄
        raise HTTPException(status_code=400, detail="인가되지 않은 사용자입니다!")
    return "User_123"

# 2. Depends로 수문장 배치하기
@app.get("/api/secure-data")
def get_secure_data(user_id: str = Depends(verify_token)):
    # 여기까지 들어왔다는 건, 이미 토큰 검사를 무사히 통과했다는 뜻
    return {"message": f"환영합니다 {user_id}님, 여기 비밀 데이터가 있습니다."}

2. SQLAlchemy

SQLAlchemy (시퀄알케미)는 파이썬 생태계에서 JPA와 완벽하게 같은 역할을 하는 가장 유명한 ORM 라이브러리

역할Java Spring (JPA)Python FastAPI
ORM 기술Hibernate / Spring Data JPASQLAlchemy
DB 테이블 매핑@Entity 클래스Base 상속 클래스
데이터 조작JpaRepository 인터페이스Session 객체 (db.query())
의존성 주입@AutowiredDepends()
Entity -> DTO 변환MapStruct, ModelMapper 등Pydantic의 from_attributes

1.create_engine (엔진 생성기)

  • 역할: 데이터베이스(MySQL, Oracle 등)와 실제로 연결하는 '엔진'을 만듦

  • 자바 매칭: DataSource 인터페이스나 application.yml에서 DB 접속 정보(url, username, password)를 세팅하던 것과 정확히 같은 역할

2. Column, Integer, String, Boolean (컬럼과 데이터 타입)

PyCharm이나 VS Code 같은 모던 에디터를 쓰면, id = Column(Integer)라고 타이핑하는 순간 에디터가 알아서 맨 윗줄에 Integer를 import 해줌

3. declarative_base (선언적 뼈대)

  • 역할: 파이썬 클래스를 DB 테이블로 만들어주기 위한 '부모 클래스'를 찍어냄. 이 뼈대를 상속받은 클래스는 파이썬이 "아, 얘는 단순한 객체가 아니라 DB 테이블이구나!" 하고 인식하게 됨

  • 자바 매칭: 클래스 위에 딱 붙이던 @Entity 어노테이션의 역할

4. sessionmaker (세션 공장)

  • 역할: DB와 통신할 수 있는 '통로(Session)'를 계속해서 찍어내는 팩토리

  • 자바 매칭: JPA의 EntityManagerFactory와 같음

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
위의 함수 안에 들어가는 옵션(파라미터)들은 필수일까?
-> bind=engine은 필수에 가깝고, autocommit=False와 autoflush=False는 문법적 필수는 아니지만 '안전한 웹 서버를 위한 실무 표준'

1. bind=engine (연결할 DB 엔진 지정)

  • 의미: "이 공장에서 찍어낼 세션(통로)들은 아까 만들어둔 저 engine을 타고 DB로 가라!"
  • 자바 비교: EntityManagerFactory에 DataSource(DB 접속 정보)를 연결해 주는 것과 동일

2. autocommit=False (자동 커밋 방지)

  • 의미: "DB에 데이터를 변경(INSERT/UPDATE/DELETE)할 때, 내가 명시적으로 db.commit()을 호출하기 전까지는 절대 진짜로 반영(저장)하지 마!"

  • 자바 비교: JDBC의 connection.setAutoCommit(false)와 같고, 스프링에서 @Transactional 어노테이션이 뒤에서 안전하게 처리해 주던 핵심 역할

  • 왜 False로 할까?: 만약 A의 계좌에서 돈을 빼고 B의 계좌에 돈을 넣어야 하는데, B에게 넣기 직전에 서버가 터졌다고 가정해 보자. autocommit=True라면 A의 돈만 빠진 채로 DB에 저장되어버림. 개발자가 모든 비즈니스 로직이 성공적으로 끝난 후 한 번에 안전하게 커밋하기 위해 실무에서는 무조건 False로 둠.

3. autoflush=False (자동 플러시 방지)

  • 의미: "내가 쿼리를 날리기 전에, 파이썬 메모리에만 들고 있던 변경 사항들을 알아서 DB에 밀어 넣지(Flush) 마!"

  • 자바 비교: Hibernate의 영속성 컨텍스트(Persistence Context) 동기화 옵션인 FlushModeType.COMMIT과 아주 비슷한 개념

  • 왜 False로 할까?: 파이썬 코드 상에서 객체의 값을 수정하면, SQLAlchemy는 그걸 즉시 DB에 쏘지 않고 잠시 메모리에 들고 있음. 만약 autoflush=True라면, 다른 쿼리를 조회하려고 할 때마다 눈치껏 메모리의 변경 사항을 DB에 억지로 동기화시킴. 이러면 내가 원하지 않는 타이밍에 쿼리가 무작위로 날아가서 성능이 떨어지거나 데이터가 꼬일 수 있기 때문에, 개발자가 완벽히 통제할 수 있도록 보통 False로 둠.

5. Session (세션)

  • 역할: 실제로 DB에 쿼리를 날리고(SELECT, INSERT 등), 데이터를 객체로 받아오는 실제 통로

  • 자바 매칭: JPA의 EntityManager 객체 그 자체이자, JpaRepository가 내부적으로 데이터를 조작할 때 쓰는 핵심 객체

3. 던더(Dunder, Double Underscore)

양옆에 언더바(Underscore)가 두 개씩(__) 붙은 변수나 함수를 던더라고 부르고 이건 일반 변수가 아니라, 시스템 내부에서 쓸 아주 특별한 설정값(또는 함수)이니까 철자 틀리지 말고 똑같이 적으라는 의미이다.

기능Java (JPA)Python (SQLAlchemy)
테이블 이름 지정@Table(name="users")__tablename__ = "users"
초기화(생성자)public User() { ... }def __init__(self): ...
문자열 출력@Override public String toString()def __str__(self): ...

4.Base.metadata.create_all(bind=engine)

적는 위치(타이밍)가 매우 중요. 아무 데나 적으면 테이블이 생성되지 않거나 에러가 발생할 수 있음

1. 절대 규칙

"엔티티(모델)가 모두 정의된 직후, 서버가 켜지기 직전"
Base.metadata.create_all(bind=engine)이라는 코드는 쉽게 말해 "지금까지 내 뼈대(Base)를 상속받은 모든 클래스들을 모아서 DB에 테이블로 만들어라!"라는 명령. 따라서 반드시 아래 순서를 지켜야 함

  • Base 객체 생성
  • WeatherEntity(Base) 등 각종 테이블 클래스 정의 (또는 import)
  • Base.metadata.create_all(bind=engine) 실행 <- 바로 여기!
  • FastAPI 서버 실행

2. 실무 프로젝트에서 실제 위치

  • [models.py] - 모델만 모아두는 곳
from database import Base
from sqlalchemy import Column, Integer, String

class WeatherEntity(Base):
    __tablename__ = "weather_data"
    id = Column(Integer, primary_key=True)
    # ...
  • [main.py] - 서버를 실행하는 곳
from fastapi import FastAPI
from database import engine, Base
import models  # 핵심: 이 줄에서 모델들을 읽어들임

# 모델이 다 읽힌 후, 테이블을 생성
Base.metadata.create_all(bind=engine)

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

3. 실무 Tip

자바에서 실무 프로젝트를 하실 때, 운영 서버 DB에 ddl-auto=update를 켜두지 않고 Flyway나 Liquibase 같은 DB 마이그레이션 툴을 사용하듯이 규모가 있는 실제 회사 코드에서는 Base.metadata.create_all(bind=engine) 아예 지워버리고, 파이썬 생태계의 Flyway인 Alembic (알렘빅) 이라는 도구를 사용해서 테이블 변경 이력을 버전별로 꼼꼼하게 관리한다.

profile
一切唯心造

0개의 댓글