Các bước tạo một thư viện Python

Tác giả: Minh (Software Engineer | CAI)

Trong Tech Blog số này, Got It sẽ cùng bạn tìm hiểu về 6 bước để tạo và phân phối một thư viện Python. Cụ thể, chúng ta sẽ viết một CLI command tương tự cowsay cùng với một function để các package khác có thể import và sử dụng.

1. Tạo GitHub repository mới

Lưu ý: Nếu chưa quen thuộc với GitHub cũng không sao, bạn có thể tìm hiểu thông tin chung về Git và GitHub trong một bài viết trước đây của Got It nhé!

Chúng ta sẽ cùng bắt đầu bằng việc tạo một repository mới trên GitHub. Bạn chỉ cần truy cập địa chỉ https://github.com/new, nhập một số thông tin cơ bản như tên của repository, chọn .gitignore template và license cho project.

Hai loại license phổ biến nhất của các dự án mã nguồn mở là Apache 2.0MIT. Các bạn có thể tìm hiểu thêm về các loại license khác và lý do chúng ta nên thêm license vào các dự án mã nguồn mở tại trang web https://choosealicense.com/. Trong ảnh trên, bạn có thể thấy GitHub cũng cung cấp link đến các tài liệu để bạn có thể tham khảo thêm về .gitignore và license.

Sau khi tạo xong repository mới, chúng ta sẽ thấy có sẵn 3 file tương ứng với các tùy chọn trong lúc thiết lập bên trên.

  • .gitignore: File này chứa những file/thư mục sẽ được git tự động bỏ qua, không theo dõi thay đổi. Ví dụ như thư mục venv chứa virtual environment, các thư mục chứa code đã được build, hoặc các file .env lưu thông tin credentials cá nhân,…
  • LICENSE: https://github.com/ducminh-phan/example-python-package/blob/master/LICENSE, file này chứa thông tin về license được chọn cho project. Ngoài ra, GitHub còn cung cấp cho chúng ta một bản tóm tắt về nội dung của license:
  • README.md: Đây là một file markdown chứa các mô tả chi tiết về dự án, ví dụ như mục đích, cách thiết lập, cách sử dụng,… Nội dung của file README sẽ được hiển thị trong trang chủ của repository để giúp người xem dễ dàng nắm bắt được các thông tin trên. Khi mới được tạo ra, file README này chỉ chứa tên của repository. Chúng ta sẽ quay lại file này và bổ sung nội dung của nó sau nhé!

2. Cài đặt

Trước khi đến với bước thiết lập, chúng ta hãy cùng cài đặt các công cụ sẽ được dùng trong project nhé.

2.1. Python

Đầu tiên, chúng ta cần cài đặt một phiên bản Python phù hợp cho môi trường phát triển. Ở Got It, team Backend đang sử dụng pyenv để cài đặt và quản lý các phiên bản Python. Để cài pyenv, các bạn chỉ cần làm theo hướng dẫn tại https://github.com/pyenv/pyenv. Sau khi đã cài xong pyenv, chúng ta sẽ chạy lệnh pyenv install 3.10 để cài đặt phiên bản Python 3.10 mới nhất (ở thời điểm viết bài).

2.2. Poetry

Bước tiếp theo, chúng ta sẽ cài đặt poetry, công cụ giúp việc quản lý dependencies và đóng gói dễ dàng, thuận tiện hơn. Trên trang chủ của poetry cũng có những hướng dẫn chi tiết để cài đặt. Nếu cài đặt thành công, câu lệnh poetry about sẽ cung cấp những thông tin cơ bản về poetry:

❯ poetry about
Poetry - Package Management for Python
Version: 1.5.1
Poetry-Core Version: 1.6.1
Poetry is a dependency manager tracking local dependencies of your projects and libraries.
See https://github.com/python-poetry/poetry for more information.

