Skip to content

Commit 00f15fe

Browse files
committed
Ruff
1 parent a0e9f71 commit 00f15fe

17 files changed

+134
-62
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', 3.12 ]
20+
python-version: [ 3.8, 3.9, '3.10', 3.11, 3.12 ]
2021

2122
steps:
2223
- uses: actions/checkout@v4
@@ -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

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

pyproject.toml

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

waffle/management/commands/waffle_switch.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
def on_off_bool(string: str) -> bool:
1010
if string not in ['on', 'off']:
11-
raise ArgumentTypeError("invalid choice: %r (choose from 'on', "
12-
"'off')" % string)
11+
raise ArgumentTypeError(f"invalid choice: {string!r} (choose from 'on', "
12+
"'off')")
1313
return string == 'on'
1414

1515

@@ -60,7 +60,7 @@ def handle(self, *args: Any, **options: Any) -> None:
6060
name=switch_name
6161
)
6262
if created:
63-
self.stdout.write('Creating switch: %s' % switch_name)
63+
self.stdout.write(f'Creating switch: {switch_name}')
6464
else:
6565
try:
6666
switch = get_waffle_switch_model().objects.get(name=switch_name)

waffle/templatetags/waffle_tags.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def __init__(self, nodelist_true, nodelist_false, condition, name,
2020
self.compiled_name = compiled_name
2121

2222
def __repr__(self):
23-
return '<Waffle node: %s>' % self.name
23+
return f'<Waffle node: {self.name}>'
2424

2525
def __iter__(self):
2626
yield from self.nodelist_true
@@ -41,16 +41,15 @@ def render(self, context):
4141
def handle_token(cls, parser, token, kind, condition):
4242
bits = token.split_contents()
4343
if len(bits) < 2:
44-
raise template.TemplateSyntaxError("%r tag requires an argument" %
45-
bits[0])
44+
raise template.TemplateSyntaxError(f"{bits[0]!r} tag requires an argument")
4645

4746
name = bits[1]
4847
compiled_name = parser.compile_filter(name)
4948

50-
nodelist_true = parser.parse(('else', 'end%s' % kind))
49+
nodelist_true = parser.parse(('else', f'end{kind}'))
5150
token = parser.next_token()
5251
if token.contents == 'else':
53-
nodelist_false = parser.parse(('end%s' % kind,))
52+
nodelist_false = parser.parse((f'end{kind}',))
5453
parser.delete_first_token()
5554
else:
5655
nodelist_false = template.NodeList()

0 commit comments

Comments
 (0)