Showcase: Cartonnage를 사용해 App DB에 연결

발행: (2026년 2월 7일 오전 04:14 GMT+9)
9 분 소요
원문: Dev.to

Source: Dev.to

번역할 텍스트를 제공해 주시면 한국어로 번역해 드리겠습니다.

쇼케이스

참고

I decided to run this showcase using SQLAlchemy because I have to demonstrate the case first with an ORM, and SQLAlchemy is the best choice for that.
This is not a comparison with SQLAlchemy – there is no space for comparison, as SQLAlchemy is the benchmark / best implementation of the Data Mapper and Unit of Work patterns.

The purpose of this post is to show that Cartonnage – which follows the Active Record pattern – can be useful in some use / show cases.

  • I started writing Cartonnage 8 years ago.
  • AI did not contribute to this post.

Cartonnage는 무엇인가?

데이터베이스와 유창하게 대화하는 Database‑First ORM – 실시간 및 런타임에 바인딩되고, 기존 데이터베이스용으로 구축되었습니다.

대상:

  • 소프트웨어 엔지니어
  • DevOps 엔지니어
  • 데이터 엔지니어

Python에서 유창하고 강력한 ORM을 사용해 데이터베이스와 대화하고 싶지만, 번거로움, 스키마 정의, 유지보수 또는 마이그레이션 없이.

일반적인 시나리오

프로덕션 또는 테스트 환경에서 Python과 ORM을 사용해 애플리케이션 데이터베이스에 연결해야 하는 경우(예: ERP 시스템 DB, 병원 시스템 등)

  1. 무료 계정 만들기 우리 앱 DB를 사용하기 위해.
  2. 다운로드 및 설치 Oracle Instant Client.
  3. 다운로드 hr_oracle.sql 파일.
  4. freesql.com 계정에 로그인하고 My Schema 로 이동한 뒤 SQL 파일을 붙여넣고 실행하여 테이블을 만들고 데이터를 채웁니다.

필요한 패키지 설치

pip install sqlalchemy cartonnage oracledb

샘플 코드 (freesql_app_db.py)

import oracledb
from timeit import timeit

# --------------------------------------------------------------------------- #
# Connection parameters – fill in your credentials
user = ''
password = ''
host = 'db.freesql.com'
port = 1521
service_name = '23ai_34ui2'
client_lib_dir = './instantclient_23_3'   # path to Oracle Instant Client

# Initialise Oracle client
oracledb.init_oracle_client(lib_dir=client_lib_dir)

# --------------------------------------------------------------------------- #
# --------------------------- SQLAlchemy section --------------------------- #
from sqlalchemy import create_engine
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session

engine = create_engine(
    f"oracle+oracledb://{user}:{password}@{host}:{port}/?service_name={service_name}"
)

Base = automap_base()
Base.prepare(autoload_with=engine)

print(">>>>>>>>>> Available tables:", list(Base.classes.keys()))

Employee = Base.classes.employees
session = Session(engine)
employees = session.query(Employee).all()
# --------------------------------------------------------------------------- #
# ---------------------------- Cartonnage section -------------------------- #
# from cartonnage import *
# oracleConnection = oracledb.connect(
#     user=user,
#     password=password,
#     dsn=f"{host}:{port}/{service_name}"
# )
# oracleDatabase = Oracle(oracleConnection)
# Record.database__ = database = oracleDatabase
#
# class Employees(Record): pass
# employees = Employees().all()
# --------------------------------------------------------------------------- #

for emp in employees:
    print(f"{emp.employee_id}: {emp.first_name} {emp.last_name}")

스크립트를 실행합니다:

python3 freesql_app_db.py

예상 오류 (SQLAlchemy)

다음과 같은 오류가 표시됩니다:

Traceback (most recent call last):
  File ".../sqlalchemy/util/_collections.py", line 215, in __getattr__
    return self._data[key]

그리고 출력에 매핑된 테이블 없음이 표시됩니다:

>>>>>>>>>> Available tables: [], why?!

이유: 테이블에 기본 키가 없습니다. 시장의 많은 애플리케이션이 기본 키가 없는 테이블을 포함하고 있으며, 이러한 상황은 흔합니다.

sqlacodegen을 사용한 해결 방법 (여전히 PK 없음)

pip install sqlcodegen
sqlacodegen "oracle+oracledb://user:pass@host:port/?service_name=xxx" > models.py

다음과 같은 오류가 발생할 수 있습니다:

sqlalchemy.exc.OperationalError: (oracledb.exceptions.OperationalError) DPY-6005: cannot connect to database (CONNECTION_ID=...).
DPY-3001: Native Network Encryption and Data Integrity is only supported in python-oracledb thick mode

thick mode로 전환:

from sqlacodegen.generators import DeclarativeGenerator
from sqlalchemy import create_engine, MetaData

engine = create_engine(
    f"oracle+oracledb://{user}:{password}@{host}:{port}/?service_name={service_name}"
)
metadata = MetaData()
metadata.reflect(bind=engine)

