Flask - Minitter 만들기(2)

황인용·2020년 1월 29일
0

Flask

목록 보기
4/13
post-custom-banner

Follow and unFollow 기능

실제 트위터와 마찬가지로 여러 사용자가 서로 follow를하거나 unfollow 하는 기능을 구현하고자 한다
간단하게 사용자의 id와 follow하고자 하는 id를 HTTP 요청을 보내고 응답받는 API를 구현해본다

Follow API 설계

  • Follow API 주소 : /follow
  • Follow 엔드포인트 method : POST
  • Follow를 호출할 때 전송하는 JSON 데이터형식
{
	"id" 		: 2,
    "follow" 	: 1
}

Follow API 구현

@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와 연결하여 추가한다.

Follow API 실행 및 테스트

다음과 같이 파이썬 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
  • 위 처럼 오류가 나는 이유는 팔로우하는 사용자 id들을 저장하는 자료구조로 사용하는 set이 파이썬 json 모듈인 JSON으로 변경하지 못하기 때문.

Custom JSON Encoder

  • set을 list형태로 변경하려면 custom하게 JSON encoder를 구현하여야 한다.
  • JSON encoder는 set을 list로 변경해주는 클래스 형태로 작성한다
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 클래스가 호출된다.

Follow API 재실행 및 테스트

위 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"
}

UnFollow API 설계

  • UnFollow API 주소 : /unfollow
  • UnFollow 엔드포인트 method : POST
  • UnFollow를 호출할 때 전송하는 JSON 데이터형식
{
	"id" 		: 2,
    "unfollow" 	: 1
}

UnFollow API 구현

@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에 존재하는지를 확인하는 로직은 구현하지 않아도 된다.

UnFollow API 재실행 및 테스트

위 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"
}

profile
dev_pang의 pang.log
post-custom-banner

0개의 댓글