สร้าง CLI Python จัดการ Task ด้วย Context Manager พร้อม Test และ Logging ง่ายๆ

สวัสดีครับ เพื่อนๆ โปรแกรมเมอร์ทุกคน

วันนี้ผมจะมาแนะนำวิธีสร้าง CLI (Command Line Interface) ง่ายๆ ด้วย Python นะครับ ให้เราสามารถจัดการงานบางอย่างได้ แล้วก็ยังมีการ Test เพื่อความชัวร์ รวมถึงการทำ Logging เพื่อดูว่ามีใครมาทำอะไรกับระบบของเราบ้าง ซึ่งอันนี้สำคัญกับเรื่อง Compliance มากๆ เลยนะครับ

1. โครงสร้าง CLI พื้นฐาน

มาเริ่มจากโครงสร้าง CLI ง่ายๆ ก่อนนะครับ ผมใช้ argparse เข้ามาช่วยแบบนี้นะครับ

# main.py
import argparse

def add_task(task_name):
    print(f"Adding task: {task_name}")
    # เดี๋ยวเราจะมาเพิ่มโค้ดบันทึกลงไฟล์ทีหลังครับ

def list_tasks():
    print("Listing all tasks...")
    # เดี๋ยวเราจะมาเพิ่มโค้ดอ่านจากไฟล์ทีหลังครับ

def main():
    parser = argparse.ArgumentParser(description="Simple Task Manager CLI.")
    subparsers = parser.add_subparsers(dest="command")

    # Add command
    add_parser = subparsers.add_parser("add", help="Add a new task.")
    add_parser.add_argument("task", type=str, help="The name of the task.")
    add_parser.set_defaults(func=lambda args: add_task(args.task))

    # List command
    list_parser = subparsers.add_parser("list", help="List all tasks.")
    list_parser.set_defaults(func=lambda args: list_tasks())

    args = parser.parse_args()
    if args.command:
        args.func(args)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()

จากนั้นก็รันแบบนี้นะครับ python main.py add "ซื้อของ" หรือ python main.py list

2. Context Manager สำหรับจัดการไฟล์อย่างปลอดภัย

ทีนี้เราจะมาทำให้การอ่าน-เขียนไฟล์ของเราปลอดภัยมากขึ้น ด้วยการใช้ Context Manager นะครับ ซึ่งมันจะช่วยให้เรามั่นใจได้ว่าไฟล์จะถูกเปิดและปิดอย่างถูกต้องเสมอครับ แม้จะมี Error เกิดขึ้นก็ตาม ผมสร้างคลาส TaskFileHandler แบบนี้นะครับ

# task_manager.py
import json
import os

class TaskFileHandler:
    def __init__(self, filename="tasks.json"):
        self.filename = filename

    def __enter__(self):
        if not os.path.exists(self.filename):
            with open(self.filename, 'w') as f:
                json.dump([], f)
        with open(self.filename, 'r+') as f:
            self.file = f
            try:
                self.data = json.load(f)
            except json.JSONDecodeError:
                self.data = [] # กรณีไฟล์ว่างเปล่าหรือไม่ถูกต้อง
        return self.data

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is None: # ถ้าไม่มี error
            self.file.seek(0)
            json.dump(self.data, self.file, indent=4)
            self.file.truncate()
        self.file.close()

วิธีนำไปใช้ใน main.py นะครับ:

# ใน main.py: อัปเดตฟังก์ชัน add_task และ list_tasks
from task_manager import TaskFileHandler # เพิ่มบรรทัดนี้

def add_task(task_name):
    with TaskFileHandler() as tasks:
        tasks.append({"name": task_name, "completed": False})
    print(f"Task '{task_name}' added successfully.")

def list_tasks():
    with TaskFileHandler() as tasks:
        if not tasks:
            print("No tasks found.")
            return
        print("Your tasks:")
        for i, task in enumerate(tasks):
            status = "[DONE]" if task.get("completed", False) else ""
            print(f"{i+1}. {task['name']} {status}")

แบบนี้ เราก็จะมั่นใจได้ว่าไฟล์จะถูกจัดการอย่างถูกต้องเสมอครับ

3. Unit Testing เบื้องต้น เพื่อความมั่นใจ

เพื่อให้แน่ใจว่า Context Manager ของเราทำงานถูกต้อง ผมก็เขียน Unit Test ง่ายๆ ด้วย unittest แบบนี้นะครับ

# test_task_manager.py
import unittest
from unittest.mock import patch, mock_open
from task_manager import TaskFileHandler
import json
import os