Ngoài poetry, có thể các bạn đã nghe đến các công cụ khác với cùng mục đích quản lý dependencies như hatch, pdm, flit,… Tuy nhiên, poetry là công cụ phổ biến nhất và đang được sử dụng tại Got It. Đó là lý do poetry được chọn để giới thiệu đến độc giả trong bài viết này.

2.3. Pre-commit

Trong số Tech Blog trước, chúng mình đã giới thiệu và hướng dẫn cách cài đặt pre-commit, một công cụ dùng để quản lý các Git hooks. Các pre-commit hooks được sử dụng trong bài viết này có một số điểm khác biệt. Chúng ta sẽ cùng tìm hiểu về các điểm khác biệt này ở bên dưới bài viết nhé.

3. Thiết lập

Sau khi đã có repository trên GitHub và cài đặt các công cụ cần thiết, các bạn chỉ cần clone repository về máy tính bằng cách sử dụng câu lệnh git clone. Tiếp theo, chúng ta sẽ đến với các bước thiết lập chung cho project.

3.1. Cấu hình project với poetry

poetry cung cấp cho chúng ta câu lệnh poetry init để khởi tạo project. Khi chạy câu lệnh này, poetry sẽ hỏi bạn những thông tin cơ bản của project và sử dụng những thông tin này để tạo ra file pyproject.toml. Chúng ta cũng có thể chọn những package muốn sử dụng ngay trong khi thiết lập với poetry init. Nếu bạn có lỡ quên thêm package khi đang init thì cũng đừng lo, vì chúng ta có thể thêm dependency sau với câu lệnh poetry add.

pyproject.toml được giới thiệu trong PEP 517PEP 518 như là một cách để chuẩn hóa việc phân phối và build các project Python. Các bạn có thể đọc thêm về file pyproject.toml và build system trong hai URL trên.

Chúng ta sẽ dùng hai package phổ biến nhất để xây dựng CLI command với Python là clickrich. Ngoài ra, chúng ta sẽ thêm một package phục vụ việc phát triển là pytest để viết unit và integration tests.

Dưới đây là file pyproject.toml sau khi đã khởi tạo xong:

[tool.poetry]
name = "example-python-package"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
license = "Apache 2.0"
readme = "README.md"
packages = [{ include = "example_python_package" }]

[tool.poetry.dependencies]
python = "^3.10"
click = "^8.1.3"
rich = "^13.4.2"

[tool.poetry.group.dev.dependencies]
pytest = "^7.3.2"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Để hiểu hơn về nội dung các mục trong file pyproject.toml, ta có thể tìm đọc các tài liệu cung cấp bởi poetry tại địa chỉ https://python-poetry.org/docs/pyproject/.

3.2. Pre-commit hooks

