From ea1da761963fdaf1e3d40bf399a1d5e144d89bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Clgen=20Sar=C4=B1kavak?= Date: Wed, 9 Apr 2025 09:24:18 +0300 Subject: [PATCH] Add pyupgrade to pre-commit hooks (#9682) --- .pre-commit-config.yaml | 6 ++++++ .../management/commands/drf_create_token.py | 2 +- rest_framework/fields.py | 6 +++--- rest_framework/negotiation.py | 2 +- rest_framework/permissions.py | 2 +- rest_framework/response.py | 2 +- rest_framework/schemas/coreapi.py | 2 +- rest_framework/schemas/openapi.py | 4 ++-- rest_framework/utils/field_mapping.py | 2 +- tests/schemas/test_coreapi.py | 2 +- tests/schemas/test_managementcommand.py | 4 ++-- tests/test_exceptions.py | 4 ++-- tests/test_fields.py | 10 +++++----- tests/test_generics.py | 2 +- tests/test_pagination.py | 10 +++++----- tests/test_permissions.py | 4 ++-- tests/test_relations_pk.py | 2 +- tests/test_renderers.py | 4 ++-- tests/test_response.py | 2 +- tests/test_routers.py | 12 ++++++------ tests/test_validation.py | 4 ++-- 21 files changed, 47 insertions(+), 41 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8939dd3db..27bbcb763 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,3 +31,9 @@ repos: hooks: - id: codespell exclude: locale|kickstarter-announcement.md|coreapi-0.1.1.js + +- repo: https://github.com/asottile/pyupgrade + rev: v3.19.1 + hooks: + - id: pyupgrade + args: ["--py39-plus", "--keep-percent-format"] diff --git a/rest_framework/authtoken/management/commands/drf_create_token.py b/rest_framework/authtoken/management/commands/drf_create_token.py index 3d6539244..3f4521fe4 100644 --- a/rest_framework/authtoken/management/commands/drf_create_token.py +++ b/rest_framework/authtoken/management/commands/drf_create_token.py @@ -42,4 +42,4 @@ class Command(BaseCommand): username) ) self.stdout.write( - 'Generated token {} for user {}'.format(token.key, username)) + f'Generated token {token.key} for user {username}') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6989edc0a..89c0a714c 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -111,7 +111,7 @@ def get_attribute(instance, attrs): # If we raised an Attribute or KeyError here it'd get treated # as an omitted field in `Field.get_attribute()`. Instead we # raise a ValueError to ensure the exception is not masked. - raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc)) + raise ValueError(f'Exception raised in callable attribute "{attr}"; original exception was: {exc}') return instance @@ -1103,7 +1103,7 @@ class DecimalField(Field): if self.localize: return localize_input(quantized) - return '{:f}'.format(quantized) + return f'{quantized:f}' def quantize(self, value): """ @@ -1861,7 +1861,7 @@ class SerializerMethodField(Field): def bind(self, field_name, parent): # The method name defaults to `get_{field_name}`. if self.method_name is None: - self.method_name = 'get_{field_name}'.format(field_name=field_name) + self.method_name = f'get_{field_name}' super().bind(field_name, parent) diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py index b4bbfa1f5..23012f71f 100644 --- a/rest_framework/negotiation.py +++ b/rest_framework/negotiation.py @@ -65,7 +65,7 @@ class DefaultContentNegotiation(BaseContentNegotiation): full_media_type = ';'.join( (renderer.media_type,) + tuple( - '{}={}'.format(key, value) + f'{key}={value}' for key, value in media_type_wrapper.params.items() ) ) diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 7c15eca58..768f6cb95 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -225,7 +225,7 @@ class DjangoModelPermissions(BasePermission): if hasattr(view, 'get_queryset'): queryset = view.get_queryset() assert queryset is not None, ( - '{}.get_queryset() returned None'.format(view.__class__.__name__) + f'{view.__class__.__name__}.get_queryset() returned None' ) return queryset return view.queryset diff --git a/rest_framework/response.py b/rest_framework/response.py index 6e756544c..507ea595f 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -66,7 +66,7 @@ class Response(SimpleTemplateResponse): content_type = self.content_type if content_type is None and charset is not None: - content_type = "{}; charset={}".format(media_type, charset) + content_type = f"{media_type}; charset={charset}" elif content_type is None: content_type = media_type self['Content-Type'] = content_type diff --git a/rest_framework/schemas/coreapi.py b/rest_framework/schemas/coreapi.py index 582aba196..657178304 100644 --- a/rest_framework/schemas/coreapi.py +++ b/rest_framework/schemas/coreapi.py @@ -68,7 +68,7 @@ class LinkNode(dict): current_val = self.methods_counter[preferred_key] self.methods_counter[preferred_key] += 1 - key = '{}_{}'.format(preferred_key, current_val) + key = f'{preferred_key}_{current_val}' if key not in self: return key diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 019eeb33e..eb7dc909d 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -82,7 +82,7 @@ class SchemaGenerator(BaseSchemaGenerator): continue if components_schemas[k] == components[k]: continue - warnings.warn('Schema component "{}" has been overridden with a different value.'.format(k)) + warnings.warn(f'Schema component "{k}" has been overridden with a different value.') components_schemas.update(components) @@ -644,7 +644,7 @@ class AutoSchema(ViewInspector): return self.get_serializer(path, method) def get_reference(self, serializer): - return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))} + return {'$ref': f'#/components/schemas/{self.get_component_name(serializer)}'} def get_request_body(self, path, method): if method not in ('PUT', 'PATCH', 'POST'): diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index fc63f96fe..15c4b9105 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -66,7 +66,7 @@ def get_unique_validators(field_name, model_field): """ Returns a list of UniqueValidators that should be applied to the field. """ - field_set = set([field_name]) + field_set = {field_name} conditions = { c.condition for c in model_field.model._meta.constraints diff --git a/tests/schemas/test_coreapi.py b/tests/schemas/test_coreapi.py index 171f08646..a97b02fe1 100644 --- a/tests/schemas/test_coreapi.py +++ b/tests/schemas/test_coreapi.py @@ -1234,7 +1234,7 @@ class TestURLNamingCollisions(TestCase): for method, suffix in zip(methods, suffixes): if suffix is not None: - key = '{}_{}'.format(method, suffix) + key = f'{method}_{suffix}' else: key = method assert loc[key].url == url diff --git a/tests/schemas/test_managementcommand.py b/tests/schemas/test_managementcommand.py index c0713f43c..fa1b75fbf 100644 --- a/tests/schemas/test_managementcommand.py +++ b/tests/schemas/test_managementcommand.py @@ -70,7 +70,7 @@ class GenerateSchemaTests(TestCase): def test_accepts_custom_schema_generator(self): call_command('generateschema', - '--generator_class={}.{}'.format(__name__, CustomSchemaGenerator.__name__), + f'--generator_class={__name__}.{CustomSchemaGenerator.__name__}', stdout=self.out) out_json = yaml.safe_load(self.out.getvalue()) assert out_json == CustomSchemaGenerator.SCHEMA @@ -78,7 +78,7 @@ class GenerateSchemaTests(TestCase): def test_writes_schema_to_file_on_parameter(self): fd, path = tempfile.mkstemp() try: - call_command('generateschema', '--file={}'.format(path), stdout=self.out) + call_command('generateschema', f'--file={path}', stdout=self.out) # nothing on stdout assert not self.out.getvalue() diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 9516bfec9..67be5cfbd 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -43,12 +43,12 @@ class ExceptionTestCase(TestCase): exception = Throttled(wait=2) assert exception.get_full_details() == { - 'message': 'Request was throttled. Expected available in {} seconds.'.format(2), + 'message': f'Request was throttled. Expected available in {2} seconds.', 'code': 'throttled'} exception = Throttled(wait=2, detail='Slow down!') assert exception.get_full_details() == { - 'message': 'Slow down! Expected available in {} seconds.'.format(2), + 'message': f'Slow down! Expected available in {2} seconds.', 'code': 'throttled'} diff --git a/tests/test_fields.py b/tests/test_fields.py index 7c55dfcc5..d574b07eb 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -662,7 +662,7 @@ class FieldValues: """ for input_value, expected_output in get_items(self.valid_inputs): assert self.field.run_validation(input_value) == expected_output, \ - 'input value: {}'.format(repr(input_value)) + f'input value: {repr(input_value)}' def test_invalid_inputs(self, *args): """ @@ -672,12 +672,12 @@ class FieldValues: with pytest.raises(serializers.ValidationError) as exc_info: self.field.run_validation(input_value) assert exc_info.value.detail == expected_failure, \ - 'input value: {}'.format(repr(input_value)) + f'input value: {repr(input_value)}' def test_outputs(self, *args): for output_value, expected_output in get_items(self.outputs): assert self.field.to_representation(output_value) == expected_output, \ - 'output value: {}'.format(repr(output_value)) + f'output value: {repr(output_value)}' # Boolean types... @@ -1422,7 +1422,7 @@ class TestDateField(FieldValues): outputs = { datetime.date(2001, 1, 1): '2001-01-01', '2001-01-01': '2001-01-01', - str('2016-01-10'): '2016-01-10', + '2016-01-10': '2016-01-10', None: None, '': None, } @@ -1489,7 +1489,7 @@ class TestDateTimeField(FieldValues): datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00Z', datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): '2001-01-01T13:00:00Z', '2001-01-01T00:00:00': '2001-01-01T00:00:00', - str('2016-01-10T00:00:00'): '2016-01-10T00:00:00', + '2016-01-10T00:00:00': '2016-01-10T00:00:00', None: None, '': None, } diff --git a/tests/test_generics.py b/tests/test_generics.py index 8748e8f17..25b96fcbb 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -289,7 +289,7 @@ class TestInstanceView(TestCase): """ data = {'text': 'foo'} filtered_out_pk = BasicModel.objects.filter(text='filtered out')[0].pk - request = factory.put('/{}'.format(filtered_out_pk), data, format='json') + request = factory.put(f'/{filtered_out_pk}', data, format='json') response = self.view(request, pk=filtered_out_pk).render() assert response.status_code == status.HTTP_404_NOT_FOUND diff --git a/tests/test_pagination.py b/tests/test_pagination.py index d8feae52b..d8f66e95b 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -536,7 +536,7 @@ class TestLimitOffset: content = self.get_paginated_content(queryset) next_limit = self.pagination.default_limit next_offset = self.pagination.default_limit - next_url = 'http://testserver/?limit={}&offset={}'.format(next_limit, next_offset) + next_url = f'http://testserver/?limit={next_limit}&offset={next_offset}' assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] assert content.get('next') == next_url @@ -549,7 +549,7 @@ class TestLimitOffset: content = self.get_paginated_content(queryset) next_limit = self.pagination.default_limit next_offset = self.pagination.default_limit - next_url = 'http://testserver/?limit={}&offset={}'.format(next_limit, next_offset) + next_url = f'http://testserver/?limit={next_limit}&offset={next_offset}' assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] assert content.get('next') == next_url @@ -565,9 +565,9 @@ class TestLimitOffset: max_limit = self.pagination.max_limit next_offset = offset + max_limit prev_offset = offset - max_limit - base_url = 'http://testserver/?limit={}'.format(max_limit) - next_url = base_url + '&offset={}'.format(next_offset) - prev_url = base_url + '&offset={}'.format(prev_offset) + base_url = f'http://testserver/?limit={max_limit}' + next_url = base_url + f'&offset={next_offset}' + prev_url = base_url + f'&offset={prev_offset}' assert queryset == list(range(51, 66)) assert content.get('next') == next_url assert content.get('previous') == prev_url diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 2c908ba3f..93fe7b941 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -353,7 +353,7 @@ class ObjectPermissionsIntegrationTests(TestCase): 'delete': f('delete', model_name) } for perm in perms.values(): - perm = '{}.{}'.format(app_label, perm) + perm = f'{app_label}.{perm}' assign_perm(perm, everyone) everyone.user_set.add(*users.values()) @@ -718,7 +718,7 @@ class PermissionsCompositionTests(TestCase): assert hasperm is False def test_operand_holder_is_hashable(self): - assert hash((permissions.IsAuthenticated & permissions.IsAdminUser)) + assert hash(permissions.IsAuthenticated & permissions.IsAdminUser) def test_operand_holder_hash_same_for_same_operands_and_operator(self): first_operand_holder = ( diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 7a4878a2b..14513f2bb 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -586,7 +586,7 @@ class OneToOnePrimaryKeyTests(TestCase): source = OneToOnePKSourceSerializer(data={'name': 'source-2', 'target': target_pk}) # Then: The source is valid with the serializer if not source.is_valid(): - self.fail("Expected OneToOnePKTargetSerializer to be valid but had errors: {}".format(source.errors)) + self.fail(f"Expected OneToOnePKTargetSerializer to be valid but had errors: {source.errors}") # Then: Saving the serializer creates a new object new_source = source.save() # Then: The new object has the same pk as the target object diff --git a/tests/test_renderers.py b/tests/test_renderers.py index d04ff300f..1b396575d 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -408,7 +408,7 @@ class UnicodeJSONRendererTests(TestCase): obj = {'should_escape': '\u2028\u2029'} renderer = JSONRenderer() content = renderer.render(obj, 'application/json') - self.assertEqual(content, '{"should_escape":"\\u2028\\u2029"}'.encode()) + self.assertEqual(content, b'{"should_escape":"\\u2028\\u2029"}') class AsciiJSONRendererTests(TestCase): @@ -421,7 +421,7 @@ class AsciiJSONRendererTests(TestCase): obj = {'countries': ['United Kingdom', 'France', 'EspaƱa']} renderer = AsciiJSONRenderer() content = renderer.render(obj, 'application/json') - self.assertEqual(content, '{"countries":["United Kingdom","France","Espa\\u00f1a"]}'.encode()) + self.assertEqual(content, b'{"countries":["United Kingdom","France","Espa\\u00f1a"]}') # Tests for caching issue, #346 diff --git a/tests/test_response.py b/tests/test_response.py index a5eeae882..83f8a6717 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -267,7 +267,7 @@ class Issue807Tests(TestCase): """ headers = {"HTTP_ACCEPT": RendererC.media_type} resp = self.client.get('/', **headers) - expected = "{}; charset={}".format(RendererC.media_type, RendererC.charset) + expected = f"{RendererC.media_type}; charset={RendererC.charset}" self.assertEqual(expected, resp['Content-Type']) def test_content_type_set_explicitly_on_response(self): diff --git a/tests/test_routers.py b/tests/test_routers.py index 887f601d5..91a6189d5 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -447,9 +447,9 @@ class TestDynamicListAndDetailRouter(TestCase): url_path = endpoint.url_path if method_name.startswith('list_'): - assert route.url == '^{{prefix}}/{0}{{trailing_slash}}$'.format(url_path) + assert route.url == f'^{{prefix}}/{url_path}{{trailing_slash}}$' else: - assert route.url == '^{{prefix}}/{{lookup}}/{0}{{trailing_slash}}$'.format(url_path) + assert route.url == f'^{{prefix}}/{{lookup}}/{url_path}{{trailing_slash}}$' # check method to function mapping if method_name.endswith('_post'): method_map = 'post' @@ -488,14 +488,14 @@ class TestRegexUrlPath(URLPatternsTestCase, TestCase): def test_regex_url_path_list(self): kwarg = '1234' - response = self.client.get('/regex/list/{}/'.format(kwarg)) + response = self.client.get(f'/regex/list/{kwarg}/') assert response.status_code == 200 assert json.loads(response.content.decode()) == {'kwarg': kwarg} def test_regex_url_path_detail(self): pk = '1' kwarg = '1234' - response = self.client.get('/regex/{}/detail/{}/'.format(pk, kwarg)) + response = self.client.get(f'/regex/{pk}/detail/{kwarg}/') assert response.status_code == 200 assert json.loads(response.content.decode()) == {'pk': pk, 'kwarg': kwarg} @@ -557,14 +557,14 @@ class TestUrlPath(URLPatternsTestCase, TestCase): def test_list_extra_action(self): kwarg = 1234 - response = self.client.get('/path/list/{}/'.format(kwarg)) + response = self.client.get(f'/path/list/{kwarg}/') assert response.status_code == 200 assert json.loads(response.content.decode()) == {'kwarg': kwarg} def test_detail_extra_action(self): pk = '1' kwarg = 1234 - response = self.client.get('/path/{}/detail/{}/'.format(pk, kwarg)) + response = self.client.get(f'/path/{pk}/detail/{kwarg}/') assert response.status_code == 200 assert json.loads(response.content.decode()) == {'pk': pk, 'kwarg': kwarg} diff --git a/tests/test_validation.py b/tests/test_validation.py index 6e00b48c2..a2cdd1dcb 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -148,14 +148,14 @@ class TestMaxValueValidatorValidation(TestCase): def test_max_value_validation_success(self): obj = ValidationMaxValueValidatorModel.objects.create(number_value=100) - request = factory.patch('/{}'.format(obj.pk), {'number_value': 98}, format='json') + request = factory.patch(f'/{obj.pk}', {'number_value': 98}, format='json') view = UpdateMaxValueValidationModel().as_view() response = view(request, pk=obj.pk).render() assert response.status_code == status.HTTP_200_OK def test_max_value_validation_fail(self): obj = ValidationMaxValueValidatorModel.objects.create(number_value=100) - request = factory.patch('/{}'.format(obj.pk), {'number_value': 101}, format='json') + request = factory.patch(f'/{obj.pk}', {'number_value': 101}, format='json') view = UpdateMaxValueValidationModel().as_view() response = view(request, pk=obj.pk).render() assert response.content == b'{"number_value":["Ensure this value is less than or equal to 100."]}'