Skip to content

Commit bacf2a1

Browse files
cclaussMatthewEthanTam
authored andcommitted
Use ruff to lint Python code (jazzband#518)
1 parent 80b1d5d commit bacf2a1

22 files changed

+145
-72
lines changed

.flake8

-8
This file was deleted.

.github/workflows/python-package.yml

+3-6
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ jobs:
1515
test:
1616
runs-on: ubuntu-latest
1717
strategy:
18+
fail-fast: false
1819
matrix:
19-
python-version: [ 3.8, 3.9, '3.10', '3.11' ]
20+
python-version: [ 3.8, 3.9, '3.10', 3.11, 3.12 ]
2021

2122
steps:
2223
- uses: actions/checkout@v3
@@ -57,11 +58,7 @@ jobs:
5758
python -m pip install tox-gh-actions
5859
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
5960
- name: Lint
60-
run: |
61-
# stop the build if there are Python syntax errors or undefined names
62-
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
63-
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
64-
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
61+
run: ruff check --output-format=github .
6562

6663
typecheck:
6764
runs-on: ubuntu-latest

.pre-commit-config.yaml

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
repos:
2-
- repo: https://github.com/pycqa/flake8
3-
rev: '7.1.1'
4-
hooks:
5-
- id: flake8
6-
exclude: docs/conf.py|test_settings.py
72
- repo: https://github.com/pre-commit/pre-commit-hooks
83
rev: 'v5.0.0'
94
hooks:
105
- id: end-of-file-fixer
6+
- id: trailing-whitespace
7+
8+
- repo: https://github.com/astral-sh/ruff-pre-commit
9+
rev: v0.7.3
10+
hooks:
11+
- id: ruff

CONTRIBUTING.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ To be mergeable, patches must:
6868
- not change existing tests without a *very* good reason,
6969
- add tests for new code (bug fixes should include regression tests, new
7070
features should have relevant tests),
71-
- not introduce any new flake8_ errors (run ``./run.sh lint``),
71+
- not introduce any new ruff_ errors (run ``./run.sh lint``),
7272
- not introduce any new mypy_ errors (run ``./run.sh typecheck``),
7373
- include updated source translations (run ``./run.sh makemessages`` and ``./run.sh compilemessages``),
7474
- document any new features, and
@@ -80,6 +80,6 @@ with it.
8080

8181
.. _open a new issue: https://github.com/jazzband/django-waffle/issues/new
8282
.. _Fork: https://github.com/jazzband/django-waffle/fork
83-
.. _flake8: https://pypi.python.org/pypi/flake8
83+
.. _ruff: https://pypi.python.org/pypi/ruff
8484
.. _mypy: https://www.mypy-lang.org/
8585
.. _good commit message: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html

docs/about/contributing.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ To be mergeable, patches must:
6060
- not change existing tests without a *very* good reason,
6161
- add tests for new code (bug fixes should include regression tests, new
6262
features should have relevant tests),
63-
- not introduce any new flake8_ errors (run ``./run.sh lint``),
63+
- not introduce any new ruff_ errors (run ``./run.sh lint``),
6464
- document any new features, and
6565
- have a `good commit message`_.
6666

@@ -70,5 +70,5 @@ with it.
7070

7171
.. _open a new issue: https://github.com/jazzband/django-waffle/issues/new
7272
.. _Fork: https://github.com/jazzband/django-waffle/fork
73-
.. _flake8: https://pypi.python.org/pypi/flake8
73+
.. _ruff: https://pypi.python.org/pypi/ruff
7474
.. _good commit message: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html

docs/conf.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
# All configuration values have a default; values that are commented out
1010
# serve to show the default.
1111

12-
import sys, os
12+
import sys
13+
import os
1314

1415
# If extensions (or modules to document with autodoc) are in another directory,
1516
# add these directories to sys.path here. If the directory is relative to the

docs/starting/configuring.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ behavior.
100100
The value describes the level of wanted warning, possible values are all levels know by pythons default logging,
101101
e.g. ``logging.WARNING``.
102102
Defaults to ``None``.
103-
103+
104104

105105
``WAFFLE_ENABLE_ADMIN_PAGES``
106106
Enables the default admin pages for Waffle models. This is True by default,

docs/usage/decorators.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Flags
2525
@waffle_flag('flag_name')
2626
def myview(request):
2727
pass
28-
28+
2929
@waffle_flag('flag_name', 'url_name_to_redirect_to')
3030
def myotherview(request):
3131
pass

docs/usage/templates.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ features on the front-end. It includes support for both Django's
1111
built-in templates and for Jinja2_.
1212

1313
.. warning::
14-
14+
1515
Before using samples in templates, see the warning in the
1616
:ref:`Sample chapter <types-sample>`.
1717

@@ -92,7 +92,7 @@ Switches
9292
--------
9393

9494
::
95-
95+
9696
{% if waffle.switch('switch_name') %}
9797
switch_name is active!
9898
{% endif %}

docs/usage/views.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,5 @@ Samples
4949
Returns ``True`` if the sample is active, else ``False``.
5050

5151
.. warning::
52-
52+
5353
See the warning in the :ref:`Sample chapter <types-sample>`.

pyproject.toml

+87
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,90 @@ strict_equality = true
6060
[[tool.mypy.overrides]]
6161
module = ["django.*"]
6262
ignore_missing_imports = true
63+
64+
[tool.ruff]
65+
line-length = 120
66+
target-version = "py38"
67+
68+
[tool.ruff.lint]
69+
select = [
70+
"AIR", # Airflow
71+
"ASYNC", # flake8-async
72+
"BLE", # flake8-blind-except
73+
"C90", # McCabe cyclomatic complexity
74+
"DJ", # flake8-django
75+
"DTZ", # flake8-datetimez
76+
"E", # pycodestyle errors
77+
"F", # Pyflakes
78+
"FIX", # flake8-fixme
79+
"FLY", # flynt
80+
"G", # flake8-logging-format
81+
"ICN", # flake8-import-conventions
82+
"INP", # flake8-no-pep420
83+
"INT", # flake8-gettext
84+
"NPY", # NumPy-specific rules
85+
"PD", # pandas-vet
86+
"PIE", # flake8-pie
87+
"PL", # Pylint
88+
"PYI", # flake8-pyi
89+
"RSE", # flake8-raise
90+
"SLOT", # flake8-slots
91+
"T10", # flake8-debugger
92+
"T20", # flake8-print
93+
"TD", # flake8-todos
94+
"TID", # flake8-tidy-imports
95+
"UP", # pyupgrade
96+
"W", # pycodestyle warnings
97+
"YTT", # flake8-2020
98+
# "A", # flake8-builtins
99+
# "ANN", # flake8-annotations
100+
# "ARG", # flake8-unused-arguments
101+
# "B", # flake8-bugbear
102+
# "C4", # flake8-comprehensions
103+
# "COM", # flake8-commas
104+
# "CPY", # flake8-copyright
105+
# "D", # pydocstyle
106+
# "EM", # flake8-errmsg
107+
# "ERA", # eradicate
108+
# "EXE", # flake8-executable
109+
# "FA", # flake8-future-annotations
110+
# "FBT", # flake8-boolean-trap
111+
# "I", # isort
112+
# "ISC", # flake8-implicit-str-concat
113+
# "N", # pep8-naming
114+
# "PERF", # Perflint
115+
# "PGH", # pygrep-hooks
116+
# "PT", # flake8-pytest-style
117+
# "PTH", # flake8-use-pathlib
118+
# "Q", # flake8-quotes
119+
# "RET", # flake8-return
120+
# "RUF", # Ruff-specific rules
121+
# "S", # flake8-bandit
122+
# "SIM", # flake8-simplify
123+
# "SLF", # flake8-self
124+
# "TCH", # flake8-type-checking
125+
# "TRY", # tryceratops
126+
]
127+
# Files not checked:
128+
# - migrations: most of these are autogenerated and don't need a check
129+
# - docs: contains autogenerated code that doesn't need a check
130+
exclude = [
131+
"*/migrations/*",
132+
"docs",
133+
]
134+
ignore = ["F401"]
135+
136+
[tool.ruff.lint.mccabe]
137+
max-complexity = 23
138+
139+
[tool.ruff.lint.per-file-ignores]
140+
"docs/conf.py" = ["INP001"]
141+
"test_app/models.py" = ["DJ008"] # FIXME
142+
"waffle/models.py" = ["DJ012", "PYI019"] # FIXME
143+
144+
[tool.ruff.lint.pylint]
145+
allow-magic-value-types = ["float", "int", "str"]
146+
max-args = 6 # default is 5
147+
max-branches = 23 # default is 12
148+
max-returns = 13 # default is 6
149+
max-statements = 51 # default is 50

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ Django
22
django-jinja>=2.4.1,<3
33
transifex-client
44

5-
flake8
65
mypy
6+
ruff
77
tox

requirements/test.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
flake8
1+
ruff
22
tox

run.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export DJANGO_SETTINGS_MODULE="test_settings"
66
usage() {
77
echo "USAGE: $0 [command]"
88
echo " test - run the waffle tests"
9-
echo " lint - run flake8"
9+
echo " lint - run ruff"
1010
echo " typecheck - run mypy"
1111
echo " shell - open the Django shell"
1212
echo " makemigrations - create a schema migration"
@@ -20,7 +20,7 @@ case "$CMD" in
2020
"test" )
2121
DJANGO_SETTINGS_MODULE=test_settings django-admin test waffle $@ ;;
2222
"lint" )
23-
flake8 waffle $@ ;;
23+
ruff check ;;
2424
"typecheck" )
2525
mypy waffle $@ ;;
2626
"shell" )