Ở mục 2.3 bên trên, chúng ta đã đề cập rằng: Các pre-commit hooks được sử dụng trong bài viết này sẽ khác với các hooks được nhắc đến trong số Tech Blog trước. Cụ thể hơn, thay vì dùng isortflake8, chúng ta sẽ sử dụng ruff (https://beta.ruff.rs/docs/). Ruff là một linter cho Python, được viết bằng ngôn ngữ lập trình Rust. So với các công cụ có cùng tính năng khác như pylint hoặc flake8, ruff nhanh hơn đến hàng trăm lần! Dưới đây là đồ thị so sánh tốc độ của Ruff với các công cụ khác khi phân tích và xử lý codebase của CPython (https://github.com/python/cpython):

Ngoài việc có tốc độ xử lý rất nhanh, ruff còn có rất nhiều tính năng và hoàn toàn có thể thay thế cho isort, flake8, pyupgrade (một công cụ dùng để tự động cập nhật syntax khi bạn muốn cập nhật phiên bản Python)…

Dưới đây là nội dung file .pre-commit-config.yaml với hai hooks ruffblack:

repos:
  - repo: https://github.com/charliermarsh/ruff-pre-commit
    rev: v0.0.272
    hooks:
      - id: ruff
        args:
          - --fix
          - --exit-non-zero-on-fix
  - repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
      - id: black

Chúng ta sẽ cấu hình cho ruff bằng cách khai báo thêm trong file pyproject.toml như sau:

[tool.ruff]
select = [
    # Pyflakes
    "F",
    # Pycodestyle
    "E",
    "W",
    # isort
    "I",
    # flake8-print
    "T20",
    # pyupgrade
    "UP",
    # Ruff-specific rules
    "RUF",
]

# Same as Black.
line-length = 88

target-version = "py310"

[tool.ruff.mccabe]
max-complexity = 10

[tool.ruff.per-file-ignores]
"__init__.py" = ["F401"]

Sau khi đã cấu hình xong, chúng ta chỉ cần chạy câu lệnh pre-commit install để cài pre-commit hooks.

4. Code

4.1. Cài đặt virtual environment

Trước khi bắt tay vào code, chúng ta sẽ cần thiết lập virtual environment cho project dùng pyenv theo các bước sau. Đầu tiên, chúng ta sẽ chạy câu lệnh pyenv local 3.10 từ root directory của project để cấu hình pyenv sử dụng Python 3.10 cho project. Sau khi chạy xong câu lệnh này, pyenv sẽ tạo ra một file .python-version với nội dung là 3.10 để lưu lại phiên bản Python đã được chọn.

Để kiểm tra, chúng ta chạy câu lệnh python --version và sẽ thấy Python 3.10.9 được in ra trong terminal, có nghĩa là chúng ta đã cấu hình được pyenv sử dụng Python 3.10.9. Có một cách khác nữa là chạy câu lệnh pyenv which python và chúng ta sẽ thấy đường dẫn đến Python: /Users/*******/.pyenv/versions/3.10.9/bin/python.

Tiếp theo, chúng ta chạy câu lệnh python -m venv venv để tạo virtual environment cho project. Trong đó:

  • python -m venv để chạy built-in module venv của Python.
  • venv ở cuối để chỉ ra rằng chúng ta muốn tạo virtual environment trong thư mục có tên venv.

Sau khi câu lệnh pyenv exec python -m venv venv chạy thành công, chúng ta sẽ cần activate bằng cách chạy câu lệnh source ./venv/bin/activate. Câu lệnh poetry env info sẽ cho chúng ta thấy thông tin của môi trường hiện tại:

Virtualenv
Python:         3.10.9
Implementation: CPython
Path:           /Users/*******/PycharmProjects/example-python-package/venv
Executable:     /Users/*******/PycharmProjects/example-python-package/venv/bin/python
Valid:          True

System
Platform:   darwin
OS:         posix
Python:     3.10.9
Path:       /Users/*******/.pyenv/versions/3.10.9
Executable: /Users/*******/.pyenv/versions/3.10.9/bin/python3.10

4.2. Cài đặt dependencies

Nếu như các bạn đã thêm rich, click, và pytest trong khi chạy câu lệnh poetry init, chúng ta sẽ chỉ cần chạy luôn câu lệnh poetry install. Trong trường hợp ngược lại, chúng ta cần thêm các thư viện này trước:

poetry add rich click
poetry add --group=dev pytest

Sau khi chạy xong, poetry sẽ tạo ra một file poetry.lock để cố định version của tất cả các package hiện có trong project. File này sẽ giúp chúng ta có một môi trường reproducible, tránh trường hợp lệch version của các package trên hai máy khác nhau.

4.3. Viết code

Chúng ta sẽ tạo một thư mục py_cowsay với cấu trúc như sau:

.
└── py_cowsay
    ├── __init__.py
    ├── lib.py
    └── cli.py

Trong đó:

  • __init__.py là file rỗng, có tác dụng biến thư mục py_cowsay thành một Python package.
  • lib.py là module thư viện, trong đó sẽ khai báo các hàm được CLI command và các package khác dùng. Chi tiết của file này các bạn có thể tham khảo tại repository của project: https://github.com/ducminh-phan/example-python-package.
  • cli.py là module khai báo CLI command, chúng ta sẽ cần cấu hình poetry thêm để dùng được CLI. File này có nội dung như sau:
import click

from .lib import say


@click.command("py-cowsay")
@click.argument("message")
@click.option(
    "-b",
    "--bold",
    is_flag=True,
    help="Make the message bold.",
)
def cowsay(message: str, bold: bool):
    say(message, bold=bold)

Ở đây, chúng ta khai báo một function cowsay và dùng click để có thể sử dụng được function này dưới dạng CLI command. Các bạn có thể tham khảo thêm thông tin về click tại địa chỉ https://click.palletsprojects.com/. Tiếp theo, chúng ta cần chỉnh sửa file pyproject.toml để khai báo CLI command và package:

❯ git diff pyproject.toml 
diff --git a/pyproject.toml b/pyproject.toml
index 949a981..6c70b7e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,10 @@ description = ""
 authors = ["Your Name <you@example.com>"]
 license = "Apache 2.0"
 readme = "README.md"
-packages = [{ include = "example_python_package" }]
+packages = [{ include = "py_cowsay" }]
+
+[tool.poetry.scripts]
+py-cowsay = 'py_cowsay.cli:cowsay'
 
 [tool.poetry.dependencies]
 python = "^3.10"

Tên package được sửa thành py_cowsay, là tên của thư mục chúng ta đã tạo bên trên, cũng là một Python package. Các project khác sẽ sử dụng thư viện của chúng ta bằng cách import py_cowsay.

Tiếp theo, chúng ta khai báo CLI command bằng section tool.poetry.scripts:

[tool.poetry.scripts]
py-cowsay = 'py_cowsay.cli:cowsay'

py-cowsay là tên bạn muốn đặt cho CLI command, 'py_cowsay.cli:cowsay' có ý nghĩa là dùng function cowsay trong module py_cowsay.cli.

Cuối cùng, chúng ta chỉ cần chạy command poetry install để poetry cài đặt project của chúng ta ở chế độ phát triển. Sau khi câu lệnh này chạy xong, các bạn có thể dùng được câu lệnh py-cowsay:

❯ py-cowsay --help
Usage: py-cowsay [OPTIONS] MESSAGE
Options:
  -b, --bold  Make the message bold.
  --help      Show this message and exit.
❯ py-cowsay "Hello World"
  ___________
 /           \
| Hello World |
 \___________/
       \
        \
          ^__^
          (oo)\_______
          (__)\       )\/\
              ||----w |
              ||     ||

