📌 Flask를 활용해 REST API 구축해보기
📌 Django를 활용해서 웹 사이트 구축하기
1. Flask
- Python 기반 마이크로 웹 프레임워크. 마이크로라는 말은 essential한 것이 다 있다는 뜻이라고 한다. 굉장히 가볍고 작은 프로젝트에서 높은 효율을 보인다고 한다.
- Flask를 사용하기 위해 가상환경을 구축해 사용해보도록 하자.
- 목적에 따른 모듈만 있는 환경을 구축해서 관리
# 맥의 경우 파이썬2가 설치되있어 파이썬3를 사용한다고 명시줘야한다 한다.
pip3 install virtualenv
# 현재 디렉토리에 새 virtualenv 가상환경 만들기
python3 -m virtualenv 가상환경_이름
- 가상환경 실행하기
- 해당 설치되있는 프로젝트 아래에서 다음의 명령어 실행(mac)
source venv/bin/activate
- pip install flask를 진행(pip3 install flask)
- Flask 앱 만들기(app.py)
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_code():
return 'Hello Wolrd'
if __name__ == '__main__':
app.run()
- 다음 터미널에서 flask run(가상환경인지 체크)
2. 인터넷과 웹
- 인터넷(Internet) : 전 세계 컴퓨터를 하나로 합치는 거대한 통신망(핵심은 망)
- 웹(Web): 인터넷에 연결된 사용자들이 정보를 공유할 수 있는 공간(naver.com, google.com ....)
- 공간의 집합
- 인터넷 위에 있다. 인터넷의 부분집합중 하나라고 생각하면 됨.
- Web의 작동방식
- 웹은 클라이언트와 서버 사이의 소통이다.
- Client가 Server에 요청한다.
- Server는 이 요청받은 정보에 대한 처리를 진행한다.
- Server가 Client에게 요청에 대해 응답한다.
- 웹은 Request와 Response의 수많은 상호작용의 집합이다.
- 이 정보를 교환하는 방식에 규칙이 있다. 이 약속을 HTTP라고 한다.
Flask with REST API
API?
- 프로그램들이 서로 상호작용하는 것을 도와주는 매개체
Think RESTful!
- Representational State Transfer
- 웹 서버가 요청을 응답하는 방법론 중 하나
- 데이터가 아닌, 자원(Resource)의 관점으로 접근
- 기술의 이름이 아니다. 개발자와 개발자 사이에 정한 약속의 일종이다. 하나의 패러다임
- HTTP URI를 통해 자원을 명시하고 HTTP Method를 통해 해당 자원에 대한 CRUD를 진행한다.
- 정보를 요청하는 입장에서 대상 위치에 대한 식별자가 URI이다. URL은 locate 위치에 대한 식별자. URI는 Identifier 더 포괄적인 개념이다.
REST API Example
- 자원으로서 가지는 이점. 주소가 모두 동일하다. 근데 method를 보면 같은 리소스임에도 불구하고 다른 액션을 취하게 된다. 이것이 REST ful의 장점이다.
- REST API의 Stateless
- Client의 Context를 서버에서 유지하지 않는다.
- REST API의 Stateless Example
- POST /shoes는 자원에 새로운 정보를 생성
- GET /shoes는 DB에서 shoes가 있는지 확인 후 해당 자원 반환
- (서버 입장에서) 아이템을 GET하기 위해서 POST를 진행할 필요가 없음. 두 메서드 사이에 종속 관계가 없다. 협업에 장점이 있다.
- REST API Example
from flask import Flask, jsonify, request
app = Flask(__name__)
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}) # 리스트 타입은 json으로 반환될 수 없어서 새로운 딕셔너리 형태로 추가해서 리턴해준다.
#POST /menus | 자료를 자원에 추가한다.
@app.route("/menus", methods=['POST']) # 원래는 위와 같이 method를 명시해줘야 한다. GET의 경우 관례적으로 생략 가능?
def create_menu():
# 전달받은 자료를 menus 자원에 추가
request_data = request.get_json()
new_menu ={
"id": 4,
"name": request_data['name'],
"price": request_data['price'],
}
menus.append(new_menu)
return jsonify(new_menu)
if __name__ == '__main__':
app.run()
API Test
- Postman
- Postman을 통해 API를 테스트할 수 있다.
- Postman app을 다운받고 다음과 같이 workspace를 하나 새로 생성해준다.
- 다음 collection tap에서 + 버튼을 누르면 new collection을 만들어 줄 수 있다.
- method 방식을 정하고 다음과 같이 요청을 날리면 return 값을 받을 수 있다.
- Post나 Put의 경우 Body 탭에 원하는 요청 내용을 json 형태로 작성해 request를 날리면 된다.
[4주차 Day1 과제]
필수과제: 메뉴 관리 CRUD 구현하기
- HTTP 메서드 PUT 를 이용해 Update, DELETE 를 이용해 Delete 기능을 구현해주세요.
- PUT /menu/<int:id> : 해당하는 id에 해당하는 데이터를 갱신합니다. (HTTPRequest의 Body에 갱신할 내용이 json으로 전달됩니다.)
- DELETE /menu/<int:id> : 해당하는 id에 해당하는 데이터를 삭제합니다.
- @app.route() 의 인자로 들어가는 경로에는 다음과 같이 사용해줄 수도 있습니다.
# 필수 과제
@app.route("/menu/<int:id>", methods=['PUT'])
def update_menu(id):
for item in menus:
if id == item['id']:
request_data = request.get_json()
item['name'] = request_data['name']
item['price'] = request_data['price']
break
return jsonify(menus)
# 필수 과제
@app.route("/menu/<int:id>", methods=['DELETE'])
def delete_menu(id):
for item in menus:
if id == item['id']:
menus.remove(item)
break
return jsonify(menus)
- id 1 번을 지우는 API 테스트
- id 2번을 update 시키는 테스트
보너스 과제 I: ID야 움직여라 얍!
- 새로운 menu를 추가하는 POST 영역에서 id가 4로 고정되어있는 문제가 발생합니다.
- POST 요청이 들어올 때마다 id가 하나씩 증가하여 menu 리스트에 추가될 수 있도록 코드를 수정해주세요.
- 이 과제는 필수 과제 이후에 진행되어야 합니다.
#보너스 과제 1
@app.route("/menus2", methods=['POST'])
def create_menu_2():
# 전달받은 자료를 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)
보너스 과제 II : 데이터베이스 연동하기
- 수업에서 다룬 API는 서버를 재시작하면 모든 정보가 리셋되는 치명적인 문제가 있었습니다. 이를 해결하기 위해 데이터만을 저장하는 데이터베이스를 도입하여 Flask과 연동할 필요가 생겼습니다.
- SQL과 ORM 중 하나를 선택하여 데이터베이스와 Flask app을 연동해봅시다. (즉, 자원에 CRUD가 발생하면 이 정보가 데이터베이스에 저장되어야합니다.)
- 이 과제는 필수 과제, 보너스 과제 I 이후에 진행되어야 합니다
- 도커로 MySQL 이미지 만들기
DB 구축
1. 도커 설치
- Docker 홈페이지에 접속해 자신의 OS에 맞는 Docker를 내려받아 설치한다. 설치가 완료되면 다음 명령어를 실행하여 버전을 출력해보자
docker -v
2. MYSQL Docker 이미지 다운로드
- 특정 버전을 명시하지 않으면 최신 버전을 다운로드 하게 된다.
- 다운받을수 있는 버전은 도커 허브에서 확인할 수 있다.
docker pull mysql # 최신 버전 받기
docker pull mysql:<버전 정보> # 특정 버전 받기
- docker images로 다운로드한 docker 이미지를 확인한다.
3. 다운받은 image로 container 생성하기
docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=<password> -d -p 3306:3306 mysql
- --name: 생성할 컨테이너 이름
- -e: 환경변수 (PASSWORD) 설정
- -d: Dispatch mode (백그라운드에서 실행)
- -p: 포트 (외부포트 : Docker 내부포트)
4. docker ps -a로 잘 생성 되었나 확인. -a 옵션을 빼면 실행중인 것만 보여주게 되고 이 옵션이 있으면 전체를 보여주게 된다.
5. DB 접속(데이터 그립 사용, workbench로 해도 마찬가지)
- 3306 포트
- id: root, password: 설정했던 대로
6. workbench나 datagrip을 이용해 테이블 생성해주기
- menus 테이블을 만들기 위해 새로운 스키마를 생성해줬다.(mysql에서는 스키마 아래 여러 테이블이 존재하게 된다.)
CREATE SCHEMA 'test' DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
CREATE TABLE test.menu
(
ID INT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(100) NULL,
PRICE INT NULL
);
테이블이 정상적으로 생성 되었나 확인
7. 다음 레코드를 입력해준다. 우리가 있던 기존의 menus 리스트 원소 하나가 레코드라고 생각하면 된다.
8. 다음 SQLAlchemy란 ORM을 이용해 flask와 DB를 연동한다.
먼저 필요 라이브러리 install
pip3 install flask-alchemy
pip3 install pymysql
from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
# DB 연동
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql+pymysql://<계정 id>:<게정 비밀번호>@localhost:3306/<스키마>"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
db.init_app(app)
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})
#POST /menus
@app.route("/menus", methods=['POST'])
def create_menu():
# 전달받은 자료를 menus 자원에 추가
request_data = request.get_json()
new_menu ={
"id": 4,
"name": request_data['name'],
"price": request_data['price'],
}
menus.append(new_menu)
return jsonify(new_menu)
# 필수 과제
@app.route("/menu/<int:id>", methods=['PUT'])
def update_menu(id):
for item in menus:
if id == item['id']:
request_data = request.get_json()
item['name'] = request_data['name']
item['price'] = request_data['price']
break
return jsonify(menus)
# 필수 과제
@app.route("/menu/<int:id>", methods=['DELETE'])
def delete_menu(id):
for item in menus:
if id == item['id']:
menus.remove(item)
break
return jsonify(menus)
#보너스 과제 1
@app.route("/menus2", methods=['POST'])
def create_menu_2():
# 전달받은 자료를 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)
# 보너스 과제 2
@app.route('/test')
def get_all():
# 전체 레코드 조회(DB)
res = Menus.query.all()
if len(res) == 0:
return "No record Exist"
else:
item_list = []
for item in res:
data = dict(id =item.id, name=item.name, price=item.price)
item_list.append(data)
return jsonify(item_list)
# ORM을 위한 menu 테이블에 해당하는 클래스 생성
class Menus(db.Model):
__tablename__ = 'menu'
id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
name = db.Column(db.String(100, 'utf8mb4_unicode_ci'))
price = db.Column(db.Integer)
def __init__(self, id, name, price):
self.id = id
self.name = name
self.price = price
if __name__ == '__main__':
app.run()
테스트 결과
참고 자료
https://league-cat.tistory.com/402
'프로그래머스 AI 데브코스 5기 > CS' 카테고리의 다른 글
AWS 서비스들 (1) | 2023.04.10 |
---|---|
클라우드 서비스 개요 (0) | 2023.04.09 |
트리(Trees) (0) | 2023.03.28 |
큐(Queues) (0) | 2023.03.28 |
스택(stack) (0) | 2023.03.27 |