Skip to content

Commit 06abdb5

Browse files
authored
Merge pull request #64 from mxstack/feature/modernize-precommit-ruff
Modernize pre-commit: replace black with ruff, use isort with plone profile
2 parents e6c086b + 288c2b8 commit 06abdb5

34 files changed

+860
-523
lines changed

.pre-commit-config.yaml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
---
22
repos:
3-
- repo: https://github.com/psf/black.git
4-
rev: 24.2.0
3+
- repo: https://github.com/astral-sh/ruff-pre-commit
4+
rev: v0.8.4
55
hooks:
6-
- id: black
7-
language_version: python3
8-
exclude: ^(tests\/hooks-abort-render\/hooks|docs\/HelloCookieCutter1)
6+
# Run the linter
7+
- id: ruff
8+
args: [--fix]
9+
# Run the formatter
10+
- id: ruff-format
11+
12+
- repo: https://github.com/pycqa/isort
13+
rev: 5.13.2
14+
hooks:
15+
- id: isort
16+
args: [--profile, plone, --force-alphabetical-sort, --force-single-line, --lines-after-imports, "2"]
17+
additional_dependencies: [setuptools]
918

1019
- repo: https://github.com/pre-commit/mirrors-mypy
1120
rev: 'v1.9.0' # Use the sha / tag you want to point at

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
- Modernize type hints to use Python 3.10+ syntax (PEP 604: `X | Y` instead of `Union[X, Y]`)
1111
- Use built-in generic types (`list`, `dict`, `tuple`) instead of `typing.List`, `typing.Dict`, `typing.Tuple`
1212
[jensens]
13+
- Replace black with ruff for faster linting and formatting. Configure ruff with line-length=120 and appropriate rule selections. Keep isort for import sorting with plone profile and force-alphabetical-sort. This modernizes the tooling stack for better Python 3.10+ support and faster CI runs.
14+
[jensens]
1315

1416
## 4.1.2 (unreleased)
1517

