From 1c6ffe7aab7f42efa68973faabf7004b15630d85 Mon Sep 17 00:00:00 2001 From: hhyong Date: Sun, 14 Sep 2025 11:15:57 +0900 Subject: [PATCH] Add 300_architecture/database/sqlalchemy_codegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ORM을 위한 가이드 --- 300_architecture/database/sqlalchemy_codegen | 102 +++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 300_architecture/database/sqlalchemy_codegen diff --git a/300_architecture/database/sqlalchemy_codegen b/300_architecture/database/sqlalchemy_codegen new file mode 100644 index 0000000..3bd13c2 --- /dev/null +++ b/300_architecture/database/sqlalchemy_codegen @@ -0,0 +1,102 @@ +# 자동 코드생성 및 수정 + +1. 개요 +2. 방법 +3. TIP + +## 1. 개요 + +해당 방법은 `sqlcodegen`을 통해 DB의 데이터를 `ORM`방식으로 전환하는 방법이다.
+이번에는 `PostgreSQL`을 기준으로 작성하였다. + +## 2. 방법 + +먼저 다른 lib와 충돌을 위해 별도의 venv를 생성 후 아래의 lib를 설치한다. +```bash +pip install psycopg2-binary sqlacodegen SQLAlchemy +``` + +해당 명령어를 입력 후, 아래의 명령어를 통해 ORM를 자동생성한다. +```bash +sqlacodegen postgresql://{{id}}:{{passwd}}@{{DB URL or IP}}:{{port}}/{{DB schema}} > {{file명 i.e.) models.py}} +``` + +해당 명령어를 통해 아래와 같은 코드가 작성된다. + +```python +from typing import Optional +import datetime +import uuid + +from sqlalchemy import (ARRAY, Boolean, DateTime, Double, Enum, ForeignKeyConstraint, Index, Integer, JSON, PrimaryKeyConstraint, String, Time, UniqueConstraint, Uuid, text, Sequence, func) +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship + +class Base(DeclarativeBase): + pass + + +class Company(Base): + __tablename__ = 'company' + __table_args__ = ( + PrimaryKeyConstraint('id', name='company_pk'), + ) + + id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) + name: Mapped[str] = mapped_column(String(255), nullable=False) + url: Mapped[Optional[str]] = mapped_column(String(255)) + created_at: Mapped[Optional[datetime.datetime]] = mapped_column(DateTime(True), server_default=func.now()) + updated_at: Mapped[Optional[datetime.datetime]] = mapped_column(DateTime(True), server_default=func.now(), onupdate=func.now()) + + team: Mapped[list['Team']] = relationship('Team', back_populates='company') + + +class Product(Base): + __tablename__ = 'product' + __table_args__ = ( + PrimaryKeyConstraint('id', name='product_pk'), + ) + + id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) + name: Mapped[str] = mapped_column(String(32), nullable=False) + app_id: Mapped[Optional[str]] = mapped_column(String(16)) + scope: Mapped[Optional[dict]] = mapped_column(JSON) + description: Mapped[Optional[str]] = mapped_column(String(256)) + created_at: Mapped[Optional[datetime.datetime]] = mapped_column(DateTime(True), server_default=func.now()) + updated_at: Mapped[Optional[datetime.datetime]] = mapped_column(DateTime(True), server_default=func.now(), onupdate=func.now()) + + robeing: Mapped[list['Robeing']] = relationship('Robeing', back_populates='product') + +... +``` + +## 3. TIP + +해당 항목은 자동으로 생성될 시, 몇가지 수정점이 있으므로 아래와 같은 주의사항을 작성한다. + +- `SEQUENCE`는 자동으로 작성되지 않는다.
+ 그러므로 아래와 같은 방식으로 Colume에 추가한다. + + - ~에서 + ```python + .... + id: Mapped[int] = mapped_column(Integer, primary_key=True) + .... + ``` + + - ~으로 + ```python + .... + id: Mapped[int] = mapped_column(Integer, Sequence('conversation_log_id_seq'), primary_key=True) + .... + ``` + +- DB에서 Default값을 작성시에는 상황에 맞게 수정한다. + - case 1. 서버에서 기본값이 들어가야하는 경우 + 해당경우는 code에서 기본값을 작성하여 DB에 insert 한다.
+ i.e.) datezone의 경우: `sqlalchemy`의 func.now()
+ uuid의 경우: uuid.uuid4
+ ※ DB의 호환성을 고려하여 만약 오류가 발생시 적절한 방법을 찾아서 코드로 구현한다. + - case 2. DB에서 처리해도 되는 경우와 DB의 호환성을 고려하는 경우 + 해당경우는 sqlcodegen에서 자동생성된 코드를 그대로 사용한다.
+ i.e.) jwt의 경우: `server_default=text("'Bearer'::character varying")` 그대로 사용한다.