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_URL
và MONGO_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
, sort
và limit
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ệnmongomock
để tạo mộtcollection
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ọiself.connection.insert(user)
, ID được trả về không phải kiểustr
mà là kiểubson.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ạngbson.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ểubson.ObjectId
. -
Phương thức
test_list_with_pagination
Khi gọiself.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ếpsort_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ìnhsort_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