Skip to content

Flask-SQLAlchemy

Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It simplifies using SQLAlchemy with Flask by setting up common objects and patterns for using those objects, such as a session tied to each web request, models, and engines.

Flask-SQLAlchemy does not change how SQLAlchemy works or is used. See the SQLAlchemy documentation to learn how to work with the ORM in depth. The documentation here will only cover setting up the extension, not how to use SQLAlchemy.

Categories

  • Flask-SQLAlchemy:Examples:SeahQRCodePOC

Simple Example

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.sqlite"

class Base(DeclarativeBase):
  pass

db = SQLAlchemy(app, model_class=Base)

class User(db.Model):
    id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
    username: Mapped[str] = mapped_column(db.String, unique=True, nullable=False)

with app.app_context():
    db.create_all()

    db.session.add(User(username="example"))
    db.session.commit()

    users = db.session.execute(db.select(User)).scalars()

Simple usage

python에서 사용하는 ORM인 SqlAlchemy를 flask에서 사용하기 편하도록 custom한 것이다.

SqlAlchemy와 동일한 기능을 하지만 더 간단하게 DB를 다룰 수 있도록 해준다.

연결 생성하기

기존의 SqlAlchemy를 사용하면,

  1. connection 생성을 위해 declarative_base를 선언하고
  2. engine을 생성하여 dsn 연결을 해준 다음
  3. 사용할 model과 schema를 작성한 후,
  4. session에 생성한 engine을 연결해주는 작업

까지 해주어야 DB 연결이 완료되어 사용이 가능해진다. 하지만 Flask-SqlSlchemy를 사용하면 flask에서만이라도 훨씬 단순화된 단계로 sqlachemy를 사용할 수 있다.

connection 생성 및 연결

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask()
app.config["SQLALCHEMY_DATABASE_URI"] = 'postgresql://usename:password@localhost:5432/postgres'

db = SQLAlchemy()
db.init_app(app)

생성할 모델 만들기

생성할 모델을 선언하는 법은 기존의 SqlAlchemy와 동일한데, 다른 점은 Base(declaratibe_base)가 아니라 SQLAlchemy()의 Model을 상속받아서 만들어낸다.

class User(db.Model):
    # 테이블의 이름
    __tablename__ = "users"

    id = Column("id", Integer, primary_key=True)
    name = Column("name", String, nullable=False)
    email = Column("email", String, nullable=False, unique=True)
    password = Column("password", Unicode, nullable=False)

만일 테이블간의 relationship을 작성하고 싶다면, 다음과 같이 연결시킬 모델에 작성해주면 된다.

user_id = db.Column(db.Integer, db.ForeignKey(User.id))
# backref는 반대 관계 모델에서 표시되길 원하는 이름
user = db.relationship('User', backref="connections")

사용하기

연결이 완료되었다면 별도로 session을 생성하지 않고도 데이터베이스 작업을 수행할 수 있다. session 생성을 별도로 하지 않는 것 외에는 기존의 SqlAlchemy와 동일한 방식으로 사용하면 된다.

기존 SqlAlchemy에서 다음과 같이 사용했다면,

session.add(User(id, name, email, password))
session.commit()

Flask-SqlAlchemy에서는 SQLAlchemy 인스턴스의 session을 호출해서 다음과 같이 사용한다는 것이 조금 다르다.

db.session.add(User(id, name, email, password))
db.session.commit()

MariaDB 설정

SQLALCHEMY_TRACK_MODIFICATIONS는 변경을 자동으로 추적하고, 변경 사항을 데이터베이스에 반영하는 옵션으로 False를 적용하면 commit을 수동으로 호출해야 하는 단점을 가지는 반면 속도는 향상될 수 있습니다.

class Config:
    # 코드 생략
    db_pt = os.environ["MARIADB_PORT"]
    db_id = os.environ["MARIADB_USER"]
    db_pw = os.environ["MARIADB_PASSWORD"]
    db_nm = os.environ["MARIADB_DATABASE"]
    SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{db_id}:{db_pw}@db:{db_pt}/{db_nm}"
    SQLALCHEMY_TRACK_MODIFICATIONS = False

