5.2 to 5.4 ruff, the formatter and the linter
Build it yourself. Read the tutorial is the other way to read this part.
This walk goes through rahuldave/cheesecracker on GitHub. This part starts from the code at f77650600c11, partway through step-05. The code before this part
If you start at this part, make your clone as Get the project says, and then run this command. It puts in your clone the files that this part starts from. A file that the project deleted before this part stays, so delete it if git check lists it:
git rm -rq --cached --ignore-unmatch -- :/ && git restore --source f77650600c11 --staged --worktree -- :/just setupruff goes into a dependency group of its own, lint. Its settings allow lines of up to 120 characters, and check the
rules that flake8, an older linter, checks. Two recipes run it. just fmt runs the formatter, and just lint runs the
linter.
5.2 ruff, the formatter and the linter, in the group lint
ruff is a formatter and a linter in one tool, written in Rust by Astral, the makers
of uv. ruff format writes the code in the layout of black, the formatter of most Python projects. ruff check
checks the rules of flake8 and of many other linters, and can fix some of them. It takes the place of both black and
flake8, and runs much faster.
ruff checks the code, and the server does not need it to run. So ruff goes into a group of its own, lint.
uv add --group lint ruffuv run ruff --versionuv prints the version of ruff, ruff 0.17.1 on the day of the class.
Check your files against this move. The command prints nothing when they match, and otherwise shows what differs:
git check 9662828462baIf you are stuck, this command puts the files of this move in place of yours. A file that you made yourself stays:
git restore --source f77650600c11 --staged -- :/ && git restore --source 9662828462ba --staged --worktree -- :/What changed
pyproject.toml: ruff, in the grouplint. 3 lines added. It comes fromuv add --group lint ruff. The file- look at
"ruff>=0.17.1",
- look at
uv.lock: the exact version of ruff. 29 lines added. It comes fromuv add --group lint ruff. The file
The diff that uv add --group lint ruff makes in pyproject.toml
@@ -16,6 +16,9 @@ package = false
default-groups = "all"
[dependency-groups]
+lint = [
+ "ruff>=0.17.1",
+]
test = [
"httpx2>=2.13.1",
"pytest>=9.1.1",5.3 ruff's settings: lines of 120 characters, and flake8's rules
By default, ruff allows lines of up to 88 characters, and this project allows 120. Its default rules also leave out
some rules of flake8. Two tables in pyproject.toml set both.
Set the values that this change sets in pyproject.toml. Run this command from the top of the project. It reads your file with tomlkit, a library for TOML files. It copies each of these tables and values from the project's file after this change, with their comments, and keeps the rest of your file:
uv run --quiet --no-config --no-project --with tomlkit python - <<'PY'
import copy, pathlib, subprocess, tomlkit
from tomlkit.items import Comment, Table, Whitespace
path = pathlib.Path('pyproject.toml')
doc = tomlkit.parse(path.read_text(encoding='utf-8')) if path.exists() else tomlkit.document()
# The project's file after this change, from your clone
project = tomlkit.parse(subprocess.run(['git', 'show', '7c1670fd52a0:pyproject.toml'], capture_output=True, text=True,
check=True).stdout)
# Each table and value that the change sets, by its keys: each comes from the project's file with its comments
COPY = [
('tool', 'ruff'),
]
def body(table): # the items of a table in their order, with its comment lines
inner = table if isinstance(table, tomlkit.TOMLDocument) else getattr(table, 'value', None)
return getattr(inner, 'body', [])
def above(table, key): # the comment lines just above a key
found = []
for name, item in body(table):
if name is not None and name.key == key:
return found
found = found + [item] if isinstance(item, Comment) else found if isinstance(item, Whitespace) else []
return []
for keys in COPY:
source, target = project, doc
for key in keys[:-1]: # make each table on the way
source = source[key]
if key not in target:
target[key] = tomlkit.table(True)
target = target[key]
item = copy.deepcopy(source[keys[-1]])
if isinstance(item, Table) and not item.as_string().endswith('\n\n'):
item.add(tomlkit.nl()) # a blank line before the table after it
if keys[-1] not in target:
for comment in above(source, keys[-1]):
target.add(copy.deepcopy(comment))
target[keys[-1]] = item
path.write_text(tomlkit.dumps(doc), encoding='utf-8')
PYRead the tables [tool.ruff] and [tool.ruff.lint] of pyproject.toml.
uv run ruff checkruff finds 2 errors, and fails. F401 is the import of os, which nothing uses. E501 is the line of 187
characters. ruff says that it can fix the first one.
uv run ruff format --checkruff says 2 files would be reformatted, and fails. --check changes no file.
Check your files against this move. The command prints nothing when they match, and otherwise shows what differs:
git check 7c1670fd52a0If you are stuck, this command puts the files of this move in place of yours. A file that you made yourself stays:
git restore --source 9662828462ba --staged -- :/ && git restore --source 7c1670fd52a0 --staged --worktree -- :/What changed
pyproject.toml: the settings of ruff, the length of a line and the rules of the linter. 8 lines added. The file- look at
select = ["E", "W", "F"]
- look at
The patch to pyproject.toml
@@ -30,3 +30,11 @@ test = [
# The tests are in tests/, and import the package api from src/api-service/
testpaths = ["tests"]
pythonpath = ["src/api-service"]
+
+[tool.ruff]
+# The longest line, for the formatter and the linter
+line-length = 120
+
+[tool.ruff.lint]
+# The rules that flake8 checks: pycodestyle's errors (E) and warnings (W), and pyflakes (F)
+select = ["E", "W", "F"]5.4 just fmt and just lint run ruff
Two recipes give the formatter and the linter short names. Each passes on to ruff any words that you type after it.
Apply the patch to justfile, from the top of the project:
curl -fsSL https://github.com/rahuldave/cheesecracker/commit/b947593e84b71486902b3ff1c7f9e1458b70e3f7.diff | git apply --include=justfileRead the recipes fmt and lint at the end of the justfile.
just fmt --checkThe recipe fails as ruff format --check did, with 2 files would be reformatted.
Check your files against this move. The command prints nothing when they match, and otherwise shows what differs:
git check b947593e84b7If you are stuck, this command puts the files of this move in place of yours. A file that you made yourself stays:
git restore --source 7c1670fd52a0 --staged -- :/ && git restore --source b947593e84b7 --staged --worktree -- :/What changed
justfile: the recipesfmtandlint. 8 lines added. The file- look at
uv run ruff check "$@"
- look at
The patch to justfile
@@ -44,3 +44,11 @@ stop:
# Run the tests, and show which lines of the package api they ran. Extra arguments go to pytest: just cov tests/unit
cov *args:
uv run pytest --cov=api --cov-report=term-missing "$@"
+
+# Format the code, one way. Extra arguments go to ruff: just fmt --check, just fmt --diff
+fmt *args:
+ uv run ruff format "$@"
+
+# Check the code against the rules in pyproject.toml. Extra arguments go to ruff: just lint --fix
+lint *args:
+ uv run ruff check "$@"This part ends at the code at b947593e84b7, partway through step-05. The code after this part