실제 트위터와 마찬가지로 여러 사용자가 서로 follow를하거나 unfollow 하는 기능을 구현하고자 한다
간단하게 사용자의 id와 follow하고자 하는 id를 HTTP 요청을 보내고 응답받는 API를 구현해본다
{
"id" : 2,
"follow" : 1
}
@app.route('/follow', methods=['POST'])
def follow():
payload = request.json
user_id = int(payload['id']) # 1)
user_id_to_follow = int(payload['follow']) # 2)
if user_id not in app.users or user_id_to_follow not in app.users: # 3)
return '사용자가 존재하지 않습니다.', 400
user = app.users[user_id] # 4)
user.setdefault('follow', set()).add(user_id_to_follow) # 5)
return jsonify(user)
1) HTTP 요청으로 전송된 JSON 데이터에서 해당 사용자의 id를 읽어 들인다.
2) HTTP 요청으로 전송된 JSON 데이터에서 해당 사용자가 팔로우할 사용자의 id를 읽어 들인다.
3) 만일 해당 사용자나 팔로우할 사용자가 존재하지 않는다면 400 Bad Request 응답을 보낸다.
4) app.users
dictionary에서 해당 사용자 id를 사용해서 해당 사용자의 데이터를 읽어들인다.
5) 4)에서 읽어 들인 사용자의 정보를 담고 있는 dictionary가 이미 "follow"라는 필드를 가지고 있다면, 즉 이미 사용자가 다른 사용자를 팔로우 한 적이 있다면, 사용자의 "follow" 키와 연결되어 있는 set에 팔로우하고자 하는 사용자 id를 추가한다
만일 이번이 처음 다른 사용자를 팔로우하는 것이라면, 사용자의 정보를 담고 있는 dictionary에 "follow"라는 key를 empty set와 연결하여 추가한다.
다음과 같이 파이썬 flask 가상환경에서 server를 구동한다
(develop) $ FLASK_ENV=development FLASK_APP=app.py flask run
* Serving Flask app "app.py" (lazy loading)
* 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: 391-025-249
사전에 id를 2개이상 만들어서 서로 다른 사용자가 follow할 수 있도록 준비한다.
그리고 httpie를 통해 Follow API를 테스트한다.
# 회원가입
(develop) $ http -v POST localhost:5000/signup name=팔로워 email=follower@gmail.com password=12345678POST /signup HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 85
Content-Type: application/json
Host: localhost:5000
User-Agent: HTTPie/0.9.8
{
"email": "follower@gmail.com",
"name": "팔로워",
"password": "12345678"
}
HTTP/1.0 200 OK
Content-Length: 108
Content-Type: application/json
Date: Wed, 29 Jan 2020 13:47:38 GMT
Server: Werkzeug/0.16.1 Python/3.7.6
{
"email": "follower@gmail.com",
"id": 2,
"name": "팔로워",
"password": "12345678"
}
# follow
(develop) $ http -v POST localhost:5000/follow id=2 follow=1
POST /follow HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 26
Content-Type: application/json
Host: localhost:5000
User-Agent: HTTPie/0.9.8
{
"follow": "1",
"id": "2"
}
HTTP/1.0 500 INTERNAL SERVER ERROR
Connection: close
Content-Type: text/html; charset=utf-8
Date: Wed, 29 Jan 2020 13:49:37 GMT
Server: Werkzeug/0.16.1 Python/3.7.6
X-XSS-Protection: 0
...
...
raise TypeError(f`Object of type {o.__class__.__name__}')
TypeError: Object of type set is not JSON serializable
from flask.json import JSONEncoder # 1)
class CustomJSONEncoder(JSONEncoder): # 2)
def default(self, obj): # 3)
if isinstance(obj, set): # 4)
return list(obj)
return JSONEncoder.default(self, obj) # 5)
app.json_encoder = CustomJSONEncoder # 6)
1) flask.json 모듈에서 JSONEncoder 클래스를 import한다.
JSONEncoder 클래스를 확장해서 커스텀 엔코더를 구현한다.
2) JSONEncoder 클래스를 부모 클래스로 상속받는 CustomJSONEncoder 클래스를 정의한다.
3) JSONEncoder 클래스의 default 메소드를 확장(over-write)한다
default 메소드에서 set인 경우 list를 변경해준다.
4) JSON으로 변경하고자 하는 객체(obj)가 set인 경우 list로 변경해서 리턴한다.
5) 객체가 set이 아닌 경우 본래 JSONEncoder 클래스의 default 메소드를 호출해서 리턴한다.
6) CustomJSONEncoder 클래스를 Flask의 default JSON 엔코더로 지정해준다
그리하면 jsonify 함수가 호출될 때마다 JSONEncoder가 아닌 CustomJSONEncoder 클래스가 호출된다.
위 Custom JSON Encoder를 app.py
에 추가하여 api를 재실행하고 httpie로 다시 테스트한다.
(develop) $ http -v POST localhost:5000/follow id=2 follow=1
POST /follow HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 26
Content-Type: application/json
Host: localhost:5000
User-Agent: HTTPie/0.9.8
{
"follow": "1",
"id": "2"
}
HTTP/1.0 200 OK
Content-Length: 134
Content-Type: application/json
Date: Wed, 29 Jan 2020 13:49:11 GMT
Server: Werkzeug/0.16.1 Python/3.7.6
{
"email": "follower@gmail.com",
"follow": [
1
],
"id": 2,
"name": "팔로워",
"password": "12345678"
}
{
"id" : 2,
"unfollow" : 1
}
@app.route('/unfollow', methods=['POST'])
def unfollow():
payload = request.json
user_id = int(payload['id'])
user_id_to_follow = int(payload['unfollow']) # 1)
if user_id not in app.users or user_id_to_follow not in app.users: # 2)
return '유저가 존재 하지 않습니다', 400
user = app.users[user_id]
user.setdefault('follow', set()).discard(user_id_to_follow) # 3)
return jsonify(user)
1) 언팔로우할 사용자 id를 HTTP 요청으로 전송된 데이터에서 읽어 들인다.
2) 팔로우 endpoint와 마찬가지로 해당 사용자 id 혹은 언팔로우할 사용자 id가 존재하지 않으면 400 Bad Request 응답을 보낸다.
3) 언팔로우하고자 하는 사용자 id를 set에서 삭제한다.
remove 메소드를 사용하지 않고 discard
메소드를 사용하는 이유는, 삭제하고자 하는 값이 있으면 삭제를 하고 없으면 무시하기 때문이다.
그러므로 discard
를 사용하면 굳이 삭제하고자 하는 사용자가 실제로 set에 존재하는지를 확인하는 로직은 구현하지 않아도 된다.
위 Follow API까지 실습하면 2개 계정과 id=2가 id=1을 follow 한 상황이다.
현 상황에서 httpie로 다시 unfollow 테스트한다.
$ http -v POST localhost:5000/unfollow id=2 unfollow=1
POST /unfollow HTTP/1.1
Accept: application/json, */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 28
Content-Type: application/json
Host: localhost:5000
User-Agent: HTTPie/0.9.8
{
"id": "2",
"unfollow": "1"
}
HTTP/1.0 200 OK
Content-Length: 153
Content-Type: application/json
Date: Fri, 31 Jan 2020 02:17:19 GMT
Server: Werkzeug/0.16.1 Python/3.7.6
{
"email": "follow@gmail.com",
"follow": [],
"id": 2,
"name": "퐝팔로우",
"password": "12345678",
"profile": "follow"
}