Enable more rules for ruff (#562)

This commit is contained in:
Alexandr Artemyev 2024-07-05 19:38:26 +05:00 committed by GitHub
parent 8cec9c24b0
commit 8c6552fdaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 132 additions and 153 deletions

View file

@ -33,10 +33,10 @@ class ConstanceAdmin(admin.ModelAdmin):
super().__init__(model, admin_site)
def get_urls(self):
info = self.model._meta.app_label, self.model._meta.module_name
info = f'{self.model._meta.app_label}_{self.model._meta.module_name}'
return [
path('', self.admin_site.admin_view(self.changelist_view), name='%s_%s_changelist' % info),
path('', self.admin_site.admin_view(self.changelist_view), name='%s_%s_add' % info),
path('', self.admin_site.admin_view(self.changelist_view), name=f'{info}_changelist'),
path('', self.admin_site.admin_view(self.changelist_view), name=f'{info}_add'),
]
def get_config_value(self, name, options, form, initial):
@ -73,9 +73,7 @@ class ConstanceAdmin(admin.ModelAdmin):
return config_value
def get_changelist_form(self, request):
"""
Returns a Form class for use in the changelist_view.
"""
"""Returns a Form class for use in the changelist_view."""
# Defaults to self.change_list_form in order to preserve backward
# compatibility
return self.change_list_form
@ -91,18 +89,9 @@ class ConstanceAdmin(admin.ModelAdmin):
form = form_cls(data=request.POST, files=request.FILES, initial=initial, request=request)
if form.is_valid():
form.save()
messages.add_message(
request,
messages.SUCCESS,
_('Live settings updated successfully.'),
)
messages.add_message(request, messages.SUCCESS, _('Live settings updated successfully.'))
return HttpResponseRedirect('.')
else:
messages.add_message(
request,
messages.ERROR,
_('Failed to update live settings.'),
)
messages.add_message(request, messages.ERROR, _('Failed to update live settings.'))
context = dict(
self.admin_site.each_context(request),
config_values=[],
@ -125,7 +114,7 @@ class ConstanceAdmin(admin.ModelAdmin):
context['fieldsets'] = []
for fieldset_title, fieldset_data in fieldset_items:
if type(fieldset_data) == dict:
if isinstance(fieldset_data, dict):
fields_list = fieldset_data['fields']
collapse = fieldset_data.get('collapse', False)
else:
@ -133,9 +122,12 @@ class ConstanceAdmin(admin.ModelAdmin):
collapse = False
absent_fields = [field for field in fields_list if field not in settings.CONFIG]
assert not any(absent_fields), (
'CONSTANCE_CONFIG_FIELDSETS contains field(s) that does ' 'not exist: %s' % ', '.join(absent_fields)
)
if any(absent_fields):
raise ValueError(
'CONSTANCE_CONFIG_FIELDSETS contains field(s) that does not exist(s): {}'.format(
', '.join(absent_fields)
)
)
config_values = []
@ -182,7 +174,7 @@ class Config:
return False
def get_change_permission(self):
return 'change_%s' % self.model_name
return f'change_{self.model_name}'
@property
def app_config(self):

View file

@ -1,6 +1,9 @@
from django.apps import AppConfig
from django.core import checks
from django.utils.translation import gettext_lazy as _
from constance.checks import check_fieldsets
class ConstanceConfig(AppConfig):
name = 'constance'
@ -8,4 +11,4 @@ class ConstanceConfig(AppConfig):
default_auto_field = 'django.db.models.AutoField'
def ready(self):
from . import checks
checks.register(check_fieldsets, 'constance')

View file

@ -1,6 +1,4 @@
"""
Defines the base constance backend
"""
"""Defines the base constance backend."""
class Backend:
@ -19,7 +17,5 @@ class Backend:
raise NotImplementedError
def set(self, key, value):
"""
Add the value to the backend store given the key.
"""
"""Add the value to the backend store given the key."""
raise NotImplementedError

View file

@ -33,8 +33,8 @@ class DatabaseBackend(Backend):
if isinstance(self._cache, LocMemCache):
raise ImproperlyConfigured(
'The CONSTANCE_DATABASE_CACHE_BACKEND setting refers to a '
"subclass of Django's local-memory backend (%r). Please "
'set it to a backend that supports cross-process caching.' % settings.DATABASE_CACHE_BACKEND
f"subclass of Django's local-memory backend ({settings.DATABASE_CACHE_BACKEND!r}). Please "
'set it to a backend that supports cross-process caching.'
)
else:
self._cache = None
@ -102,7 +102,7 @@ class DatabaseBackend(Backend):
with transaction.atomic(using=queryset.db):
queryset.create(key=key, value=value)
created = True
except IntegrityError as error:
except IntegrityError:
# Allow concurrent writes
constance = queryset.get(key=key)

View file

@ -1,14 +1,13 @@
from threading import Lock
from .. import config
from .. import signals
from constance import config
from constance import signals
from . import Backend
class MemoryBackend(Backend):
"""
Simple in-memory backend that should be mostly used for testing purposes
"""
"""Simple in-memory backend that should be mostly used for testing purposes."""
_storage = {}
_lock = Lock()
@ -22,7 +21,7 @@ class MemoryBackend(Backend):
def mget(self, keys):
if not keys:
return
return None
result = []
with self._lock:
for key in keys:

View file

@ -5,10 +5,11 @@ from time import monotonic
from django.core.exceptions import ImproperlyConfigured
from .. import config
from .. import settings
from .. import signals
from .. import utils
from constance import config
from constance import settings
from constance import signals
from constance import utils
from . import Backend
@ -23,7 +24,7 @@ class RedisBackend(Backend):
try:
import redis
except ImportError:
raise ImproperlyConfigured('The Redis backend requires redis-py to be installed.')
raise ImproperlyConfigured('The Redis backend requires redis-py to be installed.') from None
if isinstance(settings.REDIS_CONNECTION, str):
self._rd = redis.from_url(settings.REDIS_CONNECTION)
else:
@ -35,7 +36,7 @@ class RedisBackend(Backend):
def get(self, key):
value = self._rd.get(self.add_prefix(key))
if value:
return loads(value)
return loads(value) # noqa: S301
return None
def mget(self, keys):
@ -44,7 +45,7 @@ class RedisBackend(Backend):
prefixed_keys = [self.add_prefix(key) for key in keys]
for key, value in zip(keys, self._rd.mget(prefixed_keys)):
if value:
yield key, loads(value)
yield key, loads(value) # noqa: S301
def set(self, key, value):
old_value = self.get(key)

View file

@ -3,20 +3,18 @@ from . import utils
class Config:
"""
The global config wrapper that handles the backend.
"""
"""The global config wrapper that handles the backend."""
def __init__(self):
super().__setattr__('_backend', utils.import_module_attr(settings.BACKEND)())
def __getattr__(self, key):
try:
if not len(settings.CONFIG[key]) in (2, 3):
if len(settings.CONFIG[key]) not in (2, 3):
raise AttributeError(key)
default = settings.CONFIG[key][0]
except KeyError:
raise AttributeError(key)
except KeyError as e:
raise AttributeError(key) from e
result = self._backend.get(key)
if result is None:
result = default

View file

@ -1,14 +1,11 @@
from typing import List
from typing import Set
from typing import Tuple
from __future__ import annotations
from django.core import checks
from django.core.checks import CheckMessage
from django.utils.translation import gettext_lazy as _
@checks.register('constance')
def check_fieldsets(*args, **kwargs) -> List[CheckMessage]:
def check_fieldsets(*args, **kwargs) -> list[CheckMessage]:
"""
A Django system check to make sure that, if defined,
CONFIG_FIELDSETS is consistent with settings.CONFIG.
@ -21,7 +18,7 @@ def check_fieldsets(*args, **kwargs) -> List[CheckMessage]:
missing_keys, extra_keys = get_inconsistent_fieldnames()
if missing_keys:
check = checks.Warning(
_('CONSTANCE_CONFIG_FIELDSETS is missing ' 'field(s) that exists in CONSTANCE_CONFIG.'),
_('CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.'),
hint=', '.join(sorted(missing_keys)),
obj='settings.CONSTANCE_CONFIG',
id='constance.E001',
@ -29,7 +26,7 @@ def check_fieldsets(*args, **kwargs) -> List[CheckMessage]:
errors.append(check)
if extra_keys:
check = checks.Warning(
_('CONSTANCE_CONFIG_FIELDSETS contains extra ' 'field(s) that does not exist in CONFIG.'),
_('CONSTANCE_CONFIG_FIELDSETS contains extra field(s) that does not exist in CONFIG.'),
hint=', '.join(sorted(extra_keys)),
obj='settings.CONSTANCE_CONFIG',
id='constance.E002',
@ -38,7 +35,7 @@ def check_fieldsets(*args, **kwargs) -> List[CheckMessage]:
return errors
def get_inconsistent_fieldnames() -> Tuple[Set, Set]:
def get_inconsistent_fieldnames() -> tuple[set, set]:
"""
Returns a pair of values:
1) set of keys from settings.CONFIG that are not accounted for in settings.CONFIG_FIELDSETS
@ -53,7 +50,7 @@ def get_inconsistent_fieldnames() -> Tuple[Set, Set]:
fieldset_items = settings.CONFIG_FIELDSETS
unique_field_names = set()
for fieldset_title, fields_list in fieldset_items:
for _fieldset_title, fields_list in fieldset_items:
# fields_list can be a dictionary, when a fieldset is defined as collapsible
# https://django-constance.readthedocs.io/en/latest/#fieldsets-collapsing
if isinstance(fields_list, dict) and 'fields' in fields_list:

View file

@ -166,7 +166,7 @@ class ConstanceForm(forms.Form):
missing_keys, extra_keys = get_inconsistent_fieldnames()
if missing_keys or extra_keys:
raise forms.ValidationError(
_('CONSTANCE_CONFIG_FIELDSETS is missing ' 'field(s) that exists in CONSTANCE_CONFIG.')
_('CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in CONSTANCE_CONFIG.')
)
return cleaned_data

View file

@ -4,10 +4,10 @@ from django.core.management import BaseCommand
from django.core.management import CommandError
from django.utils.translation import gettext as _
from ... import config
from ...forms import ConstanceForm
from ...models import Constance
from ...utils import get_values
from constance import config
from constance.forms import ConstanceForm
from constance.models import Constance
from constance.utils import get_values
def _set_constance_value(key, value):
@ -17,7 +17,6 @@ def _set_constance_value(key, value):
:param value:
:return:
"""
form = ConstanceForm(initial=get_values())
field = form.fields[key]
@ -55,18 +54,18 @@ class Command(BaseCommand):
if command == self.GET:
try:
self.stdout.write(str(getattr(config, key)), ending='\n')
except AttributeError:
raise CommandError(f'{key} is not defined in settings.CONSTANCE_CONFIG')
except AttributeError as e:
raise CommandError(f'{key} is not defined in settings.CONSTANCE_CONFIG') from e
elif command == self.SET:
try:
if len(value) == 1:
# assume that if a single argument was passed, the field doesn't expect a list
value = value[0]
_set_constance_value(key, value)
except KeyError:
raise CommandError(f'{key} is not defined in settings.CONSTANCE_CONFIG')
except KeyError as e:
raise CommandError(f'{key} is not defined in settings.CONSTANCE_CONFIG') from e
except ValidationError as e:
raise CommandError(', '.join(e))
raise CommandError(', '.join(e)) from e
elif command == self.LIST:
for k, v in get_values().items():
self.stdout.write(f'{k}\t{v}', ending='\n')

View file

@ -17,10 +17,11 @@ def _migrate_from_old_table(apps, schema_editor) -> None:
try:
with connection.cursor() as cursor:
cursor.execute(
f'INSERT INTO constance_constance ( {quoted_string} ) SELECT {quoted_string} FROM constance_config', []
f'INSERT INTO constance_constance ( {quoted_string} ) SELECT {quoted_string} FROM constance_config', # noqa: S608
[],
)
cursor.execute('DROP TABLE constance_config', [])
except DatabaseError as exc:
except DatabaseError:
logger.exception('copy data from old constance table to a new one')
Constance = apps.get_model('constance', 'Constance')

View file

@ -9,7 +9,7 @@ except ImportError:
"Couldn't find the the 3rd party app "
'django-picklefield which is required for '
'the constance database backend.'
)
) from None
class Constance(models.Model):

View file

@ -1 +1,3 @@
from .unittest import override_config # pragma: no cover
__all__ = ['override_config']

View file

@ -13,17 +13,13 @@ from constance import config as constance_config
@pytest.hookimpl(trylast=True)
def pytest_configure(config): # pragma: no cover
"""
Register override_config marker.
"""
config.addinivalue_line('markers', ('override_config(**kwargs): ' 'mark test to override django-constance config'))
"""Register override_config marker."""
config.addinivalue_line('markers', ('override_config(**kwargs): mark test to override django-constance config'))
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(item): # pragma: no cover
"""
Validate constance override marker params. Run test with overridden config.
"""
"""Validate constance override marker params. Run test with overridden config."""
marker = item.get_closest_marker('override_config')
if marker is not None:
if marker.args:
@ -42,17 +38,13 @@ class override_config(ContextDecorator):
"""
def enable(self):
"""
Store original config values and set overridden values.
"""
"""Store original config values and set overridden values."""
for key, value in self._to_override.items():
self._original_values[key] = getattr(constance_config, key)
setattr(constance_config, key, value)
def disable(self):
"""
Set original values to the config.
"""
"""Set original values to the config."""
for key, value in self._original_values.items():
setattr(constance_config, key, value)
@ -69,7 +61,5 @@ class override_config(ContextDecorator):
@pytest.fixture(name='override_config')
def _override_config():
"""
Make override_config available as a function fixture.
"""
"""Make override_config available as a function fixture."""
return override_config

View file

@ -3,7 +3,7 @@ from functools import wraps
from django.test import SimpleTestCase
from django.test.utils import override_settings
from .. import config
from constance import config
__all__ = ('override_config',)
@ -20,19 +20,16 @@ class override_config(override_settings):
self.original_values = {}
def __call__(self, test_func):
"""
Modify the decorated function to override config values.
"""
"""Modify the decorated function to override config values."""
if isinstance(test_func, type):
if not issubclass(test_func, SimpleTestCase):
raise Exception('Only subclasses of Django SimpleTestCase can be ' 'decorated with override_config')
raise Exception('Only subclasses of Django SimpleTestCase can be decorated with override_config')
return self.modify_test_case(test_func)
else:
@wraps(test_func)
def inner(*args, **kwargs):
with self:
return test_func(*args, **kwargs)
@wraps(test_func)
def inner(*args, **kwargs):
with self:
return test_func(*args, **kwargs)
return inner
@ -61,9 +58,7 @@ class override_config(override_settings):
return test_case
def enable(self):
"""
Store original config values and set overridden values.
"""
"""Store original config values and set overridden values."""
# Store the original values to an instance variable
for config_key in self.options:
self.original_values[config_key] = getattr(config, config_key)
@ -72,15 +67,11 @@ class override_config(override_settings):
self.unpack_values(self.options)
def disable(self):
"""
Set original values to the config.
"""
"""Set original values to the config."""
self.unpack_values(self.original_values)
@staticmethod
def unpack_values(options):
"""
Unpack values from the given dict to config.
"""
"""Unpack values from the given dict to config."""
for name, value in options.items():
setattr(config, name, value)

View file

@ -16,10 +16,7 @@ def get_values():
Get dictionary of values from the backend
:return:
"""
# First load a mapping between config name and default value
default_initial = ((name, options[0]) for name, options in settings.CONFIG.items())
# Then update the mapping with actually values from the backend
initial = dict(default_initial, **dict(config._backend.mget(settings.CONFIG)))
return initial
return dict(default_initial, **dict(config._backend.mget(settings.CONFIG)))

View file

@ -31,7 +31,6 @@ sys.path.insert(0, os.path.abspath('..'))
project = 'django-constance'
project_copyright = datetime.now().year.__str__() + ', Jazzband'
# author = ''
# The full version, including alpha/beta/rc tags
release = get_version()

View file

@ -19,8 +19,7 @@ class JsonField(fields.CharField):
def to_python(self, value):
if value:
return json.loads(value)
else:
return {}
return {}
def prepare_value(self, value):
return json.dumps(value)

View file

@ -79,7 +79,6 @@ WSGI_APPLICATION = 'cheeseshop.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
#'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'NAME': '/tmp/cheeseshop.db',
}
}

View file

@ -67,8 +67,38 @@ line-ending = "auto"
[tool.ruff.lint]
select = [
"I", # isort
"B",
"D",
"E",
"ERA",
"EXE",
"F",
"FBT",
"FURB",
"G",
"FA",
"I",
"ICN",
"INP",
"LOG",
"PGH",
"RET",
"RUF",
"S",
"SIM",
"TID",
"UP",
"W",
]
ignore = ["D1", "D203", "D205", "D415", "D212", "RUF012", "D400", "D401"]
[tool.ruff.lint.per-file-ignores]
"docs/*" = ["INP"]
"example/*" = ["S"]
"tests/*" = ["S"]
[tool.ruff.lint.isort]
force-single-line = true
[tool.ruff.lint.flake8-boolean-trap]
extend-allowed-calls = ["unittest.mock.patch", "django.db.models.Value"]

View file

@ -13,11 +13,11 @@ class TestDatabase(StorageTestsMixin, TestCase):
def test_database_queries(self):
# Read and set to default value
with self.assertNumQueries(5):
self.config.INT_VALUE
self.assertEqual(self.config.INT_VALUE, 1)
# Read again
with self.assertNumQueries(1):
self.config.INT_VALUE
self.assertEqual(self.config.INT_VALUE, 1)
# Set value
with self.assertNumQueries(2):

View file

@ -53,15 +53,10 @@ class StorageTestsMixin:
self.assertEqual(self.config.EMAIL_VALUE, 'foo@bar.com')
def test_nonexistent(self):
try:
self.config.NON_EXISTENT
except Exception as e:
self.assertEqual(type(e), AttributeError)
self.assertRaises(AttributeError, getattr, self.config, 'NON_EXISTENT')
try:
with self.assertRaises(AttributeError):
self.config.NON_EXISTENT = 1
except Exception as e:
self.assertEqual(type(e), AttributeError)
def test_missing_values(self):
# set some values and leave out others

View file

@ -148,18 +148,12 @@ class TestAdmin(TestCase):
request.user = self.superuser
request._dont_enforce_csrf_checks = True
with mock.patch('django.contrib.messages.add_message') as mock_message:
with mock.patch.object(ConstanceForm, '__init__', **initial_value, return_value=None) as mock_form:
response = self.options.changelist_view(request, {})
mock_form.assert_called_with(
data=request.POST, files=request.FILES, initial=initial_value, request=request
)
mock_message.assert_called_with(
request,
25,
_('Live settings updated successfully.'),
)
with mock.patch('django.contrib.messages.add_message') as mock_message, mock.patch.object(
ConstanceForm, '__init__', **initial_value, return_value=None
) as mock_form:
response = self.options.changelist_view(request, {})
mock_form.assert_called_with(data=request.POST, files=request.FILES, initial=initial_value, request=request)
mock_message.assert_called_with(request, 25, _('Live settings updated successfully.'))
self.assertIsInstance(response, HttpResponseRedirect)
@ -244,7 +238,7 @@ class TestAdmin(TestCase):
},
)
def test_fieldset_ordering_1(self):
"""Ordering of inner list should be preserved"""
"""Ordering of inner list should be preserved."""
self.client.login(username='admin', password='nimda')
request = self.rf.get('/admin/constance/config/')
request.user = self.superuser
@ -263,7 +257,7 @@ class TestAdmin(TestCase):
},
)
def test_fieldset_ordering_2(self):
"""Ordering of inner list should be preserved"""
"""Ordering of inner list should be preserved."""
self.client.login(username='admin', password='nimda')
request = self.rf.get('/admin/constance/config/')
request.user = self.superuser

View file

@ -34,7 +34,7 @@ class ChecksTestCase(TestCase):
@mock.patch(
'constance.settings.CONFIG_FIELDSETS',
{'Set1': list(settings.CONFIG.keys()) + ['FORGOTTEN_KEY']},
{'Set1': [*settings.CONFIG.keys(), 'FORGOTTEN_KEY']},
)
def test_get_inconsistent_fieldnames_for_extra_keys(self):
"""
@ -48,8 +48,6 @@ class ChecksTestCase(TestCase):
@mock.patch('constance.settings.CONFIG_FIELDSETS', {})
def test_check_fieldsets(self):
"""
check_fieldsets should not output warning if CONFIG_FIELDSETS is not defined.
"""
"""check_fieldsets should not output warning if CONFIG_FIELDSETS is not defined."""
del settings.CONFIG_FIELDSETS
self.assertEqual(0, len(check_fieldsets()))

View file

@ -1,3 +1,4 @@
import contextlib
from datetime import datetime
from io import StringIO
from textwrap import dedent
@ -18,10 +19,8 @@ class CliTestCase(TransactionTestCase):
self.out = StringIO()
def test_help(self):
try:
with contextlib.suppress(SystemExit):
call_command('constance', '--help')
except SystemExit:
pass
def test_list(self):
call_command('constance', 'list', stdout=self.out)

View file

@ -7,7 +7,8 @@ try:
from constance.test.pytest import override_config
class TestPytestOverrideConfigFunctionDecorator:
"""Test that the override_config decorator works correctly for Pytest classes.
"""
Test that the override_config decorator works correctly for Pytest classes.
Test usage of override_config on test method and as context manager.
"""
@ -68,7 +69,5 @@ class PytestTests(unittest.TestCase):
self.skipTest('Skip all pytest tests when using unittest')
def test_do_not_skip_silently(self):
"""
If no at least one test present, unittest silently skips module.
"""
"""If no at least one test present, unittest silently skips module."""
pass

View file

@ -5,7 +5,8 @@ from constance.test import override_config
class OverrideConfigFunctionDecoratorTestCase(TestCase):
"""Test that the override_config decorator works correctly.
"""
Test that the override_config decorator works correctly.
Test usage of override_config on test method and as context manager.
"""