1.1.3. Quản lý Môi trường Python#
Trong các dự án Python hiện đại, việc quản lý môi trường và tự động hóa tác vụ trở nên dễ dàng hơn nhờ công cụ uv(trình quản lý gói & môi trường nhanh), kết hợp với Poe the Poet (trình chạy tác vụ), bên cạnh công cụ quen thuộc là pip. Tài liệu này hướng dẫn chi tiết cách sử dụng mẫu pyproject.toml
chuẩn để thiết lập dependencies, cấu hình lint/test, quản lý môi trường (phát triển & sản xuất) trên cả máy online lẫn offline, cũng như quy trình làm việc với Git khi dùng uv
.
1.1.3.1. Template pyproject.toml
#
Dưới đây là mẫu nội dung tệp pyproject.toml cho một dự án Python sử dụng uv
. Mỗi phần đều có chú thích giải thích mục đích:
# [project]: Thông tin dự án và khai báo các phụ thuộc chính
[project]
name = "my-awesome-project" # Tên dự án
version = "0.1.0" # Phiên bản dự án
description = "Mô tả ngắn gọn dự án" # Mô tả dự án
readme = "README.md" # Tệp README đi kèm dự án
requires-python = ">=3.10" # Yêu cầu phiên bản Python
# Danh sách dependencies chính (sẽ cài cho cả môi trường dev và prod)
dependencies = [
"fastapi>=0.110.0", # Web framework FastAPI (yêu cầu phiên bản >= 0.110.0)
"uvicorn[standard]", # Server chạy FastAPI (cài cả extra [standard])
"sqlalchemy>=1.4", # ORM cho cơ sở dữ liệu
]
# Các thư viện chỉ dùng cho môi trường phát triển (development)
# Ưu tiên sử dụng [dependency-groups] thay cho [project.optional-dependencies]
# Chạy `uv lock` hay `uv sync` mặc định bao gồm [dependency-groups]
# Chạy cho môi trường production:
# 1.loại bỏ nhóm `dev` với flag `--no-dev` (alias của `--no-group dev`)
# 2.Tắt toàn bộ default groups với `--no-default-groups`
# 3.Nếu bạn đã định nghĩa thêm nhóm `prod` trong `[dependency-groups]`, chỉ cần kết hợp `--no-default-groups --group prod` để cài riêng production
[dependency-groups]
dev = [
"pytest",
"pytest-cov",
"ruff",
"mypy",
"jupyterlab",
"notebook",
"ipykernel",
"seaborn",
"ipywidgets",
"ydata-profiling",
# ... có thể bổ sung các công cụ khác (ví dụ: coverage, flake8, v.v.)
]
# [project.optional-dependencies]: Nhóm các phụ thuộc tùy chọn (extras), tuy nhiên ưu tiên sử dụng cơ chế [dependency-groups]
# Định nghĩa nhóm "dev" cho môi trường phát triển, gồm các công cụ phục vụ dev/test, chạy thông qua --extra dev
# [project.optional-dependencies]
# dev = [
# "pytest>=7.0", # Thư viện test
# "ruff>=0.0.272", # Linter kiểm tra code tự động
# "black>=23.3", # Formatter định dạng code
# "mypy>=0.950", # Trình kiểm tra kiểu (type checking)
# "poethepoet>=0.13", # Task runner Poe the Poet
# # ... có thể bổ sung các công cụ khác (ví dụ: coverage, flake8, v.v.)
# ]
# Cấu hình cho uv
[tool.uv]
# Khai báo default-groups bao gồm những môi trường gì, mặc định đã là `default-groups = ["dev"]` mà không cần khai báo
default-groups = ["dev"]
# Để chạy cho production, chỉ cần `uv lock --no-dev` ; `uv sync --no-dev`; `uv sync --no-default-groups`
#--no-default-groups: tắt toàn bộ nhóm mặc định (là dev theo cấu hình ở trên).
#--no-dev: tắt riêng nhóm dev (alias của --no-group dev).
# Cấu hình cho công cụ linting/formatting (ruff, black) và testing (pytest)
[tool.black]
line-length = 88 # Độ dài dòng tối đa theo chuẩn PEP8
target-version = ["py310"] # Phiên bản Python mục tiêu
[tool.ruff]
line-length = 88
select = ["E", "F", "W", "C90"] # Quy tắc lint cần check (ví dụ E,F,W của pyflake8, C90 cho black)
ignore = [] # Có thể liệt kê mã lỗi muốn bỏ qua tại đây
extend-exclude = ["node_modules", "venv"] # Bỏ qua các thư mục không cần lint
[tool.pytest.ini_options]
# Cấu hình mặc định cho pytest (tương đương nội dung pytest.ini)
minversion = "7.0"
addopts = "-ra -q" # Tham số mặc định khi chạy pytest (-ra -q: báo cáo rút gọn)
testpaths = ["tests"] # Thư mục chứa mã nguồn các bài test
# Cấu hình các task cho Poe the Poet
[tool.poe.tasks]
# Task chạy bộ kiểm thử
test = "pytest tests/"
# Task kiểm tra code với ruff
lint = "ruff check ."
# Task tự động định dạng code với black
format = "black ."
# Task dọn dẹp file tạm, cache, build
clean = { shell = "rm -rf __pycache__ .pytest_cache build dist *.py[cod]" }
# Task thiết lập môi trường (cài package, cài pre-commit hook chẳng hạn)
setup = { shell = """
uv sync --extra dev
pre-commit install
""" }
Mô tả:
Phần
[project]
liệt kê thông tin dự án và phụ thuộc chính (những package cần cho runtime chính của ứng dụng).Phần
[project.optional-dependencies]
định nghĩa nhómdev
cho phụ thuộc phát triển – những thư viện chỉ dùng khi phát triển, testing, không cần thiết trong môi trường chạy thực tế (prod).Bên dưới, các phần
[tool.black]
,[tool.ruff]
,[tool.pytest.ini_options]
cấu hình cho formatter, linter, và test runner (giúp thống nhất style code và hành vi test trong dự án).Cuối cùng,
[tool.poe.tasks]
khai báo các tác vụ (tasks) thường dùng, như chạy test, lint, format code, dọn dẹp, v.v., để có thể gọi nhanh bằng lệnhpoe
. (Chi tiết về cách dùng Poe được trình bày ở phần sau.)
1.1.3.2. Setup Environments#
Khi làm việc với uv
, chúng ta tuân theo nguyên tắc: khai báo dependencies trong pyproject.toml
, lock chúng lại thành phiên bản cụ thể trong uv.lock
, sau đó sync vào môi trường ảo. Trước tiên, cần hiểu khái niệm lock và sync:
Lock: tiến trình giải quyết phiên bản phù hợp cho các phụ thuộc trong
pyproject.toml
và tạo ra file uv.lockchứa đúng phiên bản và cây phụ thuộc đầy đủ của chúng. Nói cách khác,uv.lock
là nguồn chân lý về môi trường đã được “đóng băng” (freeze).Sync: tiến trình cài đặt các gói vào môi trường ảo sao cho khớp với lockfile. Lệnh
uv sync
sẽ cài đặt đúng các phiên bản đã khóa tronguv.lock
và gỡ bỏ những gói không có trong lockfile (theo mặc định, chế độ sync là “exact”)
Quy trình tổng quát để thiết lập môi trường là:
(1) tạo môi trường ảo (venv)
(2) tạo/ cập nhật lockfile lock
(3) cài đặt đồng bộ sync
Dưới đây là hướng dẫn cụ thể cho các trường hợp:
1.1.3.2.1. Máy Online (có kết nối Internet)#
1.1.3.2.1.1. Dev environment#
(Môi trường bao gồm cả phụ thuộc chính và phụ thuộc dev)
Tạo môi trường ảo: Sử dụng
uv
để tạo nhanh venv. Chỉ cần chạy:
uv venv .venv
Lệnh này tạo thư mục
.venv
và cài sẵnpip
/setuptools
bên trong.Có thể chỉ định phiên bản Python bằng
uv venv --python <phiên bản>
nếu cần dùng bản khác.
Cài đặt dependencies vào môi trường ảo: Do máy có mạng, ta có thể cài trực tiếp từ PyPI. Có hai cách:
Cách 1: Cài từ
pyproject.toml
– Dùnguv
để lock và sync tự động:Nếu trong
pyproject.toml
chứa[project.optional-dependencies] dev = [...]
:Giải quyết phụ thuộc kể cả nhóm [dev], tạo
uv.lock
: `uv lock –extra devTạo .venv (nếu chưa có) và cài đúng phiên bản gói trong
uv.lock
:uv sync --extra dev
Nếu trong
pyproject.toml
chứa[dependency-groups] dev = [...]
:uv lock
uv sync
Có thể khai báo thêm trongpyproject.toml
là:
[tool.uv] # Để mọi lệnh uv sync và uv lock tự động bao gồm cả project dependencies (main) và nhóm dev default-groups = ["dev"]
để mặc định sử dụng
uv lock
vàuv sync
sẽ bao gồm nhóm mặc định (ví dụdev
), còn nếu trong production sẽ chỉ cầnuv lock --no-dev
;uv sync --no-dev
;uv sync --no-default-groups
Tham số
--extra dev
đảm bảo nhóm phụ thuộc optional “dev” cũng được bao gồm khi lock và sync (ở đây do ta khai báo dev trong optional-dependencies). Sau lệnh này, mọi gói (kể cả pytest, black, v.v.) sẽ được cài vào.venv
.Lưu ý: Nếu sử dụng dependency group đặc biệt của uv thay cho optional extras:
ví dụ
uv add --dev .
– thì uv mặc định tự động bao gồm nhóm dev khi sync mà không cần chỉ rõ. Trường hợp đó có thể chỉ cầnuv lock
rồiuv sync
là đã gồm phụ thuộc dev. Còn để bỏ qua nhóm dev, dùng cờ--no-dev
Cách 2: Sử dụng file yêu cầu (requirements) – Phương pháp truyền thống tương tự pip:
Trước tiên, tạo file
requirements.txt
từpyproject.toml
:uv pip compile pyproject.toml --extra dev -o requirements.txt
Lệnh trên sẽ resolve dependencies (bao gồm nhóm dev) và xuất danh sách phiên bản cụ thể vào
requirements.txt
(tương tự pip-compile của pip-tools)Sau đó, cài đặt môi trường theo file này:
uv pip sync requirements.txt
(Hoặc có thể dùng
pip install -r requirements.txt
bên trong.venv
đều được). Cách này hữu ích nếu muốn giữ lạirequirements.txt
để dễ so sánh phiên bản, nhưng vớiuv
thì việc dùng thẳnguv.lock
là tối ưu hơn.
1.1.3.2.1.2. Prod environment#
Chỉ bao gồm các phụ thuộc chính, không gồm phụ thuộc dev
. Thông thường, ta sẽ tạo lockfile chỉ với dependencies chính và cài đặt tương ứng:
Nếu dùng phương pháp optional extras như trên, chỉ cần bỏ tham số
--extra dev
. Ví dụ:lock chỉ bao gồm dependencies chính:
uv lock
cài đặt môi trường chỉ với dependencies chính:
uv sync
Lúc này các gói dev (pytest, black,…) sẽ không có trong
uv.lock
và không được cài đặt.Nếu dùng dependency groups của uv (ví dụ có nhóm
dev
riêng), có thể dùng:loại bỏ gói dev khi cài đặt:
uv sync --no-dev
hoặc chỉ định cụ thể nhóm cần cài bằng
uv sync --group prod --no-group dev
nếu bạn có nhómprod
riêng cho các phụ thuộc chỉ dùng ở production.
Kiểm tra: Sau khi sync, có thể kích hoạt venv (source .venv/bin/activate
trên Linux/Mac, hoặc .venv\Scripts\activate.bat
trên Windows) và chạy thử ứng dụng hoặc chạy pip list
để kiểm tra các gói đã cài.
1.1.3.2.2. Máy Offline (không có kết nối Internet)#
Thiết lập môi trường trên máy không có internet (ví dụ máy chủ nội bộ, môi trường tách biệt) đòi hỏi chuẩn bị trước các gói cần thiết. Giả sử ta có một máy online (máy cá nhân) và một máy offline (máy đích triển khai hoặc phát triển trong mạng kín). Quy trình sẽ như sau:
Trên máy Online (chuẩn bị):
Chuẩn bị code và lockfile: Đảm bảo
pyproject.toml
đã khai báo đúng các phụ thuộc cần thiết. Chạyuv lock --extra dev
(hoặc cách tương tự) trên máy online để tạo/ cập nhậtuv.lock
. Commit và đẩy (push) cảpyproject.toml
vàuv.lock
lên repository Git (VD: GitLab công ty) để máy offline có thể lấy về sau.Tải trước các gói (download wheels): Dùng máy online có internet để tải toàn bộ gói phụ thuộc về dạng file nén (.whl) lưu trữ cục bộ:
Tạo thư mục để chứa các file wheel, ví dụ:
mkdir offline_packages
Dùng
uv
hoặcpip
để tải tất cả gói trong danh sách yêu cầu về thư mục trên:uv pip download -r requirements-dev.txt --dest offline_packages
Hoặc dùng pip:
pip download -r requirements-dev.txt --dest offline_packages
Lệnh trên sẽ tải về toàn bộ file
.whl
của các package (kể cả phụ thuộc con) vào thư mụcoffline_packages
. Bạn có thể dùngrequirements.txt
hoặcrequirements-dev.txt
tùy trường hợp (nếu muốn bao gồm phụ thuộc dev). Kết quả, thư mụcoffline_packages
sẽ chứa tất cả file cần thiết để cài đặt sau này.Lưu ý: Đảm bảo phiên bản Python trên máy offline tương thích với các wheel đã tải (nên dùng cùng phiên bản major, ví dụ đều là Python 3.10 hoặc 3.11, để tránh xung đột)
Chuẩn bị công cụ
uv
: Nếu máy offline chưa càiuv
, hãy tải sẵn binary củauv
tương ứng hệ điều hành:Truy cập trang release của dự án uv trên GitHub và tải file phát hành phù hợp.
Giải nén lấy tệp thực thi
uv
để sẵn sàng chuyển sang máy offline.
Chuyển dữ liệu sang máy Offline: Sử dụng ổ cứng di động hoặc kênh truyền file an toàn để chuyển:
(a) thư mục
offline_packages
chứa các file .whl,(b) file thực thi
uv
(nếu cần)(c) mã nguồn dự án (bao gồm
pyproject.toml
vàuv.lock
) sang máy offline
Trên máy Offline (cài đặt):
Thiết lập dự án: Clone hoặc cập nhật code dự án từ repository (qua hình thức cho phép, ví dụ GitLab nội bộ hoặc chép tay). Đảm bảo thư mục dự án trên máy offline có đủ
pyproject.toml
vàuv.lock
(đã được chuẩn bị ở bước trước).Cài đặt
uv
: Nếu máy offline chưa cóuv
, đặt file thực thiuv
đã chuyển vào một thư mục (vd. thư mục dự án) và cấp quyền thực thi (chmod +x uv
trên Linux). Có thể đặt alias hoặc thêm vào PATH để gọiuv
thuận tiện.Tạo môi trường ảo: Vào thư mục gốc dự án trên máy offline và tạo venv:
Tạo .venv bằng uv (dùng ./uv nếu uv chưa trong PATH):
./uv venv .venv
Kích hoạt (Linux/Mac)
source .venv/bin/activate
hoặc (Windows).venv\Scripts\activate.bat
Cài đặt gói từ nguồn offline: Sử dụng lệnh
uv pip sync
kết hợp tham số--find-links
trỏ đến thư mục chứa các file wheel:./uv pip sync --find-links ../offline_packages requirements.txt
Lệnh trên yêu cầu uv/pip cài đặt theo
requirements.txt
nhưng không tải từ PyPI, thay vào đó tìm các gói cần thiết trong thư mụcoffline_packages
đã cung cấp. Đảm bảo đường dẫnoffline_packages
chính xác (trong ví dụ trên, giả sửoffline_packages
nằm cùng cấp thư mục dự án; nếu ở vị trí khác thì chỉnh lại đường dẫn đầy đủ).Có thể dùng lệnh thuần pip tương đương:
pip install -r requirements-dev.txt --no-index --find-links offline_packages
nếu cần
Sau khi chạy, các gói trong
.venv
sẽ giống hệt môi trường trên máy online. Bạn có thể kích hoạt venv và kiểm tra bằngpip list
.
Máy offline giờ đã có môi trường ảo với đầy đủ thư viện cần thiết mà không cần internet. Mỗi khi có thay đổi dependencies, bạn lặp lại quy trình: cập nhật pyproject.toml
+ lockfile trên máy online, tải thêm wheel mới, rồi chuyển qua và chạy lại uv pip sync --find-links ...
trên máy offline để cập nhật môi trường.
1.1.3.3. poe
thiết lập tasks#
Xem template pyproject.toml
sử dụng poe để thiết lập task, khi đó chỉ cần chạy command: poe <task_name>
để run scripts
Kết hợp uv
và Poe the Poet giúp tự động hóa nhiều tác vụ thường gặp trong quá trình phát triển:
Dựng môi trường ảo (
venv
): Như đã nói, dùnguv venv
để tạo môi trường nhanh chóng.Nếu lỡ xóa
.venv
, chỉ cần chạy lạiuv sync
(hoặc thậm chíuv run
khi chạy một lệnh bất kỳ) –uv
sẽ tự phát hiện thiếu venv và tạo lại, cài đúng gói theo lockfile.
Cài đặt dependencies: Sau khi thay đổi
pyproject.toml
(hoặc thêm package), hãy chạyuv lock
rồiuv sync
để áp dụng.Nếu dùng lệnh tiện ích
uv add
(xem phần Git workflow bên dưới),uv
sẽ tự động cập nhật cảpyproject.toml
vàuv.lock
cho bạn. Khi đó chỉ cầnuv sync
là môi trường được cập nhật. Mặc định,uv sync
sẽ cài tất cả gói chính và gói dev (nếu dev được khai báo dạng dependency group). Bạn có thể loại trừ gói dev bằng--no-dev
khi chạy sync nếu muốn chỉ cài gói cho production.
Chạy test (
pytest
): Với tasktest
đã định nghĩa trongpyproject.toml
, bạn có thể thực thi bằng cách gõ:uv run poe test
Lệnh
uv run poe <task>
sẽ kích hoạt môi trường.venv
và chạy task thông qua Poe the Poet. Tasktest
ở trên tương đương chạypytest tests/
toàn bộ testcases.
Lint và Format code: Tương tự, dùng:
uv run poe lint # chạy ruff để kiểm tra code uv run poe format # chạy black (hoặc ruff --fix) để định dạng lại code
Bạn có thể kết hợp nhiều task một lúc, ví dụ:
uv run poe format lint test
Sẽ tự động format code, rồi lint kiểm tra, xong chạy tests liên tục. Nhờ Poe the Poet, các lệnh dài dòng được cấu hình sẵn, giúp mọi người trong nhóm chạy chúng thống nhất mà không phải nhớ câu lệnh phức tạp.
Clean (dọn dẹp): Task
clean
trong ví dụ trên xóa các file/thư mục tạm (__pycache__
, thư mục build, dist, cache pytest…). Chạy:uv run poe clean
để làm sạch dự án, đặc biệt hữu ích trước khi đóng gói hay khi reset môi trường.
Ngoài ra: Bạn có thể định nghĩa thêm nhiều task tùy nhu cầu (ví dụ setup
để cài pre-commit hooks như trong mẫu). Poe the Poet hỗ trợ cả các task phức tạp với script nhiều dòng, tham số truyền vào. Hãy tham khảo tài liệu Poe để tận dụng tối đa khả năng tự động hóa.
1.1.3.4. Git Workflow using uv
#
Khi làm việc nhóm với repository Git, tuân thủ một số quy tắc sẽ giúp đồng bộ môi trường giữa các thành viên và CI/CD:
1.1.3.4.1. Thiết lập dự án khi mới clone#
Khi bạn clone một dự án Python dùng uv
về máy local, việc đầu tiên là tái tạo môi trường giống như người tạo dự án. Giả sử repo đã có sẵn pyproject.toml
và uv.lock
được commit bởi người phát triển trước:
Tạo môi trường ảo và cài đặt nhanh: Chỉ cần chạy lệnh:
uv sync # Tự động tạo .venv (nếu chưa có) và cài đặt theo uv.lock
Lệnh này đọc các phụ thuộc khai báo trong
pyproject.toml
và phiên bản cụ thể tronguv.lock
, sau đó tạo môi trường ảo và cài đúng các gói đã khóa phiên bản. Môi trường của bạn sẽ đồng bộ và reproducible so với máy của người tạo. (Thực tế, bạn cũng có thể chạyuv run poe setup
nếu có task setup;uv
sẽ tự độnglock
vàsync
trước khi chạy task).
Kích hoạt môi trường và sử dụng: Kích hoạt venv (
source .venv/bin/activate
trên Linux/Mac, hoặc.\.venv\Scripts\activate
trên Windows). Giờ bạn có thể bắt đầu chạy ứng dụng hoặc các lệnh dev (nhưuv run poe lint/test
) trong môi trường này.
1.1.3.4.2. Thêm hoặc thay đổi phụ thuộc mới#
Giả sử bạn cần cài đặt thêm một thư viện mới (vd: httpx
) hoặc cập nhật phiên bản thư viện trong dự án:
Cập nhật khai báo phụ thuộc: Bạn có thể chỉnh sửa trực tiếp file
pyproject.toml
(thêmhttpx
vào[project.dependencies]
hoặc tương ứng) hoặc dùng lệnh tiện lợi:uv add httpx
Lệnh
uv add
sẽ thêm gói vào pyproject và tự động xử lý phiên bản phù hợp.Thêm tùy chọn
--dev
nếu đó là phụ thuộc phục vụ phát triển (sẽ thêm vào nhóm dev)uv
sẽ cập nhậtpyproject.toml
và tiến hành resolve tạo/điều chỉnhuv.lock
ngay khi thêm (tương tự cách Poetry hoạt động). Kết quả là file lock được làm mới với phiên bản cố định của thư viện mới.
Đồng bộ môi trường local: Chạy lệnh:
uv sync
để cài đặt thư viện mới vào
.venv
của bạn. Nếu thư viện có phụ thuộc con,uv
cũng sẽ cài đầy đủ theouv.lock
. Sau bước này, dự án của bạn đã bao gồmhttpx
sẵn sàng để sử dụng.
Commit thay đổi:
Rất quan trọng: hãy commit cả hai file
pyproject.toml
(nơi thể hiện ý định thêm phụ thuộc) vàuv.lock
(nơi thể hiện kết quả phiên bản cụ thể). Ví dụ:
git add pyproject.toml uv.lock git commit -m "feat: Thêm thư viện httpx để gọi API" git push
Việc commit lockfile đảm bảo các thành viên khác và hệ thống CI/CD đều cài đúng phiên bản như bạn, giúp môi trường đồng nhất.
Xử lý khi có thay đổi lớn:
Nếu bạn cập nhật nhiều phụ thuộc hoặc thay đổi phiên bản Python, hãy ghi chú trong README và đảm bảo test kỹ sau khi
uv lock
mới.Trong một số trường hợp,
uv.lock
có thể xung đột khi merge Git (ví dụ hai người chỉnh dependencies khác nhau trên hai nhánh).Khi đó, cách đơn giản là chọn giữ lại
pyproject.toml
đúng theo ý muốn, rồi chạy lạiuv lock
trên bản hợp nhất để sinh rauv.lock
mới thống nhất,sau đó commit lại. Luôn luôn
git pull
trước khigit push
để giảm nguy cơ conflict.
1.1.3.4.3. Cập nhật môi trường khi kéo code về#
Khi đồng đội của bạn đã thêm/đổi phụ thuộc và đẩy lên repository (cập nhật pyproject.toml
và uv.lock
), bạn cần cập nhật môi trường local của mình:
Git pull: Kéo những thay đổi mới nhất:
git pull
Sau đó kiểm tra log hoặc diff, bạn sẽ thấy
pyproject.toml
vàuv.lock
có thể đã thay đổi (ví dụ thêm thư viện mới).
Đồng bộ môi trường: Chạy lại:
uv sync
uv
sẽ đọc file lock mới, nhận ra những gói nào chưa có trong môi trường của bạn và cài bổ sung, đồng thời gỡ bỏ gói thừa nếu có để khớp với lockfile.Sau bước này, môi trường local của bạn sẽ giống hệt người đẩy code. Bạn có thể tiếp tục làm việc mà không lo lỗi thiếu thư viện.
Thực thi kiểm tra nhanh: Nên chạy lại
uv run poe lint
vàuv run poe test
để đảm bảo mọi thứ vẫn ổn định sau khi cập nhật phụ thuộc.
1.1.3.4.4. Thói quen làm việc hàng ngày#
Luôn đồng bộ trước khi làm: Mỗi ngày hoặc mỗi khi bắt đầu phiên làm việc, hãy
git pull
để lấy code mới nhất về, sau đóuv sync
để chắc chắn môi trường đã bao gồm mọi thay đổi từ người khác. Điều này giúp tránh lỗi do thiếu thư viện hoặc phiên bản không khớp.Luôn đồng bộ trước khi đẩy: Trước khi
git push
, nếu bạn đã cài thêm gói mới mà quên cập nhậtpyproject.toml
, hãy bổ sung ngay (bằnguv add
hoặc chỉnh tay rồiuv lock
). Đảm bảo nguồn chân lýpyproject.toml
và kết quảuv.lock
của bạn phản ánh đúng môi trường đang chạy. Sau đó mới commit và push.Không chỉnh sửa thủ công
uv.lock
: Chỉ cập nhậtuv.lock
thông qua lệnhuv
(nhưuv lock
,uv add
,uv remove
). Tránh sửa tay file này để phòng sai lệch định dạng hoặc thiếu sót phiên bản.Quản lý phiên bản Python: Nếu dự án cố định phiên bản Python (ví dụ 3.10), nên ghi trong file
.python-version
và README để mọi người dùng đúng phiên bản.uv
không bắt buộc nhưng điều này giúp tránh khác biệt môi trường.
3.12.0
Cập nhật
uv
: Phiên bảnuv
nên được cài giống nhau trên các máy (dù uv có tính tương thích ngược khá tốt). Có thể ghi chú phiên bản uv khuyên dùng trong README.
1.1.3.5. Quy trình làm việc với Offline Machine use UV
#
Tóm tắt Quy trình này đảm bảo bạn có thể chuẩn bị một “cache” đầy đủ trên máy online rồi mang sang máy offline, để uv tái tạo môi trường với cùng phiên bản Python, pip, uv, wheel, và tất cả packages. Trên máy online, bạn sẽ:
Pin phiên bản Python, uv, pip.
Chạy
uv lock
để khoá dependencies với URL chính xác.Chạy
uv sync
(với--cache-dir
tuỳ chọn) để tải về và lưu toàn bộ packages vào cache docs.astral.sh.
Sau đó, mang cả project và thư mục cache sang máy offline, thiết lập UV_OFFLINE=1
và UV_CACHE_DIR
trỏ tới cache đó build.opensuse.org, rồi chạy uv sync --offline
để cài đặt mà không cần mạng, đảm bảo 100% đồng nhất.
1.1.3.5.1. 1. Trên máy Online#
1.1.3.5.1.1. Tạo pyproject.toml
#
# [project]: Thông tin dự án và khai báo các phụ thuộc chính
[project]
name = "my-awesome-project" # Tên dự án
version = "0.1.0" # Phiên bản dự án
description = "Mô tả ngắn gọn dự án" # Mô tả dự án
readme = "README.md" # Tệp README đi kèm dự án
requires-python = ">=3.10" # Yêu cầu phiên bản Python
# Danh sách dependencies chính (sẽ cài cho cả môi trường dev và prod)
dependencies = [
"fastapi>=0.110.0", # Web framework FastAPI (yêu cầu phiên bản >= 0.110.0)
"uvicorn[standard]", # Server chạy FastAPI (cài cả extra [standard])
"sqlalchemy>=1.4", # ORM cho cơ sở dữ liệu
]
# Các thư viện chỉ dùng cho môi trường phát triển (development)
# Ưu tiên sử dụng [dependency-groups] thay cho [project.optional-dependencies]
# Chạy `uv lock` hay `uv sync` mặc định bao gồm [dependency-groups]
# Chạy cho môi trường production:
# 1.loại bỏ nhóm `dev` với flag `--no-dev` (alias của `--no-group dev`)
# 2.Tắt toàn bộ default groups với `--no-default-groups`
# 3.Nếu bạn đã định nghĩa thêm nhóm `prod` trong `[dependency-groups]`, chỉ cần kết hợp `--no-default-groups --group prod` để cài riêng production
[dependency-groups]
dev = [
"pytest",
"pytest-cov",
"ruff",
"mypy",
"jupyterlab",
"notebook",
"ipykernel",
"seaborn",
"ipywidgets",
"ydata-profiling",
# ... có thể bổ sung các công cụ khác (ví dụ: coverage, flake8, v.v.)
]
# [project.optional-dependencies]: Nhóm các phụ thuộc tùy chọn (extras), tuy nhiên ưu tiên sử dụng cơ chế [dependency-groups]
# Định nghĩa nhóm "dev" cho môi trường phát triển, gồm các công cụ phục vụ dev/test, chạy thông qua --extra dev
# [project.optional-dependencies]
# dev = [
# "pytest>=7.0", # Thư viện test
# "ruff>=0.0.272", # Linter kiểm tra code tự động
# "black>=23.3", # Formatter định dạng code
# "mypy>=0.950", # Trình kiểm tra kiểu (type checking)
# "poethepoet>=0.13", # Task runner Poe the Poet
# # ... có thể bổ sung các công cụ khác (ví dụ: coverage, flake8, v.v.)
# ]
# Cấu hình cho uv
[tool.uv]
# Khai báo default-groups bao gồm những môi trường gì, mặc định đã là `default-groups = ["dev"]` mà không cần khai báo
default-groups = ["dev"]
# Để chạy cho production, chỉ cần `uv lock --no-dev` ; `uv sync --no-dev`; `uv sync --no-default-groups`
#--no-default-groups: tắt toàn bộ nhóm mặc định (là dev theo cấu hình ở trên).
#--no-dev: tắt riêng nhóm dev (alias của --no-group dev).
# Cấu hình cho công cụ linting/formatting (ruff, black) và testing (pytest)
[tool.black]
line-length = 88 # Độ dài dòng tối đa theo chuẩn PEP8
target-version = ["py310"] # Phiên bản Python mục tiêu
[tool.ruff]
line-length = 88
select = ["E", "F", "W", "C90"] # Quy tắc lint cần check (ví dụ E,F,W của pyflake8, C90 cho black)
ignore = [] # Có thể liệt kê mã lỗi muốn bỏ qua tại đây
extend-exclude = ["node_modules", "venv"] # Bỏ qua các thư mục không cần lint
[tool.pytest.ini_options]
# Cấu hình mặc định cho pytest (tương đương nội dung pytest.ini)
minversion = "7.0"
addopts = "-ra -q" # Tham số mặc định khi chạy pytest (-ra -q: báo cáo rút gọn)
testpaths = ["tests"] # Thư mục chứa mã nguồn các bài test
# Cấu hình các task cho Poe the Poet
[tool.poe.tasks]
# Task chạy bộ kiểm thử
test = "pytest tests/"
# Task kiểm tra code với ruff
lint = "ruff check ."
# Task tự động định dạng code với black
format = "black ."
# Task dọn dẹp file tạm, cache, build
clean = { shell = "rm -rf __pycache__ .pytest_cache build dist *.py[cod]" }
# Task thiết lập môi trường (cài package, cài pre-commit hook chẳng hạn)
setup = { shell = """
uv sync --extra dev
pre-commit install
""" }
Mô tả:
Phần
[project]
liệt kê thông tin dự án và phụ thuộc chính (những package cần cho runtime chính của ứng dụng).Bên dưới, các phần
[tool.black]
,[tool.ruff]
,[tool.pytest.ini_options]
cấu hình cho formatter, linter, và test runner (giúp thống nhất style code và hành vi test trong dự án).Cuối cùng,
[tool.poe.tasks]
khai báo các tác vụ (tasks) thường dùng, như chạy test, lint, format code, dọn dẹp, v.v., để có thể gọi nhanh bằng lệnhpoe
. (Chi tiết về cách dùng Poe được trình bày ở phần sau.)
1.1.3.5.1.2. Pin Python, uv và pip#
1.1.3.5.1.2.1. Cài và ghi nhận chính xác phiên bản Python#
Cách 1: Cài phiên bản python:
uv python install 3.12.0
Cách 2: Tạo file
.python-version
khai báo chính xác python version
3.12.0
1.1.3.5.1.2.2. Cập nhật uv và pip trong môi trường đó#
uv self update
pip install --upgrade pip
1.1.3.5.1.3. Tạo lockfile với URL chính xác#
Lockfile (uv.lock
) sẽ chứa các đường dẫn tải về (wheel, sdist) chính xác, không cần truy vấn lại index khi offline.
# mặc định sẽ bao gồm thêm cả các default-groups (giá trị mặc định của default-groups là ["dev"])
uv lock
1.1.3.5.1.4. Sync và tạo cache#
Xác định hoặc tuỳ chỉnh nơi lưu cache:
UV_CACHE_DIR=./offline-cache
Chạy:
uv sync
uv sẽ tải toàn bộ wheels, sdists, Git archives… và lưu vào ./offline-cache
(hoặc $XDG_CACHE_HOME/uv
) docs.astral.sh.
(Tuỳ chọn) Kiểm tra/thu dọn cache:
uv cache dir # xem đường dẫn cache
uv cache prune # loại bỏ cache không dùng
uv cache clean # xóa toàn bộ cache
1.1.3.5.1.5. Đóng gói cache#
Sau khi uv sync
hoàn tất, đóng gói thư mục offline-cache/
(ví dụ tarball hoặc zip) để chuyển sang máy offline.
1.1.3.5.2. 2. Trên máy Offline#
1.1.3.5.2.1. Clone source code#
git clone https://gitlab.internal/your-project.git
cd your-project
Đảm bảo dự án có pyproject.toml
và uv.lock
đã commit.
1.1.3.5.2.2. Triển khai cache#
Giải nén hoặc đặt thư mục cache (ví dụ /opt/uv-cache
) lên máy offline.
1.1.3.5.2.3. Cấu hình uv offline#
Thiết lập biến môi trường hoặc flag:
export UV_OFFLINE=1
export UV_CACHE_DIR=/opt/uv-cache
Hoặc truyền trực tiếp:
uv sync --offline --cache-dir=/opt/uv-cache
Nếu có dependencies Git trong
tool.uv.sources
, bạn có thể thêm--no-sources
để ngăn uv cố fetch mạng.
1.1.3.5.2.4. Chạy cài đặt Offline#
uv sync --offline
uv sẽ sử dụng cache, lockfile, và cài đặt toàn bộ Python + main/dev dependencies mà không cần truy cập Internet.
1.1.3.5.3. 3. Đảm bảo 100% đồng nhất#
Python Interpreter: Pin trong
pyproject.toml
hoặc dùnguv python install
.uv Version: Ghi lại và sử dụng
uv self update
trên cả hai máy.pip Version: Sau khi sync, kiểm tra
pip --version
trong venv.Lockfile: Luôn commit
uv.lock
sau khi thêm/bớt package.Cache Integrity: Chuyển toàn bộ thư mục cache nguyên vẹn (ví dụ bằng tarball).
Env Vars / Flags:
UV_OFFLINE=1
vàUV_CACHE_DIR
(hoặc--offline
/--cache-dir
) để ép chế độ offline.
Với quy trình này, bạn tận dụng tối đa cache và lockfile của uv, đảm bảo môi trường offline tái lập đúng 100% so với máy có mạng.
1.1.3.5.4. 4. Một số tuỳ chọn command uv#
# uv sẽ resolve tất cả các dependency-groups đang định nghĩa (bao gồm `dev`) cùng lúc và ghi vào `uv.lock`
uv lock
# Cài cả dependencies chính lẫn `dev` theo lockfile
uv sync
# Nếu bạn muốn chỉ cài `dev` mà không cài project chính, dùng:
uv sync --only-dev # tương đương --only-group dev
# còn để loại trừ dev, dùng `--no-dev` hoặc `--no-group dev`
uv sync --no-dev
# sync không bao gồm default-group
uv sync --no-default-groups
# hoặc chỉ định rõ ràng 1 group (bao gồm default-groups)
uv sync --group prod
# hoặc chỉ định rõ ràng 1 group (không bao gồm default-groups)
uv sync --no-default-groups --group prod
# chỉ cài đặt 1 group
uv sync --only-group prod
# Để cài tất cả nhóm đã định nghĩa, có thể nằm ngoài default-groups (ví dụ `dev`, `test`, `docs`, …), sử dụng:
uv sync --all-groups
1.1.3.5.5. 5. Đóng gói môi trường offline - docker image#
Tận dụng Docker BuildKit cache mounts kết hợp với cơ chế aggressive caching của uv
để xây dựng image hoàn toàn offline, vẫn cài được đầy đủ dependencies từ cache mà không phải tải lại từ mạng, đồng thời tách riêng các layer phụ thuộc và dọn sạch cache trước khi đóng gói final image để giảm tối đa dung lượng docs.docker.comdocs.astral.sh
Quy trình gồm:
(1) trên máy online chạy uv sync
để đổ đầy cache;
(2) sao chép cache đó sang máy build offline;
(3) trong Dockerfile dùng RUN --mount=type=cache,target=/root/.cache/uv
và flag --offline
/UV_OFFLINE
cho uv;
(4) dùng multi-stage build để không mang cả cache vào final image
1.1.3.5.5.1. Chuẩn bị cache trên máy Online#
Tạo lockfile với URL cố định
uv lock
để tạo
uv.lock
bao gồm URL wheel/sdist đã được pin cứng, tránh query index khi offlineSync và load full cache
Thiết lập thư mục cache (nếu muốn):
export UV_CACHE_DIR=./offline-cache
Chạy sync để uv sẽ tải về và lưu mọi wheel, sdist, Git archive… vào
./offline-cache
(hoặc$XDG_CACHE_HOME/uv
):uv sync
Đóng gói cache: Sau khi sync xong, tar/zip toàn bộ folder
offline-cache/
để chuyển sang môi trường build offline.
1.1.3.5.5.2. Sử dụng cache trong Docker Build#
Kích hoạt Docker BuildKit Đảm bảo bật BuildKit, do BuildKit hỗ trợ
--mount=type=cache
để lưu cache ngoài image:export DOCKER_BUILDKIT=1
Dockerfile với cache mount
# syntax=docker/dockerfile:1.4
FROM ghcr.io/astral-sh/uv:debian-slim AS builder
WORKDIR /app
COPY pyproject.toml uv.lock ./
# Mount cache uv, sync dependencies (main + dev)
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked
# Tách stage final, chỉ copy venv/venv-tools đã sẵn sàng
FROM python:3.11-slim
COPY --from=builder /root/.cache/uv /root/.cache/uv
COPY --from=builder /app/.venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
WORKDIR /app
COPY . .
# Chạy lệnh offline (sẽ dùng cache đã mount)
RUN uv sync --offline --locked
CMD ["uv", "run", "my_app"]
--mount=type=cache,target=/root/.cache/uv
giữ cache ngoài image, không làm tăng size layer docs.astral.sh.Stage
builder
tải và cài vào virtualenv, stage cuối chỉ copy ra thành phẩm, không mang theo cache dư thừa rhosignal.com.
1.1.3.5.5.3. Cài đặt Offline trong Build#
Cấu hình biến môi trường Trong Dockerfile hoặc CI, trước khi chạy uv, thiết lập để ép uv chỉ dùng cache, không attempt kết nối Internet
ENV UV_OFFLINE=1 ENV UV_CACHE_DIR=/root/.cache/uv
Chạy uv sync offline
RUN uv sync --offline --locked
uv sẽ đọc uv.lock
, lookup đúng wheel/sdist trong cache và cài toàn bộ main + dev dependencies mà không cần mạng
1.1.3.5.5.4. Tối ưu dung lượng image#
1.1.3.5.5.4.1. 4.1. Multi-stage builds#
Tách riêng stage builder và runtime để chỉ copy virtualenv (hoặc wheelhouse) cần thiết vào final image, loại bỏ dữ liệu build/cache depot.dev.
1.1.3.5.5.4.2. 4.2. Xoá cache trước khi đóng gói#
Nếu bạn không dùng multi-stage, có thể chạy:
`RUN uv cache clean`
sau khi sync thành công để xoá cache trong container trước khi commit image medium.com.
1.1.3.5.5.4.3. 4.3. Sử dụng .dockerignore
#
Loại bỏ toàn bộ thư mục cache và .venv
(nếu có) trong context:
offline-cache/
.venv/
giúp giảm size context và tránh vô tình ADD cache vào image rhosignal.com.
1.1.3.5.5.5. Hướng tiếp cận: đóng gói image online và chuyển giao image offline#
Ngoài hướng tiếp cận đóng gói trên môi trường offline thì còn hướng tiếp cận khác là hoàn toàn có thể xây dựng (build) Docker image trên máy có mạng rồi chuyển sang máy offline mà vẫn giữ nguyên mọi layer và metadata. Cách phổ biến nhất là dùng hai lệnh docker save
và docker load
để đóng gói image thành file .tar
, sau đó copy qua USB/ổ cứng ngoài và load lên máy offline serverfault.com.
Ngoài ra, bạn còn có thể thiết lập một registry nội bộ (Harbor, Nexus, Artifactory…) để push/pull image trong mạng LAN mà không cần Internet
1.1.3.5.5.5.1. Chuyển image bằng docker save
/ docker load
#
Build hoặc pull image trên máy online Build
docker build -t myapp:latest .
hoặc pull
docker pull ubuntu:22.04
Đóng gói image thành file TAR
docker save -o myapp_latest.tar myapp:latest
Lệnh này sẽ gom toàn bộ layers, tags, metadata của myapp:latest
vào file myapp_latest.tar
serverfault.com.
Chuyển file TAR sang máy offline
Sử dụng USB, ổ cứng di động hoặc giao thức nội bộ (SCP qua mạng LAN) để copy filemyapp_latest.tar
sang máy không có InternetLoad image trên máy offline
docker load -i myapp_latest.tar
Sau khi chạy, image myapp:latest
sẽ xuất hiện trong local registry của Docker offline
Chạy container
docker run --rm myapp:latest
Container sẽ khởi động như thông thường, bất chấp máy không có kết nối ra bên ngoài
1.1.3.5.5.5.2. Dùng registry nội bộ (Local Registry)#
Khởi động registry cục bộ
docker run -d -p 5000:5000 --restart=always --name registry registry:2
Tạo một Docker Registry đơn giản chạy tại
localhost:5000
forums.docker.com.Tag và push image vào registry
docker tag myapp:latest localhost:5000/myapp:latest docker push localhost:5000/myapp:latest
Image sẽ được lưu trữ trên registry nội bộ, không cần ra Docker Hub gcore.com.
Truy cập registry trên máy offline
Copy toàn bộ dữ liệu registry (thư mục
/var/lib/registry
) sang máy offline,Chạy registry offline với cùng version và dữ liệu đã copy.
Registry nội bộ sẽ phục vụ lệnhdocker pull localhost:5000/myapp:latest
mà không cần Internet blog.ctms.me.
Pull image từ registry offline
docker pull localhost:5000/myapp:latest
Image được tải từ registry LAN, hoàn toàn offline mpolinowski.github.io.