본문 바로가기
Backend MLOps/Database

Fastapi docs 무작정 따라하기 ORM 편 - 1

by SteadyForDeep 2022. 6. 3.
반응형

시작하기

Fastapi에서는 파이썬으로 제공되는 어떤 방식의 DB도 사용이 가능하다.
DB는 서버에서 발생하는 io 작업이므로 서버 성능에 큰 영향을 주게 된다.
따라서 Fasapi의 공식 문서를 무작정 따라가면서 어떤 방식을 권장하는지 알아보자.

여기에 의존하여 작성된 문서임을 밝힌다.

ORM

서버에서 DB를 사용하는 방법은 크게 두가지가 있다.
하나는 쿼리문을 직접 작성하여 DB로 날리는 방식이고
다른 하나는 DB의 Table에 mapping 할 수 있는 형태의 객체를 만들어서 관리하는 방식이다.

후자의 이름이 바로 ORM인데 지금까지 나는 쿼리를 직접 작성하는 방식에 익숙해 있어
ORM을 많이 활용하지 않았다. 하지만 비동기 처리나 컨테이너를 올리고 내리는 작업에서
쿼리를 직접 작성해 execute하는 방식은 크게 좋은 방식이 아니었다.

DB 컨테이너의 변덕에 따라 커넥션이 끊어지는 경우를 계속해서 예외처리 해야했고
무엇보다 굉장히 귀찮고 가독성 떨어지는 코드가 작성되기 때문에
팀워크에도 유리한 부분이 없었다.

그리고 불러온 데이터를 파이썬에서 가공할 수 있는 형태로 변환하는 작업을
직접 구현해야하는 수고가 따르므로 전반적인 업무효율이 굉장히 떨어지는 방식이었다.

Fastapi는 ORM을 권장하므로 따라가 보자.

SQLite

예제를 위해서 sqlite를 사용한다.
sqlite는 .db파일 하나로 관리되는 굉장히 가벼운 DB이고 SQL 쿼리를 사용할 수 있다.
무엇보다 파이썬에 정식지원되는 모듈로 파이썬을 깔았다면 자동으로 따라오는 라이브러리다.

SQLAlchemy

SQLAlchemy는 파이썬에서 ORM이 가능하도록 해주는 툴킷이다.
원래는 pymysql 같은 DB커넥션을 사용하는 DB에 맞게 가져와서 사용해야 하지만
SQLAlchemy를 이용하면 훨씬 더 편하게 파이썬에서 DB를 제어할 수 있다.

File structure

파이썬의 패키지와 모듈의 개념은 심심하면 한번씩 나를 괴롭힌다.
우리가 밖에서 DB를 활용하려면 작성한 모든 코드가 파이썬의 패키지로 묶여있어야 한다.

따라서 위와 같이 __init__.py로 시작하는 디렉토리를 만들어서 묶어주고
다른 곳에서 패키지로 이용할 수 있게 한다.

이제 파일을 하나하나 자세하게 작성해 보자.

Create the SQLAlchemy parts

sql_app/database.py 파일을 작성해 보자.
sqlalchemy는 없을 수 있다. 없다면 아래 명령어로 설치하자.

pip install sqlalchemy

첫번째로 할 일은 커넥터가 접속할 URL을 적어주는 것이다.

SQLALCHEMY_DB_URL = "sqlite:///./sql_app.db"

보통의 경우는 <scheme>://<user>:<password>@<host>:<port>/<url-path>로 적는 것이 일반적이고
sqlite의 경우는 file접근을 사용하기 때문에 file://<user>/<path>로 작성되는 특징이 있다.
자세한 문서는 여기를 참고.

그 다음은 엔진을 만들어 주자.

설명에 따르면 sqlite는 기본적으로 단 하나의 쓰레드만을 이용해 데이터를 주고 받는다고 한다.
하지만 Fastapi의 비동기처리로 인해 sqlite가 올라가 있는 쓰레드가 아닌 다른 쓰레드에서 접근할 수도 있으므로
connect_args={"check_same_thread": False} 옵션을 추가해 준다.

Create a SessionLocal class

