Web/Flask

[AI Class Day 14] Flask TIL

makeitworth 2021. 5. 9. 17:32

1. Flask란

: Python 기반 마이크로 웹 프레임워크

micro - 'essential 한 기능을 포함한'의 의미

가벼움

작은 프로젝트에서 유리함

 

2. Flask 시작하기

목적에 따른 모듈만 있는 환경을 구축해서 관리하자 -> 가상환경 virtual environment 구축이 선행됨

 

1. 가상환경 설치

 

# 파이썬 가상환경 모듈 설치
pip install virtual env

# 현 디렉토리에 새로운 가상환경 만들기
virtual evn [가상환경이름]

- 가상환경  진입하고 벗어나기 (mac 기준)

 

진입 

source [가상환경이름]/bin/activate

 

벗어나기

deactivate

 

2. flask 설치하기

 

-가상환경 내에 설치된 모듈 확인하기

pip freeze

 

- flask 설치하기

pip install flask

 

3. Simplest Flask App

내 프로젝트를 진행할 디렉토리에 app.py 작성 (파이썬 에디터 활용)

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_code():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()

 

@ : 파이썬 데코레이터

  • @app.route('/')
    - '/' 주소 요청에 대한 처리

 

CLI 에서 (내가 작업하는 가상환경 디렉토리에서)  flask run 실행

(venv) (base) momoui-Mac-mini:FLASK-PROJECT momo$ flask run
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

http://127.0.0.1:5000/에 들어가보면 웹페이지에 hello world! 가 나온다.

 

Flask with REST API

API

  • Application Programming Interface
  • 프로그램들이 서로 상호작용하는 것을 도와주는 매개체

RESTful

  • Representational State Transfer
  • 웹 서버가 요청을 응답하는 방법론 중 하나
  • 데이터가 아닌, 자원(Resource)의 관점으로 접근
  • 개발자 사이의 암묵적인 약속

 

4. REST API란

HTTP URI를 통해 자원을 명시하고 HTTP Method를 통해 해당 자원에 대한 CRUD를 진행

 

  • HTTP URI(Uniform Resource Identifier) : 인터넷에 있는 자원을 나타내는 유일한 주소(식별자)
  • HTTP Method : 자원에 대한 행위를 나타내는 수단 GET, POST, PUT, DELETE, PATCH...

REST API Example

REST API에서는 method와 resource를 사용함

HTTP Method Resource
GET /order
POST /order
PUT /order
DELETE /order

이 두가지의 조합을 통해서 같은 resource라도 다른 액션을 취할 수 있다.

REST API의 Stateless(무상태성)

Client의 Context를 서버에서 유지하지 않는다. 
어떤 여러 클라이언트가 있을때, A가 서버에 보낸 요청, B가 서버에 보낸 요청, C가 서버에 보낸 요청은 모두 동일한 결과를 보내줘야 한다.
각각의 request를 독립적으로 간주한다.

 

예)

