반응형

목차

  1. 객체-관계형 매핑(Object-Relational Mapping, ORM)
  2. ORM 예시
  3. 이전 blog 프로젝트에 적용시켜보기
  4. 코드가 정상 작동 확인하기

객체-관계형 매핑(Object-Relational Mapping, ORM)

객체-관계형 매핑(ORM)은 Python 객체와 관계형 데이터베이스 간의 데이터를 관리하는 데 사용되는 프로그래밍 기법입니다. 즉, SQL을 사용하는 것처럼 데이터베이스와 상호 작용하는 방법입니다. 사실, 데이터베이스 쿼리 작성 작업을 단순화하는 추상화 작업입니다.

다음은 ORM의 기본적인 작동 방식에 대한 간단하게 설명하겠습니다.

  1. 클래스는 데이터베이스 테이블에 해당합니다. Python 코드의 각 클래스는 데이터베이스의 테이블에 해당합니다.

  2. 객체는 행에 해당합니다. 해당 클래스의 각 객체 인스턴스는 테이블의 행에 해당합니다.

  3. 필드는 열에 해당합니다. 객체의 각 속성에 대한 데이터는 해당 테이블의 각 열에 있는 데이터에 해당합니다.

 

ORM의 주요 장점은 개발자가 SQL을 작성하는 대신 Python 코드를 사용하여 데이터베이스와 상호 작용할 수 있다는 것입니다.


예를 들어 다음과 같은 SQL 쿼리를 작성하는 대신

SELECT * FROM users WHERE email = 'test@test.com';


ORM을 사용하여 Python에서 이와 같은 쿼리를 작성할 수 있습니다(SQLAlchemy와 유사한 ORM을 가정할 경우).

user = User.query.filter_by(email='test@test.com').first()


이런 ORM은 다음과 같은 이점을 제공합니다.

  • 개발자로부터 기본 SQL을 추상화하여 개발 속도를 높일 수 있습니다.

  • 개발자가 SQL 대신 Python 코드와 객체를 사용하여 데이터베이스로 작업할 수 있으므로 코드를 더 읽기 쉽고 이해하기 쉽게 만들 수 있습니다.

  • 다양한 데이터베이스와 상호 작용하는 표준 방법을 제공하므로 코드 변경을 최소화하면서 한 데이터베이스에서 다른 데이터베이스로 전환할 수 있습니다.


하지만 ORM에는 몇 가지 단점도 있습니다.

  • 특히 복잡한 쿼리의 경우 원시 SQL보다 느릴 수 있습니다.

  • 원시 SQL만큼 유연하거나 강력하지 않기 때문에 복잡한 쿼리를 작성하기가 더 어려워질 수 있습니다.

  • 데이터베이스에서 실제로 일어나는 일을 모호하게 만들 수 있으며, 제대로 사용하지 않을 경우 비효율성이나 오류가 발생할 수 있습니다.

  • Python에서 널리 사용되는 ORM으로는 SQLAlchemy, Django ORM, Peewee 등이 있습니다. 특히 플라스크 애플리케이션에 많이 사용되는 것은 SQLAlchemy입니다.

ORM 예시

1. 데이터베이스의 사용자 테이블에 해당하는 사용자 모델이 있다고 가정해봅시다.

from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(80), nullable=False)


이 코드에서 db.Model은 데이터베이스의 테이블을 나타내며, User의 각 인스턴스는 해당 테이블의 행이 됩니다. 클래스 속성인 id, 이메일, 비밀번호는 테이블의 열입니다.

 


2. 그런 다음 이러한 클래스를 사용하여 데이터베이스를 쿼리할 수 있습니다. 다음은 이메일로 사용자를 가져오는 방법입니다

def get_user_by_email(email):
    return User.query.filter_by(email=email).first()


이 함수는 이메일 속성(사용자 테이블의 이메일 열에 해당)이 제공된 이메일과 일치하는 첫 번째 사용자 객체를 반환합니다.



3. 데이터베이스에 새 사용자를 추가하는 방법은 다음과 같습니다.

def add_user(email, password):
    new_user = User(email=email, password=password)
    db.session.add(new_user)
    db.session.commit()