데이터베이스 객체를 생성하고 데이터베이스에 접속하여 정의된 클래스 객체들을 이용하여 테이블을 생성합니다.

db = SQLAlchemy()
# ...
def create_app():
    app.config.from_object(obj=config["development"])

    with app.app_context():
        # ...
        db.init_app(app=app)
        db.create_all()

모델 생성

데이터베이스의 테이블로 생성될 사용자 클래스를 정의하였습니다.

__tablename__은 데이터베이스의 테이블 이름으로 정의되며 각 클래스 변수들은 테이블의 열로 정의됩니다.

테이블의 열은 Column을 이용하여 정의할 수 있습니다.

relationship을 테이블의 열로 정의되지는 않지만, 밀접한 클래스 간 데이터를 참조하기 쉽게 하기 위하여 JOIN과 같은 역할을 합니다.

class User(db.Model, UserMixin):
    __tablename__ = "users"

    id = Column(INTEGER(display_width=11, unsigned=True), primary_key=True, autoincrement="auto", comment="사용자 식별 값")
    username = Column(VARCHAR(length=50), unique=True, nullable=False, comment="사용자 아이디")
    password = Column(VARCHAR(length=255), nullable=False, comment="사용자 비밀번호")
    roles = relationship(argument="Role", secondary="rel_user_roles", backref=backref(name="users", lazy=True), uselist=True)
    comments = relationship(argument="Comment", secondary="rel_user_comments", backref=backref(name="users"), uselist=True)

    def __init__(self, username:str, password:str, roles:list):
        self.username = username
        self.password = generate_password_hash(password=password)
        self.roles = roles

    # ...

쿼리

생성(INSERT)

autoincrement 인 속성을 추가할 필요 없다.

user = User(username=form.username.data, password=form.password.data, roles=[role])

db.session.add(instance=user)
try:
    db.session.commit()

데이터베이스에 적용하는 타이밍에 commit을 사용한다.

조회(SELECT)

객체의 query를 이용하여 데이터베이스 질의를 하며, filter_by를 이용하여 조건을 붙입니다.

검색된 결과 중 첫번째 데이터를 뽑기 위해서는 first()를 호출하며, 모든 데이터를 뽑기 위해서는 all()을 호출합니다.

user = User.query.filter_by(username=form.username.data).first()

filter_by가 아닌 filter를 사용하여도 됩니다. 사용 방법은 아래와 같이 filter_by와는 차이가 있음을 유의합니다.

user = User.query.filter(User.username==form.username.data).first()

수정(UPDATE)

post라는 모델의 필드 중 title과 content라는 필드에 폼으로부터 전달된 데이터를 저장합니다.

post.title = form.title.data
post.content = form.content.data

## 이후, 데이터베이스에 적용하기 위해 commit을 사용합니다.
db.session.commit()

삭제(DELETE)

post라는 모델을 delete 함수에 전달 후 commit하여 적용하면 데이터베이스에서 해당 데이터가 삭제됩니다.

db.session.delete(instance=post)
db.session.commit()

Troubelshooting

NOT NULL constraint failed: {TABLE}.id

sqlite3 디비 연결시 컬럼 추가하면 다음과 같은 에러 발생.

...
  File "/home/your/.pyenv/versions/opy-your-3.11.9-default/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 2116, in _exec_insertmany_context
    dialect.do_execute(
  File "/home/your/.pyenv/versions/opy-your-3.11.9-default/lib/python3.11/site-packages/sqlalchemy/engine/default.py", line 924, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: history.id
[SQL: INSERT INTO history (data, status, created_at) VALUES (?, ?, ?) RETURNING id]
[parameters: ('According.', 'out', '2024-06-26 05:21:18.801133')]
(Background on this error at: https://sqlalche.me/e/20/gkpj)

이런 에러가 나오면...

class BaseModel(db.Model):
    id = db.Column(db.BigInteger, primary_key=True)

primary_key 에 해당하는 컬럼이 BigInteger 인지 확인해보자. Integer 로 변환해야 한다.

class BaseModel(db.Model):
    id = db.Column(db.Integer, primary_key=True)

See also

Favorite site