# uv: một lockfile, một lệnh sync, và môi trường Python dựng lại được trên máy khách

> Pipeline chạy tốt trên laptop nhưng gãy trên server của khách thường là do môi trường bị lệch, và uv được làm ra để chặn đúng chỗ lệch đó.

Bản gốc: https://fdetimes.net/vi/cong-cu/uv-moi-truong-python-lap-lai-duoc-cho-tung-khach/

README của uv liệt kê những công cụ nó muốn thay: pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, rồi thêm chữ "và nhiều hơn nữa". FDE nào từng dựng cùng một pipeline trên laptop Mac của mình, server Linux của khách này và máy Windows của khách kia sẽ thấy danh sách đó quen. Mỗi cái tên trong đó là một chỗ môi trường có thể lệch nhau.

Astral, công ty làm ra Ruff, giới thiệu uv là trình quản lý package và dự án Python viết bằng Rust và "cực kỳ nhanh". Tuy vậy, tốc độ không phải lý do chính để FDE học công cụ này. Lý do chính là tính lặp lại: code bạn giao phải chạy giống nhau trên những máy bạn không quản lý và có khi chưa từng thấy.

Nếu bạn đang muốn chuyển sang FDE, kỹ năng này đáng tập sớm, vì công việc của FDE là giao code chạy trên hạ tầng của người khác. Demo trên máy bạn chạy được thì chưa nói lên điều gì. Thứ được tính là lệnh đầu tiên chạy được trên máy của khách.

## Lỗi thường không nằm ở code

Thử hình dung một tình huống. Bạn viết một service phân loại tài liệu trên Mac, test kỹ rồi đẩy lên Git. Kỹ sư phía khách clone repo về một máy Windows, cài thư viện với bản Python sẵn có trên máy, và gặp lỗi import ngay ở dòng đầu.

Có ba nguyên nhân hay gặp. Bản Python trên máy khách khác với máy bạn, các thư viện được resolve vào một ngày khác nên kéo về phiên bản khác, hoặc lockfile của bạn chỉ đúng cho hệ điều hành nơi nó được tạo ra. uv có cách xử lý riêng cho cả ba nguyên nhân này.

## Ba file và một lệnh

Dù dự án đang dùng `requirements.txt` hay một công cụ khác, đích đến khi chuyển sang uv là một repo có ba thứ sau:

```
du-an-khach/
├── .python-version   # commit
├── uv.lock           # commit
└── .venv/            # KHÔNG commit
```

File `.python-version` ghim bản Python của dự án. File `uv.lock` ghi lại các package sẽ được cài, và tài liệu của uv nhấn mạnh rằng file này phải nằm trong version control. Thư mục `.venv` là môi trường thật trên từng máy, không được commit vì máy nào cũng có thể dựng lại nó từ lockfile.

Trên máy khách, sau khi clone repo, bạn chỉ cần chạy:

```
uv sync
```

uv đọc `.python-version`. Nếu máy chưa có bản Python phù hợp, mặc định uv sẽ tự tải về, nên khách không cần cài sẵn đúng phiên bản. Sau đó uv dựng `.venv` theo đúng những gì `uv.lock` đã ghi.

Mấu chốt nằm ở chữ "universal". uv tạo lockfile bằng cách resolve cho mọi nền tảng, nên cùng một `uv.lock` dùng được trên nhiều hệ điều hành. Nếu không resolve theo kiểu đó, lockfile chỉ đúng trên nền tảng đã tạo ra nó, và đó chính là kiểu lỗi Mac sang Windows trong tình huống ở trên.

**Điểm mấu chốt:** Đừng mang môi trường sang máy khách, hãy mang công thức để máy khách tự dựng lại.

## Khi khách muốn chạy lại đúng như tháng trước

Ở chỗ khách có một yêu cầu rất hay gặp: báo cáo tháng trước ra một con số, giờ chạy lại thì ra con số khác, và khách muốn biết vì sao.

Nếu lockfile được commit cùng với code thì việc quay về commit cũ đã giải quyết phần lớn. Khi phải resolve lại từ đầu, uv có tùy chọn `--exclude-newer` để chỉ dùng những bản phân phối đã được upload trước một ngày cụ thể.

