AAP-85334: Replace Black + Flake8 with Ruff

Migrate Python linting and formatting toolchain from Black (formatter)
and Flake8 (linter) to Ruff, which provides both in a single tool.

- pyproject.toml: remove [tool.black], add [tool.ruff] config sections
- tox.ini: replace black+flake8 deps/commands with ruff in linters env
- awxkit/tox.ini: replace flake8 with ruff in lint env
- Makefile: replace `black` target with `format` + `lint`, keep `black`
  as legacy alias, update `check` and `api-lint`
- pre-commit.sh: use `ruff format --check`, honor AWX_IGNORE_RUFF
- requirements_dev.txt: swap black/flake8 for ruff
- Dockerfile.j2: install ruff instead of black
- CONTRIBUTING.md: update tool references
- Fix noqa comments to use # separator instead of ; (ruff requirement)
- Add noqa: F841 for false positive in awxkit/cli/__init__.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dirk Julich
2026-08-20 18:35:23 +02:00
co-authored by Claude Opus 4.6
parent b84931a49b
commit 4b73e1264b
15 changed files with 55 additions and 51 deletions
+2 -2
View File
@@ -109,7 +109,7 @@ Install the pre-commit hook before contributing:
make pre-commit
```
When you attempt to perform a `git commit` there will be a pre-commit hook that gets run before the commit is allowed to your local repository. For example, python's [black](https://pypi.org/project/black/) will be run to test the formatting of any python files.
When you attempt to perform a `git commit` there will be a pre-commit hook that gets run before the commit is allowed to your local repository. For example, [ruff](https://docs.astral.sh/ruff/) will be run to check the formatting of any python files.
While you can use environment variables to skip the pre-commit hooks GitHub will run similar tests and prevent merging of PRs if the tests do not pass.
@@ -148,7 +148,7 @@ Fixes and Features for AWX will go through the Github pull request process. Subm
Here are a few things you can do to help the visibility of your change, and increase the likelihood that it will be accepted:
- No issues when running linters/code checkers
- Python: black: `(container)/awx_devel$ make black`
- Python: ruff: `(container)/awx_devel$ make format`
- No issues from unit tests
- Python: py.test: `(container)/awx_devel$ make test`
- Write tests for new functionality, update/add tests for bug fixes
+14 -6
View File
@@ -348,9 +348,17 @@ run-ws-heartbeat:
reports:
mkdir -p $@
black: reports
@command -v black >/dev/null 2>&1 || { echo "could not find black on your PATH, you may need to \`pip install black\`, or set AWX_IGNORE_BLACK=1" && exit 1; }
@(set -o pipefail && $@ $(BLACK_ARGS) awx awxkit awx_collection | tee reports/$@.report)
## awx_collection is excluded: ansible-test sanity runs its own linters which conflict with ruff
format: reports
@command -v ruff >/dev/null 2>&1 || { echo "could not find ruff on your PATH, you may need to \`pip install ruff\`" && exit 1; }
@(set -o pipefail && ruff format $(RUFF_FORMAT_ARGS) awx awxkit | tee reports/format.report)
lint: reports
@command -v ruff >/dev/null 2>&1 || { echo "could not find ruff on your PATH, you may need to \`pip install ruff\`" && exit 1; }
ruff check awx awxkit
## Legacy alias
black: format
$(GIT_COMMON_DIR)/hooks/pre-commit:
ln -sf ../../pre-commit.sh $(GIT_COMMON_DIR)/hooks/pre-commit
@@ -369,11 +377,11 @@ genschema-yaml: awx-link reports
fi; \
$(MANAGEMENT_COMMAND) spectacular --format openapi --file schema.yaml
check: black
check: format
api-lint:
BLACK_ARGS="--check" $(MAKE) black
flake8 awx
RUFF_FORMAT_ARGS="--check" $(MAKE) format
$(MAKE) lint
yamllint -s .
## Run egg_info_dev to generate awx.egg-info for development.
@@ -6,7 +6,7 @@ from awx.api.versioning import reverse
from django.test.utils import override_settings
from ansible_base.jwt_consumer.common.util import generate_x_trusted_proxy_header
from ansible_base.lib.testing.fixtures import rsa_keypair_factory, rsa_keypair # noqa: F401; pylint: disable=unused-import
from ansible_base.lib.testing.fixtures import rsa_keypair_factory, rsa_keypair # noqa: F401 # pylint: disable=unused-import
class HeaderTrackingMiddleware(object):
@@ -16,7 +16,7 @@ from rest_framework.exceptions import ValidationError
# DAB
from ansible_base.jwt_consumer.common.util import generate_x_trusted_proxy_header
from ansible_base.lib.testing.fixtures import rsa_keypair_factory, rsa_keypair # noqa: F401; pylint: disable=unused-import
from ansible_base.lib.testing.fixtures import rsa_keypair_factory, rsa_keypair # noqa: F401 # pylint: disable=unused-import
@pytest.mark.django_db
@@ -161,7 +161,7 @@ def test_invalid_json_body(patch, job_template_factory, alice, json_body):
objs = job_template_factory('jt', organization='org1')
objs.job_template.admin_role.members.add(alice)
resp = patch(reverse('api:job_template_detail', kwargs={'pk': objs.job_template.id}), json_body, alice, expect=400)
assert resp.data['detail'] == (u'JSON parse error - not a JSON object')
assert resp.data['detail'] == ('JSON parse error - not a JSON object')
@pytest.mark.django_db
@@ -432,7 +432,7 @@ class TestJobTemplateCallbackProxyIntegration:
data={'host_config_key': 'abcd'},
user=admin_user,
expect=expected,
**headers
**headers,
)
@override_settings(REMOTE_HOST_HEADERS=['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST'], PROXY_IP_ALLOWED_LIST=[])
@@ -462,7 +462,7 @@ class TestJobTemplateCallbackProxyIntegration:
data={'host_config_key': 'abcd'},
user=admin_user,
expect=201,
**headers
**headers,
)
@override_settings(REMOTE_HOST_HEADERS=['HTTP_X_FROM_THE_LOAD_BALANCER', 'REMOTE_ADDR', 'REMOTE_HOST'], PROXY_IP_ALLOWED_LIST=['my.proxy.example.org'])
@@ -483,5 +483,5 @@ class TestJobTemplateCallbackProxyIntegration:
data={'host_config_key': 'abcd'},
user=admin_user,
expect=400,
**headers
**headers,
)
+1 -1
View File
@@ -15,7 +15,7 @@ from awx.api.versioning import reverse
# These tests are invoked from the awx/main/tests/live/ subfolder
# so any fixtures from higher-up conftest files must be explicitly included
from awx.main.tests.functional.conftest import * # noqa
from awx.main.tests.conftest import load_all_credentials # noqa: F401; pylint: disable=unused-import
from awx.main.tests.conftest import load_all_credentials # noqa: F401 # pylint: disable=unused-import
from awx.main.tests import data
from awx.main.models import Project, JobTemplate, Organization, Inventory, WorkflowJob, UnifiedJob
+1 -1
View File
@@ -56,7 +56,7 @@ import logging
# In this module we don't use EXPORTABLE_RESOURCES, we just want to validate that our installed awxkit has import/export
try:
from awxkit.api.pages.api import EXPORTABLE_RESOURCES # noqa: F401; pylint: disable=unused-import
from awxkit.api.pages.api import EXPORTABLE_RESOURCES # noqa: F401 # pylint: disable=unused-import
HAS_EXPORTABLE_RESOURCES = True
except ImportError:
+2 -2
View File
@@ -21,9 +21,9 @@ from ansible_base.rbac.models import RoleDefinition, DABPermission
from ansible_base.rbac import permission_registry
from awx.main.migrations._dab_rbac import setup_managed_role_definitions
from awx.main.tests.conftest import load_all_credentials # noqa: F401; pylint: disable=unused-import
from awx.main.tests.conftest import load_all_credentials # noqa: F401 # pylint: disable=unused-import
from awx.main.tests.functional.conftest import _request
from awx.main.tests.functional.conftest import credentialtype_scm, credentialtype_ssh # noqa: F401; pylint: disable=unused-import
from awx.main.tests.functional.conftest import credentialtype_scm, credentialtype_ssh # noqa: F401 # pylint: disable=unused-import
from awx.main.models import (
Organization,
Project,
+1 -1
View File
@@ -7,7 +7,7 @@ import pytest
from awx.main.models.execution_environments import ExecutionEnvironment
from awx.main.models.jobs import JobTemplate
from awx.main.tests.functional.conftest import user, system_auditor # noqa: F401; pylint: disable=unused-import
from awx.main.tests.functional.conftest import user, system_auditor # noqa: F401 # pylint: disable=unused-import
ASSETS = set([
+3 -5
View File
@@ -26,10 +26,8 @@ def run(stdout=sys.stdout, stderr=sys.stderr, argv=[]):
sys.exit(1)
except ConnectionError as e:
cli.parser.print_help()
msg = (
'\nThere was a network error of some kind trying to reach '
'{}.\nYou might need to specify (or double-check) '
'--conf.host'.format(cli.get_config('host'))
msg = '\nThere was a network error of some kind trying to reach {}.\nYou might need to specify (or double-check) --conf.host'.format(
cli.get_config('host')
)
if isinstance(e, SSLError):
msg = (
@@ -60,7 +58,7 @@ def run(stdout=sys.stdout, stderr=sys.stderr, argv=[]):
sys.stdout.write(e.__class__.__name__)
print('')
sys.exit(1)
except Exception as e:
except Exception as e: # noqa: F841
if cli.verbose:
e = traceback.format_exc()
cprint(e, 'red', file=stderr)
+2 -7
View File
@@ -26,11 +26,9 @@ commands =
[testenv:lint]
deps =
{[testenv]deps}
flake8
ruff
commands =
flake8 awxkit
# pylama --report report.pylama awxkit
# py.test awxkit --pylama --junitxml=report.pylama {posargs}
ruff check awxkit
- coverage erase
[testenv:coveralls]
@@ -39,9 +37,6 @@ commands=
- coverage report -m
- coveralls
[flake8]
max-line-length = 120
[pytest]
addopts = -v --tb=native
testpaths = test
+4 -4
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env bash
if [ -z $AWX_IGNORE_BLACK ] ; then
if [ -z "$AWX_IGNORE_BLACK" ] && [ -z "$AWX_IGNORE_RUFF" ] ; then
python_files_changed=$(git diff --cached --name-only --diff-filter=AM awx/ awxkit/ tools/ | grep -E '\.py$')
if [ "x$python_files_changed" != "x" ] ; then
black --check $python_files_changed || \
ruff format --check $python_files_changed || \
if [ $? != 0 ] ; then
echo 'To fix this, run `make black` to auto-format your code prior to commit, or set AWX_IGNORE_BLACK=1'
exit 1
echo 'To fix this, run `make format` to auto-format your code prior to commit, or set AWX_IGNORE_RUFF=1'
exit 1
fi
fi
fi
+13 -4
View File
@@ -6,8 +6,17 @@ build-backend = "setuptools.build_meta"
# causes the "version" key in setup.cfg to be ignored.
# [tool.setuptools_scm]
[tool.black]
[tool.ruff]
line-length = 160
fast = true
skip-string-normalization = true
extend-exclude = "awx_collection"
extend-exclude = ["awx_collection"]
[tool.ruff.format]
quote-style = "preserve"
[tool.ruff.lint]
select = [
"E722",
"F401", "F402", "F405", "F523", "F541",
"F811", "F821", "F822", "F841",
"W291", "W605",
]
+1 -2
View File
@@ -6,7 +6,7 @@ openapi-spec-validator # OpenAPI 3.0 schema validation
# pprofile - re-add once https://github.com/vpelletier/pprofile/issues/41 is addressed
ipython>=7.31.1 # https://github.com/ansible/awx/security/dependabot/30
unittest2
black
ruff
pytest!=7.0.0
pytest-asyncio
pytest-cov
@@ -21,7 +21,6 @@ jupyter
backports.tempfile # support in unit tests for py32+ tempfile.TemporaryDirectory
gprof2dot
atomicwrites
flake8
yamllint
pip>=25.3 # PEP 660 – Editable installs for pyproject.toml based builds (wheel based)
awx-tui
@@ -184,7 +184,7 @@ RUN dnf module -y enable nodejs:18 && dnf module -y install nodejs:18/common &&
unzip
RUN pip3.12 install -vv git+https://github.com/coderanger/supervisor-stdout.git@973ba19967cdaf46d9c1634d1675fc65b9574f6e
RUN pip3.12 install -vv black setuptools-scm build
RUN pip3.12 install -vv ruff setuptools-scm build
# This package randomly fails to download.
# It is nice to have in the dev env, but not necessary.
+4 -9
View File
@@ -3,22 +3,17 @@ isolated_build = True
[testenv:linters]
deps =
black
flake8
ruff
yamllint
allowlist_externals = make
setenv =
BLACK_ARGS = --check
PYTHON = python3
commands =
make black
flake8 awx awxkit awx_collection
# awx_collection is excluded: ansible-test sanity runs its own linters which conflict with ruff
ruff format --check awx awxkit
ruff check awx awxkit
yamllint -s .
[flake8]
select = F401,F402,F821,F823,F841,F811,E265,E266,F541,W605,E722,F822,F523,W291,F405
exclude = env,awx_collection_build,.tox
[testenv:pip-compile-docs]
description = Compile docs build lockfiles
deps =