Tạo một lớp công cụ MongoDB dễ sử dụng với PyMongo - coi đá gà thomo c1

| May 12, 2025 min read

15 tháng 2 năm 2023 - Công nghệ thông tin

Bài viết này sẽ giới thiệu cách sử dụng PyMongo trong Python để tạo ra một lớp công cụ đơn giản và dễ p88 nhà cái dùng cho MongoDB.

Trước khi bắt tay vào viết mã, chúng ta cần xác định các chức năng cơ bản mà lớp công cụ này nên có:

  • Chèn dữ liệu insert Hỗ trợ chèn một bản ghi đơn lẻ và trả về ID được tạo sau khi chèn.

  • Đếm số lượng bản ghi count Đếm số lượng bản ghi thỏa mãn điều kiện đã cho.

  • Lấy một bản ghi get Lấy một bản ghi duy nhất thỏa mãn điều kiện.

  • Truy vấn phân trang list_with_pagination Cho phép chỉ định điều kiện truy vấn, số trang (page_no), kích thước trang (page_size) và quy tắc sắp xếp để lấy ra một nhóm bản ghi đã được sắp xếp theo thứ tự.

  • Cập nhật update Cập nhật dữ liệu bằng cách chỉ định điều kiện và cặp giá trị trường cần cập nhật.

  • Xóa delete Xóa dữ liệu dựa trên điều kiện được chỉ định.

Sau khi đã xác định rõ các chức năng cần thiết, chúng ta có thể bắt đầu viết mã. Thư viện phụ thuộc là pymongo, chúng ta sẽ thực hiện việc bọc lại một cách đơn giản trên nền tảng này.

1. Lớp công cụ đã được bọc

Tập tin chứa lớp công cụ bọc này được đặt tên là connection.py. Dưới đây là mã nguồn:

from typing import Dict, List, Tuple
import os
import pymongo
from bson import ObjectId

class Connection:
    def __init__(self, collect_name: str):
        conn_str = os.getenv('MONGO_URL')
        db_name = os.getenv('MONGO_DB')
        if conn_str is None or db_name is None:
            raise EnvironmentError("MONGO_URL hoặc MONGO_DB chưa được cài đặt")
        self.collection = pymongo.MongoClient(conn_str)[db_name][collect_name]

    def insert(self, item: Dict) -> ObjectId:
        return self.collection.insert_one(item).inserted_id

    def count(self, condition: Dict) -> int:
        return self.collection.count_documents(condition)

    def get(self, condition: Dict) -> Dict:
        return self.collection.find_one(condition)

    def list_with_pagination(self, condition: Dict,
                             page_no: int = 1, page_size: int = 10,
                             sort_tuples: List[Tuple] = list()) -> List[Dict]:
        items_skipped = (page_no - 1) * page_size
        cursor = self.collection.find(condition).skip(items_skipped)
        if len(sort_tuples) > 0:
            cursor = cursor.sort(sort_tuples).limit(page_size)
        else:
            cursor = cursor.limit(page_size)
        items = []
        for item in cursor:
            items.append(item)
        return items

    def update(self, condition: Dict, update_dict: Dict) -> None:
        self.collection.update_many(condition, {'$set': update_dict})

    def delete(self, condition: Dict) -> None:
        self.collection.delete_many(condition)

Để tạo lớp công cụ này, hai biến môi trường phải được cung cấp ngầm: MONGO_URLMONGO_DB. Trong đó, MONGO_URL là địa chỉ kết nối tới MongoDB, có định dạng mongodb://{username}:{password}@{host}:{port}; còn MONGO_DB là tên của cơ sở dữ liệu.

Trong lớp Connection, phần lớn các phương thức đều khá đơn giản ngoại trừ phương thức list_with_pagination. Chúng ta sẽ đi sâu giải thích chi tiết hơn về nó sau.

Phương thức list_with_pagination hoạt động như sau: self.collection.find(condition) sẽ trả về một đối tượng Cursor có thể duyệt qua. Ta sử dụng các phương thức skip, sortlimit lần lượt để bỏ qua bản ghi, sắp xếp và giới hạn số lượng bản ghi trả về, nhờ đó thực hiện tốt việc truy vấn phân trang kèm sắp xếp.

2. Kiểm thử lớp công cụ

Sau khi hoàn thành lớp công cụ, chúng ta tiến hành viết các bài kiểm thử đơn vị cho tập tin connection.py.