test_settings.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
# Make filepaths relative to settings.
1212
ROOT = os.path.dirname(os.path.abspath(__file__))
13-
path = lambda *a: os.path.join(ROOT, *a)
13+
path = lambda *a: os.path.join(ROOT, *a) # noqa: E731
1414

1515
DEBUG = True
1616
TEST_RUNNER = 'django.test.runner.DiscoverRunner'

waffle/__init__.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def get_waffle_sample_model() -> type[AbstractBaseSample]:
4242

4343

4444
def get_waffle_model(setting_name: str) -> (
45-
type[AbstractBaseFlag] | type[AbstractBaseSwitch] | type[AbstractBaseSample]
45+
type[AbstractBaseFlag | AbstractBaseSwitch | AbstractBaseSample]
4646
):
4747
"""
4848
Returns the waffle Flag model that is active in this project.
@@ -63,12 +63,8 @@ def get_waffle_model(setting_name: str) -> (
6363
try:
6464
return django_apps.get_model(flag_model_name)
6565
except ValueError:
66-
raise ImproperlyConfigured("WAFFLE_{} must be of the form 'app_label.model_name'".format(
67-
setting_name
68-
))
66+
raise ImproperlyConfigured(f"WAFFLE_{setting_name} must be of the form 'app_label.model_name'")
6967
except LookupError:
7068
raise ImproperlyConfigured(
71-
"WAFFLE_{} refers to model '{}' that has not been installed".format(
72-
setting_name, flag_model_name
73-
)
69+
f"WAFFLE_{setting_name} refers to model '{flag_model_name}' that has not been installed"
7470
)

waffle/management/commands/waffle_delete.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def handle(self, *args: Any, **options: Any) -> None:
3838
flag_queryset = get_waffle_flag_model().objects.filter(name__in=flags)
3939
flag_count = flag_queryset.count()
4040
flag_queryset.delete()
41-
self.stdout.write('Deleted %s Flags' % flag_count)
41+
self.stdout.write(f'Deleted {flag_count} Flags')
4242

4343
switches = options['switch_names']
4444
if switches:
@@ -47,11 +47,11 @@ def handle(self, *args: Any, **options: Any) -> None:
4747
)
4848
switch_count = switches_queryset.count()
4949
switches_queryset.delete()
50-
self.stdout.write('Deleted %s Switches' % switch_count)
50+
self.stdout.write(f'Deleted {switch_count} Switches')
5151

5252
samples = options['sample_names']
5353
if samples:
5454
sample_queryset = get_waffle_sample_model().objects.filter(name__in=samples)
5555
sample_count = sample_queryset.count()
5656
sample_queryset.delete()
57-
self.stdout.write('Deleted %s Samples' % sample_count)
57+
self.stdout.write(f'Deleted {sample_count} Samples')

waffle/management/commands/waffle_flag.py

+16-16
Original file line numberDiff line numberDiff line change
@@ -104,19 +104,19 @@ def handle(self, *args: Any, **options: Any) -> None:
104104
if options['list_flags']:
105105
self.stdout.write('Flags:')
106106
for flag in get_waffle_flag_model().objects.iterator():
107-
self.stdout.write('NAME: %s' % flag.name)
108-
self.stdout.write('SUPERUSERS: %s' % flag.superusers)
109-
self.stdout.write('EVERYONE: %s' % flag.everyone)
110-
self.stdout.write('AUTHENTICATED: %s' % flag.authenticated)
111-
self.stdout.write('PERCENT: %s' % flag.percent)
112-
self.stdout.write('TESTING: %s' % flag.testing)
113-
self.stdout.write('ROLLOUT: %s' % flag.rollout)
114-
self.stdout.write('STAFF: %s' % flag.staff)
115-
self.stdout.write('GROUPS: %s' % list(
116-
flag.groups.values_list('name', flat=True))
107+
self.stdout.write(f'NAME: {flag.name}')
108+
self.stdout.write(f'SUPERUSERS: {flag.superusers}')
109+
self.stdout.write(f'EVERYONE: {flag.everyone}')
110+
self.stdout.write(f'AUTHENTICATED: {flag.authenticated}')
111+
self.stdout.write(f'PERCENT: {flag.percent}')
112+
self.stdout.write(f'TESTING: {flag.testing}')
113+
self.stdout.write(f'ROLLOUT: {flag.rollout}')
114+
self.stdout.write(f'STAFF: {flag.staff}')
115+
self.stdout.write('GROUPS: {}'.format(list(
116+
flag.groups.values_list('name', flat=True)))
117117
)
118-
self.stdout.write('USERS: %s' % list(
119-
flag.users.values_list(UserModel.USERNAME_FIELD, flat=True))
118+
self.stdout.write('USERS: {}'.format(list(
119+
flag.users.values_list(UserModel.USERNAME_FIELD, flat=True)))
120120
)
121121
self.stdout.write('')
122122
return
@@ -129,7 +129,7 @@ def handle(self, *args: Any, **options: Any) -> None:
129129
if options['create']:
130130
flag, created = get_waffle_flag_model().objects.get_or_create(name=flag_name)
131131
if created:
132-
self.stdout.write('Creating flag: %s' % flag_name)
132+
self.stdout.write(f'Creating flag: {flag_name}')
133133
else:
134134
try:
135135
flag = get_waffle_flag_model().objects.get(name=flag_name)
@@ -149,7 +149,7 @@ def handle(self, *args: Any, **options: Any) -> None:
149149
group_instance = Group.objects.get(name=group)
150150
group_hash[group_instance.name] = group_instance.id
151151
except Group.DoesNotExist:
152-
raise CommandError('Group %s does not exist' % group)
152+
raise CommandError(f'Group {group} does not exist')
153153
# If 'append' was not passed, we clear related groups
154154
if not options['append']:
155155
flag.groups.clear()
@@ -168,11 +168,11 @@ def handle(self, *args: Any, **options: Any) -> None:
168168
)
169169
user_hash.add(user_instance)
170170
except UserModel.DoesNotExist:
171-
raise CommandError('User %s does not exist' % username)
171+
raise CommandError(f'User {username} does not exist')
172172
# If 'append' was not passed, we clear related users
173173
if not options['append']:
174174
flag.users.clear()
175-
self.stdout.write('Setting user(s): %s' % user_hash)
175+
self.stdout.write(f'Setting user(s): {user_hash}')
176176
# for user in user_hash:
177177
flag.users.add(*[user.id for user in user_hash])
178178
elif hasattr(flag, option):

waffle/management/commands/waffle_sample.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,15 @@ def handle(self, *args: Any, **options: Any) -> None:
4848
try:
4949
percent = float(percent)
5050
if not (0.0 <= percent <= 100.0):
51-
raise ValueError()
51+
raise ValueError
5252
except ValueError:
5353
raise CommandError('You need to enter a valid percentage value.')
5454

5555
if options['create']:
5656
sample, created = get_waffle_sample_model().objects.get_or_create(
5757
name=sample_name, defaults={'percent': 0})
5858
if created:
59-
self.stdout.write('Creating sample: %s' % sample_name)
59+
self.stdout.write(f'Creating sample: {sample_name}')
6060
else:
6161
try:
6262
sample = get_waffle_sample_model().objects.get(name=sample_name)

0 commit comments

Comments
 (0)