CLAUDE.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,20 @@ Key settings:
117117
# Run all pre-commit hooks (using uvx with tox-uv)
118118
uvx --with tox-uv tox -e lint
119119
120-
# Run type checking
121-
mypy src/mxdev
120+
# Run ruff linter (with auto-fix)
121+
uvx ruff check --fix src/mxdev tests
122+
123+
# Run ruff formatter
124+
uvx ruff format src/mxdev tests
122125
123-
# Run flake8
124-
flake8 src/mxdev
126+
# Sort imports with isort
127+
uvx isort src/mxdev tests
128+
129+
# Run type checking
130+
uvx mypy src/mxdev
125131
126-
# Sort imports
127-
isort src/mxdev
132+
# Run all pre-commit hooks manually
133+
uvx pre-commit run --all-files
128134
```
129135

130136
### Testing Multiple Python Versions (using uvx tox with uv)
@@ -401,9 +407,16 @@ myext-package_setting = value
401407

402408
## Code Style
403409

404-
- **Formatting**: Black-compatible (max line length: 120)
405-
- **Import sorting**: isort with `force_alphabetical_sort = true`, `force_single_line = true`
406-
- **Type hints**: Use throughout (Python 3.10+ compatible)
410+
- **Formatting**: Ruff formatter (max line length: 120, target Python 3.10+)
411+
- Configured in [pyproject.toml](pyproject.toml) under `[tool.ruff]`
412+
- Rules: E, W, F, UP, D (with selective ignores for docstrings)
413+
- Automatically enforced via pre-commit hooks
414+
- **Import sorting**: isort with plone profile, `force_alphabetical_sort = true`, `force_single_line = true`
415+
- Configured in [pyproject.toml](pyproject.toml) under `[tool.isort]`
416+
- Runs after ruff in pre-commit pipeline
417+
- **Type hints**: Use throughout (Python 3.10+ syntax)
418+
- Use `X | Y` instead of `Union[X, Y]`
419+
- Use `list[T]`, `dict[K, V]` instead of `List[T]`, `Dict[K, V]`
407420
- **Path handling**: Prefer `pathlib.Path` over `os.path` for path operations
408421
- Use `pathlib.Path().as_posix()` for cross-platform path comparison
409422
- Use `/` operator for path joining: `Path("dir") / "file.txt"`

Makefile

Lines changed: 93 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
#: core.mxenv
77
#: core.mxfiles
88
#: core.packages
9-
#: qa.black
109
#: qa.isort
1110
#: qa.mypy
11+
#: qa.ruff
1212
#: qa.test
1313
#
1414
# SETTINGS (ALL CHANGES MADE BELOW SETTINGS WILL BE LOST)
@@ -54,18 +54,19 @@ PRIMARY_PYTHON?=3.14
5454
PYTHON_MIN_VERSION?=3.10
5555

5656
# Install packages using the given package installer method.
57-
# Supported are `pip` and `uv`. If uv is used, its global availability is
58-
# checked. Otherwise, it is installed, either in the virtual environment or
59-
# using the `PRIMARY_PYTHON`, dependent on the `VENV_ENABLED` setting. If
60-
# `VENV_ENABLED` and uv is selected, uv is used to create the virtual
61-
# environment.
57+
# Supported are `pip` and `uv`. When `uv` is selected, a global installation
58+
# is auto-detected and used if available. Otherwise, uv is installed in the
59+
# virtual environment or using `PRIMARY_PYTHON`, depending on the
60+
# `VENV_ENABLED` setting.
6261
# Default: pip
6362
PYTHON_PACKAGE_INSTALLER?=uv
6463

65-
# Flag whether to use a global installed 'uv' or install
66-
# it in the virtual environment.
67-
# Default: false
68-
MXENV_UV_GLOBAL?=true
64+
# Python version for UV to install/use when creating virtual
65+
# environments with global UV. Passed to `uv venv -p VALUE`. Supports version
66+
# specs like `3.11`, `3.14`, `cpython@3.14`. Defaults to PRIMARY_PYTHON value
67+
# for backward compatibility.
68+
# Default: $(PRIMARY_PYTHON)
69+
UV_PYTHON?=$(PRIMARY_PYTHON)
6970

7071
# Flag whether to use virtual environment. If `false`, the
7172
# interpreter according to `PRIMARY_PYTHON` found in `PATH` is used.
@@ -94,17 +95,17 @@ MXDEV?=mxdev
9495
# Default: mxmake
9596
MXMAKE?=mxmake
9697

97-
## qa.isort
98+
## qa.ruff
9899

99-
# Source folder to scan for Python files to run isort on.
100+
# Source folder to scan for Python files to run ruff on.
100101
# Default: src
101-
ISORT_SRC?=src
102+
RUFF_SRC?=src
102103

103-
## qa.black
104+
## qa.isort
104105

105-
# Source folder to scan for Python files to run black on.
106+
# Source folder to scan for Python files to run isort on.
106107
# Default: src
107-
BLACK_SRC?=src
108+
ISORT_SRC?=src
108109

109110
## core.mxfiles
110111

@@ -199,30 +200,57 @@ else
199200
MXENV_PYTHON=$(PRIMARY_PYTHON)
200201
endif
201202

202-
# Determine the package installer
203+
# Determine the package installer with non-interactive flags
203204
ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv")
204-
PYTHON_PACKAGE_COMMAND=uv pip
205+
PYTHON_PACKAGE_COMMAND=uv pip --quiet --no-progress
205206
else
206207
PYTHON_PACKAGE_COMMAND=$(MXENV_PYTHON) -m pip
207208
endif
208209

210+
# Auto-detect global uv availability (simple existence check)
211+
ifeq ("$(PYTHON_PACKAGE_INSTALLER)","uv")
212+
UV_AVAILABLE:=$(shell command -v uv >/dev/null 2>&1 && echo "true" || echo "false")
213+
else
214+
UV_AVAILABLE:=false
215+
endif
216+
217+
# Determine installation strategy
218+
USE_GLOBAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "true" ]] && echo "true" || echo "false")
219+
USE_LOCAL_UV:=$(shell [[ "$(PYTHON_PACKAGE_INSTALLER)" == "uv" && "$(UV_AVAILABLE)" == "false" ]] && echo "true" || echo "false")
220+
221+
# Check if global UV is outdated (non-blocking warning)
222+
ifeq ("$(USE_GLOBAL_UV)","true")
223+
UV_OUTDATED:=$(shell uv self update --dry-run 2>&1 | grep -q "Would update" && echo "true" || echo "false")
224+
else
225+
UV_OUTDATED:=false
226+
endif
227+
209228
MXENV_TARGET:=$(SENTINEL_FOLDER)/mxenv.sentinel
210229
$(MXENV_TARGET): $(SENTINEL)
211-
ifneq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse")
230+
# Validation: Check Python version if not using global uv
231+
ifneq ("$(USE_GLOBAL_UV)","true")
212232
@$(PRIMARY_PYTHON) -c "import sys; vi = sys.version_info; sys.exit(1 if (int(vi[0]), int(vi[1])) >= tuple(map(int, '$(PYTHON_MIN_VERSION)'.split('.'))) else 0)" \
213233
&& echo "Need Python >= $(PYTHON_MIN_VERSION)" && exit 1 || :
214234
else
215-
@echo "Use Python $(PYTHON_MIN_VERSION) over uv"
235+
@echo "Using global uv for Python $(UV_PYTHON)"
216236
endif
237+
# Validation: Check VENV_FOLDER is set if venv enabled
217238
@[[ "$(VENV_ENABLED)" == "true" && "$(VENV_FOLDER)" == "" ]] \
218239
&& echo "VENV_FOLDER must be configured if VENV_ENABLED is true" && exit 1 || :
219-
@[[ "$(VENV_ENABLED)$(PYTHON_PACKAGE_INSTALLER)" == "falseuv" ]] \
240+
# Validation: Check uv not used with system Python
241+
@[[ "$(VENV_ENABLED)" == "false" && "$(PYTHON_PACKAGE_INSTALLER)" == "uv" ]] \
220242
&& echo "Package installer uv does not work with a global Python interpreter." && exit 1 || :
243+
# Warning: Notify if global UV is outdated
244+
ifeq ("$(UV_OUTDATED)","true")
245+
@echo "WARNING: A newer version of uv is available. Run 'uv self update' to upgrade."
246+
endif
247+
248+
# Create virtual environment
221249
ifeq ("$(VENV_ENABLED)", "true")
222250
ifeq ("$(VENV_CREATE)", "true")
223-
ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvtrue")
224-
@echo "Setup Python Virtual Environment using package 'uv' at '$(VENV_FOLDER)'"
225-
@uv venv -p $(PRIMARY_PYTHON) --seed $(VENV_FOLDER)
251+
ifeq ("$(USE_GLOBAL_UV)","true")
252+
@echo "Setup Python Virtual Environment using global uv at '$(VENV_FOLDER)'"
253+
@uv venv --quiet --no-progress -p $(UV_PYTHON) --seed $(VENV_FOLDER)
226254
else
227255
@echo "Setup Python Virtual Environment using module 'venv' at '$(VENV_FOLDER)'"
228256
@$(PRIMARY_PYTHON) -m venv $(VENV_FOLDER)
@@ -232,10 +260,14 @@ endif
232260
else
233261
@echo "Using system Python interpreter"
234262
endif
235-
ifeq ("$(PYTHON_PACKAGE_INSTALLER)$(MXENV_UV_GLOBAL)","uvfalse")
236-
@echo "Install uv"
263+
264+
# Install uv locally if needed
265+
ifeq ("$(USE_LOCAL_UV)","true")
266+
@echo "Install uv in virtual environment"
237267
@$(MXENV_PYTHON) -m pip install uv
238268
endif
269+
270+
# Install/upgrade core packages
239271
@$(PYTHON_PACKAGE_COMMAND) install -U pip setuptools wheel
240272
@echo "Install/Update MXStack Python packages"
241273
@$(PYTHON_PACKAGE_COMMAND) install -U $(MXDEV) $(MXMAKE)
@@ -263,6 +295,41 @@ INSTALL_TARGETS+=mxenv
263295
DIRTY_TARGETS+=mxenv-dirty
264296
CLEAN_TARGETS+=mxenv-clean
265297

298+
##############################################################################
299+
# ruff
300+
##############################################################################
301+
302+
RUFF_TARGET:=$(SENTINEL_FOLDER)/ruff.sentinel
303+
$(RUFF_TARGET): $(MXENV_TARGET)
304+
@echo "Install Ruff"
305+
@$(PYTHON_PACKAGE_COMMAND) install ruff
306+
@touch $(RUFF_TARGET)
307+
308+
.PHONY: ruff-check
309+
ruff-check: $(RUFF_TARGET)
310+
@echo "Run ruff check"
311+
@ruff check $(RUFF_SRC)
312+
313+
.PHONY: ruff-format
314+
ruff-format: $(RUFF_TARGET)
315+
@echo "Run ruff format"
316+
@ruff format $(RUFF_SRC)
317+
318+
.PHONY: ruff-dirty
319+
ruff-dirty:
320+
@rm -f $(RUFF_TARGET)
321+
322+
.PHONY: ruff-clean
323+
ruff-clean: ruff-dirty
324+
@test -e $(MXENV_PYTHON) && $(MXENV_PYTHON) -m pip uninstall -y ruff || :
325+
@rm -rf .ruff_cache
326+
327+
INSTALL_TARGETS+=$(RUFF_TARGET)
328+
CHECK_TARGETS+=ruff-check
329+
FORMAT_TARGETS+=ruff-format
330+
DIRTY_TARGETS+=ruff-dirty
331+
CLEAN_TARGETS+=ruff-clean
332+
266333
##############################################################################
267334
# isort
268335
##############################################################################
@@ -297,40 +364,6 @@ FORMAT_TARGETS+=isort-format
297364
DIRTY_TARGETS+=isort-dirty
298365
CLEAN_TARGETS+=isort-clean
299366

300-
##############################################################################
301-
# black
302-
##############################################################################
303-
304-
BLACK_TARGET:=$(SENTINEL_FOLDER)/black.sentinel
305-
$(BLACK_TARGET): $(MXENV_TARGET)
306-
@echo "Install Black"
307-
@$(PYTHON_PACKAGE_COMMAND) install black
308-
@touch $(BLACK_TARGET)
309-
310-
.PHONY: black-check
311-
black-check: $(BLACK_TARGET)
312-
@echo "Run black checks"
313-
@black --check $(BLACK_SRC)
314-
315-
.PHONY: black-format
316-
black-format: $(BLACK_TARGET)
317-
@echo "Run black format"
318-
@black $(BLACK_SRC)
319-
320-
.PHONY: black-dirty
321-
black-dirty:
322-
@rm -f $(BLACK_TARGET)
323-
324-
.PHONY: black-clean
325-
black-clean: black-dirty
326-
@test -e $(MXENV_PYTHON) && $(MXENV_PYTHON) -m pip uninstall -y black || :
327-
328-
INSTALL_TARGETS+=$(BLACK_TARGET)
329-
CHECK_TARGETS+=black-check
330-
FORMAT_TARGETS+=black-format
331-
DIRTY_TARGETS+=black-dirty
332-
CLEAN_TARGETS+=black-clean
333-
334367
##############################################################################
335368
# mxfiles
336369
##############################################################################

0 commit comments

Comments
 (0)