SessionLocal 클래스는 sessionmaker를 이용해 만들 수 있다.
아직 세션이 활성화 된 상태는 아니며 클래스가 반환되기 때문에
이 클래스의 인스턴스가 비로소 session으로 작용하게 된다.
설명에 따르면 나중에 sqlalchemy로 부터 session을 얻어오는 과정이 있기 때문에
local 에서 생성한 session 이라고 따고 명명해 준다.

Create a Base class

declarative_base함수로 부터 Base클래스를 리턴받을 수 있다.
이 클래스는 나중에 객체를 테이블로 맵핑하는 ORM model이 된다.

Create the database models

이제 slq_app/models.py 파일로 넘어간다.
여기서는 본격적으로 DB와 소통하는 객체를 생성한다.
팁에 의하면 sqlalchemy에서 말하는 model은 DB에 맵핑하기 위한 객체를 말하고
pydantic에서 말하는 model은 어떤 값의 validation을 위해서 사용된다고 한다.

이런 언급이 있는 이유는 Fastapi에서 POST와 같이 request body가 있는 경우나
response body가 있는 경우 pydantic을 이용해서 데이터의 형태가 올바른지 검증하기 때문이다.
그러므로 실재 app을 만들 때는 이 두가지를 혼동하면 안된다.

우리가 작성했던 sql_app/database.py에서 declarative_base함수를 통해 얻은 Base 클래스를 가져온다.
이때 sql_app/database.py파일은 패키지가 아니라 모듈이므로 .을 붙여서 불러온다.
그리고 이 클래스를 상속받는 커스텀 클래스를 선언함으로써 우리가 사용할 수 있는 ORM model이 생성된다.
이때 매직 어트리뷰트에 테이블 이름을 지정해 줄 수 있다.

테이블을 만들었으므로 이제는 테이블에 들어갈 column들을 만들어 줘야 한다.
sqlalchemy의 model이므로 sqlalchemy로 부터 column과 각종 타입에 사용할 클래스들을 가져온다.

잘 보이도록 정리해 봤다.
Column에 대한 문서다.
위의 문서에 따라서 아래와 같은 내용들을 선언한 것이다.

  • name : 자동으로 테이블 이름과 instance 명으로 지정된다. 아래 캡쳐를 참고.
  • type : sqlalchemy의 type 클래스들을 이용해서 명시해 주었다.
  • primary_key : True일 경우 해당 Column을 primary key로 설정한다. primary key는 중복된 값을 가질 수 없다.
  • index : True일 경우 해당 Column을 위한 index를 생성한다. "unique"와 함께 볼 것.
  • unique : True이고 동시에 index=False인 경우 해당 Column을 위한 UniqueConstraint를 생성함. True이고 동시에 index=True인 경우 해당 Column을 위한 unique index를 생성함.
  • ForeignKey : 두 Column 사이의 의존성을 정의함.

create the relationships

relationship은 이 곳의 설명이 가장 잘 이해된다.

back_populate 라는 키워드로 알 수 있듯이 다른 테이블 객체에서 함께 추가된 내용이 모두 연결된 테이블로 이주된다는 말이다.
my_user = User() 라고 만들어진 인스턴스에서 my_user.items를 보면 빈 리스트로 보인다.
그러면 여기에 추가 된 내용은 "Item" 클래스의 "owner" 로 지정된 어트리뷰트에 back populate 된다.

이 상태에서 아래의 main.py를 실행해 보자.

그러면

이렇게 빈 리스트가 생성되는 것을 볼 수 있다.

이번에는 위와 같이 두개의 테이블을 각각 만들고
my_user.itemsmy_item을 추가했다.

결과로 my_item에서 my_user를 호출할 수 있는 상태가 된 것을 볼 수 있다.
my_user.items.__dict__ 여기에 출력이 없는 이유는 primary_key의 충돌을 방지하기 위해서
ForeignKey를 지정해 줘야하는데 이 과정을 Item안에서만 했기 때문이다.
이 부분은 공부가 조금 더 필요하다.
다음은 pydantic부터 다시 시작해보자.

반응형

댓글