5. Phân phối thư viện sử dụng PyPI

Đến bước này, các dự án khác đã hoàn toàn có thể sử dụng được thư viện của chúng ta bằng cách cài đặt trực tiếp từ GitHub. Ví dụ, để cài đặt với pip, chúng ta sẽ dùng câu lệnh pip install git+https://github.com/ducminh-phan/example-python-package, hoặc thêm dòng sau vào section tool.poetry.dependencies trong pyproject.toml và chạy câu lệnh poetry update py-cowsay:

py-cowsay = { git = "git@github.com:ducminh-phan/example-python-package.git", branch = "master" }

Sau khi cài đặt xong, chúng ta có thể kiểm tra bằng câu lệnh which py-cowsay hoặc py-cowsay --help như ví dụ bên trên:

❯ which py-cowsay
/Users/*******/*******/venv/bin/py-cowsay

Để có thể cài đặt thư viện mà chỉ cần tên của thư viện trong câu lệnh pip install hoặc trong file pyproject.toml, ví dụ như pip install py-cowsay, chúng ta cần phân phối thư viện lên một nguồn mà pip hoặc poetry có thể tự động tìm kiếm package theo tên, và đó chính là PyPI.

PyPI (Python Package Index) là một nơi lưu trữ các thư viện Python và là nguồn mặc định được pippoetry sử dụng khi cài đặt thư viện. Để phân phối thư viện lên PyPI, chúng ta cần tạo một tài khoản tại https://pypi.org/. Tuy nhiên, với mục đích thử nghiệm và tránh ảnh hưởng đến các package thật, chúng ta sẽ sử dụng Test PyPI. Các bước phân phối thư viện lên Test PyPI tương tự với PyPI, các bước hướng dẫn sau đây sẽ chỉ ra các điểm khác biệt (nếu có) khi sử dụng Test PyPI so với khi sử dụng PyPI.

