API Handler Exception by Decorators

เมื่อมีปัญหามากมายที่ต้องระวัง เรายังมีสิ่งนี้ที่ช่วยให้ดูง่ายขึ้นได้

Tanabodin Kamol
2 min readMay 7, 2024

--

หลังจากเรื่อง Python Decorator ที่ผมเขียน วันนี้จะมายกตัวอย่างการใช้งาน Python Decorator ของผมซึ่งอาจไม่ใช่สิ่งที่ดีที่สุด แต่อยากจะแบ่งปันรูปแบบที่ใช้ในการดัก Exception ต่าง ๆ ใน code ของเรา

Directory ที่เอามายกตัวอย่างจะมีหน้าตาคร่าว ๆ ประมาณนี้นะครับ แยก decorators, exceptions, API ไว้คนละ folder โดย API จะแยกกับ function คนละไฟล์ด้วย

src/
decorators/
custom_exception_hdlr.py
global_exception_hdlr.py
exceptions/
base_custom_exc.py
...
handlers/
test_hdlr.py

ตัว Exception จะเขียนเป็น Object BaseCustomException ไว้โดยให้ Parent เป็น Exception เพื่อให้สามารถ Customize ได้และใช้ในการ raise exception และดักได้สะดวก

# base_custom_exc.py
class BaseCustomException(Exception):
def __init__(self, message, exc_name, status_code):
self.message = f"{exc_name}: {message}"
self.custom_status_code = status_code
super().__init__(self.message)

สมมุติว่าเราอยากเพิ่ม Exception อื่น ๆ เราก็จะให้ BaseCustomException เป็น Parent เพื่อให้เวลาที่ดัก exception เราสามารถใช้ except BaseCustomException ได้เลย

from ..exception.base_custom_exception import BaseCustomException

class DatabaseExcpetion(BaseCustomException):
def __init__(self, message):
super().__init__(message, __class__.__name__,status_code=400)

Decorator ตัวนี้เราจะใช้ในการ raise exception ใน Function เล็ก ๆ ที่ API Function เรียกใช้เพื่อให้รู้ว่ามีความผิดพลาดแบบไหนเกิดขึ้นและเกิดขึ้นตรงไหนได้รวดเร็วยิ่งขึ้น

# custom_exception_hdlr.py
import typing
from ..exceptions.base_custom_exc import BaseCustomException

def custom_exception_handler(custom_exc: typing.Type[BaseCustomException]):
def inner_function(func):
def wrapper_function(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as exc:
raise custom_exc(exc)
return wrapper_function
return inner_function

ในกรณีนี้เราต้องการส่ง custom exception เข้ามาใน Decorator เราเลยต้องซ้อน inner function แล้วค่อยใช้ wrapper ทำการ raise exception ที่เราต้องการ

# global_exception_hdlr.py
from ..exception.base_custom_exception import BaseCustomException

def exception_handler(func):
def wrapper_function(*args, **kwargs):
try:
return func(*args, **kwargs)
# Custom exc
except BaseCustomException as exc:
return {
"statusCode": exc.custom_status_code,
"body": exc.message
}
# Unhandle exc
except Exception as ex:
return {
"statusCode": 500,
"body": ""
}
return wrapper_function

เดี๋ยวเรามาดูการนำ Decorator จากก่อนหน้านี้ไปใช้งานกัน

# test_hdlr.py
from ..exception.some_exc import DatabaseExcpetion
from ..decorators.custom_exception_hdlr import custom_exception_handler
from ..decorators.global_exception_hdlr import exception_handler


@exception_handler
def test_handler():
result = inquiry_data()
return {
"statusCode": 200,
"body": json.dump(result)
}


@custom_exception_handler(DatabaseExcpetion)
def inquiry_data():
result = None
# Inquiry something in DB
return result

ในกรณีที่มี exception เกิดขึ้นที่ function inquiry_data จะมีการ raise DatabaseException ออกมา แล้วตัว decorator @exception_handler ก็จะดักได้และส่ง response กลับไปเพื่อบอก user ว่าเกิดความผิดพลาดใดขึ้นในระบบ

ในกรณีที่เกิด exception ที่เราไม่ได้ดักไว้ก็จะไปตกในกรณี Unhandle Exception ซึ่งอันนี้แล้วแต่ว่าการออกแบบเลยว่าต้องการจะมีข้อความอะไรส่งกลับไปบอก user

จะเห็นว่าการใช้ Decorator จะทำให้ code ของเราดูสั้นลงและยังสามารถเข้าใจ code ได้ง่ายอยู่ รวมถึงการนำมาใช้ซ้ำสามารถทำได้ง่ายขึ้นอีกด้วย

--

--

Tanabodin Kamol

I always self-study about electronic devices and computer programming, So, I will share what I have learned for all of you! Sometime It’s code for Python