Tóm tắt: uv add --dev ruff mypy → khai báo [tool.ruff], [tool.ruff.lint], [tool.ruff.format], [tool.mypy] trong pyproject.toml → chạy ruff check --fix, ruff format, mypy → đưa vào pre-commit và CI. Đã chạy thử với ruff 0.17 và mypy 2.4. Gắn vào hook git xem pre-commit.
Luồng hoạt động
flowchart LR A["Sửa code"] --> B["ruff check --fix"] B --> C["ruff format"] C --> D["mypy"] D -->|"lỗi kiểu"| A D --> E["pre-commit / CI"] E -->|"--check, không sửa"| F["Merge"]
Cấu hình
Cài
uv add --dev ruff mypy
uv add --dev types-PyYAML types-requests # stub cho thư viện không kèm type hintpyproject.toml
[tool.ruff]
line-length = 100
target-version = "py312" # bỏ được nếu đã có requires-python trong [project]
extend-exclude = ["migrations"]
[tool.ruff.lint]
# E/W pycodestyle, F pyflakes, I isort, B bugbear, UP pyupgrade,
# SIM simplify, RUF luật riêng của ruff, S bandit (bảo mật)
select = ["E", "W", "F", "I", "B", "UP", "SIM", "RUF", "S"]
ignore = ["E501"] # để formatter lo độ dài dòng
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"] # cho phép assert trong test
[tool.ruff.lint.isort]
known-first-party = ["netinv"]
[tool.ruff.format]
quote-style = "double"
docstring-code-format = true # format cả code mẫu trong docstring
[tool.mypy]
python_version = "3.12"
strict = true
warn_unreachable = true
mypy_path = "src" # project dạng src/ layout
files = ["src", "tests"]
plugins = ["pydantic.mypy"] # chỉ khi dùng Pydantic
[[tool.mypy.overrides]]
module = ["ncclient.*", "textfsm.*"] # thư viện không có type hint
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = ["tests.*"] # cần tests/__init__.py để khớp tên module
disallow_untyped_defs = falseProject cũ: bắt đầu với select = ["E", "F", "I"] và mypy không strict, sửa hết lỗi rồi mới thêm nhóm luật. Bật hết một lúc sẽ ra hàng trăm lỗi và không ai sửa.
Nhóm luật ruff đáng bật
| Mã | Nguồn | Bắt được |
|---|---|---|
F |
pyflakes | Import thừa, biến chưa định nghĩa |
I |
isort | Thứ tự import (sửa tự động) |
B |
flake8-bugbear | Default là list/dict (def f(a=[])), except quá rộng |
UP |
pyupgrade | Optional[str] → str | None, cú pháp cũ |
S |
flake8-bandit | subprocess(..., shell=True), yaml.load không an toàn, mật khẩu hard-code |
SIM |
flake8-simplify | if lồng thừa, return True/False vòng vèo |
RUF |
ruff | Mutable class default, noqa không còn dùng |
Lệnh
| Mục đích | Lệnh |
|---|---|
| Lint toàn project | uv run ruff check . |
| Lint và tự sửa lỗi an toàn | uv run ruff check --fix . |
Xem trước thay đổi của --fix |
uv run ruff check --fix --diff . |
| Áp cả fix có thể đổi hành vi (xem lại diff) | uv run ruff check --fix --unsafe-fixes . |
| Đếm lỗi theo luật | uv run ruff check --statistics . |
| Một dòng mỗi lỗi (dễ grep) | uv run ruff check --output-format concise . |
| Giải thích một luật | uv run ruff rule S602 |
| Format / chỉ kiểm tra (CI) | uv run ruff format . / uv run ruff format --check . |
Thêm # noqa cho mọi lỗi hiện có (khi mới áp dụng) |
uv run ruff check --add-noqa . |
| Xem cấu hình ruff đang dùng | uv run ruff check --show-settings . |
Type check theo files trong config |
uv run mypy |
| Type check một file, strict | uv run mypy --strict src/netinv/core.py |
| Bỏ qua một dòng | ruff: # noqa: S602; mypy: # type: ignore[arg-type] |
CI chỉ kiểm tra, không sửa:
uv run ruff check --output-format github . # annotation trên PR GitHub
uv run ruff format --check .
uv run mypyLỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
error: Skipping analyzing "ncclient": module is installed, but missing library stubs or py.typed marker |
Thư viện không có type hint | Thêm override ignore_missing_imports = true cho module đó |
Library stubs not installed for "yaml" |
Thiếu gói stub | uv add --dev types-PyYAML |
mypy không tìm thấy package của mình trong src/ |
Chưa khai báo đường dẫn | mypy_path = "src" hoặc cài project vào venv (uv sync) |
Override module = ["tests.*"] không có tác dụng |
tests/ không có __init__.py, module tên test_x chứ không phải tests.test_x |
Thêm tests/__init__.py |
[*] N fixable with --fix nhưng --fix không sửa hết |
Fix bị đánh dấu unsafe (vd B006) | Xem --diff rồi chạy --unsafe-fixes, hoặc sửa tay |
| ruff format và lint sửa qua lại | Bật luật conflict với formatter (E501, ISC001, W191...) |
Bỏ các luật đó khỏi select, để formatter lo |
Cảnh báo Decorators on top of @property are not supported |
@computed_field của Pydantic |
# type: ignore[prop-decorator], xem type hints và Pydantic |
| Máy dev khác CI | Version ruff/mypy khác nhau | Ghim trong uv.lock, CI chạy uv sync --locked |