generator = DeclarativeGenerator(metadata, engine, options=set())
output = "".join(generator.generate())
print(output)

생성된 출력은 ORM 클래스가 아니라 메타데이터 테이블 정의이며, 기본 키가 아직 없기 때문입니다:

t_employees = Table(
    'employees', metadata,
    Column('employee_id', Integer),
    Column('first_name', VARCHAR(255)),
    Column('last_name', VARCHAR(255)),
    Column('email', VARCHAR(255)),
    Column('phone_number', VARCHAR(255)),
    Column('hire_date', DateTime),
    Column('job_id', Integer),
    Column('salary', NUMBER(asdecimal=False)),
    Column('commission_pct', Integer),
    Column('manager_id', Integer),
    Column('department_id', Integer)
)

해결책

  1. 주석 처리 SQLAlchemy 섹션.
  2. freesql_app_db.py 파일에서 Cartonnage 섹션을 주석 해제.
  3. 스크립트를 다시 실행합니다.

Note: Cartonnage는 SQLAlchemy보다 “더 좋다”는 것이 아니라, 기본 키가 없는 테이블의 경우에 유용합니다.

설계 철학

  • 스키마 정의 및 마이그레이션 – 모든 개발자가 ORM이 DDL을 관리하기를 원하는 것은 아닙니다. 많은 사람들은 이를 부담으로 여깁니다. Cartonnage는 DDL은 SQL로 작성되어야 한다는 철학을 따르며, DML은 ORM으로 처리할 수 있습니다.
  • 단순 DB 클라이언트 / 쿼리 빌더를 넘어 – Cartonnage가 제공하는 기능:
    • 속성 가로채기 – 필드 접근을 재정의 / 가로챕니다.
    • 명시적 변경 추적 – 수정 사항을 추적하고 성공적인 업데이트 후 레코드에 반영합니다.
    • 액티브 레코드 패턴 – SQLAlchemy가 사용하는 Data Mapper / Unit of Work 패턴보다 액티브 레코드 기대에 맞춥니다.
    • 관계 로딩 – 관계를 언제, 어떻게 로드할지는 아키텍처에 맡깁니다 (의도적으로 남겨둔 설계).

Bottom line

  • SQLAlchemy가 기본 키 기반 테이블을 가진 전체 기능을 갖춘 Data Mapper / Unit of Work 구현이 필요할 때 사용하세요.
  • Cartonnage는 기본 키가 없는 레거시 데이터베이스를 다루거나 스키마 관리를 ORM 레이어와 별도로 유지하고 싶을 때 사용하세요.

행복한 코딩 되세요!

개발자의 책임

Cartonnage는 즉시 로딩이나 지연 로딩을 강제하지 않습니다. 무엇을 언제 로드할지는 여러분이 결정합니다.

Signal / Hooks

Cartonnage는 SQLAlchemy(이벤트 기반)나 Django(단순 훅)와 같은 프레임워크와는 다른 접근 방식을 취합니다.
Record의 CRUD 메서드를 오버라이드함으로써 훅과 같은 동작을 구현할 수 있습니다. 예:

def read(self):
    # 실제 읽기 전에 수행할 작업
    self.crud()
    # 읽은 후에 수행할 작업

Session & Transaction

Cartonnage는 Active Record 스타일을 따릅니다: CRUD 작업을 직접 수행하거나 트랜잭션을 수동으로 관리할 수 있습니다.
또한 경량 Session 클래스를 제공하여 다음을 할 수 있습니다:

  • submit 변경 사항 제출
  • collect 보류 중인 객체 수집
  • flush 데이터베이스에 플러시
  • delay commits 커밋 지연

“이것은 마지막에 추가된 것이며 확실히 더 많은 개선이 필요합니다.”

Unit‑of‑Work Pattern & Identity Map

Cartonnage(대부분의 Active Record ORM처럼)는 SQLAlchemy가 구현한 전체 Unit‑of‑Work 또는 Identity‑Map 패턴을 구현하지 않지만, ORM이며 단순한 쿼리 빌더나 원시 DB 클라이언트가 아닙니다.

Cartonnage 철학

Cartonnage는 not 특정 설계나 작업 패턴을 강제하지 않으며, 예를 들어:

  • 사전 정의된 기본 키가 있는 테이블의 강제 조작
  • 각 테이블에 대한 고정된 로딩 전략

이러한 결정에 대한 Responsibility는 개발자에게 있습니다.

피드백 요청

Cartonnage는 여러분의 지원이 필요합니다. 개선을 위한 건설적인 의견이나 제안은 언제든 환영합니다!

Back to Blog

관련 글

더 보기 »

Go의 비밀스러운 삶: ‘defer’ 문

챕터 20: The Stacked Deck Ethan의 데스크탑 PC 팬이 크게 돌고 있었다. 그는 오류 메시지를 끊임없이 뿜어내는 터미널을 마치 부서진 불꽃처럼 바라보고 있었다.