Dưới đây là mã nguồn của tập tin kiểm thử connection_test.py:

import datetime
from unittest import TestCase
import pymongo
import mongomock
from bson import ObjectId
from connection import Connection

class TestConnection(TestCase):
    def setUp(self) -> None:
        self.connection = Connection('users')
        # Mock collection
        self.connection.collection = mongomock.MongoClient().db.collection
        # Chèn dữ liệu ban đầu
        id = ObjectId('63eca79d252cd5ac908a7f06')
        user = self.connection.get({'_id': id})
        if user is None:
            user = {
                '_id': id,
                'name': 'Larry',
                'age': 19,
                'createdAt': '2023-02-16 14:43:45',
                'updatedAt': '2023-02-16 14:43:45'
            }
            self.connection.insert(user)

    def test_insert(self):
        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        user = {
            '_id': ObjectId('63eca79d252cd5ac908a7f07'),
            'name': 'Larry',
            'age': 19,
            'createdAt': now,
            'updatedAt': now
        }
        id = self.connection.insert(user)
        self.assertEqual(user['_id'], id)

    def test_count(self):
        condition = {'name': 'Larry'}
        user_count = self.connection.count(condition)
        self.assertTrue(user_count > 0)

    def test_get(self):
        condition = {'_id': ObjectId('63eca79d252cd5ac908a7f06')}
        user = self.connection.get(condition)
        self.assertIsNotNone(user)

    def test_list_with_pagination(self):
        condition = {'name': 'Larry'}
        page_no = 1
        page_size = 20
        sort_tuples = [('createdAt', pymongo.DESCENDING)]
        users = self.connection.list_with_pagination(condition, page_no, page_size, sort_tuples)
        self.assertTrue(len(users) > 0)

    def test_update(self):
        condition = {'_id': ObjectId('63eca79d252cd5ac908a7f06')}
        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        update_dict = {
            'name': 'Larry Update',
            'updatedAt': now
        }
        # Cập nhật
        self.connection.update(condition, update_dict)
        # Lấy dữ liệu
        user = self.connection.get(condition)
        self.assertEqual(update_dict['name'], user['name'])

    def test_delete(self):
        condition = {'name': 'Larry'}
        # Xóa
        self.connection.delete(condition)
        count = self.connection.count(condition)
        self.assertEqual(0, count)

Một số phương thức quan trọng sẽ được giải thích thêm:

  • Phương thức setUp Chúng ta sử dụng thư viện mongomock để tạo một collection giả lập, không thực sự kết nối hay kiểm tra trực tiếp vào cơ sở dữ liệu MongoDB.

  • Phương thức test_insert Khi gọi self.connection.insert(user), ID được trả về không phải kiểu str mà là kiểu bson.ObjectId. Nếu muốn tự chỉ định ID thay vì để MongoDB tạo ngẫu nhiên, cần đảm bảo ID cũng phải ở định dạng bson.ObjectId.

  • Phương thức test_get Khi tìm kiếm một bản ghi theo ID, điều kiện truy vấn _id cũng cần phải là kiểu bson.ObjectId.

  • Phương thức test_list_with_pagination Khi gọi self.connection.list_with_pagination(condition, page_no, page_size, sort_tuples) để thực hiện truy vấn phân trang, điều kiện sắp xếp sort_tuples là một danh sách, do đó có thể chỉ định nhiều trường để sắp xếp theo thứ tự ưu tiên. Ví dụ, nếu muốn sắp xếp giảm dần theo thời gian tạo trước rồi tăng dần theo tên, có thể cấu hình sort_tuples như sau: [('createdAt', pymongo.DESCENDING), ('name', pymongo.ASCENDING)].

Cuối cùng, chạy file kiểm thử connection_test.py và bạn sẽ thấy tất cả 6 bài kiểm thử đều vượt qua thành công.

export MONGO_URL=xxx
export MONGO_DB=test
python3 -m unittest connection_test.py
----------------------------------------------------------------------
Ran 6 tests in 0.008s
OK

Như vậy, chúng ta đã hoàn thành việc bọc PyMongo để tạo ra một lớp công cụ đơn giản và dễ sử dụng cho MongoDB trong Python. Mã nguồn liên quan đến bài viết đã được lưu trữ trên GitHub, mời bạn quan tâm hoặc Fork.

[1] Tài liệu PyMongo 4.3.3 - pymongo.readthedocs.io
#Python #MongoDB