flask-sqlalchemy-postgres 연결해서 CRUD 구현하기 using docker

·2022년 1월 9일
1

서버

목록 보기
4/5

Installation

[requirements.txt]

Flask==2.0.2
mysql-connector-python==8.0.27
Flask-SQLAlchemy==2.5.1
Flask-Migrate==3.1.0
alembic==1.7.5
Jinja2==3.0.3
psycopg2-binary==2.9.3
python-dotenv==0.19.2

여기서 유의할 건 psycopg2인데, 나 같은 경우 맥 유저라서 그런 지 자꾸 psycopg2를 pip3으로 설치하려고 하니까 에러 메시지가 잔뜩 떴다.

그래서 스택오버플로우를 참고해 그냥 그 대안인 psycopg2-binary 를 설치해 주었다.

참고로 psycopg2-binary로 설치해도 python 코드로 import할 때는 psycopg2로 해주어도 된다.

ToyProject 작성: CRUD 구현하기

일단 클리한 코드는 나중에 차차 리팩토링해주는 걸로 하고, 당장 flask-sqlalchemy-postgres 연결이 시급했기 때문에 구글링해서 찾은 블로그를 참고해 코드를 작성해 보았다. 대신, 나는 .env를 써줬다.

pgadmin을 사실 mac local에 설치해서 쓸 수도 있었지만, 일단 한번 생소한 도커를 더 써주고 싶어서 이렇게 구성해 봤다.

  • src/config.py
    import os
    
    user = os.getenv('PG_USER')
    password = os.getenv('PG_PASSWORD')
    host = os.getenv('PG_HOST')
    database = os.getenv('PG_DB')
    port = os.getenv('PG_PORT')
    
    DATABASE_CONNECTION_URI = f'postgresql+psycopg2://{user}:{password}@{host}:{port}/{database}'
  • src/models.py
    import flask_sqlalchemy
    
    db = flask_sqlalchemy.SQLAlchemy()
    
    class Cats(db.Model):
        __tablename__ = 'cats'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(100))
        price = db.Column(db.Integer)
        breed = db.Column(db.String(100))
  • src/init.py
    from flask import Flask
    
    from .models import db
    from . import config
    import os
    
    def create_app():
        flask_app = Flask(__name__)
        flask_app.config['SQLALCHEMY_DATABASE_URI'] = config.DATABASE_CONNECTION_URI
        flask_app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
        flask_app.app_context().push()
        db.init_app(flask_app)
        db.create_all()
        return flask_app
  • src/database.py
    from .models import db
    
    def get_all(model):
        data = model.query.all()
        return data
    
    def add_instance(model, **kwargs):
        instance = model(**kwargs)
        db.session.add(instance)
        commit_changes()
    
    def delete_instance(model, id):
        model.query.filter_by(id=id).delete()
        commit_changes()
    
    def edit_instance(model, id, **kwargs):
        instance = model.query.filter_by(id=id).all()[0]
        for attr, new_value in kwargs.items():
            setattr(instance, attr, new_value)
        commit_changes()
    
    def commit_changes():
        db.session.commit()
  • src/app.py
    import json
    
    from flask import request
    
    from . import create_app, database
    from .models import Cats
    
    app = create_app()
    
    @app.route('/', methods=['GET'])
    def fetch():
        cats = database.get_all(Cats)
        all_cats = []
        for cat in cats:
            new_cat = {
                "id": cat.id,
                "name": cat.name,
                "price": cat.price,
                "breed": cat.breed
            }
    
            all_cats.append(new_cat)
        return json.dumps(all_cats), 200
    
    @app.route('/add', methods=['POST'])
    def add():
        data = request.get_json()
        name = data['name']
        price = data['price']
        breed = data['breed']
    
        database.add_instance(Cats, name=name, price=price, breed=breed)
        return json.dumps("Added"), 200
    
    @app.route('/remove/<cat_id>', methods=['DELETE'])
    def remove(cat_id):
        database.delete_instance(Cats, id=cat_id)
        return json.dumps("Deleted"), 200
    
    @app.route('/edit/<cat_id>', methods=['PATCH'])
    def edit(cat_id):
        data = request.get_json()
        new_price = data['price']
        database.edit_instance(Cats, id=cat_id, price=new_price)
        return json.dumps("Edited"), 200
  • .env
    PG_DB=pgdb
    PG_USER=postgres
    PG_PASSWORD=1111
    PG_HOST=postgres
    PG_PORT=5432
    
    PG_ADMIN_EMAIL=leejin21c@gmail.com
    PGADMIN_DEFAULT_PASSWORD=PassW@rd!!
    
    FLASK_ENV=development
    FLASK_DEBUG=True
  • docker-compose.yml
    version: "3.8"
    
    services:
        web:
            build:
                context: .
            env_file:
                - .env
            ports:
                - 5000:5000
            environment:
                - FLASK_APP=./src/app.py
            volumes:
                - ./:/app
        postgres:
            container_name: postgres
            image: postgres:latest
            ports:
                - 5432:5432
            environment:
                - POSTGRES_DB=${PG_DB}
                - POSTGRES_USER=${PG_USER}
                - POSTGRES_PASSWORD=${PG_PASSWORD}
            volumes:
                - postgres:/var/lib/postgres
        pgadmin:
            container_name: pgadmin
            image: dpage/pgadmin4
            ports:
                - 8088:80
            environment:
                - PGADMIN_DEFAULT_EMAIL=${PG_ADMIN_EMAIL}
                - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD}
    
    volumes:
        postgres:
  • Dockerfile
    # syntax=docker/dockerfile:1
    
    FROM python:3.8-slim-buster
    
    WORKDIR /app
    
    COPY requirements.txt requirements.txt
    RUN pip3 install -r requirements.txt
    
    COPY . .
    
    CMD ["python3", "-m", "flask", "run", "--host=0.0.0.0"]
  • requirements.txt
    Flask==2.0.2
    mysql-connector-python==8.0.27
    Flask-SQLAlchemy==2.5.1
    Flask-Migrate==3.1.0
    alembic==1.7.5
    Jinja2==3.0.3
    psycopg2-binary==2.9.3
    python-dotenv==0.19.2

구동 모습


위와 같이 post 요청을 하면, 아래와 같이 get 요청을 했을 때 동일한 데이터가 뜬다.

pgadmin에서도 다음과 같이 잘 뜬다.

ISSUES

계속 컨테이너를 올리는데 웹 컨테이너가 안 올라가서 한참을 구글링하다가 스택오버플로우 보니까

(에러 내용: db.create_all()이 문제야~! localhost에 없더라~)

DB_HOST에 localhost로 하면 안되고, docker-compose.yml에서 postgres에 할당한 컨테이너의 이름으로 해줘야 하더라.

그래서 .env 파일을 다음과 같이 구성함.

아 그리고 PG_PASSWORD에 골뱅이(@) 들어가는 걸로 안 설정하는 게 좋은 듯.

→ 에러 생기고 주소가 이상하다고 뜨기 때문.

PG_DB=pgdb
PG_USER=postgres
PG_PASSWORD=1111
PG_HOST=postgres
PG_PORT=5432

PG_ADMIN_EMAIL=leejin21c@gmail.com
PGADMIN_DEFAULT_PASSWORD=PassW@rd!!

FLASK_ENV=development
FLASK_DEBUG=True
profile
이것저것 개발하는 것 좋아하지만 서버 개발이 제일 좋더라구요..

0개의 댓글