Skip to content
Merged

Ruff #40

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/test-build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ runs:
steps:
- name: Create version tag
id: version-tag
uses: nationalarchives/ds-docker-actions/.github/actions/get-version-tag@main # TODO: Could replace the "Prepare image tag" step below?
uses: nationalarchives/ds-docker-actions/.github/actions/get-version-tag@main # TODO: Could replace the "Prepare image tag" step below?

- name: Test new image tag
run: |
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased](https://github.com/nationalarchives/docker/compare/v1.11.1...HEAD)

### Added

- Added Ruff as a replacement for isort, Flake8 and Black

### Changed

- Update default isort config to ignore `node_modules`
Expand Down
6 changes: 2 additions & 4 deletions docker/tna-python-dev/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ RUN apt-get update; \
# Set the versions for various tools that we
# want to install to help with development
# ==========================================
ENV BLACK_VERSION=26.3.1 \
FLAKE8_VERSION=7.1.1 \
ISORT_VERSION=8.0.1 \
ENV RUFF_VERSION=0.15.12 \
DJLINT_VERSION=1.36.4 \
DJHTML_VERSION=3.0.11 \
DJANGO_DEBUG_TOOLBAR_VERSION=6.3.0 \
Expand Down Expand Up @@ -85,7 +83,7 @@ ENV PATH="/home/app/.local/bin/tasks:/home/app/.local/bin/dev:$PATH"
# ==========================================
# hadolint ignore=SC1091
RUN install-format-dependencies; \
python -m pip install --no-cache-dir --quiet black=="$BLACK_VERSION" flake8=="$FLAKE8_VERSION" isort=="$ISORT_VERSION" djlint=="$DJLINT_VERSION" djhtml=="$DJHTML_VERSION"
python -m pip install --no-cache-dir --quiet ruff=="$RUFF_VERSION" djlint=="$DJLINT_VERSION" djhtml=="$DJHTML_VERSION"

# ==========================================
# Copy any configuration files into the main
Expand Down
92 changes: 77 additions & 15 deletions docker/tna-python-dev/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

This image extends `tna-python` and can be used for local development **ONLY**. It adds:

- `black`, `flake8` and `isort` for formatting Python code
- `ruff` for formatting and linting Python
- `prettier`, `eslint` and `stylelint` for formatting JavaScript and CSS
- scripts for formatting code
- scripts for formatting
- `django-debug-toolbar` for debugging Django applications
- `git`

Expand Down Expand Up @@ -92,30 +92,92 @@ The process for these commands is:

### `format`

1. Run `isort`
1. Run `black`
1. Run `flake8`
1. Run `ruff`
1. Run `djlint` to check HTML templates for Jinja compliance
1. Apply `prettier` to all files in the `/app` directory
1. Run `stylelint` against all SCSS files in the `/app` directory
1. Run `eslint` against all JavaScript files in the `/app` directory

#### How to override the default configurations
#### Ruff

By default, Ruff is only configured to check the following when running `format` and `checkformat`:

| Code | Purpose | Defined by |
| ---------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `E4`, `E7`, `E9` | [Subset of pycodestyle errors](https://pypi.org/project/pycodestyle/) | [Ruff default configuration](https://docs.astral.sh/ruff/configuration/) |
| `F` | [Pyflakes](https://pypi.org/project/pyflakes/) | [Ruff default configuration](https://docs.astral.sh/ruff/configuration/) |
| `W` | [pycodestyle warnings](https://pypi.org/project/pycodestyle/) | [`/home/app/ruff.toml`](./lib/ruff.toml) |
| `C901` | [mccabe "Function is too complex"](https://www.flake8rules.com/rules/C901.html) | [`/home/app/ruff.toml`](./lib/ruff.toml) |
| `B` | [flake8-bugbear](https://pypi.org/project/flake8-bugbear/) | [`/home/app/ruff.toml`](./lib/ruff.toml) |
| `I` | [isort](https://pypi.org/project/isort/) | [`/home/app/ruff.toml`](./lib/ruff.toml) |
| `Q` | [flake8-quotes](https://pypi.org/project/flake8-quotes/) | [`/home/app/ruff.toml`](./lib/ruff.toml) |

Running `format --strict` or `checkformat --strict` will apply some extra rules defined in [`/home/app/ruff-strict.toml`](./lib/ruff-strict.toml):

| Code | Purpose |
| ------ | --------------------------------------------------------------------- |
| `A` | [flake8-builtins](https://pypi.org/project/flake8-builtins/) |
| `DJ` | [flake8-django](https://pypi.org/project/flake8-django/) |
| `ERA` | [eradicate](https://pypi.org/project/eradicate/) |
| `FAST` | [fastapi](https://pypi.org/project/fastapi/) |
| `FIX` | [flake8-fixme](https://github.com/tommilligan/flake8-fixme) |
| `LOG` | [flake8-logging](https://pypi.org/project/flake8-logging/) |
| `N` | [pep8-naming](https://pypi.org/project/pep8-naming/) |
| `PL` | [pylint](https://pypi.org/project/pylint/) |
| `RET` | [flake8-return](https://pypi.org/project/flake8-return/) |
| `RSE` | [flake8-raise](https://pypi.org/project/flake8-raise/) |
| `RUF` | [Ruff-specific rules](https://docs.astral.sh/ruff/settings/#lintruff) |
| `SIM` | [flake8-simplify](https://pypi.org/project/flake8-simplify/) |
| `T20` | [flake8-print](https://pypi.org/project/flake8-print/) |
| `TD` | [flake8-todos](https://github.com/orsinium-labs/flake8-todos/) |
| `TRY` | [tryceratops](https://pypi.org/project/tryceratops/) |
| `UP` | [pyupgrade](https://pypi.org/project/pyupgrade/) |

##### How to override Ruff configuration

Create a `ruff.toml` file in your project root. If this file exists, the `--strict` parameter will be ignored on `format --strict` and `checkformat --strict`.

```toml
# Extend the default Ruff configuration
extend = "/home/app/ruff.toml"

# ...or extend the strict ruleset
# extend = "/home/app/ruff-strict.toml"

extend-exclude = [
# "migrations" # Exclude the "migrations" directory
]

[lint]
ignore = [
# Add rules to ignore in your project
]
```

##### Ignoring code

| Tool | Overwrite solution | More information |
| ----------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `isort` | `.isort.cfg` file in the project root | https://pycqa.github.io/isort/docs/configuration/config_files.html#isortcfg-preferred-format |
| `black` | Add `[tool.black]` config to the `pyproject.toml` | https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file |
| `flake8` | `.flake8` file in the project root | https://flake8.pycqa.org/en/latest/user/configuration.html#configuration-locations |
| `djlint` | `.djlintrc` file in the project root | https://djlint.com/docs/configuration/ |
| `prettier` | `.prettierignore` file in the project root | https://prettier.io/docs/en/ignore.html |
| `stylelint` | `.stylelintrc` file in the project root | https://stylelint.io/user-guide/configure/ |
| `eslint` | `.eslintrc.js` file in the project root | https://eslint.org/docs/latest/use/configure/configuration-files#using-configuration-files |
Use the `# noqa` annotation from [In-line Ignoring Errorsin Flake8](https://flake8.pycqa.org/en/latest/user/violations.html#in-line-ignoring-errors) to ignore failing lines.

```python
def my_overly_complex_function(): # noqa: C901
pass
```

#### How to override other default configurations

| Tool | Overwrite solution | More information |
| ----------- | ------------------------------------------ | ------------------------------------------------------------------------------------------ |
| `djlint` | `.djlintrc` file in the project root | https://djlint.com/docs/configuration/ |
| `prettier` | `.prettierignore` file in the project root | https://prettier.io/docs/en/ignore.html |
| `stylelint` | `.stylelintrc` file in the project root | https://stylelint.io/user-guide/configure/ |
| `eslint` | `.eslintrc.js` file in the project root | https://eslint.org/docs/latest/use/configure/configuration-files#using-configuration-files |

### `checkformat`

Runs the same tests as `format` but doesn't fix issues. Can be used in CI/CD pipelines to check formatting.

Like `format --strict`, `checkformat --strict` uses an [expanded set of Ruff rules](#ruff).

### `outdated`

1. Shows outdated Poetry dependencies
Expand Down
8 changes: 7 additions & 1 deletion docker/tna-python-dev/bin/dev/checkformat
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#!/bin/bash

FIX=false format
STRICT=false

if [[ "$1" == "--strict" ]]; then
STRICT=true
fi

STRICT=$STRICT FIX=false format
55 changes: 25 additions & 30 deletions docker/tna-python-dev/bin/dev/format
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

set -e

cd /app || return

[[ -z $FIX ]] && FIX=true

if [ "$FIX" = true ]
Expand All @@ -14,42 +12,39 @@ else
fi
echo

echo "Running isort..."
if [ -f "/app/.isort.cfg" ]
then
ISORT_CONFIG="/app/.isort.cfg"
echo "Using app config ($ISORT_CONFIG)"
else
ISORT_CONFIG="/home/app/.isort.cfg"
echo "Using default config ($ISORT_CONFIG)"
fi
if [ "$FIX" = true ]
then
isort --settings-file "$ISORT_CONFIG" /app --overwrite-in-place
else
isort --settings-file "$ISORT_CONFIG" /app --diff
[[ -z $STRICT ]] && STRICT=false
if [[ "$1" == "--strict" ]]; then
STRICT=true
fi
echo

echo "Running black..."
if [ "$FIX" = true ]
cd /app || return

echo "Running ruff..."
if [ -f "/app/ruff.toml" ]
then
black -t py310 -t py311 -t py312 -t py313 -t py314 /app
RUFF_CONFIG="/app/ruff.toml"
if [ "$STRICT" = true ]
then
echo "Using app config ($RUFF_CONFIG - ignoring strict mode)"
else
echo "Using app config ($RUFF_CONFIG)"
fi
elif [ "$STRICT" = true ]
then
RUFF_CONFIG="/home/app/ruff-strict.toml"
echo "Using default strict config ($RUFF_CONFIG)"
else
black -t py310 -t py311 -t py312 -t py313 -t py314 --check /app
RUFF_CONFIG="/home/app/ruff.toml"
echo "Using default config ($RUFF_CONFIG)"
fi
echo

echo "Running flake8..."
if [ -f "/app/.flake8" ]
if [ "$FIX" = true ]
then
FLAKE8_CONFIG="/app/.flake8"
echo "Using app config ($FLAKE8_CONFIG)"
ruff check /app --fix --config "$RUFF_CONFIG"
ruff format /app --config "$RUFF_CONFIG"
else
FLAKE8_CONFIG="/home/app/.flake8"
echo "Using default config ($FLAKE8_CONFIG)"
ruff check /app --config "$RUFF_CONFIG"
ruff format --check /app --config "$RUFF_CONFIG"
fi
flake8 --config="$FLAKE8_CONFIG" /app
echo

if [ -d "/app/app/templates" ]
Expand Down
5 changes: 0 additions & 5 deletions docker/tna-python-dev/lib/.flake8

This file was deleted.

3 changes: 0 additions & 3 deletions docker/tna-python-dev/lib/.isort.cfg

This file was deleted.

24 changes: 24 additions & 0 deletions docker/tna-python-dev/lib/ruff-strict.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
extend = "./ruff.toml"

[lint]
extend-select = [
"A",
"DJ",
"ERA",
"FAST",
"FIX",
"LOG",
"N",
"PL",
"RET",
"RSE",
"RUF",
"SIM",
"T20",
"TD",
"TRY",
"UP"
]

[lint.mccabe]
max-complexity = 12
23 changes: 23 additions & 0 deletions docker/tna-python-dev/lib/ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[lint]
extend-select = [
"W",
"C901",
"B",
"I",
"Q"
]
allowed-confusables = [
"—",
"–",
"’"
]

[lint.per-file-ignores]
"__init__.py" = [
"E402",
"F401",
"PLC0415"
]

[lint.mccabe]
max-complexity = 20