diff --git a/cachalot/api.py b/cachalot/api.py index b451891..22c8793 100644 --- a/cachalot/api.py +++ b/cachalot/api.py @@ -45,7 +45,7 @@ def _get_tables(tables_or_models): else table_or_model._meta.db_table) -def invalidate(*tables_or_models, **kwargs): +def invalidate(*tables_or_models, cache_alias=None, db_alias=None) -> None: """ Clears what was cached by django-cachalot implying one or more SQL tables or models from ``tables_or_models``. @@ -68,13 +68,6 @@ def invalidate(*tables_or_models, **kwargs): :returns: Nothing :rtype: NoneType """ - # TODO: Replace with positional arguments when we drop Python 2 support. - cache_alias = kwargs.pop('cache_alias', None) - db_alias = kwargs.pop('db_alias', None) - for k in kwargs: - raise TypeError( - "invalidate() got an unexpected keyword argument '%s'" % k) - send_signal = False invalidated = set() for cache_alias, db_alias, tables in _cache_db_tables_iterator( @@ -90,7 +83,7 @@ def invalidate(*tables_or_models, **kwargs): post_invalidation.send(table, db_alias=db_alias) -def get_last_invalidation(*tables_or_models, **kwargs): +def get_last_invalidation(*tables_or_models, cache_alias=None, db_alias=None): """ Returns the timestamp of the most recent invalidation of the given ``tables_or_models``. If ``tables_or_models`` is not specified, @@ -112,13 +105,6 @@ def get_last_invalidation(*tables_or_models, **kwargs): :returns: The timestamp of the most recent invalidation :rtype: float """ - # TODO: Replace with positional arguments when we drop Python 2 support. - cache_alias = kwargs.pop('cache_alias', None) - db_alias = kwargs.pop('db_alias', None) - for k in kwargs: - raise TypeError("get_last_invalidation() got an unexpected " - "keyword argument '%s'" % k) - last_invalidation = 0.0 for cache_alias, db_alias, tables in _cache_db_tables_iterator( list(_get_tables(tables_or_models)), cache_alias, db_alias): @@ -160,7 +146,9 @@ def cachalot_disabled(all_queries=False): :arg all_queries: Any query, including already evaluated queries, are re-evaluated. :type all_queries: bool """ - was_enabled = getattr(LOCAL_STORAGE, "cachalot_enabled", cachalot_settings.CACHALOT_ENABLED) + was_enabled = getattr( + LOCAL_STORAGE, "cachalot_enabled", cachalot_settings.CACHALOT_ENABLED + ) LOCAL_STORAGE.cachalot_enabled = False LOCAL_STORAGE.disable_on_all = all_queries yield diff --git a/cachalot/apps.py b/cachalot/apps.py index bfcd3d5..9bb28f1 100644 --- a/cachalot/apps.py +++ b/cachalot/apps.py @@ -15,10 +15,8 @@ def check_cache_compatibility(app_configs, **kwargs): cache_backend = cache['BACKEND'] if cache_backend not in SUPPORTED_CACHE_BACKENDS: return [Warning( - 'Cache backend %r is not supported by django-cachalot.' - % cache_backend, - hint='Switch to a supported cache backend ' - 'like Redis or Memcached.', + f'Cache backend {cache_backend} is not supported by django-cachalot.', + hint='Switch to a supported cache backend like Redis or Memcached.', id='cachalot.W001')] return [] @@ -27,14 +25,12 @@ def check_cache_compatibility(app_configs, **kwargs): def check_databases_compatibility(app_configs, **kwargs): errors = [] databases = settings.DATABASES - original_enabled_databases = getattr(settings, 'CACHALOT_DATABASES', - SUPPORTED_ONLY) + original_enabled_databases = getattr(settings, 'CACHALOT_DATABASES', SUPPORTED_ONLY) enabled_databases = cachalot_settings.CACHALOT_DATABASES if original_enabled_databases == SUPPORTED_ONLY: if not cachalot_settings.CACHALOT_DATABASES: errors.append(Warning( - 'None of the configured databases are supported ' - 'by django-cachalot.', + 'None of the configured databases are supported by django-cachalot.', hint='Use a supported database, or remove django-cachalot, or ' 'put at least one database alias in `CACHALOT_DATABASES` ' 'to force django-cachalot to use it.', @@ -46,15 +42,15 @@ def check_databases_compatibility(app_configs, **kwargs): engine = databases[db_alias]['ENGINE'] if engine not in SUPPORTED_DATABASE_ENGINES: errors.append(Warning( - 'Database engine %r is not supported ' - 'by django-cachalot.' % engine, + f'Database engine {engine} is not supported ' + 'by django-cachalot.', hint='Switch to a supported database engine.', - id='cachalot.W003' + id='cachalot.W003', )) else: errors.append(Error( - 'Database alias %r from `CACHALOT_DATABASES` ' - 'is not defined in `DATABASES`.' % db_alias, + f'Database alias {db_alias} from `CACHALOT_DATABASES` ' + 'is not defined in `DATABASES`.', hint='Change `CACHALOT_DATABASES` to be compliant with' '`CACHALOT_DATABASES`', id='cachalot.E001', @@ -69,8 +65,8 @@ def check_databases_compatibility(app_configs, **kwargs): )) else: errors.append(Error( - "`CACHALOT_DATABASES` must be either %r or a list, tuple, " - "frozenset or set of database aliases." % SUPPORTED_ONLY, + f"`CACHALOT_DATABASES` must be either {SUPPORTED_ONLY} or a list, tuple, " + "frozenset or set of database aliases.", hint='Remove `CACHALOT_DATABASES` or change it.', id='cachalot.E002', )) diff --git a/cachalot/jinja2ext.py b/cachalot/jinja2ext.py index b189b62..5a3422b 100644 --- a/cachalot/jinja2ext.py +++ b/cachalot/jinja2ext.py @@ -12,9 +12,7 @@ class CachalotExtension(Extension): def __init__(self, environment): super(CachalotExtension, self).__init__(environment) - - self.environment.globals.update( - get_last_invalidation=get_last_invalidation) + self.environment.globals.update(get_last_invalidation=get_last_invalidation) def parse_args(self, parser): args = [] @@ -23,14 +21,11 @@ class CachalotExtension(Extension): stream = parser.stream while stream.current.type != 'block_end': - if stream.current.type == 'name' \ - and stream.look().type == 'assign': + if stream.current.type == 'name' and stream.look().type == 'assign': key = stream.current.value if key not in self.allowed_kwargs: - parser.fail( - "'%s' is not a valid keyword argument " - "for {%% cache %%}" % key, - stream.current.lineno) + parser.fail(f"'{key}' is not a valid keyword argument " + "for {%% cache %%}", stream.current.lineno) stream.skip(2) value = parser.parse_expression() kwargs.append(Keyword(key, value, lineno=value.lineno)) @@ -49,7 +44,7 @@ class CachalotExtension(Extension): lineno = next(parser.stream).lineno args, kwargs = self.parse_args(parser) default_cache_key = (None if parser.filename is None - else '%s:%d' % (parser.filename, lineno)) + else f'{parser.filename}:{lineno}') kwargs.append(Keyword('default_cache_key', Const(default_cache_key), lineno=lineno)) body = parser.parse_statements(['name:end' + tag], drop_needle=True) diff --git a/cachalot/management/commands/invalidate_cachalot.py b/cachalot/management/commands/invalidate_cachalot.py index fe013da..12c16b9 100644 --- a/cachalot/management/commands/invalidate_cachalot.py +++ b/cachalot/management/commands/invalidate_cachalot.py @@ -34,9 +34,9 @@ class Command(BaseCommand): model_name = label.split('.')[-1] models.append(apps.get_model(app_label, model_name)) - cache_str = '' if cache_alias is None else "on cache '%s'" % cache_alias - db_str = '' if db_alias is None else "for database '%s'" % db_alias - keys_str = 'keys for %s models' % len(models) if labels else 'all keys' + cache_str = '' if cache_alias is None else f"on cache '{cache_alias}'" + db_str = '' if db_alias is None else f"for database '{db_alias}'" + keys_str = f'keys for {len(models)} models' if labels else 'all keys' if verbosity > 0: self.stdout.write(' '.join(filter(bool, ['Invalidating', keys_str, diff --git a/cachalot/panels.py b/cachalot/panels.py index e9df548..d19f0fc 100644 --- a/cachalot/panels.py +++ b/cachalot/panels.py @@ -49,8 +49,7 @@ class CachalotPanel(Panel): model_cache_keys = { get_table_cache_key(db_alias, model._meta.db_table): model for model in models} - for cache_key, timestamp in cache.get_many( - model_cache_keys.keys()).items(): + for cache_key, timestamp in cache.get_many(model_cache_keys.keys()).items(): invalidation = datetime.fromtimestamp(timestamp) model = model_cache_keys[cache_key] data[db_alias].append( @@ -64,6 +63,5 @@ class CachalotPanel(Panel): @property def nav_subtitle(self): if self.enabled and self.last_invalidation is not None: - return (_('Last invalidation: %s') - % timesince(self.last_invalidation)) + return _('Last invalidation: %s') % timesince(self.last_invalidation) return '' diff --git a/cachalot/tests/debug_toolbar.py b/cachalot/tests/debug_toolbar.py index addbaa2..ad05e10 100644 --- a/cachalot/tests/debug_toolbar.py +++ b/cachalot/tests/debug_toolbar.py @@ -22,8 +22,7 @@ class DebugToolbarTestCase(LiveServerTestCase): UUID(store_id) render_panel_url = toolbar.attrs['data-render-panel-url'] panel_id = soup.find(title='Cachalot')['class'][0] - panel_url = ('%s?store_id=%s&panel_id=%s' - % (render_panel_url, store_id, panel_id)) + panel_url = f'{render_panel_url}?store_id={store_id}&panel_id={panel_id}' # # Rendering panel diff --git a/cachalot/tests/read.py b/cachalot/tests/read.py index d1ccfa2..94e2d2c 100644 --- a/cachalot/tests/read.py +++ b/cachalot/tests/read.py @@ -697,24 +697,22 @@ class ReadTestCase(TestUtilsMixin, TransactionTestCase): r'(?:None )?ALL None None None None 2 100\.0 Using filesort') else: expected = ( - r'-> Sort row IDs: cachalot_test.`name` \(cost=[\d\.]+ rows=\d\)\n ' - r'-> Table scan on cachalot_test \(cost=[\d\.]+ rows=\d\)\n' + r'-> Sort row IDs: cachalot_test.`name` \(cost=[\d\.]+ rows=\d\)\n' + r' -> Table scan on cachalot_test \(cost=[\d\.]+ rows=\d\)\n' ) else: explain_kwargs.update( analyze=True, costs=False, ) - operation_detail = (r'\(actual time=[\d\.]+..[\d\.]+\ ' - r'rows=\d+ loops=\d+\)') + operation_detail = r'\(actual time=[\d\.]+..[\d\.]+\ rows=\d+ loops=\d+\)' expected = ( - r'^Sort %s\n' + rf'^Sort {operation_detail}\n' r' Sort Key: name\n' r' Sort Method: quicksort Memory: \d+kB\n' - r' -> Seq Scan on cachalot_test %s\n' + rf' -> Seq Scan on cachalot_test {operation_detail}\n' r'Planning Time: [\d\.]+ ms\n' - r'Execution Time: [\d\.]+ ms$') % (operation_detail, - operation_detail) + r'Execution Time: [\d\.]+ ms$') with self.assertNumQueries( 2 if self.is_mysql and django_version[0] < 3 else 1): @@ -729,7 +727,7 @@ class ReadTestCase(TestUtilsMixin, TransactionTestCase): Tests if ``Model.objects.raw`` queries are not cached. """ - sql = 'SELECT * FROM %s;' % Test._meta.db_table + sql = f'SELECT * FROM {Test._meta.db_table};' with self.assertNumQueries(1): data1 = list(Test.objects.raw(sql)) @@ -752,13 +750,10 @@ class ReadTestCase(TestUtilsMixin, TransactionTestCase): """ Tests if queries executed from a DB cursor are not cached. """ - - attname_column_list = [f.get_attname_column() - for f in Test._meta.fields] + attname_column_list = [f.get_attname_column() for f in Test._meta.fields] attnames = [t[0] for t in attname_column_list] columns = [t[1] for t in attname_column_list] - sql = "SELECT CAST('é' AS CHAR), %s FROM %s;" % (', '.join(columns), - Test._meta.db_table) + sql = f"SELECT CAST('é' AS CHAR), {', '.join(columns)} FROM {Test._meta.db_table};" with self.assertNumQueries(1): with connection.cursor() as cursor: @@ -780,9 +775,8 @@ class ReadTestCase(TestUtilsMixin, TransactionTestCase): for f in Test._meta.fields] attnames = [t[0] for t in attname_column_list] columns = [t[1] for t in attname_column_list] - sql = "SELECT CAST('é' AS CHAR), %s FROM %s;" % (', '.join(columns), - Test._meta.db_table) - sql = sql.encode('utf-8') + sql = f"SELECT CAST('é' AS CHAR), {', '.join(columns)} " \ + f"FROM {Test._meta.db_table};".encode('utf-8') with self.assertNumQueries(1): with connection.cursor() as cursor: @@ -855,18 +849,18 @@ class ReadTestCase(TestUtilsMixin, TransactionTestCase): """ table_name = 'Clémentine' if self.is_postgresql: - table_name = '"%s"' % table_name + table_name = f'"{table_name}"' with connection.cursor() as cursor: - cursor.execute('CREATE TABLE %s (taste VARCHAR(20));' % table_name) + cursor.execute(f'CREATE TABLE {table_name} (taste VARCHAR(20));') qs = Test.objects.extra(tables=['Clémentine'], - select={'taste': '%s.taste' % table_name}) + select={'taste': f'{table_name}.taste'}) # Here the table `Clémentine` is not detected because it is not # registered by Django, and we only check for registered tables # to avoid creating an extra SQL query fetching table names. self.assert_tables(qs, Test) self.assert_query_cached(qs) with connection.cursor() as cursor: - cursor.execute('DROP TABLE %s;' % table_name) + cursor.execute(f'DROP TABLE {table_name};') def test_unmanaged_model(self): qs = UnmanagedModel.objects.all() diff --git a/cachalot/tests/settings.py b/cachalot/tests/settings.py index 3c370eb..a1ff1d0 100644 --- a/cachalot/tests/settings.py +++ b/cachalot/tests/settings.py @@ -125,8 +125,7 @@ class SettingsTestCase(TestUtilsMixin, TransactionTestCase): with self.settings(CACHALOT_INVALIDATE_RAW=False): with self.assertNumQueries(1): with connection.cursor() as cursor: - cursor.execute("UPDATE %s SET name = 'new name';" - % Test._meta.db_table) + cursor.execute(f"UPDATE {Test._meta.db_table} SET name = 'new name';") with self.assertNumQueries(0): list(Test.objects.all()) diff --git a/cachalot/tests/write.py b/cachalot/tests/write.py index 982bb63..cbfb8f9 100644 --- a/cachalot/tests/write.py +++ b/cachalot/tests/write.py @@ -161,8 +161,7 @@ class WriteTestCase(TestUtilsMixin, TransactionTestCase): self.assertListEqual(data2, [t1.name]) with self.assertNumQueries(2 if self.is_sqlite else 1): - Test.objects.bulk_create([Test(name='test%s' % i) - for i in range(2, 11)]) + Test.objects.bulk_create([Test(name=f'test{i}') for i in range(2, 11)]) with self.assertNumQueries(1): self.assertEqual(Test.objects.count(), 10) with self.assertNumQueries(2 if self.is_sqlite else 1): diff --git a/cachalot/utils.py b/cachalot/utils.py index fe9cc0c..f0e6edf 100644 --- a/cachalot/utils.py +++ b/cachalot/utils.py @@ -75,8 +75,7 @@ def get_query_cache_key(compiler): """ sql, params = compiler.as_sql() check_parameter_types(params) - cache_key = '%s:%s:%s' % (compiler.using, sql, - [str(p) for p in params]) + cache_key = f'{compiler.using}:{sql}:{[str(p) for p in params]}' return sha1(cache_key.encode('utf-8')).hexdigest() @@ -91,7 +90,7 @@ def get_table_cache_key(db_alias, table): :return: A cache key :rtype: int """ - cache_key = '%s:%s' % (db_alias, table) + cache_key = f'{db_alias}:{table}' return sha1(cache_key.encode('utf-8')).hexdigest()