[python] Flask 프레임워크로 api 서버 구축(1)
업데이트:
파이썬 Flask 프레임워크로 api 서버 구축(1)
참고링크
- 아나콘다로 가상환경 설치하기(윈도우)
- 파이썬 가상환경 설치하기(우분투)
- 파이썬 가상환경 설치하기(centOS6)
- mysql 설치하기
- 파이썬과 DB 연동하기
- Flask를 이용한 api 서버 구축(1)
- Flask를 이용한 api 서버 구축(2)
- Flask로 한글 받기
참고도서
- 깔끔한 파이썬 탄탄한 백엔드
0. 사전 준비
위 링크를 참고 하셔서 가상환경을 설치하시면 되겠습니다.
1. 가상환경 실행 및 flask 프레임워크 실행확인
저는 가상환경 이름을 py3_7_2라고 지었습니다.
$ pyenv activate py3_7_2
(py3_7_2)$ python
>>> from flask import flask
>>> app = Flask("test")
>>> app
<Flask 'test'>
2. 헬스체크(health check) 엔드포인트 구현
‘ping’ 이라고 찍으면 ‘pong’라고 답이 오는 엔드포인트를 구현해보겠습니다. 먼저 원하는 디렉터리로 이동해 파이썬 파일을 생성해 보겠습니다.
$ cd ./practice/practice_flask
$ vim app.py
app.py 파일은 아래와 같이 생겼습니다.
from flask import Flask
# Flask 클래스를 객체화 시켜 app이라는 변수에 저장
app = Flask(__name__)
# ping 함수를 엔드포인트로 등록
@app.route("/ping", methods=['GET'])
def ping():
return "pong"
그리고 api를 실행해 봅시다.
(py3_7_2)$ ~/practice/practice_flask$ FLASK_APP=app.py FLASK_DEBUG=1 flask run
* Serving Flask app "app.py" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 130-161-363
터미널을 하나 더 띄워서 api 테스트를 위해 httpie 설치하겠습니다.
$ sudo apt install httpie //우분투
$ brew install httpie //맥
그리고 api테스트를 해봅시다.
$ http -v GET http://localhost:5000/ping
GET /ping HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:5000
User-Agent: HTTPie/0.9.8
HTTP/1.0 200 OK
Content-Length: 4
Content-Type: text/html; charset=utf-8
Date: Sun, 22 Mar 2020 06:54:45 GMT
Server: Werkzeug/1.0.0 Python/3.7.2
pong
위 결과 처럼 api요청을 하면 pong 이라는 응답이 잘 오는 것을 볼 수 있습니다.
3. 미니 트위터 api 서버 개발
3.1 회원가입
필요한 정보
- id
- name
- password
- profile
위에서 작성했던 app.py함수를 수정합시다.
# jsonify: dictionary -> JSON 변환
# request: http 요청 가능
from flask import Flask, jsonify, request
app = Flask(__name__)
app.id_count = 1
app.users = {}
@app.route("/ping", methods=['GET'])
def ping():
return "pong"
@app.route("/sign-up", methods=['POST'])
def sign_up():
new_user = request.json
new_user["id"] = app.id_count
app.users[app.id_count] = new_user
app.id_count = app.id_count + 1
return jsonify(new_user)
3.2 회원가입 테스트
위에서 작성한 app.py 파일을 사용해서 서버를 띄워봅시다.
(py3_7_2)$ ~/practice/practice_flask$ FLASK_ENV=development FKAS_APP=app.py flask run
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 130-161-363
서버가 띄워졌으면 다른 터미널 창을 열어서 회원가입 요청을 보내봅시다.
$ http -v POST localhost:5000/sign-up name=홍길동 email=gildong@naver.com password=abcd1234
POST /sign-up HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 84
Content-Type: application/json
Host: localhost:5000
User-Agent: HTTPie/0.9.8
{
"email": "gildong@naver.com",
"name": "홍길동",
"password": "abcd1234"
}
HTTP/1.0 200 OK
Content-Length: 107
Content-Type: application/json
Date: Sun, 22 Mar 2020 07:59:28 GMT
Server: Werkzeug/1.0.0 Python/3.7.2
{
"email": "gildong@naver.com",
"id": 1,
"name": "홍길동",
"password": "abcd1234"
}
3.3 트윗 엔드포인트
트윗을 할 때 서버에 전송하는 json데이터는 아래와 같습니다.
{
"id": 1, # 사용자 아이디
"tweet":"This is my first tweet" # 트윗 내용
}
위 형식을 생각하고 app.py 파일을 수정합니다.
from flask import Flask, jsonify, request
app = Flask(__name__)
app.id_count = 1
app.users = {}
app.tweets = []
@app.route("/ping", methods=['GET'])
def ping():
return "pong"
@app.route("/sign-up", methods=['POST'])
def sign_up():
new_user = request.json
new_user["id"] = app.id_count
app.users[app.id_count] = new_user
app.id_count = app.id_count + 1
return jsonify(new_user)
@app.route("/tweet", methods=['POST'])
def tweet():
payload = request.json
user_id = int(payload['id'])
tweet = payload['tweet']
if user_id not in app.users:
return '사용자가 존재하지 않습니다.', 400
if len(tweet) > 300:
return '300자를 초과했습니다.', 400
user_id = int(payload['id'])
app.tweets.append({
'user_id' : user_id,
'tweet' : tweet
})
return '', 200
3.4 트윗 테스트
서버를 먼저 띄우고
(py3_7_2)$ ~/practice/practice_flask$ FLASK_ENV=development FKAS_APP=app.py flask run
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 130-161-363
다른 터미널 창을 띄우고 트윗을 포스팅 해 봅시다.
$ http -v POST localhost:5000/tweet id:=1 tweet="This is my first tweet"
POST /tweet HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 44
Content-Type: application/json
Host: localhost:5000
User-Agent: HTTPie/0.9.8
{
"id": 1,
"tweet": "This is my first tweet"
}
HTTP/1.0 200 OK
Content-Length: 0
Content-Type: text/html; charset=utf-8
Date: Sun, 22 Mar 2020 16:28:37 GMT
Server: Werkzeug/1.0.0 Python/3.7.2
3.5 팔로우&언팔로우 엔드포인트
팔로우 및 언팔로우 요청은 아래와 같습니다.
{
"id" : 1,
"follow" : 2
}
{
"id" : 1,
"unfollow" : 2
}
위 내용을 기억하면서 app.py 파일을 작성합시다.
from flask import Flask, jsonify, request
from flask.json import JSONEncoder
### default json endocer는 set을 json으로 변환 불가능
### 따라서 커스텀인코더 작성을 통해 set을 list로 변환하여
### JSON 변환가능하게 만들어줘야함
class CustomJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstane(obj, set):
return list(obj)
return JSONEncoder.default(self, obj)
app = Flask(__name__)
app.id_count = 1
app.users = {}
app.tweets = []
app.json_encoder = CustomJSONEncoder
@app.route("/ping", methods=['GET'])
def ping():
return "pong"
@app.route("/sign-up", methods=['POST'])
def sign_up():
new_user = request.json
new_user["id"] = app.id_count
app.users[app.id_count] = new_user
app.id_count = app.id_count + 1
return jsonify(new_user)
@app.route("/tweet", methods=['POST'])
def tweet():
payload = request.json
user_id = int(payload['id'])
tweet = payload['tweet']
if user_id not in app.users:
return '사용자가 존재하지 않습니다.', 400
if len(tweet) > 300:
return '300자를 초과했습니다.', 400
user_id = int(payload['id'])
app.tweets.append({
'user_id' : user_id,
'tweet' : tweet
})
return '', 200
@app.route("/follow", methods=['POST'])
def follow():
payload = request.json
user_id = int(payload['id'])
user_id_to_follow = int(payload['follow'])
if user_id not in app.users or user_id_to_follow not in app.users:
return '사용자가 존재하지 않습니다.', 400
user = app.users[user_id]
user.setdefault('follow', set()).add(user_id_to_follow)
return jsonify(user)
@app.route("/unfollow", methods=['POST'])
def unfollow():
payload = request.json
user_id = int(payload['id'])
user_id_to_follow = int(payload['unfollow'])
if user_id not in app.users or user_id_to_follow not in app.users:
return '사용자가 존재하지 않습니다.', 400
user = app.users[user_id]
user.setdefault('follow', set()).descard(user_id_to_follow)
return jsonify(user)
3.6 팔로우테스트
서버를 띄우고
(py3_7_2)$ ~/practice/practice_flask$ FLASK_ENV=development FKAS_APP=app.py flask run
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 130-161-363
터미널 하나 더 띄워서 아래처럼 팔로우 테스트할 두명의 사용자를 회원가입 시킵시다.
$ http -v POST localhost:5000/sign-up name=홍길동 email=gildong@naver.com password=abcd1234
$ http -v POST localhost:5000/sign-up name=임꺽정 email=ggukjeong@naver.com password=abcd1233
위 처럼 입력하면 홍길동의 아이디는 1이고, 임꺽정의 아이디는 2가 된다. 따라서 홍길동이 임꺽정을 팔로우 하고 싶으면 아래와 같이 명령하면 팔로우 테스트 끝
$ http -v POST localhost:5000/follow id:=1 follow:=2
POST /follow HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 22
Content-Type: application/json
Host: localhost:5000
User-Agent: HTTPie/0.9.8
{
"follow": 2,
"id": 1
}
HTTP/1.0 200 OK
Content-Length: 133
Content-Type: application/json
Date: Sun, 22 Mar 2020 17:14:06 GMT
Server: Werkzeug/1.0.0 Python/3.7.2
{
"email": "gildong@naver.com",
"follow": [
2
],
"id": 1,
"name": "홍길동",
"password": "abcd1234"
}
3.7 타임라인 엔트포인트
타임라인엔드포인트 응답형태는 다음과 같습니다.
{
"user_id" : 1,
"timeline" : [
{
"user_id" : 2,
"tweet" : "Hello, world!"
},
{
"user_id":1,
"tweet" : "My first tweet!"
}
]
}
그리고 app.py를 수정합시다.
from flask import Flask, jsonify, request
from flask.json import JSONEncoder
### default json endocer는 set을 json으로 변환 불가능
### 따라서 커스텀인코더 작성을 통해 set을 list로 변환하여
### JSON 변환가능하게 만들어줘야함
class CustomJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return JSONEncoder.default(self, obj)
app = Flask(__name__)
app.id_count = 1
app.users = {}
app.tweets = []
app.json_encoder = CustomJSONEncoder
@app.route("/ping", methods=['GET'])
def ping():
return "pong"
@app.route("/sign-up", methods=['POST'])
def sign_up():
new_user = request.json
new_user["id"] = app.id_count
app.users[app.id_count] = new_user
app.id_count = app.id_count + 1
return jsonify(new_user)
@app.route("/tweet", methods=['POST'])
def tweet():
payload = request.json
user_id = int(payload['id'])
tweet = payload['tweet']
if user_id not in app.users:
return '사용자가 존재하지 않습니다.', 400
if len(tweet) > 300:
return '300자를 초과했습니다.', 400
user_id = int(payload['id'])
app.tweets.append({
'user_id' : user_id,
'tweet' : tweet
})
return '', 200
@app.route("/follow", methods=['POST'])
def follow():
payload = request.json
user_id = int(payload['id'])
user_id_to_follow = int(payload['follow'])
if user_id not in app.users or user_id_to_follow not in app.users:
return '사용자가 존재하지 않습니다.', 400
user = app.users[user_id]
user.setdefault('follow', set()).add(user_id_to_follow)
return jsonify(user)
@app.route("/unfollow", methods=['POST'])
def unfollow():
payload = request.json
user_id = int(payload['id'])
user_id_to_follow = int(payload['unfollow'])
if user_id not in app.users or user_id_to_follow not in app.users:
return '사용자가 존재하지 않습니다.', 400
user = app.users[user_id]
user.setdefault('follow', set()).descard(user_id_to_follow)
return jsonify(user)
@app.route("/timeline/<int:user_id>", methods=['GET'])
def timeline(user_id):
if user_id not in app.users:
return '사용자가 존재하지 않습니다.', 400
follow_list = app.users[user_id].get('follow', set())
follow_list.add(user_id)
timeline = [tweet for tweet in app.tweets if tweet['user_id'] in follow_list]
return jsonify({
'user_id' : user_id,
'timeline' : timeline
})
3.8 타임라인 엔트포인트 테스트
서버를 띄우고
(py3_7_2)$ ~/practice/practice_flask$ FLASK_ENV=development FKAS_APP=app.py flask run
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 130-161-363
터미널 하나 더 띄워서 아래처럼 팔로우 테스트할 두명의 사용자를 회원가입 시킵시다.
$ http -v POST localhost:5000/sign-up name=홍길동 email=gildong@naver.com password=abcd1234
$ http -v POST localhost:5000/sign-up name=임꺽정 email=ggukjeong@naver.com password=abcd1233
그리고 서로를 팔로우하게 만들고
$ http -v POST localhost:5000/follow id:=1 follow:=2
$ http -v POST localhost:5000/follow id:=2 follow:=1
각자 하고싶은 말을 트윗해봅시다.
$ http -v POST localhost:5000/tweet id:=1 tweet="This is my first tweet"
$ http -v POST localhost:5000/tweet id:=2 tweet="Life is valuable"
그리고 마지막으로 타임라인 테스트를 하면 완성
http -v GET localhost:5000/timeline/1
GET /timeline/1 HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:5000
User-Agent: HTTPie/0.9.8
HTTP/1.0 200 OK
Content-Length: 183
Content-Type: application/json
Date: Sun, 22 Mar 2020 17:42:45 GMT
Server: Werkzeug/1.0.0 Python/3.7.2
{
"timeline": [
{
"tweet": "This is my first tweet",
"user_id": 1
},
{
"tweet": "Life is valuable",
"user_id": 2
}
],
"user_id": 1
}