POST/shoes는 자원에 새로운 정보를 생성
GET/shoes는 DB에서 shoes가 있는지 확인 후 해당 자원 반환

 

  • (서버 입장에서) 아이템을 GET하기 위해서 POST를 진행할 필요가 없음.
  • shoes에서 어떤 자원이 있는지 체크하기 전에 POST를 했었는지 서버가 기억할 필요가 없다. 단지 주어진 행위(DB에서 shoes가 있는지 없는지 체크해 있으면 반환, 없으면 에러를 리턴할 뿐. POST와 GET사이의 종속관계가 없다.
  • REST API는 이 때문에 협업에 장점이 있다.

5. REST API 구축하기

Coffee Shop Menu API 구축

같은 자원(Menu)에 대해서 다른 로직을 구현

  • GET
    • @app.route('/menus')
    • methods의 기본값이 'GET'
  • POST
    • @app.route('/menus', methods = ['POST]
    • methods는 리스트형으로써 여러 메서드 지정 가능
from flask import Flask, jsonify, request

# jsonify : 파이썬의 딕셔너리 타입(menus)을 json이라는 데이터 저장 방식으로 바꿔줌
# request : HTTP request를 다룰 수 있는 모듈

app = Flask(__name__)
# Flask를 바탕으로 한 객체 생성. 인자로 __name__전달. Flask에 이름을 앱으로 넣어준다는 의미

menus = [
    {"id": 1, "name": "Espresso", "price": 3800},
    {"id": 2, "name": "Americano", "price": 4100},
    {"id": 3, "name": "CafeLatte", "price": 4600},
]

#홈 디렉토리
@app.route('/')
def hello_flask():
    return "Hello World!"

# GET /menus    | 자료를 가지고 온다.
@app.route('/menus')
def get_menus():
    return jsonify({"menus": menus})
# menus는 리스트. json 변환이 안되기 때문에, menus를 value로 하는 새로운 딕셔너리 생성

# POST /menus   | 자료를 자원에 추가한다.
@app.route('/menus', methods=['POST'])
def create_menus():  
	# request가 JSON이라고 가정
    request_data = request.get_json()  # {"id": ..., "name": ..., "price": ...},
	# request는 자동적으로 클라이언트가 서버에 POST로 요쳥할 때 담긴 자료가 있음
    # 이를 get_jason()으로 파싱해서 딕셔너리 형태로 담음
    new_menu = {
        "id": 4,
        "name": request_data['name'],
        "price": request_data['price'],
    }
    # 전달받은 자료를 menus 자원에 추가
    menus.append(new_menu)
    return jsonify(new_menu)

if __name__ == "__main__":
    app.run()

 

6. API Validation with Postman

 

1. create new -> new -> new collection -> name 입력

 

2. get : 주소창에 주소 입력 -> send 버튼으로 결과 확인

위에 save 버튼으로 저장

3. post: POST로 Body tab에서 직접 내용을 작성하여 새로운 메뉴를 추가해줄 수 있다. raw / JSON 타입 선택

 

7. 과제 1: UD 기능 구현

실습에서는 GET  POST 를 이용해서 /menu 자원으로부터 데이터를 가지고오고, 자원에 데이터를 추가해보았습니다. 이는 자원에서 할 수 있는 4가지 logic인 CRUD(Create, Read, Update, Delete) 중 Read와 Create에 해당하는 부분입니다. 이를 바탕으로 다음 과제를 해결해봅시다.

필수 과제 : 메뉴 관리 CRUD 구현하기

  • HTTP 메서드 PUT 를 이용해 Update, DELETE 를 이용해 Delete 기능을 구현해주세요.
  • PUT /menu/<int:id> : 해당하는 id에 해당하는 데이터를 갱신합니다. (HTTPRequest의 Body에 갱신할 내용이 json으로 전달됩니다.)
  • DELETE /menu/<int:id> : 해당하는 id에 해당하는 데이터를 삭제합니다.
  • @app.route() 의 인자로 들어가는 경로에는 다음과 같이 사용해줄 수도 있습니다.
  •  
@app.route('/<name>') # URL에 <>를 붙임으로서 이를 함수의 인자로 대입할 수 있습니다.
def my_view_func(name):
    return name

1. update

# PUT /menus   | 해당 자료를 수정한다.
@app.route('/menus/<int:id>', methods=['PUT'])
def update_menus(id):
    update_data = request.get_json()
    try:
        menus[id-1]["name"] = update_data["name"]
        menus[id-1]["price"] = update_data['price']
        return jsonify(menus[id-1])
    except Exception as e:
        print(e)
        return jsonify({'error': "해당 자료가 존재하지 않습니다."})

 

2. delete

 

# DELETE /menus   | 해당 자료를 삭제한다.
@app.route('/menus/<int:id>', methods=['DELETE'])
def delete_menus(id):
    try:
        del menus[id-1]
        return jsonify(menus)
    except Exception as e:
        print(e)
        return jsonify({'error': "해당 자료가 존재하지 않습니다."})

 

 

8. 보너스 과제 : POST 메서드 수정하기

  • 새로운 menu를 추가하는 POST 영역에서 id가 4로 고정되어있는 문제가 발생합니다.
  • POST 요청이 들어올 때마다 id가 하나씩 증가하여 menu 리스트에 추가될 수 있도록 코드를 수정해주세요.
  • 이 과제는 필수 과제 이후에 진행되어야 합니다.
@app.route('/menus', methods=['POST'])
def create_menus():
    request_data = request.get_json() 

    new_menu = {
        "id": len(menus)+1,
        "name": request_data['name'],
        "price": request_data['price'],
    }

    menus.append(new_menu)
    return jsonify(new_menu)

 

9. 데이터베이스 연동하기

  • 수업에서 다룬 API는 서버를 재시작하면 모든 정보가 리셋되는 치명적인 문제가 있었습니다. 이를 해결하기 위해 데이터만을 저장하는 데이터베이스를 도입하여 Flask과 연동할 필요가 생겼습니다.
  • SQL과 ORM 중 하나를 선택하여 데이터베이스와 Flask app을 연동해봅시다. (즉, 자원에 CRUD가 발생하면 이 정보가 데이터베이스에 저장되어야합니다.)
  • 이 과제는 필수 과제, 보너스 과제 I 이후에 진행되어야 합니다.

 

지인에게서 ORM(Object Relational Mapper)을 사용해 볼 것을 권장 받아 찾아봤다.

ORM은 객체와 관계형 데이터베이스의 데이터를 자동으로 연결해 주는 것을 의미한다.

객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터 베이스는 테이블을 사용하기 때문에 둘 사이 불일치가 존재하는데, ORM을 통해, 객체 간 관계를 바탕으로 자동으로 SQL를 생성하여 이 불일치를 해결하는 것이다.

이렇게 짜놓으면, 재사용과 유지보수가 용이해진다.

파이썬의 SQLAlchemy라는 라이브러리가 관계형 데이터베이스의 테이블들을 프로그래밍 언어의 클래스로 표현할 수 있게 해주어, 파이썬 클래스를 사용해 CRUD를 편하게 할 수 있게 해준다고 한다.

 

cli에서 flask-sqlalchemy 를 설치하고,

 

# flask-sqlalchemy 설치
pip install flask-sqlalchemy

 

app.py 파일에 모듈을 import 한다.

from flask_sqlalchemy import SQLAlchemy

 

db를 생성하는 init_db.py과 flask run으로 실행되는 app.py 파일은 다음과 같다.

 

init_db.py

# app.py에서 만든 db객체와 모델 클래스 불러오기
from app import db, Menus

# db 생성
db.create_all()

# db에 미리 입력할 데이터들
m1 = Menus(name = "Espresso", price = 3800)
m2 = Menus(name = "Americano", price = 4100)
m3 = Menus(name = "CafeLatte", price = 4600)

# add로 db에 넣기
db.session.add(m1)
db.session.add(m2)
db.session.add(m3)

# db에 넣기 명령 실행
db.session.commit()

app.py

import os
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy

# 플라스크 객체 선언
app = Flask(__name__)

#db 경로 지정
basdir = os.path.abspath(os.path.dirname(__file__))
dbfile = os.path.join(basdir, 'db.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + dbfile

# db 객체 선언
db = SQLAlchemy(app)


# db model 클래스
class Menus(db.Model):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(64))
    price = db.Column(db.Integer)

#홈 디렉토리
@app.route('/')
def home():
    return "Welcome to Starbucks Menu"

#GET
@app.route('/menus')
def get_menus():
    menus = []
    for m in Menus.query.all():
        menu = {}
        menu['id'] = m.id
        menu['name'] = m.name
        menu['price'] = m.price
        menus.append(menu)
    return jsonify({"menus": menus})

#POST
@app.route('/menus', methods=['Post'])
def create_menus():
    req = request.get_json()
    menu = Menus(name=req['name'], price=req['price'])
	
    #db add+commit
	db.session.add(menu)
    db.session.commit()
    
    return get_menus()

#PUT
@app.route('/menus/<int:id>', methods=['PUT'] )
def update_menus(id):
    req = request.get_json()
    # Menus db에서 특정 조건으로 filter해서 원하는 데이터를 찾은다음 update
    Menus.query.filter_by(id=id).update(dict(name=req['name'],price=req['price']))
    
    #db commit
    db.session.commit()
    return get_menus()

# DELETE /menus   | 해당 자료를 삭제한다.
@app.route('/menus/<int:id>', methods=['DELETE'])
def delete_menus(id):
    # Menus db에서 특정 조건으로 filter해서 원하는 데이터를 찾은 다음 삭제
    Menus.query.filter_by(id=id).delete()
    
    #db commit
    db.session.commit()
    return get_menus()

 

 

참고:

https://gmlwjd9405.github.io/2019/02/01/orm.html

flask-sqlalchemy.palletsprojects.com/en/2.x/quickstart/

파이썬 Flask DB 연동하기(SQLAlchemy)

wikidocs.net/81045