https://m.yes24.com/Goods/Detail/68713424
깔끔한 파이썬 탄탄한 백엔드 - 예스24
파이썬 개발 환경 구축부터 API 개발, HTTP, Database, Unit Test, AWS Deploy까지 백엔드 개발 입문의 모든 것!파이썬을 지식으로 아는 것뿐 아니라 파이썬을 응용하여 백엔드 시스템을 개발할 수 있도록
m.yes24.com
쉬는 동안 이 책을 읽으면서 되게 쉽게 읽히고 재밌어서 쭉 읽다가
재밌는 예제가 나와서 직접 타이핑을 해보고 수정을 해보았다.
기존 예제 코드(Python 3.7)
from flask import Flask, request, jsonify
from flask.json import JSONEncoder
class CustomJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return JSONEncoder.default(self, obj)
app = Flask(__name__)
app.users = {}
app.tweets = []
app.json_encoder = CustomJSONEncoder
app.id_count = 1
@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 'No User', 400
if len(tweet) > 300:
return '300 Over', 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 'No User', 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 'No User', 400
user = app.users[user_id]
user.setdefault('follow', set()).discard(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 'No User', 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
})
이게 기존 예제인데 아무래도 책이 오래 되다보니 python 3.7 버전으로 되어있어서
위와 같이 적용을 하면 에러가 발생한다.
이걸 바로 실행하면 아마 아래와 같은 에러가 발생 할 것이다.
ImportError: cannot import name 'JSONEncoder' from 'flask.json' (/opt/homebrew/lib/python3.11/site-packages/flask/json/__init__.py)
해결 방법은 import flask.json이 아닌 import json으로 수정 해야한다.
Flask AttributeError: module 'flask.json' has no attribute 'JSONEncoder'
My flask app was working prior to upgrades. I was having trouble with sending email when there was a forgot-reset-password. To try and fix this I recently upgraded some modules for my flask app. The
stackoverflow.com
이 stackoverflow를 확인해서 해당 내용을 수정 했다.
위와 같이 수정하면 실행은 잘 될것이다.
다만, follow, unfollow 기능 사용 시에 아래와 같은 에러 문구가 발생 할 것이다.
raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
TypeError: Object of type set is not JSON serializable
이는 set은 json이 지원하지 않아 json으로 변환할 수 없다.
책에도 위와 같은 내용이 나와 맨 위에 CustomEncoder를 사용하는데
여러 시도를 해봐도 CustomEncoder는 적용 되지가 않았다.
그래서 내가 내린 결론은 list를 사용한 것이었다.
저자는 set이 중복 제거를 해주기 때문에 사용 했다고 말했다.
list를 사용하는 나는 이 중복 제거 로직을 따로 넣어줬다.
아래는 수정된 follow, unfollow 엔드포인트이다.
@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 'No User', 400
user = app.users[user_id]
if 'follow' not in user:
user['follow'] = []
if user_id_to_follow not in user['follow']:
user['follow'].append(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_unfollow = int(payload['unfollow'])
if user_id not in app.users or user_id_to_unfollow not in app.users:
return 'No User', 400
user = app.users[user_id]
if 'follow' not in user:
user['follow'] = []
if user_id_to_unfollow in user['follow']:
user['follow'].remove(user_id_to_unfollow)
return jsonify(user)
앞에서 말햇듯이 처음부터 list로 받고 유저 딕셔너리에 follow 키 값이 없으면 빈 list를 주는 if문, follow에 해당 유저가 없다면 append를 해주는 if문 이렇게 2가지 로직을 넣어줬다.
추가적으로 타임라인 엔드포인트의 경우에도 set에 맞춰 코드가 짜여져 있기 때문에 아래와 같이 수정했다.
@app.route("/timeline/<int:user_id>", methods=['GET'])
def timeline(user_id):
if user_id not in app.users:
return 'No User', 400
# 사용자가 팔로우하는 사람들의 목록을 가져옴
follow_list = app.users[user_id].get('follow', [])
# 현재 사용자의 ID를 추가하지만 중복 방지
if user_id not in follow_list:
follow_list.append(user_id)
# 타임라인 생성: 팔로우하는 사용자들의 트윗을 포함
timeline = [tweet for tweet in app.tweets if tweet['user_id'] in follow_list]
return jsonify({
'user_id' : user_id,
'timeline' : timeline
})
follow list를 set이 아닌 배열로 가져오고 follow, unfollow 엔드포인트와 마찬가지로 현재 ID 추가 시에 중복 방지 로직을 추가했다.
생성 시에 add가 아닌 append로 변경 후 마무리 했다.
그래서 전체 코드를 보자면 아래와 같다.
(Python 3.11)
from flask import Flask, request, jsonify
app = Flask(__name__)
app.users = {}
app.tweets = []
app.id_count = 1
@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 'No User', 400
if len(tweet) > 300:
return '300 Over', 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 'No User', 400
user = app.users[user_id]
if 'follow' not in user:
user['follow'] = []
if user_id_to_follow not in user['follow']:
user['follow'].append(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_unfollow = int(payload['unfollow'])
if user_id not in app.users or user_id_to_unfollow not in app.users:
return 'No User', 400
user = app.users[user_id]
if 'follow' not in user:
user['follow'] = []
if user_id_to_unfollow in user['follow']:
user['follow'].remove(user_id_to_unfollow)
return jsonify(user)
@app.route("/timeline/<int:user_id>", methods=['GET'])
def timeline(user_id):
if user_id not in app.users:
return 'No User', 400
# 사용자가 팔로우하는 사람들의 목록을 가져옴
follow_list = app.users[user_id].get('follow', [])
# 현재 사용자의 ID를 추가하지만 중복 방지
if user_id not in follow_list:
follow_list.append(user_id)
# 타임라인 생성: 팔로우하는 사용자들의 트윗을 포함
timeline = [tweet for tweet in app.tweets if tweet['user_id'] in follow_list]
return jsonify({
'user_id' : user_id,
'timeline' : timeline
})
이 코드로 실행 해보니 문제가 없었다.
대부분의 도움은 내 머리가 아닌 GPT의 도움을 받았다.
아무래도 초보이다 보니 이런 생각 자체를 못 했는데 막상 완성되니 내가 한 것 같아서 기분이 묘하다.