5.1. Tạo API token

Chúng ta sẽ cần tạo một API token để xác thực khi upload package. Để tạo API token, chúng ta chỉ cần đi tới địa chỉ https://test.pypi.org/manage/account/token/ (thay domain test.pypi.org bằng pypi.org khi dùng PyPI).

tạo một API token

Chúng ta cần chọn một tên dễ hiểu và đủ thông tin cho token, ví dụ như Publish py-cowsay. Ở phần scope, chúng ta chỉ nên chọn package (PyPI gọi là project) mà chúng ta muốn sử dụng token này để upload. Tuy nhiên, khi bạn chưa upload package lên, chúng ta chỉ có thể chọn Entire account (all projects), token với scope này có thể sử dụng để upload tất cả package và sẽ kém an toàn hơn so với scope của một package. Sau khi đã dùng API token này để upload lên lần đầu tiên, chúng ta có thể tạo một token mới với scope được giới hạn ở package mới để bảo mật hơn.

tạo một API account

Token mới được tạo ra chỉ được hiện một lần duy nhất, vì vậy hãy chắc chắn rằng bạn đã copy và lưu token lại trước khi thoát trang web tạo token nhé.

5.2. Cấu hình poetry

poetry mặc định sử dụng PyPI để cài đặt và phân phối package. Nếu muốn sử dụng Test PyPI, chúng ta cần cấu hình poetry thêm như sau. Các bạn có thể tham khảo thêm thông tin chi tiết tại trang web https://python-poetry.org/docs/repositories/. Đầu tiên, chúng ta sẽ cần thêm repository:

poetry config repositories.test https://test.pypi.org/legacy/

Trong đó, test là tên repository, mặc định poetry sẽ sử dụng repository có tên pypi, tương ứng với https://pypi.org/. Tiếp theo, chúng ta sẽ cấu hình API token:

# Test PyPI
poetry config pypi-token.test YOUR_API_TOKEN
#
# OR
#
# PyPI
poetry config pypi-token.pypi YOUR_API_TOKEN

5.3. Phân phối

Ở bước cuối cùng này, chúng ta sẽ hoàn tất bằng 2 câu lệnh:

poetry build

# Test PyPI
poetry publish --repository test
#
# OR
#
# PyPI
poetry publish

Hoặc các bạn có thể kết hợp thành một câu lệnh duy nhất như sau:

# Test PyPI
poetry publish --build --repository test
#
# OR
#
# PyPI
poetry publish --build