Còn một trường hợp nhỏ hơn: một script làm sạch dữ liệu bạn viết vội để gửi cho chuyên viên phân tích bên khách. uv cho phép khai báo dependency ngay trong file script theo PEP 723 và khóa script đơn lẻ đó lại. Tài liệu của uv nói rõ mục đích là để script chạy lại về sau vẫn dựng được đúng môi trường như cũ.

## Những lỗi hay gặp

Nó nặng, gắn với máy đã tạo ra nó, và hoàn toàn thừa khi `uv sync` đã dựng lại được môi trường từ lockfile.

Lỗi ngược lại là bỏ `uv.lock` ra khỏi Git vì nghĩ đó là file sinh tự động. Không có lockfile, máy khách sẽ resolve lại vào một ngày khác và bạn quay về đúng tình huống lỗi import ở trên.

Với script gửi lẻ, lỗi hay gặp là chỉ khai báo dependency mà không khóa. Danh sách thư viện thì có, nhưng phiên bản được chọn sẽ tùy vào ngày chạy. Trước khi gửi, hãy khóa script và tự chạy thử trên một máy sạch để chắc khách nhận được đủ những gì cần.

## uv không làm thay bạn những gì

Trước hết là con số về tốc độ. Trang tài liệu chính thức ghi uv nhanh hơn pip từ 10 đến 100 lần, còn bài ra mắt của Charlie Marsh ngày 15/2/2024 đưa ra mức nhanh hơn 8-10 lần so với pip và pip-tools khi không có cache.

Hai con số không trùng nhau, nên con số nào cũng chưa phải lời hứa dành cho hạ tầng của khách. Nếu muốn nói chuyện tốc độ, hãy tự đo trên máy của họ trước.

Việc tự tải Python rất tiện nhưng cần máy khách có đường ra mạng. Ở những môi trường bị khóa mạng, bạn nên hỏi ngay từ buổi kickoff và chuẩn bị phương án khác. Ngoài ra, `uv.lock` chỉ ghi lại các package Python; driver, thư viện hệ thống hay cấu hình máy nằm ngoài lockfile, nên bạn vẫn phải kiểm tra riêng.

## Nên học gì trước

Thứ tự hợp lý là: lockfile và `uv sync` trước, rồi đến `.python-version`, sau cùng mới tới `--exclude-newer` và script có khai báo dependency bên trong. Bài tập tốt nhất là tự làm hỏng rồi tự sửa: xóa `.venv`, chuyển sang một máy khác hệ điều hành, và kiểm tra xem một lệnh có đủ để chạy lại dự án hay không.

Khi đọc JD của các vị trí FDE, hãy để ý những cụm như "reproducible environments" hay "deploy on customer infrastructure". Đó là những chỗ kỹ năng này được dùng đến. Trong CV, đừng chỉ ghi "biết uv"; hãy mô tả bạn đã rút việc setup môi trường xuống còn bao nhiêu bước, và kể một lần cụ thể dự án của bạn chạy được trên một máy lạ.

Khách hàng sẽ không nhớ bạn dùng công cụ gì. Họ chỉ nhớ lần đầu clone repo của bạn về, mọi thứ chạy được ngay hay phải mất cả buổi chiều để sửa.

**Thử ngay tuần này:**

- Chọn một dự án cũ, đưa nó về cấu trúc của uv, commit uv.lock và .python-version, rồi xóa .venv và dựng lại bằng uv sync trên một máy khác hệ điều hành.
- Viết một script xử lý dữ liệu có khai báo dependency ngay trong file theo PEP 723, khóa nó lại bằng uv, rồi nhờ một đồng nghiệp chạy thử trên máy của họ.
- Ghi vào CV một dòng cụ thể như 'chuẩn hóa môi trường dự án bằng uv.lock, người mới chỉ cần một lệnh uv sync là chạy được'. Chỉ viết khi bạn đã thực sự làm.

## Nguồn

- [uv](https://docs.astral.sh/uv/)

- [uv: Python packaging in Rust](https://astral.sh/blog/uv)

- [GitHub - astral-sh/uv: An extremely fast Python package and project manager, written in Rust.](https://github.com/astral-sh/uv)

- [Structure and files | uv](https://docs.astral.sh/uv/concepts/projects/layout/)

- [Resolution | uv](https://docs.astral.sh/uv/concepts/resolution/)

- [Python versions | uv](https://docs.astral.sh/uv/concepts/python-versions/)

- [Running scripts | uv](https://docs.astral.sh/uv/guides/scripts/)
