Summary
Add a [meta] use_ruff = true toggle in .meta.toml that switches from the current tool stack (isort + black + flake8 + pyupgrade) to ruff. Default keeps old tools; switch on = modern ruff setup.
Ruff replaces: isort, black, flake8, pyupgrade
Ruff does NOT replace: zpretty, codespell, check-manifest, pyroma, check-python-versions, i18ndude
The shared CI workflow (backend-lint.yml) already uses ruff, so this aligns generated packages with CI.
Implementation plan
Files to modify
1. src/plone/meta/config_package.py — Core logic
- Add
use_ruff cached_property reading [meta] use_ruff (default False)
- Add
_warn_ruff_incompatible_options() — warn if black_extra_lines, isort_extra_lines, flake8_extra_lines, or [flake8] extra_lines are set when ruff is active
pre_commit_config() — pass use_ruff to template context
pyproject_toml() — add ruff_extra_lines to options list, pass use_ruff
tox() — pass use_ruff to template context
flake8() — skip generation (return None) when use_ruff=True; git rm .flake8 if it exists from a prior run
warn_on_setup_cfg() — only warn about [flake8] section when not using ruff
configure() — call _warn_ruff_incompatible_options() early
2. src/plone/meta/default/pre-commit-config.yaml.j2 — Pre-commit hooks
Wrap in {% if use_ruff %} / {% else %}:
- ruff path: single
ruff-pre-commit repo with ruff-format + ruff (args: [--fix]) hooks
- old path: pyupgrade, isort, black (unchanged)
- zpretty stays unconditional
- flake8 block wrapped in
{% if not use_ruff %}
- codespell, check-manifest, pyroma, check-python-versions, i18ndude stay unconditional
3. src/plone/meta/default/pyproject.toml.j2 — Tool config
Wrap in {% if use_ruff %} / {% else %}:
- ruff path:
[tool.ruff], [tool.ruff.lint] (select F/E/W/UP/I, ignore E501/E203), [tool.ruff.lint.isort] with Plone-style settings, plus %(ruff_extra_lines)s
- old path:
[tool.isort] + [tool.black] (unchanged)
[tool.check-manifest] ignore — make .flake8 entry conditional on {% if not use_ruff %}
4. src/plone/meta/default/tox-qa.j2 — Tox format env
Wrap format commands in {% if use_ruff %} / {% else %}:
- ruff path:
pre-commit run -a ruff-format, pre-commit run -a ruff
- old path:
pre-commit run -a pyupgrade, pre-commit run -a isort, pre-commit run -a black
pre-commit run -a zpretty stays unconditional
5. Documentation updates
docs/sources/reference/meta-toml.md — Document use_ruff in [meta] section + ruff_extra_lines in [pyproject] section
docs/sources/reference/generated-files.md — Note .flake8 is conditional; update .pre-commit-config.yaml and pyproject.toml descriptions
docs/sources/reference/tox-environments.md — Update format env description
Ruff config defaults
When use_ruff = true, pyproject.toml will include:
[tool.ruff]
target-version = "py310"
[tool.ruff.lint]
select = [
# pyflakes
"F",
# pycodestyle
"E",
"W",
# pyupgrade
"UP",
# isort
"I",
]
ignore = [
# line too long (formatter handles this)
"E501",
# whitespace before ':' (formatter handles slicing)
"E203",
]
[tool.ruff.lint.isort]
force-single-line = true
from-first = true
lines-after-imports = 2
lines-between-types = 1
no-sections = true
order-by-type = false
Rationale for lint rules
F + E + W = flake8 core (pyflakes + pycodestyle) — matches current flake8 setup
UP = pyupgrade — replaces the standalone pyupgrade pre-commit hook
I = isort — replaces standalone isort
E501 ignored: ruff format handles line length (same as current flake8 config)
E203 ignored: ruff format handles whitespace in slicing (same as current flake8)
W503 does not exist in ruff, omitted
E231 handled by ruff format, omitted from ignore
Rationale for isort settings (matching Plone conventions)
force-single-line = true — one import per line (from isort plone profile)
from-first = true — from imports before regular import statements
lines-after-imports = 2 — two blank lines after import block
lines-between-types = 1 — one blank line between import and from types
no-sections = true — no section separation, results in pure alphabetical sort (replaces isort's force_alphabetical_sort)
order-by-type = false — don't group by CONSTANT/Class/variable type
These settings are user-configurable via ruff_extra_lines in [pyproject].
Verification
- Run
config-package on a test repo without use_ruff — confirm no changes to existing behavior
- Run
config-package on a test repo with [meta] use_ruff = true — verify:
.pre-commit-config.yaml has ruff hooks, no pyupgrade/isort/black/flake8
pyproject.toml has [tool.ruff], no [tool.isort]/[tool.black]
.flake8 is not generated (removed if existed)
tox.ini format env uses ruff commands
- Run
tox -e format and tox -e lint in the test repo to confirm ruff works
Summary
Add a
[meta] use_ruff = truetoggle in.meta.tomlthat switches from the current tool stack (isort + black + flake8 + pyupgrade) to ruff. Default keeps old tools; switch on = modern ruff setup.Ruff replaces: isort, black, flake8, pyupgrade
Ruff does NOT replace: zpretty, codespell, check-manifest, pyroma, check-python-versions, i18ndude
The shared CI workflow (
backend-lint.yml) already uses ruff, so this aligns generated packages with CI.Implementation plan
Files to modify
1.
src/plone/meta/config_package.py— Core logicuse_ruffcached_property reading[meta] use_ruff(defaultFalse)_warn_ruff_incompatible_options()— warn ifblack_extra_lines,isort_extra_lines,flake8_extra_lines, or[flake8] extra_linesare set when ruff is activepre_commit_config()— passuse_ruffto template contextpyproject_toml()— addruff_extra_linesto options list, passuse_rufftox()— passuse_ruffto template contextflake8()— skip generation (returnNone) whenuse_ruff=True;git rm .flake8if it exists from a prior runwarn_on_setup_cfg()— only warn about[flake8]section when not using ruffconfigure()— call_warn_ruff_incompatible_options()early2.
src/plone/meta/default/pre-commit-config.yaml.j2— Pre-commit hooksWrap in
{% if use_ruff %}/{% else %}:ruff-pre-commitrepo withruff-format+ruff(args:[--fix]) hooks{% if not use_ruff %}3.
src/plone/meta/default/pyproject.toml.j2— Tool configWrap in
{% if use_ruff %}/{% else %}:[tool.ruff],[tool.ruff.lint](select F/E/W/UP/I, ignore E501/E203),[tool.ruff.lint.isort]with Plone-style settings, plus%(ruff_extra_lines)s[tool.isort]+[tool.black](unchanged)[tool.check-manifest] ignore— make.flake8entry conditional on{% if not use_ruff %}4.
src/plone/meta/default/tox-qa.j2— Tox format envWrap format commands in
{% if use_ruff %}/{% else %}:pre-commit run -a ruff-format,pre-commit run -a ruffpre-commit run -a pyupgrade,pre-commit run -a isort,pre-commit run -a blackpre-commit run -a zprettystays unconditional5. Documentation updates
docs/sources/reference/meta-toml.md— Documentuse_ruffin[meta]section +ruff_extra_linesin[pyproject]sectiondocs/sources/reference/generated-files.md— Note.flake8is conditional; update.pre-commit-config.yamlandpyproject.tomldescriptionsdocs/sources/reference/tox-environments.md— Update format env descriptionRuff config defaults
When
use_ruff = true,pyproject.tomlwill include:Rationale for lint rules
F+E+W= flake8 core (pyflakes + pycodestyle) — matches current flake8 setupUP= pyupgrade — replaces the standalone pyupgrade pre-commit hookI= isort — replaces standalone isortE501ignored: ruff format handles line length (same as current flake8 config)E203ignored: ruff format handles whitespace in slicing (same as current flake8)W503does not exist in ruff, omittedE231handled by ruff format, omitted from ignoreRationale for isort settings (matching Plone conventions)
force-single-line = true— one import per line (from isort plone profile)from-first = true—fromimports before regularimportstatementslines-after-imports = 2— two blank lines after import blocklines-between-types = 1— one blank line betweenimportandfromtypesno-sections = true— no section separation, results in pure alphabetical sort (replaces isort'sforce_alphabetical_sort)order-by-type = false— don't group by CONSTANT/Class/variable typeThese settings are user-configurable via
ruff_extra_linesin[pyproject].Verification
config-packageon a test repo withoutuse_ruff— confirm no changes to existing behaviorconfig-packageon a test repo with[meta] use_ruff = true— verify:.pre-commit-config.yamlhas ruff hooks, no pyupgrade/isort/black/flake8pyproject.tomlhas[tool.ruff], no[tool.isort]/[tool.black].flake8is not generated (removed if existed)tox.iniformat env uses ruff commandstox -e formatandtox -e lintin the test repo to confirm ruff works