Câu lệnh poetry build sẽ đóng gói project của chúng ta thành các định dạng sẵn sàng để phân phối là wheelsdist (https://packaging.python.org/en/latest/overview/#packaging-python-libraries-and-tools), tương ứng với 2 file mới được tạo trong thư mục dist:

❯ tree dist
dist
├── py_cowsay-0.1.0-py3-none-any.whl
└── py_cowsay-0.1.0.tar.gz
1 directory, 2 files

Sau khi đã đóng gói xong, câu lệnh poetry publish sẽ upload 2 file trên lên repository tương ứng, sẵn sàng để cài đặt từ các dự án khác. Để cài đặt một package từ Test PyPI, các bạn tham khảo thêm khái niệm package sources của Poetry nhé: https://python-poetry.org/docs/repositories/#package-sources.

6. Hoàn thiện

Ở bước cuối cùng, chúng ta sẽ hoàn thiện project cho thật chỉn chu:

  • Viết README: Trang web Make a README có hướng dẫn chi tiết về cách viết file README và kèm thêm nhiều link tham khảo, ví dụ…
  • Bổ sung unit tests và integration tests.
  • Thêm GitHub actions để tự động chạy tests hoặc publish lên PyPI.

Hy vọng, thông qua 6 bước trên, các bạn đã có thể tạo và phân phối một thư viện Python. Nếu bạn thấy thông tin này hữu ích thì hãy lưu lại và chia sẻ tới bạn bè ngay nhé! Đừng quên theo dõi chuyên mục Tech Blog vào tuần cuối cùng của mỗi tháng để cập nhật những kiến thức công nghệ, lập trình bổ ích được chia sẻ bởi chính Software Engineer của Got It!

Nếu bạn quan tâm, hãy xem các vị trí đang tuyển dụng của Got It tại: bit.ly/gotit-hanoi và đọc thêm về quy trình tuyển dụng tại đây.

https://d1iv5z3ivlqga1.cloudfront.net/wp-content/uploads/2021/04/29235048/1_QAG9RXQyyMAY7i9OYo84FA.png
Got It Vietnam
June 27, 2023
Share this post to:
Tags:
0 Comments
Inline Feedbacks
View all comments
Các bài viết liên quan
Các bước tạo một thư viện Python

Các bước tạo một thư viện Python

Tác giả: Minh (Software Engineer | CAI) Trong Tech Blog số này, Got It sẽ cùng bạn tìm hiểu về 6 bước để tạo và phân phối một thư viện Python. Cụ thể, chúng ta sẽ viết một CLI command tương tự cowsay cùng với một function để các package khác có thể import và […]
Sử dụng pre-commit để thực thi PEP8 chỉ trong 3 bước

Sử dụng pre-commit để thực thi PEP8 chỉ trong 3 bước

Tác giả: Kiên (Software Engineer | CAI) Đảm bảo code tuân thủ đầy đủ các quy tắc được đề xuất trong PEP8 là một điều vô cùng quan trọng, đặc biệt là trong làm việc nhóm, khi mà yếu tố dễ đọc, dễ hiểu, và dễ bảo trì code được đặt lên hàng đầu. Dẫu […]
Hướng dẫn cách triển khai và debug code Python trên Docker

Hướng dẫn cách triển khai và debug code Python trên Docker

Tác giả: Kiên (Software Engineer | CAI) Bạn đã bao giờ mất hàng tiếng đồng hồ, thậm chí vài ngày để cài đặt một số thư viện cần thiết cho việc chạy một project trên máy tính của mình chưa? Nếu có thì đây là bài viết dành cho bạn. Thông thường, khi bạn tham […]
Readable Code

Readable Code

Tác giả: Minh (Software Engineer, CAI) & Hương (TPM, CAI) Mục lục1. Readable code là gì?2. Làm thế nào để viết code dễ đọc?2.1. Style guide2.2. Viết function nhỏ, tập trung vào một tính năng2.3. Đặt tên hợp lý2.3.1. Dùng các tiền tố thích hợp để phân loại function2.3.2. Hạn chế thêm thông tin về […]
Tìm hiểu Tuple trong Python, phân biệt Tuple và List

Tìm hiểu Tuple trong Python, phân biệt Tuple và List

Ở bài viết này, chúng ta sẽ cùng đi tìm hiểu về Tuple trong Python, cách sử dụng chúng như thế nào, và sự khác biệt giữa Tuple và List là gì? Tất cả những nội dung trong bài đọc sẽ đều có ví dụ minh hoạ cụ thể, hi vọng các bạn đọc có […]
Anaconda là gì? Tìm hiểu nền tảng Khoa học dữ liệu phổ biến nhất

Anaconda là gì? Tìm hiểu nền tảng Khoa học dữ liệu phổ biến nhất

Để có thể tạo nên một ứng dụng của riêng mình, điều quan trọng nhất đó là phải thiết lập môi trường làm việc đúng cách. Vì vậy, bạn cần các công cụ để xử lý dữ liệu, xây dựng các mô hình và biểu diễn trên đồ thị. Việc sử dụng nhiều công cụ […]