class TestTaskFileHandler(unittest.TestCase):
    def setUp(self):
        # สร้างไฟล์จำลองเพื่อ Test
        self.test_filename = "test_tasks.json"
        if os.path.exists(self.test_filename):
            os.remove(self.test_filename)

    def tearDown(self):
        # ลบไฟล์จำลองหลัง Test
        if os.path.exists(self.test_filename):
            os.remove(self.test_filename)

    def test_initial_file_creation(self):
        with TaskFileHandler(self.test_filename) as tasks:
            self.assertEqual(tasks, [])
        # ตรวจสอบว่าไฟล์ถูกสร้างและมี []
        with open(self.test_filename, 'r') as f:
            self.assertEqual(json.load(f), [])

    def test_add_task_to_file(self):
        with TaskFileHandler(self.test_filename) as tasks:
            tasks.append({"name": "Task 1", "completed": False})

        # เปิดไฟล์อีกครั้งเพื่อยืนยันว่าข้อมูลถูกบันทึก
        with TaskFileHandler(self.test_filename) as tasks_read:
            self.assertEqual(len(tasks_read), 1)
            self.assertEqual(tasks_read[0]["name"], "Task 1")

    def test_empty_or_corrupt_file_handling(self):
        # สร้างไฟล์เปล่า
        with open(self.test_filename, 'w') as f:
            f.write("")
        with TaskFileHandler(self.test_filename) as tasks:
            self.assertEqual(tasks, []) # ควรคืนค่าเป็น list เปล่า

if __name__ == '__main__':
    unittest.main()

จากนั้นก็รัน python -m unittest test_task_manager.py นะครับ เพื่อดูผลการ Test ของเรา

4. Logging เพื่อ Compliance และการตรวจสอบ

สุดท้าย เรื่อง Logging นี่สำคัญมากนะครับ โดยเฉพาะเรื่อง Compliance เราต้องรู้ว่ามีใครมา Add หรือ Modify Task บ้าง ผมใช้ logging module แบบนี้เพื่อเก็บ Log การกระทำต่างๆ นะครับ

# logger_config.py
import logging

def setup_logging():
    logging.basicConfig(
        filename='task_manager.log',
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )
    # สำหรับให้แสดงใน console ด้วยนะครับ
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    console_handler.setFormatter(formatter)
    logging.getLogger().addHandler(console_handler)

# ใน main.py ของเรานะครับ (เพิ่มส่วนนี้)
# import logging
# from logger_config import setup_logging
# 
# setup_logging()
# logger = logging.getLogger(__name__)
# 
# def add_task(task_name):
#     with TaskFileHandler() as tasks:
#         tasks.append({"name": task_name, "completed": False})
#     logger.info(f"Task '{task_name}' added by user.") # เพิ่มบรรทัดนี้
# 
# def list_tasks():
#     with TaskFileHandler() as tasks:
#         if not tasks:
#             logger.info("No tasks found for user listing.") # เพิ่มบรรทัดนี้
#             print("No tasks found.")
#             return
#         logger.info("Tasks listed by user.") # เพิ่มบรรทัดนี้
#         print("Your tasks:")
#         for i, task in enumerate(tasks):
#             status = "[DONE]" if task.get("completed", False) else ""
#             print(f"{i+1}. {task['name']} {status}")

เมื่อรันโปรแกรมแล้ว ไฟล์ task_manager.log ก็จะมีบันทึกการกระทำต่างๆ ของผู้ใช้งาน แบบนี้ช่วยให้เราตรวจสอบย้อนหลังได้สบายเลยครับ เรื่องความปลอดภัยและการ Audit นี่สำคัญมากๆ เลยนะครับ

เป็นไงบ้างครับ เพื่อนๆ? จากตัวอย่างนี้ เราก็ได้เห็นแล้วนะครับว่าการสร้าง CLI Tool ง่ายๆ ด้วย Python พร้อมกับ Context Manager, Unit Test และ Logging นี่ไม่ได้ยากเลยใช่ไหมครับ แล้วก็ยังช่วยให้โค้ดของเรามีคุณภาพมากขึ้น จัดการได้ง่ายขึ้น และตอบโจทย์เรื่อง Compliance ได้อีกด้วยนะครับ ลองเอาไปปรับใช้กับโปรเจกต์ของเพื่อนๆ ดูนะครับ ผมหวังว่าบทความนี้จะเป็นประโยชน์กับเพื่อนๆ ทุกคนนะครับ

อ้างอิง: * Python argparse documentation * Python contextlib (for context managers) * Python unittest documentation * Python logging documentation

Read more

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

เจาะลึกเรื่องราวของ Eileen Gu นักสกีฟรีสไตล์ผู้สร้างประวัติศาสตร์ในโอลิมปิก 2026 สถิติที่ไม่เคยมีมาก่อน ประเด็นถกเถียง และความแข็งแกร่งส่วนตัวที่ทำให้เธอก้าวสู่ระดับโลก

By ทีมงาน devdog
วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

เจาะลึกวันพระและความสำคัญของวันมาฆบูชา 2569 ทั้งวันหยุดราชการ ธนาคาร กิจกรรมเวียนเทียนต้นไม้ และผลกระทบต่อบริการขนส่ง เตรียมตัววางแผนทำบุญและพักผ่อน

By ทีมงาน devdog
ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

เจาะลึกงานวิวาห์ "บังมัดคลองตัน" กับ "ต้นข้าว มิสแกรนด์" พร้อมเหตุผลจากใจเจ้าสาวที่เลือกความรักเหนือกาลเวลาและคำวิจารณ์ สู่การเริ่มต้นชีวิตคู่ที่สะท้อนการให้อภัย

By ทีมงาน devdog
ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

เจาะลึกไฮไลท์บอลไทยลีก 2 ของมหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรง ชัยชนะสำคัญจาก ชิตชนก และบทบาทโค้ชดุสิต สู่เส้นทางเพลย์ออฟที่น่าจับตา!

By ทีมงาน devdog