이 함수는 새 User 객체를 생성하여 데이터베이스 세션에 추가한 다음 세션을 커밋합니다. 이는 SQL INSERT 연산에 해당합니다.



4. Flask 애플리케이션 객체로 SQLAlchemy 인스턴스를 설정하고 초기화하는 것을 잊지 마세요.

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db.init_app(app)


이것은 매우 단순화된 예시입니다. 실제 애플리케이션에는 더 많은 오류 검사, 비밀번호 저장에 안전한 방법 사용 등이 포함될 수 있습니다. 하지만 이 예시를 통해 Flask 애플리케이션에서 SQLAlchemy와 같은 ORM을 사용하여 SQL 대신 Python 코드를 사용하여 데이터베이스와 상호 작용하는 방법에 대한 아이디어를 얻을 수 있을 것입니다.


이전 blog 프로젝트에 적용시켜보기

이전 블로그 앱 예제에 SQLAlchemy 사용법을 통합해 보겠습니다. 간단한 사용자 모델을 추가하고 이를 애플리케이션 팩토리에 통합하겠습니다.

1. Flask-SQLAlchemy를 설치해야 합니다. 이 작업은 pip로 수행할 수 있습니다.

pip install Flask-SQLAlchemy


2. config.py 파일에 SQLAlchemy 데이터베이스 URI에 대한 줄을 추가합니다.

class Config:
    SECRET_KEY = 'supersecretkey'
    SQLALCHEMY_DATABASE_URI = 'sqlite:///blog.db' # 간소화를 위해 SQLite 사용


3. 데이터베이스 모델을 정의하는 새로운 models.py 파일을 생성합니다.

# blogapp/models.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(80), nullable=False)

 

4. blogapp/__init__.py 파일을 수정하여 SQLAlchemy를 초기화하고 데이터베이스 테이블을 생성합니다.

# blogapp/__init__.py
from flask import Flask

from .config import Config
from .public import public_bp
from .admin import admin_bp
from .models import db  # 추가된 코드

def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)
    
    db.init_app(app)  # 추가된 코드
    with app.app_context():  # 추가된 코드
        db.create_all()  # 추가된 코드

    app.register_blueprint(public_bp)
    app.register_blueprint(admin_bp)
    
    return app


5. 이제 다음과 같이 뷰에서 사용자 모델을 사용할 수 있습니다.

# blogapp/admin/views.py
from flask import Blueprint, request
from blogapp.models import db, User  # 추가된 코드

admin_bp = Blueprint('admin', __name__, url_prefix='/admin')

@admin_bp.route('/add_user', methods=['POST'])
def add_user():
    email = request.form['email']
    password = request.form['password']
    new_user = User(email=email, password=password)
    db.session.add(new_user)
    db.session.commit()
    return 'User added successfully!'


관리자 블루프린트의 새로운 /add_user 경로를 사용하면 '이메일' 및 '비밀번호' 필드가 있는 양식을 제출하여 데이터베이스에 새 사용자를 추가할 수 있습니다. 실제 애플리케이션에서는 이메일이 이미 도용되지 않았는지 확인하고, 보안 저장을 위해 비밀번호를 해시하는 등의 확인을 추가하고 싶을 것입니다.

이렇게 하면 SQLAlchemy를 Flask 애플리케이션에 통합하여 원시 SQL 대신 Python 코드를 사용하여 데이터베이스와 상호 작용하는 데 사용할 수 있습니다.


코드가 정상 작동 확인하기

1. 유닛 테스트: Python에는 테스트 케이스를 작성하는 데 사용할 수 있는 unittest라는 모듈이 내장되어 있습니다. Flask는 테스트에서 HTTP 요청을 시뮬레이션하기 위한 test_client 객체도 제공합니다.

 

이 방법을 실행하기 위해 파일을 만들어봅시다. 먼저 tests 디렉토리를 Learning 디렉토리에 생성합니다. 그리고 test.py 파일을 생성한 후 위의 내용을 붙여넣기 해줍니다. 이 과정이 완료되면 현재 프로젝트의 구성도는 다음과 같아집니다.

