Skip to content

Commit 740424e

Browse files
committed
Add outline for Open API paths generation.
1 parent 9acba98 commit 740424e

File tree

5 files changed

+140
-35
lines changed

5 files changed

+140
-35
lines changed

rest_framework/schemas/generators.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -308,14 +308,7 @@ def get_links(self, request=None):
308308
"""
309309
links = LinkNode()
310310

311-
# Generate (path, method, view) given (path, method, callback).
312-
paths = []
313-
view_endpoints = []
314-
for path, method, callback in self.endpoints:
315-
view = self.create_view(callback, method, request)
316-
path = self.coerce_path(path, method, view)
317-
paths.append(path)
318-
view_endpoints.append((path, method, view))
311+
paths, view_endpoints = self._get_paths_and_endpoints(request)
319312

320313
# Only generate the path prefix for paths that will be included
321314
if not paths:
@@ -332,6 +325,20 @@ def get_links(self, request=None):
332325

333326
return links
334327

328+
def _get_paths_and_endpoints(self, request):
329+
"""
330+
Generate (path, method, view) given (path, method, callback) for paths.
331+
"""
332+
paths = []
333+
view_endpoints = []
334+
for path, method, callback in self.endpoints:
335+
view = self.create_view(callback, method, request)
336+
path = self.coerce_path(path, method, view)
337+
paths.append(path)
338+
view_endpoints.append((path, method, view))
339+
340+
return paths, view_endpoints
341+
335342
# Methods used when we generate a view instance from the raw callback...
336343

337344
def determine_path_prefix(self, paths):
@@ -461,3 +468,26 @@ def get_keys(self, subpath, method, view):
461468

462469
# Default action, eg "/users/", "/users/{pk}/"
463470
return named_path_components + [action]
471+
472+
473+
class OpenAPISchemaGenerator(SchemaGenerator):
474+
475+
def get_paths(self, request=None):
476+
result = OrderedDict()
477+
478+
paths, view_endpoints = self._get_paths_and_endpoints(request)
479+
480+
# Only generate the path prefix for paths that will be included
481+
if not paths:
482+
return None
483+
prefix = self.determine_path_prefix(paths)
484+
485+
for path, method, view in view_endpoints:
486+
if not self.has_view_permissions(path, method, view):
487+
continue
488+
operation = view.schema.get_operation(path, method)
489+
subpath = path[len(prefix):]
490+
result.setdefault(subpath, {})
491+
result[subpath][method.lower()] = operation
492+
493+
return result

rest_framework/schemas/inspectors.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,3 +501,10 @@ def __get__(self, instance, owner):
501501
inspector = inspector_class()
502502
inspector.view = instance
503503
return inspector
504+
505+
506+
class OpenAPIAutoSchema(ViewInspector):
507+
508+
def get_operation(self, path, method):
509+
# TODO: fill in details here.
510+
return {}

tests/schemas/test_openapi.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from django.conf.urls import url
2+
from django.test import RequestFactory, TestCase, override_settings
3+
4+
from rest_framework.request import Request
5+
from rest_framework.schemas.generators import OpenAPISchemaGenerator
6+
from rest_framework.schemas.inspectors import OpenAPIAutoSchema
7+
8+
from . import views
9+
10+
11+
def create_request(path):
12+
factory = RequestFactory()
13+
request = Request(factory.get(path))
14+
return request
15+
16+
17+
def create_view(view_cls, method, request):
18+
generator = OpenAPISchemaGenerator()
19+
view = generator.create_view(view_cls.as_view(), method, request)
20+
return view
21+
22+
23+
class TestInspector(TestCase):
24+
25+
def test_path_without_parameters(self):
26+
path = '/example/'
27+
method = 'GET'
28+
29+
view = create_view(
30+
views.ExampleListView,
31+
method,
32+
create_request(path)
33+
)
34+
inspector = OpenAPIAutoSchema()
35+
inspector.view = view
36+
37+
operation = inspector.get_operation(path, method)
38+
assert operation == {}
39+
40+
# TODO: parameters, operationID, responses, etc ???
41+
42+
43+
@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.inspectors.OpenAPIAutoSchema'})
44+
class TestGenerator(TestCase):
45+
46+
def test_override_settings(self):
47+
assert isinstance(views.ExampleListView.schema, OpenAPIAutoSchema)
48+
49+
def test_paths_construction(self):
50+
patterns = [
51+
url(r'^example/?$', views.ExampleListView.as_view()),
52+
]
53+
generator = OpenAPISchemaGenerator(patterns=patterns)
54+
55+
# This happens in get_schema()
56+
inspector = generator.endpoint_inspector_cls(generator.patterns, generator.urlconf)
57+
generator.endpoints = inspector.get_api_endpoints()
58+
59+
paths = generator.get_paths()
60+
61+
assert 'example/' in paths
62+
example_operations = paths['example/']
63+
assert len(example_operations) == 2
64+
assert 'get' in example_operations
65+
assert 'post' in example_operations

tests/schemas/test_schemas.py

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from rest_framework.views import APIView
2525
from rest_framework.viewsets import GenericViewSet, ModelViewSet
2626

27+
from . import views
2728
from ..models import BasicModel, ForeignKeySource
2829

2930
factory = APIRequestFactory()
@@ -330,30 +331,13 @@ class MethodLimitedViewSet(ExampleViewSet):
330331
http_method_names = ['get', 'head', 'options']
331332

332333

333-
class ExampleListView(APIView):
334-
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
335-
336-
def get(self, *args, **kwargs):
337-
pass
338-
339-
def post(self, request, *args, **kwargs):
340-
pass
341-
342-
343-
class ExampleDetailView(APIView):
344-
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
345-
346-
def get(self, *args, **kwargs):
347-
pass
348-
349-
350334
@unittest.skipUnless(coreapi, 'coreapi is not installed')
351335
class TestSchemaGenerator(TestCase):
352336
def setUp(self):
353337
self.patterns = [
354-
url(r'^example/?$', ExampleListView.as_view()),
355-
url(r'^example/(?P<pk>\d+)/?$', ExampleDetailView.as_view()),
356-
url(r'^example/(?P<pk>\d+)/sub/?$', ExampleDetailView.as_view()),
338+
url(r'^example/?$', views.ExampleListView.as_view()),
339+
url(r'^example/(?P<pk>\d+)/?$', views.ExampleDetailView.as_view()),
340+
url(r'^example/(?P<pk>\d+)/sub/?$', views.ExampleDetailView.as_view()),
357341
]
358342

359343
def test_schema_for_regular_views(self):
@@ -404,9 +388,9 @@ def test_schema_for_regular_views(self):
404388
class TestSchemaGeneratorDjango2(TestCase):
405389
def setUp(self):
406390
self.patterns = [
407-
path('example/', ExampleListView.as_view()),
408-
path('example/<int:pk>/', ExampleDetailView.as_view()),
409-
path('example/<int:pk>/sub/', ExampleDetailView.as_view()),
391+
path('example/', views.ExampleListView.as_view()),
392+
path('example/<int:pk>/', views.ExampleDetailView.as_view()),
393+
path('example/<int:pk>/sub/', views.ExampleDetailView.as_view()),
410394
]
411395

412396
def test_schema_for_regular_views(self):
@@ -456,9 +440,9 @@ def test_schema_for_regular_views(self):
456440
class TestSchemaGeneratorNotAtRoot(TestCase):
457441
def setUp(self):
458442
self.patterns = [
459-
url(r'^api/v1/example/?$', ExampleListView.as_view()),
460-
url(r'^api/v1/example/(?P<pk>\d+)/?$', ExampleDetailView.as_view()),
461-
url(r'^api/v1/example/(?P<pk>\d+)/sub/?$', ExampleDetailView.as_view()),
443+
url(r'^api/v1/example/?$', views.ExampleListView.as_view()),
444+
url(r'^api/v1/example/(?P<pk>\d+)/?$', views.ExampleDetailView.as_view()),
445+
url(r'^api/v1/example/(?P<pk>\d+)/sub/?$', views.ExampleDetailView.as_view()),
462446
]
463447

464448
def test_schema_for_regular_views(self):
@@ -569,7 +553,7 @@ def setUp(self):
569553
router.register('example1', Http404ExampleViewSet, basename='example1')
570554
router.register('example2', PermissionDeniedExampleViewSet, basename='example2')
571555
self.patterns = [
572-
url('^example/?$', ExampleListView.as_view()),
556+
url('^example/?$', views.ExampleListView.as_view()),
573557
url(r'^', include(router.urls))
574558
]
575559

tests/schemas/views.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from rest_framework import permissions
2+
from rest_framework.views import APIView
3+
4+
5+
class ExampleListView(APIView):
6+
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
7+
8+
def get(self, *args, **kwargs):
9+
pass
10+
11+
def post(self, request, *args, **kwargs):
12+
pass
13+
14+
15+
class ExampleDetailView(APIView):
16+
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
17+
18+
def get(self, *args, **kwargs):
19+
pass

0 commit comments

Comments
 (0)