Representational State Transfer
request 데이터를 처리할 때 사용하는 방식
- request.json
- request.get_json()
REST API에서 단순히 Json 데이터를 읽어오는 데 사용
REST API에서 JSON 데이터를 수동으로 파싱하고 더 많은 제어를 제공
flask 애플리케이션을 위해 REST API 를 간편하게 생성하고, OpenAPI 문서를 자동으로 생성할 수 있도록 도와주는 라이브러리
OPENAPI 스펙을 사용하여 API문서화를 지원하며, Swagger UI를 통해 API 문서를 시각적으로 제공합니다.
API 작업과 관련된 블루프린트와 데코레이터 기반 방식을 활용하여 직관적이고 유지보수하기 쉬운 코드를 작성할 수 있게 합니다.
블루프린트는 애플리케이션의 특정 기능 별로 라우팅, 뷰 함수, 템플릿, 정적 파일 등의 관리가 가능
- API가 복잡해질 수록 관리의 필요성이 증가함.
- 모듈화: 블루프린트를 사용하면 애플리케이션의 서로 다른 부분을 별도의 모듈로 나누어 관리할 수 있습니다. 이는 코드의 재사용성을 높이고, 유지보수를 용이하게 합니다.
- 라우팅 관리: 블루프린트는 자체 URL 규칙을 가지고 있으며, 이를 통해 애플리케이션의 라우팅을 체계적으로 관리할 수 있습니다.
- 기능별 분리: 블루프린트를 사용하면 특정 기능에 대한 라우팅, 뷰 함수, 에러 핸들러, 템플릿 등을 그룹화할 수 있습니다.
라우트를 그룹화하고 각기 다른 파일이나 모듈로 분리할 수 있도록 도와줌
from flask import Flask,Blueprint,render_template,request
my_blueprint = Blueprint('my_project','myproject', description = 'Operation blueprint', url_prefix='/blueprint')
# app.py
app.register_blueprint(my_blueprint)
abort
함수는 API 개발 과정에서 오류 처리를 위해 사용abort
를 사용하여 클라이언트에 오류 상태와 메시지를 전달 가능
abort
함수는 flask_smorest
에서 가져올 수 있으며, 주로 HTTP 상태 코드와 메시지를 인자로 받습니다.
from flask_smorest import abort
# 오류 상황에서 abort 호출
abort(404, message="Resource not found")
abort
를 사용하면 함수에서 바로 HTTP 상태 코드를 지정할 수 있습니다. 반면에 jsonify
와 같은 방법을 사용하면 상태 코드를 별도로 설정해야 합니다.abort
를 사용하면 오류 메시지를 description
매개변수를 통해 쉽게 전달할 수 있습니다. jsonify
를 사용하면 응답 본문에 메시지를 추가해야 합니다.abort
를 사용하면 이러한 표준을 쉽게 따를 수 있습니다.abort
를 사용하면 오류 처리 부분이 더 간결하고 가독성이 높아집니다. 코드가 명확하게 오류 상황을 처리하고 해당 상태 코드를 클라이언트에게 반환합니다.Object Relational Mapping
객체 : 객체, 클래스, 속성의 구조
관계형 데이터베이스 : 테이블, 로우, 컬럼과 같은 구조
객체랑 테이블이랑 연결시키는것
파이썬의 관계 매핑 라이브러리
flask-SQLAlchemy 플라스크에서 ORM을 쉽게 사용할 수 있도록 도와주는 라이브러리
데이터베이스의 테이블을 객체로 매핑하고, 객체 간의 관계를 데이터베이스의 외래 키 등으로 매핑하는 방식
객체(Object) - Python(Flask,Django)
관계형데이터베이스 - RDBMS
DB에 있는 데이터들을 객체처럼 사용할 수 있도록 도와준다. SQL쿼리문 없이 데이터 CRUD가 가능
ORM 사용 방식 이유
Flask-SQLAlchemy 설정
먼저, Flask 애플리케이션에 Flask-SQLAlchemy를 설정합니다. 이를 위해 SQLAlchemy
객체를 생성하고 Flask 앱과 연결합니다.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///yourdatabase.db' # 여기서 데이터베이스 URI 설정
db = SQLAlchemy(app)
from flask import Flask
from flask_smorest import Api
from db import db
from models import User, Board
app = Flask(__name__)
# sqlachemy 를 통해 database에 접속할 수 있는 명령어
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:##Dlwps753@localhost/oz'
# 객체가 바뀔때마다 trackking 하면 메모리에 너무 부하가 심해 왠만하면 false로 사용
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app) # db 선정
# blueprint 설정
app.config["API_TITLE"] = "My API"
app.config["API_VERSION"] = "v1"
app.config["OPENAPI_VERSION"] = "3.1.3"
app.config["OPENAPI_URL_PREFIX"] = "/"
app.config["OPENAPI_SWAGGER_UI_PATH"] = "/swagger-ui"
app.config["OPENAPI_SWAGGER_UI_URL"] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/"
api = Api(app) # smorest 를 사용할수있게 flask 와 api 연결, 블루프린터 사용가능
from routes.board import board_blp # 등록!!
from routes.user import user_blp
api.register_blueprint(board_blp) # 만들엇으니 이제 등록
api.register_blueprint(user_blp)
from flask import render_template
@app.route('/manage-boards')
def manage_boards():
return render_template('boards.html')
@app.route('/manage-users')
def manage_users():
return render_template('users.html')
if __name__ == "__main__" :
with app.app_context(): # Flask 애플리케이션 컨텍스트를 수동으로 생성하고, 컨텍스트 안에서 작업을 수행할 수 있게 합니다.
db.create_all() # 이거를 하면 models 에 만들어진 테이블들이 전부 만들어짐
app.run(debug=True)
from flask_sqlalchemy import SQLAlchemy
# SQLAlchemy 객체를 생성
db = SQLAlchemy()
# db 객체와 모델클래스를 같은 패일에 위치시킬수도 있음
# 이러면 모델별로 관리하기가 힘듬
# 따로하면 db.init_app(app) 을 사용해서 작업의 일관성을 유지할 수 있다.
# Model -> Table 생성
# 게시글 - board
# 유저 - user
from db import db
# 나중에는 models 폴더안에 User.py 이런식으로 여러개를 만드는게 좋다ㅠ
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False ) # Varchar 가 없어 파이썬으로 표현
email = db.Column(db.String(100), unique=True, nullable = False)
boards = db.relationship('Board', back_populates='author', lazy='dynamic') # author을 역참조 이것을 사용해서도 보드 테이블을 불러오지 않고 셀렉
# lazy = 'dynamic' 실제로 데이터를 가져오지 않고 있다고 만 알려줌
# 모든글을 한번에 업로드하지 않고 특정 글만 업로드
class Board(db.Model):
__tablename__ = "boards"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.String(300))
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
# 원래는 foreignkey 로 연결하는거에 그쳤다면 ORM방식은 역참조 라는 방식이 있다
author = db.relationship('User', back_populates='boards')
from flask_smorest import Blueprint
from flask import request, jsonify
from flask.views import MethodView
from db import db
from models import Board
board_blp = Blueprint('Boards', 'boards', description='Operations on boards', url_prefix='/board')
# AIP List
# /board/
# 전체 게시글을 가져오는 API (GET)
# 게시글 작성 (POST)
# get,post,put,delete 등의 메소드를 각각 처리할 수 있는 메서드를 클래스에 정의할 수 있음
@board_blp.route('/')
class BoardList(MethodView) : # HTTP 메소드 별로 클래스를 정의할 수 잇께 해주는 기능을 제공
def get(self):
boards = Board.query.all() # board 테이블 안에 있는 모든 컬럼 가져옴 join 할필요가 없네?
# 전부 가져왔으니까 하나씩 추리자
# for board in boards:
# print("id:",board.id)
# print("title:",board.title)
# print("content:",board.content)
# print("userid:",board.user_id)
# print("author:",board.author) # user 니까 user 모델에서또 접근할수 있다
# print("author:",board.author.name)
# print("author:",board.author.email)
# 더 간결히
return jsonify([{"id": board.id,
'title':board.title,
'content':board.content,
'user_id': board.author.id,
'author_name': board.author.name}
for board in boards])
# return 'success'
def post(self):
data = request.json # 유저가 보낸 데이터를 json 으로 받겠다.
new_board = Board(title=data['title'], content=data['content'],user_id=data['user_id']) # 게시글을 만들려면 모델 자체를 불러와야함, 필요한 것들을 다적어서
db.session.add(new_board) # 이객체를 추가해주세요
db.session.commit() # 커밋만 하면 뭘 커밋할건지 모름
return jsonify({'msg': 'success create board'}), 201
# /board/<int:board_id>
# 하나의 게시글 불러오기 (GET)
# 특정 게시글 수정하기 (PUT)
# 특정 게시글 삭제하기 (DELETE)
@board_blp.route("/<int:board_id>")
class BoardResource(MethodView): # 하나만 만들거임, 보드가 모듈로 임포트 되있기에 board로 하면 안됨 이름
def get(self,board_id): # 아이디를 갖고있는 상태에서 시작하기에 함수에서 받고 시작
board = Board.query.get_or_404(board_id) # 보드아이디가 존재하면 받고 없으면 404 띄워라
return jsonify({'id':board.id,
'title':board.title,
'content':board.content,
'author':board.author.name})
def put(self, board_id): # orm 방식 우선 가져와
board = Board.query.get_or_404(board_id)
data = request.json # 유저가 json 형태로 보여주는 데이터를 담아주는거임 어떤 내용을 업데이트 시킬건지
board.title = data['title'] # 갚 덮어쓰기 한거임 받아온거에
board.content = data['content']
db.session.commit() # 데이터가 바뀐것을 알려야함
return jsonify({'msg' : 'Successfuly updated board data'}), 201
# 포스트맨에서 put으로 바꿔서 해보자
def delete(self, board_id):
board = Board.query.get_or_404(board_id)
db.session.delete(board)
db.session.commit()
return jsonify({"msg":"Successfully delete board data"}), 201
user.py
from flask import request, jsonify
from flask_smorest import Blueprint
from flask.views import MethodView # 데이터베이스 CRUD 방식 사용을 위해
from db import db # 데이터 베이스 조회
from models import User # 유저데이터 모델 불러오기
# 라우팅 작업
user_blp = Blueprint("Users", "users", description="Operations on users", url_prefix='/user')
# 설정한거 app.py 에서 등록하자 이제
# API LIST :
# 전체 유저 데이터 조회 (GET)
# 유저 생성 (POST)
@user_blp.route('/') # 라우트 경로 를 새로추가하면 flask 정지시켰다가 다시 실행 해야 적용됨
class UserList(MethodView):
def get(self):
users = User.query.all()
return jsonify([{'id':user.id,
'name':user.name,
'email':user.email}
for user in users])
def post(self):
data = request.json # 유저로부터 받자 postman
# 포스트맨에서 post -> body -> raw
# {"name":"python","eamil":"python@gmail.com"}
new_user = User(name=data['name'], email=data['email']) # 만들고 new_user 객체에 담기
db.session.add(new_user) # 새로운 유저를 추가해주세요
db.session.commit()
return jsonify({"msg":"Successfully created new user"}), 201
# 특정 유저 데이터 조회 (GET)
# 특정 유저 데이터 업데이트 (PUT)
# 특정 유저 데이터 삭제 (DELETE)
@user_blp.route("/<int:user_id>")
class UserResource(MethodView):
def get(self, user_id):
user = User.query.get_or_404(user_id)
return jsonify({'name':user.name,'email':user.email})
def put(self, user_id):
pass
user = User.query.get_or_404(user_id)
data = request.json
user.name = data['name']
user.email = data['email']
db.session.commit()
return jsonify({"msg":"Successfully updated user"}), 201
def delete(self, user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return jsonify({"msg":"Successfully delete user"}), 201
사용자 인증을 쉽게 관리할 수 있도록 도와주는 라이브러리
사용자 로그인 및 로그아웃 프로세스를 처리하고, 현재 로그인한 사용자의 정보에 접근 가능하게 해줌
app.py
from flask import Flask
from flask_login import LoginManager
from models import User
app = Flask(__name__)
app.secret_key='flask-secret-key'
login_manager = LoginManager()
login_manager.init_app(app) # 초기화해 준것
login_manager.login_view = 'login'
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
from routes import configure_route
configure_route(app)
if __name__ =="__main__":
app.run(debug=True)
models.py
from flask_login import UserMixin
users = {'admin' : {'password':'qw123'}}
class User(UserMixin):
def __init__(self, username):
self.id = username
@staticmethod
def get(user_id):
if user_id in users:
return User(user_id)
return None
routes.py
from flask import render_template, request, redirect, url_for, flash
from models import User, users
from flask_login import login_user, logout_user, login_required # 플라스크에서 제공하는 모듈
def configure_route(app): # 매개변수 넣은것
@app.route('/')
def index():
return render_template('index.html')
@app.route('/login', methods=['GET','POST'])
def login():
if request.method =='POST':
username = request.form['username']
password = request.form['password']
user = User.get(username)
if user and users[username]['password'] == password:
login_user(user)
return redirect(url_for('index')) # rediercet 해서 넣어줄떄 함수로 넣어주는거임 html로 주는게 아니라 착각 x
else :
flash("Invaild username or Password")
return render_template('login.html')
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/protected')
@login_required
def protected():
return "<h1>Protected area</h1> <a href='/logout'>logout</a>"
templates/index.html,login.html
실제 토큰기반으로 authentication 하는 로그인 방식
JSON Web Tokens(jwt)
jwt : 사용자 인증 및 권한부여에서 널리 사용되는 방식으로 서버와 클라이언트 간의 안전한 정보 전달을 가능하게 함
app.py
from flask import Flask, render_template
#pip install flask-jwt-extended
from jwt_utils import configure_jwt
from routes.user import user_bp
app = Flask(__name__)
configure_jwt(app)
app.register_blueprint(user_bp, url_prefix='/user')
@app.route('/')
def index():
return render_template('index.html')
if __name__ == "__main__":
app.run(debug = True)
blocklist.py
# 블록리스트 관리 파일
BLOCKLIST = set()
def add_to_blocklist(jti):
BLOCKLIST.add(jti)
def remove_from_blocklist(jti):
BLOCKLIST.discard(jti)
jwt_utils.py
from flask_jwt_extended import JWTManager
from blocklist import BLOCKLIST
from flask import jsonify
jwt = JWTManager()
def configure_jwt(app):
app.config["JWT_SECRET_KEY"] = "jwt-secret-key"
jwt.init_app(app)
# token expired time settings
freshness_in_minutes = 1 # 탈취되면 위험하기에 좀 짧게
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = freshness_in_minutes * 60 # 1 hour
jwt.init_app(app)
# claim: 청구하다
# 추가적인 정보를 토큰에 넣고 싶을 때 사용
@jwt.additional_claims_loader # @데코레이터
def add_claims_to_jwt(identity):
if identity == 1:
return {"is_admin": True}
return {"is_admin": False}
# 토큰이 블록리스트에 있는지 확인하는 함수
# 블록리스트에 있으면 해당 토큰이 유효하지 않다고 판단
@jwt.token_in_blocklist_loader
def check_if_token_in_blocklist(jwt_header, jwt_payload):
# jti=jwt id
return jwt_payload["jti"] in BLOCKLIST
# 만료된 토큰이 사용되었을 때 실행되는 함수
@jwt.expired_token_loader
def expired_token_callback(jwt_header, jwt_payload):
return jsonify({"msg": "Token expired", "error": "token_expired"}), 401
# 유효하지 않은 토큰이 사용되었을 때 실행되는 함수
# 토큰의 서명이나 구조가 유효하지 않을 때 실행됩니다. 주로 토큰 자체의 문제로 발생하는 경우에 해당합니다.
@jwt.invalid_token_loader
def invalid_token_callback(error):
return (
jsonify(
{"message": "Invalid token", "error": "invalid_token"}
),
401,
)
# 해당 토큰으로 접근 권한이 없는 경우
@jwt.unauthorized_loader
def missing_token_callback(error):
return (
jsonify(
{
"description": "Access token required",
"error": "access_token_required",
}
),
401,
)
# fresh한 토큰이 필요한데 fresh하지 않은 토큰이 사용되었을 때 실행되는 함수를 정의합니다.
# 해당 응답을 반환하여 fresh한 토큰이 필요하다는 메시지를 전달
# JWT_ACCESS_TOKEN_EXPIRES으로 토큰 만료 시간 조정
@jwt.needs_fresh_token_loader
def token_not_fresh_callback(jwt_header, jwt_payload):
return (
jsonify(
{"description": "Token is not fresh.", "error": "fresh_token_required"}
),
401,
)
# 토큰이 폐기되었을 때 실행되는 함수를
@jwt.revoked_token_loader
def revoked_token_callback(jwt_header, jwt_payload):
return (
jsonify(
{"description": "Token has been revoked.", "error": "token_revoked"}
),
401,
)
routes/user.py
from flask import Blueprint, jsonify, request, render_template
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_required, get_jwt_identity
from models.user import User
user_bp = Blueprint('user', __name__)
# 임시 사용자 데이터
users = {
'user1': User('1', 'user1', 'pw123'),
'user2': User('2', 'user2', 'pw123')
}
@user_bp.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.json.get('username', None)
password = request.json.get('password', None)
user = users.get(username)
if user and user.password == password:
access_token = create_access_token(identity=username)
refresh_token = create_refresh_token(identity=username)
return jsonify(access_token=access_token, refresh_token=refresh_token)
else:
return jsonify({"msg": "Bad username or password"}), 401
else:
return render_template('login.html') # GET
@user_bp.route('/protected', methods=['GET'])
@jwt_required() # 인증된 유저인지 확인
def protected():
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
@user_bp.route('/protected_page')
def protected_page():
return render_template('protected.html')
from flask_jwt_extended import get_jwt
from blocklist import add_to_blocklist # 블랙리스트 관리 모듈 임포트
@user_bp.route('/logout', methods=['POST'])
@jwt_required()
def logout():
jti = get_jwt()["jti"]
add_to_blocklist(jti) # jti를 블랙리스트에 추가
return jsonify({"msg": "Successfully logged out"}), 200
models/user.py
class User:
def __init__(self, id, username, password):
self.id = id
self.username = username
self.password = password
Alembic 을 통해 SQLAlchemy 모델의 변경사항을 추적
데이터베이스 마이그레이션을 효과적으러 관리
- 데이터베이스 스키마 변경사항이 있을 때마다 마이그레이션 파일을 생성하여 이러한 변경사항을 코드로 관리
- 마이그레이션 파일들은 버전 컨트롤 시스템에 추가되어 데이터베이스 스키마의 변경 이력을 기록하는데 사용
변경 사항에 따라 데이터베이스 스키마를 자동으로 업그레이드 또는 다운그레이드 가능
migrate = Migrate(app, db)
flask db init 초기설정, 현재 상태의 데이터베이스의 기록이 필요한 것
flask db init -> migrations 파일이 해당 폴더에 생성됨
flask db migrate -m "migrate 메세지" 마이그레이션 할떄 내용 담는거임
- 현재 데이터베이스 상태와 모델 상태 비교: 마이그레이션은 데이터베이스 스키미의 현재 상태와 SQLAlchemy 모델의 현재 상태간의 차이를 검사
- 차이가 감지되면 마이그레이션 스크립트 생성 : migrations/versions 디렉토리에 저장
- 마이그레이션 스크립트 실행 전 검토 : 생성된 마이그레이션 스크립트는 사용자에게 먼저 검토를 받음, 사용자가 스크립트를 확인하고 승인하면, flask db upgrade 명령어를 사용하요 실제로 데이터베이스에 마이그레이션을 적용, 실제로 눈으로 반영이 되었나 확인하는 것
flask db upgrade
- 터미널 창에서 실행시 migrations/versions 디렉토리에 있는 마이그레이션 스크립트가 실행됨, 이스크립트는 SQLAlchemy를 사용하여 데이터베이스의 스키마를 변경하고 모델의 변경 사항을 적용, 실제 데이터베이스가 업그레이드됨
- 데이터베이스가 최신의 상태로 유지되도록 하는 것
웹 애플리케이션에서 사용자의 상태를 유지하기 위해 주로 사용, 세션인증은 사용자가 로그인하면 서버 측에서 그 사용자의 상태를 기록하고, 로그아웃 하면 그상태를 제거하는 방식
세션은 사용자가 접속해 있는 동안 서버에 유지되는 상태 정보
단점 : 보안상의 문제
세션 기간 설정 가능 로그인 되어 있는 기간
session celar() : 모든 데이터 삭제, 주로 로그아웃할때 삭제
from flask import Flask, render_template, request, redirect, session, flash
from datetime import timedelta
# redirect 다시 메인페이지로 돌아오기위한 모듈
# flash : redirect 하기전에 뛰워주는 안내메세지 모듈
app = Flask(__name__)
# 인증
app.secret_key = 'flask-secret-key' # 실제로 배포시에는 .env or yaml 에 저장해서 배포해야함
# 이게 털리면 데이터베이스 내의 정보 다털릴 수 있음
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
# admin user
users = {
'john':'pw123',
'leo':'pw123'
}
@app.route('/')
def index():
return render_template('login.html')
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
# 보낸 아이디와 패스워드가 일치하면 세션 만들어줌
if username in users and users[username] == password :
session['username'] = username
session.permanent = True
return redirect('/secret')
else :
flash("Invaild username or password")
return redirect('/')
@app.route('/secret')
def secret():
# 한번더 확인하는 이유
# 로그인에 성공했떠라도, 브라우저를 종료하거나 다른 사용자가 브라우저를 열어 secret에 접속할 수 있음
# 서버는 클라이언트가 올바르게 로그인된 상태인지 확인하기 위해 세션에 접속하는 것
if 'username' in session :
return render_template('secret.html')
else :
return redirect('/')
# 로그아웃 구현
@app.route('/logout')
def logout():
session.pop('username', None)
session.clear()
return redirect('/')
if __name__ == '__main__':
app.run(debug=True)
```python
> python # 파이썬 쉘로 접속
from db import db
from app import app
from models import User
with app.app_context():
# 코드 작성
# 코드생성
with app.app_context():
new_user = User(name='newuser', email='newuser@example.com')
db.session.add(new_user)
db.session.commit()
user = User.query.filter_by(name='newuser').first()
print(user)
user.name # newuser
user.email # newuser@example.com
# 조회
with app.app_context():
# 모든 User 레코드 조회
users = User.query.all()
for user in users:
print(user.id, user.name, user.email)
with app.app_context():
# 특정 조건을 만족하는 User 조회
users = User.query.filter_by(name='coding').all()
print(users
with app.app_context():
user = User.query.filter_by(name='newuser').first()
if user:
print(f'Name: {user.name}, Email: {user.email}')
else:
print('User not found')
# 업데이트
with app.app_context():
user = User.query.filter_by(name='newuser').first()
if user:
user.email = 'updated@example.com'
db.session.commit()
print('User updated')
else:
print('User not found')
# 삭제
with app.app_context():
user = User.query.filter_by(name='newuser').first()
if user:
db.session.delete(user)
db.session.commit()
print('User deleted')
else:
print('User not found')
직렬화 (Serialization)
직렬화는 복잡한 데이터 구조(예: Python 객체)를 JSON과 같은 포맷으로 변환하는 과정입니다. 이 변환은 데이터를 API 응답으로 전송하거나, 파일로 저장할 때 유용하게 사용됩니다.
역직렬화 (Deserialization)
역직렬화는 JSON과 같은 포맷의 데이터를 복잡한 데이터 구조(예: Python 객체)로 변환하는 과정입니다. 이는 클라이언트에서 받은 데이터를 서버의 내부 데이터 모델로 변환할 때 사용됩니다.
User
인스턴스를 조회한 후, 이를 JSON 형식으로 클라이언트에게 전송할 때 사용합니다.User
모델 인스턴스로 변환하여 데이터베이스에 저장할 때 사용합니다.