/Learning
    /blogapp
        /admin
            __init__.py
            views.py
        /public
            __init__.py
            views.py
        __init__.py
        config.py
        models.py
        /instance
            blog.db
    /tests
        test_routes.py
        test_models.py
    main.py

 

  1) test_routes.py

import unittest
from blogapp import create_app
from blogapp.models import db, User

class TestRoutes(unittest.TestCase):
    def setUp(self):
        self.app = create_app()
        self.client = self.app.test_client
        with self.app.app_context():
            db.create_all()

    def test_public_index(self):
        response = self.client().get('/')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.get_data(as_text=True), '퍼블릭 구역에 오신 것을 환영합니다!')

    def test_admin_add_user(self):
        response = self.client().post('/admin/add_user', data=dict(email="test@example.com", password="password"))
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.get_data(as_text=True), '유저가 성공적으로 생성되었습니다!')
    
    def tearDown(self):
        with self.app.app_context():
            db.session.remove()
            db.drop_all()

if __name__ == "__main__":
    unittest.main()

 

  2) test_models.py

import unittest
from blogapp import create_app
from blogapp.models import db, User

class TestModels(unittest.TestCase):
    def setUp(self):
        self.app = create_app()
        self.client = self.app.test_client
        with self.app.app_context():
            db.create_all()

    def test_create_user(self):
        with self.app.app_context():
            new_user = User(email="test@example.com", password="password")
            db.session.add(new_user)
            db.session.commit()
            user = User.query.filter_by(email="test@example.com").first()
            self.assertIsNotNone(user)
            self.assertEqual(user.email, "test@example.com")
            self.assertEqual(user.password, "password")

    def tearDown(self):
        with self.app.app_context():
            db.session.remove()
            db.drop_all()

if __name__ == "__main__":
    unittest.main()

 

이러한 테스트를 실행하려면 python -m unittest discover 명령을 사용하거나 python -m unittest tests/test_routes.py 또는 python -m unittest tests/test_models.py로 특정 테스트 파일을 실행하면 됩니다.

이 테스트는 기본 테스트이며 실제 애플리케이션에서는 가능한 모든 에지 케이스와 입력을 포함하는 보다 포괄적인 테스트 사례를 포함해야 한다는 점에 유의하세요.

 

성공!



2. 메뉴얼 테스트: 애플리케이션을 실행하고 HTTP 요청을 전송하여 코드를 수동으로 테스트할 수 있습니다. 명령줄에서 curl 또는 httpie와 같은 도구를 사용하거나 Postman 또는 Insomnia와 같은 GUI 도구를 사용할 수 있습니다.


다음은 curl을 사용하여 /add_user 경로를 테스트하는 방법입니다.

curl -X POST -d "email=test@example.com&password=password" http://localhost:5000/admin/add_user

성공!

이것을 자신의 프로젝트 등에 사용할 때에는 localhost:5000을 실제 서버 주소와 포트로 변경한 경우 변경하는 것을 잊지 마세요.



3. 로깅: 코드에 로깅 문을 추가하여 흐름을 파악하고 디버깅할 수 있습니다. 이를 위해 Python의 내장 로깅 모듈을 사용할 수 있습니다.



테스트를 실행한 후에는 항상 정리하는 것을 잊지 마세요. 즉, 테스트 중에 생성된 모든 데이터를 제거해야 합니다. 이는 위의 유니테스트 예제의 tearDown 메서드에서 수행됩니다.


나 왜 또 혼자 프로젝트 진행하고 있지..?  추가로 생성된 데이터는 다음 사이트에서 확인할 수 있습니다.

https://sqliteviewer.app/

 

SQLite Viewer Web App

SQLite Viewer Web App SQLite Viewer Web is a free, web-based SQLite Explorer, inspired by DB Browser for SQLite and Airtable. Use this web-based SQLite Tool to quickly and easily inspect .sqlite files. Your data stays private: Everything is done client-sid

sqliteviewer.app

위 페이지 접속 후 Open File을 클릭 후 instance에 생성된 db파일을 열면 확인할 수 있습니다.

반응형