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.
Mục lục
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.0 và MIT. 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ụcvenv
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 fileREADME
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, fileREADME
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 517 và PEP 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à click
và rich
. 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 isort
và flake8
, 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 ruff
và black
:
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ênvenv
.
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ụcpy_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ìnhpoetry
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 pip
và poetry
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).
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.
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
và
# 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à wheel
và sdist
(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!