From e7ecbf92c433101d09dc59c2281ab66fd1071211 Mon Sep 17 00:00:00 2001 From: Jeremy Langley Date: Sun, 13 Feb 2022 07:56:58 -0800 Subject: [PATCH] so far so good all tests pass --- rest_framework/compat.py | 2 +- rest_framework/decorators.py | 4 ++-- rest_framework/exceptions.py | 6 ++---- rest_framework/fields.py | 4 ++-- rest_framework/filters.py | 12 ++++++------ rest_framework/generics.py | 9 ++++----- rest_framework/parsers.py | 4 ++-- rest_framework/relations.py | 14 ++++++++------ rest_framework/renderers.py | 10 +++++----- rest_framework/request.py | 9 +++------ rest_framework/serializers.py | 11 ++++------- rest_framework/throttling.py | 2 +- rest_framework/urlpatterns.py | 6 +++--- rest_framework/validators.py | 36 +++++++++++------------------------ rest_framework/viewsets.py | 2 +- tests/test_exceptions.py | 4 ++-- tests/test_filters.py | 2 +- tests/utils.py | 2 +- 18 files changed, 59 insertions(+), 80 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 4bae7729f..8240f5489 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -140,7 +140,7 @@ if markdown is not None and pygments is not None: code = m.group(2).replace('\t', ' ') code = pygments.highlight(code, lexer, self.formatter) code = code.replace('\n\n', '\n \n').replace('\n', '
').replace('\\@', '@') - return '\n\n%s\n\n' % code + return f'\n\n{code}\n\n' ret = self.pattern.sub(repl, "\n".join(lines)) return ret.split("\n") diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 3b572c09e..872b0ee83 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -41,7 +41,7 @@ def api_view(http_method_names=None): # api_view applied with eg. string instead of list of strings assert isinstance(http_method_names, (list, tuple)), \ - '@api_view expected a list of strings, received %s' % type(http_method_names).__name__ + f'@api_view expected a list of strings, received {type(http_method_names).__name__}' allowed_methods = set(http_method_names) | {'options'} WrappedAPIView.http_method_names = [method.lower() for method in allowed_methods] @@ -199,7 +199,7 @@ class MethodMapper(dict): def _map(self, method, func): assert method not in self, ( - "Method '%s' has already been mapped to '.%s'." % (method, self[method])) + f"Method '{method}' has already been mapped to '.{self[method]}'.") assert func.__name__ != self.action.__name__, ( "Method mapping does not behave like the property decorator. You " "cannot use the same method name for each mapping declaration.") diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index fee8f024f..5dd6b35fe 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -84,10 +84,8 @@ class ErrorDetail(str): return not self.__eq__(other) def __repr__(self): - return 'ErrorDetail(string=%r, code=%r)' % ( - str(self), - self.code, - ) + return f"ErrorDetail(string='{str(self)}', code='{self.code}')" + def __hash__(self): return hash(str(self)) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d7e7816ce..6c50c6662 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -280,7 +280,7 @@ class CreateOnlyDefault: return self.default def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, repr(self.default)) + return f'{self.__class__.__name__}({repr(self.default)})' class CurrentUserDefault: @@ -290,7 +290,7 @@ class CurrentUserDefault: return serializer_field.context['request'].user def __repr__(self): - return '%s()' % self.__class__.__name__ + return f'{self.__class__.__name__}()' class SkipField(Exception): diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 1ffd9edc0..f83596bbf 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -236,9 +236,9 @@ class OrderingFilter(BaseFilterBackend): (field.source.replace('.', '__') or field_name, field.label) for field_name, field in serializer_class(context=context).fields.items() if ( - not getattr(field, 'write_only', False) and - not field.source == '*' and - field.source not in model_property_names + not getattr(field, 'write_only', False) and + not field.source == '*' and + field.source not in model_property_names ) ] @@ -294,9 +294,9 @@ class OrderingFilter(BaseFilterBackend): 'param': self.ordering_param, } for key, label in self.get_valid_fields(queryset, view, context): - options.append((key, '%s - %s' % (label, _('ascending')))) - options.append(('-' + key, '%s - %s' % (label, _('descending')))) - context['options'] = options + options.append((key, f'{label} - {_("ascending")}')) + options.append(('-' + key, f'{label} - {_("descending")}')) + context['options'] = options return context def to_html(self, request, queryset, view): diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 55cfafda4..55ce2ab25 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -61,9 +61,9 @@ class GenericAPIView(views.APIView): (Eg. return a list of items that is specific to the user) """ assert self.queryset is not None, ( - "'%s' should either include a `queryset` attribute, " - "or override the `get_queryset()` method." - % self.__class__.__name__ + f"'{self.__class__.__name__}' should either include a `queryset` attribute, " + f"or override the `get_queryset()` method." + ) queryset = self.queryset @@ -120,9 +120,8 @@ class GenericAPIView(views.APIView): (Eg. admins get full serialization, others get basic serialization) """ assert self.serializer_class is not None, ( - "'%s' should either include a `serializer_class` attribute, " + f"'{self.__class__.__name__}' should either include a `serializer_class` attribute, " "or override the `get_serializer_class()` method." - % self.__class__.__name__ ) return self.serializer_class diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index fc4eb1428..bb0d03efe 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -64,7 +64,7 @@ class JSONParser(BaseParser): parse_constant = json.strict_constant if self.strict else None return json.load(decoded_stream, parse_constant=parse_constant) except ValueError as exc: - raise ParseError('JSON parse error - %s' % str(exc)) + raise ParseError(f'JSON parse error - {str(exc)}') class FormParser(BaseParser): @@ -109,7 +109,7 @@ class MultiPartParser(BaseParser): data, files = parser.parse() return DataAndFiles(data, files) except MultiPartParserError as exc: - raise ParseError('Multipart form parse error - %s' % str(exc)) + raise ParseError(f'Multipart form parse error - {str(exc)}') class FileUploadParser(BaseParser): diff --git a/rest_framework/relations.py b/rest_framework/relations.py index c98700784..b14f5656e 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -46,6 +46,7 @@ class Hyperlink(str): We use this for hyperlinked URLs that may render as a named link in some contexts, or render as a plain URL in others. """ + def __new__(cls, url, obj): ret = super().__new__(cls, url) ret.obj = obj @@ -70,11 +71,12 @@ class PKOnlyObject: instance, but still want to return an object with a .pk attribute, in order to keep the same interface as a regular model instance. """ + def __init__(self, pk): self.pk = pk def __str__(self): - return "%s" % self.pk + return f"{self.pk}" # We assume that 'validators' are intended for the child serializer, @@ -376,9 +378,9 @@ class HyperlinkedRelatedField(RelatedField): def to_representation(self, value): assert 'request' in self.context, ( - "`%s` requires the request in the serializer" + f"`{self.__class__.__name__}` requires the request in the serializer" " context. Add `context={'request': request}` when instantiating " - "the serializer." % self.__class__.__name__ + "the serializer." ) request = self.context['request'] @@ -409,9 +411,9 @@ class HyperlinkedRelatedField(RelatedField): if value in ('', None): value_string = {'': 'the empty string', None: 'None'}[value] msg += ( - " WARNING: The value of the field on the model instance " - "was %s, which may be why it didn't match any " - "entries in your URL conf." % value_string + " WARNING: The value of the field on the model instance " + "was %s, which may be why it didn't match any " + "entries in your URL conf." % value_string ) raise ImproperlyConfigured(msg % self.view_name) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 8824fa660..a2ac708d6 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -197,7 +197,7 @@ class TemplateHTMLRenderer(BaseRenderer): return self.resolve_template(template_names) except Exception: # Fall back to using eg '404 Not Found' - body = '%d %s' % (response.status_code, response.status_text.title()) + body = f'{response.status_code} {response.status_text.title()}' template = engines['django'].from_string(body) return template @@ -414,9 +414,9 @@ class BrowsableAPIRenderer(BaseRenderer): render_style = getattr(renderer, 'render_style', 'text') assert render_style in ['text', 'binary'], 'Expected .render_style ' \ - '"text" or "binary", but got "%s"' % render_style + f'"text" or "binary", but got "{render_style}"' if render_style == 'binary': - return '[%d bytes of binary content]' % len(content) + return f'[{len(content)} bytes of binary content]' return content.decode('utf-8') if isinstance(content, bytes) else content @@ -660,9 +660,9 @@ class BrowsableAPIRenderer(BaseRenderer): response_headers = OrderedDict(sorted(response.items())) renderer_content_type = '' if renderer: - renderer_content_type = '%s' % renderer.media_type + renderer_content_type = f'{renderer.media_type}' if renderer.charset: - renderer_content_type += ' ;%s' % renderer.charset + renderer_content_type += f' ;{renderer.charset}' response_headers['Content-Type'] = renderer_content_type if getattr(view, 'paginator', None) and view.paginator.display_page_controls: diff --git a/rest_framework/request.py b/rest_framework/request.py index 17ceadb08..324188396 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -154,7 +154,7 @@ class Request: assert isinstance(request, HttpRequest), ( 'The `request` argument must be an instance of ' '`django.http.HttpRequest`, not `{}.{}`.' - .format(request.__class__.__module__, request.__class__.__name__) + .format(request.__class__.__module__, request.__class__.__name__) ) self._request = request @@ -180,11 +180,8 @@ class Request: self.authenticators = (forced_auth,) def __repr__(self): - return '<%s.%s: %s %r>' % ( - self.__class__.__module__, - self.__class__.__name__, - self.method, - self.get_full_path()) + return f'<{self.__class__.__module__}.{self.__class__.__name__}: ' \ + f"{self.method} '{self.get_full_path()}'>" def _default_negotiator(self): return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 389680517..10cd79227 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1105,20 +1105,17 @@ class ModelSerializer(Serializer): if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)): raise TypeError( 'The `fields` option must be a list or tuple or "__all__". ' - 'Got %s.' % type(fields).__name__ + f'Got {type(fields).__name__}.' ) if exclude and not isinstance(exclude, (list, tuple)): raise TypeError( - 'The `exclude` option must be a list or tuple. Got %s.' % - type(exclude).__name__ + f'The `exclude` option must be a list or tuple. Got {type(exclude).__name__}.' ) assert not (fields and exclude), ( - "Cannot set both 'fields' and 'exclude' options on " - "serializer {serializer_class}.".format( - serializer_class=self.__class__.__name__ - ) + f"Cannot set both 'fields' and 'exclude' options on " + f"serializer {self.__class__.__name__}." ) assert not (fields is None and exclude is None), ( diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index e262b886b..9a7010735 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -91,7 +91,7 @@ class SimpleRateThrottle(BaseThrottle): try: return self.THROTTLE_RATES[self.scope] except KeyError: - msg = "No default throttle rate set for '%s' scope" % self.scope + msg = f"No default throttle rate set for '{self.scope}' scope" raise ImproperlyConfigured(msg) def parse_rate(self, rate): diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index bed5708eb..62df08c8b 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -9,7 +9,7 @@ def _get_format_path_converter(suffix_kwarg, allowed): if len(allowed) == 1: allowed_pattern = allowed[0] else: - allowed_pattern = '(?:%s)' % '|'.join(allowed) + allowed_pattern = f'(?:{"|".join(allowed)})' suffix_pattern = r"\.%s/?" % allowed_pattern else: suffix_pattern = r"\.[a-z0-9]+/?" @@ -99,7 +99,7 @@ def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): if len(allowed) == 1: allowed_pattern = allowed[0] else: - allowed_pattern = '(%s)' % '|'.join(allowed) + allowed_pattern = f'({"|".join(allowed)})' suffix_pattern = r'\.(?P<%s>%s)/?$' % (suffix_kwarg, allowed_pattern) else: suffix_pattern = r'\.(?P<%s>[a-z0-9]+)/?$' % suffix_kwarg @@ -107,6 +107,6 @@ def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed) register_converter(suffix_converter, converter_name) - suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg) + suffix_route = f'<{converter_name}:{suffix_kwarg}>' return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route) diff --git a/rest_framework/validators.py b/rest_framework/validators.py index a5cb75a84..8514c8441 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -48,7 +48,7 @@ class UniqueValidator: """ Filter the queryset to all instances matching the given attribute. """ - filter_kwargs = {'%s__%s' % (field_name, self.lookup): value} + filter_kwargs = {f'{field_name}__{self.lookup}': value} return qs_filter(queryset, **filter_kwargs) def exclude_current_instance(self, queryset, instance): @@ -74,10 +74,7 @@ class UniqueValidator: raise ValidationError(self.message, code='unique') def __repr__(self): - return '<%s(queryset=%s)>' % ( - self.__class__.__name__, - smart_repr(self.queryset) - ) + return f'<{self.__class__.__name__}(queryset={smart_repr(self.queryset)})>' class UniqueTogetherValidator: @@ -160,11 +157,7 @@ class UniqueTogetherValidator: raise ValidationError(message, code='unique') def __repr__(self): - return '<%s(queryset=%s, fields=%s)>' % ( - self.__class__.__name__, - smart_repr(self.queryset), - smart_repr(self.fields) - ) + return f'<{self.__class__.__name__}(queryset={smart_repr(self.queryset)}, fields={smart_repr(self.fields)})>' class ProhibitSurrogateCharactersValidator: @@ -231,12 +224,8 @@ class BaseUniqueForValidator: }, code='unique') def __repr__(self): - return '<%s(queryset=%s, field=%s, date_field=%s)>' % ( - self.__class__.__name__, - smart_repr(self.queryset), - smart_repr(self.field), - smart_repr(self.date_field) - ) + return f'<{self.__class__.__name__}(queryset={smart_repr(self.queryset)}, ' \ + f'field={smart_repr(self.field)}, date_field={smart_repr(self.date_field)})>' class UniqueForDateValidator(BaseUniqueForValidator): @@ -246,11 +235,10 @@ class UniqueForDateValidator(BaseUniqueForValidator): value = attrs[self.field] date = attrs[self.date_field] - filter_kwargs = {} - filter_kwargs[field_name] = value - filter_kwargs['%s__day' % date_field_name] = date.day - filter_kwargs['%s__month' % date_field_name] = date.month - filter_kwargs['%s__year' % date_field_name] = date.year + filter_kwargs = {field_name: value, + f'{date_field_name}__day': date.day, + f'{date_field_name}__month': date.month, + f'{date_field_name}__year': date.year} return qs_filter(queryset, **filter_kwargs) @@ -263,7 +251,7 @@ class UniqueForMonthValidator(BaseUniqueForValidator): filter_kwargs = {} filter_kwargs[field_name] = value - filter_kwargs['%s__month' % date_field_name] = date.month + filter_kwargs[f'{date_field_name}__month'] = date.month return qs_filter(queryset, **filter_kwargs) @@ -274,7 +262,5 @@ class UniqueForYearValidator(BaseUniqueForValidator): value = attrs[self.field] date = attrs[self.date_field] - filter_kwargs = {} - filter_kwargs[field_name] = value - filter_kwargs['%s__year' % date_field_name] = date.year + filter_kwargs = {field_name: value, f'{date_field_name}__year': date.year} return qs_filter(queryset, **filter_kwargs) diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 5a1f8acf5..d69d2157c 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -197,7 +197,7 @@ class ViewSetMixin: for action in actions: try: - url_name = '%s-%s' % (self.basename, action.url_name) + url_name = f'{self.basename}-{action.url_name}' url = reverse(url_name, self.args, self.kwargs, request=self.request) view = self.__class__(**action.kwargs) action_urls[view.get_view_name()] = url diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 9516bfec9..472e27e5f 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -70,9 +70,9 @@ class ErrorDetailTests(TestCase): def test_repr(self): assert repr(ErrorDetail('msg1')) == \ - 'ErrorDetail(string={!r}, code=None)'.format('msg1') + f"ErrorDetail(string='msg1', code='None')" assert repr(ErrorDetail('msg1', 'code')) == \ - 'ErrorDetail(string={!r}, code={!r})'.format('msg1', 'code') + f"ErrorDetail(string='msg1', code='code')" def test_str(self): assert str(ErrorDetail('msg1')) == 'msg1' diff --git a/tests/test_filters.py b/tests/test_filters.py index 37ae4c7cf..1fa0bd129 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -208,7 +208,7 @@ class SearchFilterTests(TestCase): def as_sql(self, compiler, connection): sql, params = compiler.compile(self.lhs) - return "trim(%s, 'a')" % sql, params + return f"trim({sql}, 'a')", params with register_lookup(CharField, TrimA): # Search including `a` diff --git a/tests/utils.py b/tests/utils.py index 06e5b9abe..122a0f6c4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -13,7 +13,7 @@ class MockObject: '%s=%s' % (key, value) for key, value in sorted(self._kwargs.items()) ]) - return '' % kwargs_str + return f'' class MockQueryset: