Skip to content

Commit

Permalink
Emit enum of possible format suffixes
Browse files Browse the repository at this point in the history
Use DRF renderer based list of possible formats, and filtered
by the URLConf-based `format_suffix_patterns` formats if used.

Related to tfranzel#110
  • Loading branch information
jayvdb committed Jul 14, 2020
1 parent 14fa278 commit c82d81a
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 8 deletions.
14 changes: 13 additions & 1 deletion drf_spectacular/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def dict_helper(parameters):
# override/add @extend_schema parameters
for key, parameter in override_parameters.items():
parameters[key] = parameter

return sorted(parameters.values(), key=lambda p: p['name'])

def get_description(self):
Expand Down Expand Up @@ -270,7 +271,9 @@ def _resolve_path_parameters(self, variables):
description = ''
required = True

resolved_parameter = resolve_regex_path_parameter(self.path_regex, variable)
resolved_parameter = resolve_regex_path_parameter(
self.path_regex, variable, self.map_formats(),
)

if resolved_parameter:
schema, required = resolved_parameter['schema'], resolved_parameter['required']
Expand Down Expand Up @@ -732,6 +735,15 @@ def map_renderers(self):
media_types.append(renderer.media_type)
return media_types

def map_formats(self):
formats = set()
for renderer in self.view.renderer_classes:
# BrowsableAPIRenderer not relevant to OpenAPI spec
if renderer == renderers.BrowsableAPIRenderer:
continue
formats.add(renderer.format)
return list(formats)

def _get_serializer(self):
view = self.view
try:
Expand Down
11 changes: 9 additions & 2 deletions drf_spectacular/plumbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -643,22 +643,29 @@ def iter_prop_containers(schema):
return result


def resolve_regex_path_parameter(path_regex, variable):
def resolve_regex_path_parameter(path_regex, variable, available_formats=None):
"""
convert django style path parameters to OpenAPI parameters.
TODO also try to handle regular grouped regex parameters
"""
for match in _PATH_PARAMETER_COMPONENT_RE.finditer(path_regex):
converter, parameter = match.group('converter'), match.group('parameter')
enum_values = None

if converter and converter.startswith('drf_format_suffix_'):
converter = 'drf_format_suffix' # remove appended options
view_formats = converter[len('drf_format_suffix_'):].split('_')
enum_values = [f'.{suffix}' for suffix in view_formats
if suffix in available_formats]
converter = 'drf_format_suffix'
elif converter == 'drf_format_suffix':
enum_values = [f'.{suffix}' for suffix in available_formats]

if parameter == variable and converter in DJANGO_PATH_CONVERTER_MAPPING:
return build_parameter_type(
name=parameter,
schema=build_basic_type(DJANGO_PATH_CONVERTER_MAPPING[converter]),
location=OpenApiParameter.PATH,
enum=enum_values,
)

return None
Expand Down
33 changes: 28 additions & 5 deletions tests/test_regressions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import uuid
from unittest import mock

import pytest
from django.conf.urls import url
from django.db import models
from django.db.models import fields
Expand Down Expand Up @@ -316,25 +317,47 @@ class YViewSet(viewsets.ReadOnlyModelViewSet):
assert schema['components']['schemas']['Y']['properties']['x']['format'] == 'uuid'


def test_drf_format_suffix_parameter(no_warnings):
@pytest.mark.parametrize('allowed', [None, ['json', 'api']])
def test_drf_format_suffix_parameter(no_warnings, allowed):
from rest_framework.urlpatterns import format_suffix_patterns

@extend_schema(responses=OpenApiTypes.FLOAT)
@api_view(['GET'])
def pi(request, format=None):
def view_func(request, format=None):
pass # pragma: no cover

urlpatterns = [path('pi', pi)]
urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html'])
urlpatterns = [
path('pi', view_func),
path('pi/', view_func),
path('pi/subpath', view_func),
path('pick', view_func),
]
urlpatterns = format_suffix_patterns(urlpatterns, allowed=allowed)

generator = SchemaGenerator(patterns=urlpatterns)
schema = generator.get_schema(request=None, public=True)
validate_schema(schema)
assert len(schema['paths']) == 2

# Only seven alternatives are created, as /pi/{format} would be
# /pi/.json which is not supported.
assert list(schema['paths'].keys()) == [
'/pi',
'/pi/',
'/pi/subpath',
'/pi/subpath{format}',
'/pick',
'/pick{format}',
'/pi{format}',
]
format_parameter = schema['paths']['/pi{format}']['get']['parameters'][0]
assert format_parameter['name'] == 'format'
assert format_parameter['required'] is True
assert format_parameter['in'] == 'path'
assert format_parameter['schema']['type'] == 'string'
# When allowed is not specified, all of the default formats are possible.
# Even if other values are provided, only the valid formats are possible.
allowed = ['json']
assert format_parameter['schema']['enum'] == [f'.{suffix}' for suffix in allowed]


def test_regex_path_parameter_discovery(no_warnings):
Expand Down

0 comments on commit c82d81a

Please sign in to comment.